/* Copyright 2017 Andrew Dawson * * This file is part of Tusky. * * Tusky is free software: you can redistribute it and/or modify it under the terms of the GNU * General Public License as published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * * You should have received a copy of the GNU General Public License along with Tusky. If not, see * . */ package com.keylesspalace.tusky; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v7.widget.PopupMenu; import android.support.v7.widget.RecyclerView; import android.view.MenuItem; import android.view.View; import com.android.volley.AuthFailureError; import com.android.volley.Request; import com.android.volley.Response; import com.android.volley.VolleyError; import com.android.volley.toolbox.JsonObjectRequest; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /* Note from Andrew on Jan. 22, 2017: This class is a design problem for me, so I left it with an * awkward name. TimelineFragment and NotificationFragment have significant overlap but the nature * of that is complicated by how they're coupled with Status and Notification and the corresponding * adapters. I feel like the profile pages and thread viewer, which I haven't made yet, will also * overlap functionality. So, I'm momentarily leaving it and hopefully working on those will clear * up what needs to be where. */ public class SFragment extends Fragment { protected String domain; protected String accessToken; protected String loggedInAccountId; protected String loggedInUsername; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); SharedPreferences preferences = getContext().getSharedPreferences( getString(R.string.preferences_file_key), Context.MODE_PRIVATE); domain = preferences.getString("domain", null); accessToken = preferences.getString("accessToken", null); assert(domain != null); assert(accessToken != null); sendUserInfoRequest(); } protected void sendRequest( int method, String endpoint, JSONObject parameters, @Nullable Response.Listener responseListener) { if (responseListener == null) { // Use a dummy listener if one wasn't specified so the request can be constructed. responseListener = new Response.Listener() { @Override public void onResponse(JSONObject response) {} }; } String url = "https://" + domain + endpoint; JsonObjectRequest request = new JsonObjectRequest( method, url, parameters, responseListener, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { System.err.println(error.getMessage()); } }) { @Override public Map getHeaders() throws AuthFailureError { Map headers = new HashMap<>(); headers.put("Authorization", "Bearer " + accessToken); return headers; } }; VolleySingleton.getInstance(getContext()).addToRequestQueue(request); } protected void postRequest(String endpoint) { sendRequest(Request.Method.POST, endpoint, null, null); } private void sendUserInfoRequest() { sendRequest(Request.Method.GET, getString(R.string.endpoint_verify_credentials), null, new Response.Listener() { @Override public void onResponse(JSONObject response) { try { loggedInAccountId = response.getString("id"); loggedInUsername = response.getString("acct"); } catch (JSONException e) { //TODO: Help assert(false); } } }); } protected void reply(Status status) { String inReplyToId = status.getId(); Status.Mention[] mentions = status.getMentions(); List mentionedUsernames = new ArrayList<>(); for (int i = 0; i < mentions.length; i++) { mentionedUsernames.add(mentions[i].getUsername()); } mentionedUsernames.add(status.getUsername()); mentionedUsernames.remove(loggedInUsername); Intent intent = new Intent(getContext(), ComposeActivity.class); intent.putExtra("in_reply_to_id", inReplyToId); intent.putExtra("mentioned_usernames", mentionedUsernames.toArray(new String[0])); startActivity(intent); } protected void reblog(final Status status, final boolean reblog, final RecyclerView.Adapter adapter, final int position) { String id = status.getId(); String endpoint; if (reblog) { endpoint = String.format(getString(R.string.endpoint_reblog), id); } else { endpoint = String.format(getString(R.string.endpoint_unreblog), id); } sendRequest(Request.Method.POST, endpoint, null, new Response.Listener() { @Override public void onResponse(JSONObject response) { status.setReblogged(reblog); adapter.notifyItemChanged(position); } }); } protected void favourite(final Status status, final boolean favourite, final RecyclerView.Adapter adapter, final int position) { String id = status.getId(); String endpoint; if (favourite) { endpoint = String.format(getString(R.string.endpoint_favourite), id); } else { endpoint = String.format(getString(R.string.endpoint_unfavourite), id); } sendRequest(Request.Method.POST, endpoint, null, new Response.Listener() { @Override public void onResponse(JSONObject response) { status.setFavourited(favourite); adapter.notifyItemChanged(position); } }); } private void follow(String id) { String endpoint = String.format(getString(R.string.endpoint_follow), id); postRequest(endpoint); } private void block(String id) { String endpoint = String.format(getString(R.string.endpoint_block), id); postRequest(endpoint); } private void delete(String id) { String endpoint = String.format(getString(R.string.endpoint_delete), id); sendRequest(Request.Method.DELETE, endpoint, null, null); } protected void more(Status status, View view, final AdapterItemRemover adapter, final int position) { final String id = status.getId(); final String accountId = status.getAccountId(); PopupMenu popup = new PopupMenu(getContext(), view); // Give a different menu depending on whether this is the user's own toot or not. if (loggedInAccountId == null || !loggedInAccountId.equals(accountId)) { popup.inflate(R.menu.status_more); } else { popup.inflate(R.menu.status_more_for_user); } popup.setOnMenuItemClickListener( new PopupMenu.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { case R.id.status_follow: { follow(accountId); return true; } case R.id.status_block: { block(accountId); return true; } case R.id.status_delete: { delete(id); adapter.removeItem(position); return true; } } return false; } }); popup.show(); } protected void viewMedia(String url, Status.MediaAttachment.Type type) { switch (type) { case IMAGE: { Fragment newFragment = ViewMediaFragment.newInstance(url); FragmentManager manager = getFragmentManager(); manager.beginTransaction() .add(R.id.overlay_fragment_container, newFragment) .addToBackStack(null) .commit(); break; } case VIDEO: { Intent intent = new Intent(getContext(), ViewVideoActivity.class); intent.putExtra("url", url); startActivity(intent); break; } } } protected void viewThread(Status status) { Intent intent = new Intent(getContext(), ViewThreadActivity.class); intent.putExtra("id", status.getId()); startActivity(intent); } }