Merge branch 'Gargron-master'

This commit is contained in:
Vavassor 2017-03-14 18:41:07 -04:00
commit f391538984
14 changed files with 314 additions and 118 deletions

View file

@ -47,6 +47,8 @@ import com.squareup.picasso.Picasso;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.Callback; import retrofit2.Callback;
import retrofit2.Response; import retrofit2.Response;
@ -63,10 +65,13 @@ public class AccountActivity extends BaseActivity {
private TabLayout tabLayout; private TabLayout tabLayout;
private Account loadedAccount; private Account loadedAccount;
@BindView(R.id.account_locked) ImageView accountLockedView;
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_account); setContentView(R.layout.activity_account);
ButterKnife.bind(this);
Intent intent = getIntent(); Intent intent = getIntent();
accountId = intent.getStringExtra("id"); accountId = intent.getStringExtra("id");
@ -193,6 +198,12 @@ public class AccountActivity extends BaseActivity {
note.setLinksClickable(true); note.setLinksClickable(true);
note.setMovementMethod(LinkMovementMethod.getInstance()); note.setMovementMethod(LinkMovementMethod.getInstance());
if (account.locked) {
accountLockedView.setVisibility(View.VISIBLE);
} else {
accountLockedView.setVisibility(View.GONE);
}
Picasso.with(this) Picasso.with(this)
.load(account.avatar) .load(account.avatar)
.placeholder(R.drawable.avatar_default) .placeholder(R.drawable.avatar_default)

View file

@ -40,10 +40,13 @@ import retrofit2.Callback;
public class AccountFragment extends Fragment implements AccountActionListener { public class AccountFragment extends Fragment implements AccountActionListener {
private static final String TAG = "Account"; // logging tag private static final String TAG = "Account"; // logging tag
private Call<List<Account>> listCall;
public enum Type { public enum Type {
FOLLOWS, FOLLOWS,
FOLLOWERS, FOLLOWERS,
BLOCKS, BLOCKS,
MUTES,
} }
private Type type; private Type type;
@ -141,6 +144,12 @@ public class AccountFragment extends Fragment implements AccountActionListener {
return rootView; return rootView;
} }
@Override
public void onDestroy() {
super.onDestroy();
if (listCall != null) listCall.cancel();
}
@Override @Override
public void onDestroyView() { public void onDestroyView() {
if (jumpToTopAllowed()) { if (jumpToTopAllowed()) {
@ -166,15 +175,23 @@ public class AccountFragment extends Fragment implements AccountActionListener {
switch (type) { switch (type) {
default: default:
case FOLLOWS: { case FOLLOWS: {
api.accountFollowing(accountId, fromId, uptoId, null).enqueue(cb); listCall = api.accountFollowing(accountId, fromId, uptoId, null);
listCall.enqueue(cb);
break; break;
} }
case FOLLOWERS: { case FOLLOWERS: {
api.accountFollowers(accountId, fromId, uptoId, null).enqueue(cb); listCall = api.accountFollowers(accountId, fromId, uptoId, null);
listCall.enqueue(cb);
break; break;
} }
case BLOCKS: { case BLOCKS: {
api.blocks(fromId, uptoId, null).enqueue(cb); listCall = api.blocks(fromId, uptoId, null);
listCall.enqueue(cb);
break;
}
case MUTES: {
listCall = api.mutes(fromId, uptoId, null);
listCall.enqueue(cb);
break; break;
} }
} }

View file

@ -399,11 +399,18 @@ public class ComposeActivity extends BaseActivity {
if (intent != null) { if (intent != null) {
inReplyToId = intent.getStringExtra("in_reply_to_id"); inReplyToId = intent.getStringExtra("in_reply_to_id");
String replyVisibility = intent.getStringExtra("reply_visibility"); String replyVisibility = intent.getStringExtra("reply_visibility");
if (replyVisibility != null) { if (replyVisibility != null) {
/* Override any remembered visibilty and instead adopt the visibility of the status // Lowest possible visibility setting in response
* to which this replies. */ if (statusVisibility.equals("private") || replyVisibility.equals("private")) {
statusVisibility = replyVisibility; statusVisibility = "private";
} else if (statusVisibility.equals("unlisted") || replyVisibility.equals("unlisted")) {
statusVisibility = "unlisted";
} else {
statusVisibility = replyVisibility;
}
} }
mentionedUsernames = intent.getStringArrayExtra("mentioned_usernames"); mentionedUsernames = intent.getStringArrayExtra("mentioned_usernames");
} }

View file

@ -22,7 +22,6 @@ import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
@ -34,6 +33,8 @@ import com.keylesspalace.tusky.entity.AppCredentials;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import butterknife.BindView;
import butterknife.ButterKnife;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.Callback; import retrofit2.Callback;
import retrofit2.Response; import retrofit2.Response;
@ -45,10 +46,14 @@ public class LoginActivity extends BaseActivity {
private static String OAUTH_SCOPES = "read write follow"; private static String OAUTH_SCOPES = "read write follow";
private SharedPreferences preferences; private SharedPreferences preferences;
private String domain; private String domain;
private String clientId; private String clientId;
private String clientSecret; private String clientSecret;
private EditText editText;
@BindView(R.id.edit_text_domain) EditText editText;
@BindView(R.id.button_login) Button button;
@BindView(R.id.no_account) TextView noAccount;
/** /**
* Chain together the key-value pairs into a query string, for either appending to a URL or * Chain together the key-value pairs into a query string, for either appending to a URL or
@ -117,6 +122,7 @@ public class LoginActivity extends BaseActivity {
* time. */ * time. */
String prefClientId = preferences.getString(domain + "/client_id", null); String prefClientId = preferences.getString(domain + "/client_id", null);
String prefClientSecret = preferences.getString(domain + "/client_secret", null); String prefClientSecret = preferences.getString(domain + "/client_secret", null);
if (prefClientId != null && prefClientSecret != null) { if (prefClientId != null && prefClientSecret != null) {
clientId = prefClientId; clientId = prefClientId;
clientSecret = prefClientSecret; clientSecret = prefClientSecret;
@ -126,9 +132,7 @@ public class LoginActivity extends BaseActivity {
@Override @Override
public void onResponse(Call<AppCredentials> call, Response<AppCredentials> response) { public void onResponse(Call<AppCredentials> call, Response<AppCredentials> response) {
if (!response.isSuccessful()) { if (!response.isSuccessful()) {
editText.setError( editText.setError(getString(R.string.error_failed_app_registration));
"This app could not obtain authentication from that server " +
"instance.");
Log.e(TAG, "App authentication failed. " + response.message()); Log.e(TAG, "App authentication failed. " + response.message());
return; return;
} }
@ -144,15 +148,17 @@ public class LoginActivity extends BaseActivity {
@Override @Override
public void onFailure(Call<AppCredentials> call, Throwable t) { public void onFailure(Call<AppCredentials> call, Throwable t) {
editText.setError( editText.setError(getString(R.string.error_failed_app_registration));
"This app could not obtain authentication from that server " +
"instance.");
t.printStackTrace(); t.printStackTrace();
} }
}; };
getApiFor(domain).authenticateApp(getString(R.string.app_name), getOauthRedirectUri(), OAUTH_SCOPES, try {
getString(R.string.app_website)).enqueue(callback); getApiFor(domain).authenticateApp(getString(R.string.app_name), getOauthRedirectUri(), OAUTH_SCOPES,
getString(R.string.app_website)).enqueue(callback);
} catch (IllegalArgumentException e) {
editText.setError(getString(R.string.error_invalid_domain));
}
} }
} }
@ -161,25 +167,26 @@ public class LoginActivity extends BaseActivity {
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login); setContentView(R.layout.activity_login);
ButterKnife.bind(this);
if (savedInstanceState != null) { if (savedInstanceState != null) {
domain = savedInstanceState.getString("domain"); domain = savedInstanceState.getString("domain");
clientId = savedInstanceState.getString("clientId"); clientId = savedInstanceState.getString("clientId");
clientSecret = savedInstanceState.getString("clientSecret"); clientSecret = savedInstanceState.getString("clientSecret");
} }
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
preferences = getSharedPreferences( preferences = getSharedPreferences(
getString(R.string.preferences_file_key), Context.MODE_PRIVATE); getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
Button button = (Button) findViewById(R.id.button_login);
editText = (EditText) findViewById(R.id.edit_text_domain);
button.setOnClickListener(new View.OnClickListener() { button.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
onButtonClick(editText); onButtonClick(editText);
} }
}); });
TextView noAccount = (TextView) findViewById(R.id.no_account);
final Context context = this; final Context context = this;
noAccount.setOnClickListener(new View.OnClickListener() { noAccount.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -233,11 +240,12 @@ public class LoginActivity extends BaseActivity {
* redirect that was given to the server. If so, its response is here! */ * redirect that was given to the server. If so, its response is here! */
Uri uri = getIntent().getData(); Uri uri = getIntent().getData();
String redirectUri = getOauthRedirectUri(); String redirectUri = getOauthRedirectUri();
if (uri != null && uri.toString().startsWith(redirectUri)) { if (uri != null && uri.toString().startsWith(redirectUri)) {
// This should either have returned an authorization code or an error. // This should either have returned an authorization code or an error.
String code = uri.getQueryParameter("code"); String code = uri.getQueryParameter("code");
String error = uri.getQueryParameter("error"); String error = uri.getQueryParameter("error");
final TextView errorText = (TextView) findViewById(R.id.text_error);
if (code != null) { if (code != null) {
/* During the redirect roundtrip this Activity usually dies, which wipes out the /* During the redirect roundtrip this Activity usually dies, which wipes out the
* instance variables, so they have to be recovered from where they were saved in * instance variables, so they have to be recovered from where they were saved in
@ -264,15 +272,16 @@ public class LoginActivity extends BaseActivity {
editText.setError(t.getMessage()); editText.setError(t.getMessage());
} }
}; };
getApiFor(domain).fetchOAuthToken(clientId, clientSecret, redirectUri, code, getApiFor(domain).fetchOAuthToken(clientId, clientSecret, redirectUri, code,
"authorization_code").enqueue(callback); "authorization_code").enqueue(callback);
} else if (error != null) { } else if (error != null) {
/* Authorization failed. Put the error response where the user can read it and they /* Authorization failed. Put the error response where the user can read it and they
* can try again. */ * can try again. */
errorText.setText(error); editText.setError(error);
} else { } else {
// This case means a junk response was received somehow. // This case means a junk response was received somehow.
errorText.setText(getString(R.string.error_authorization_unknown)); editText.setError(getString(R.string.error_authorization_unknown));
} }
} }
} }

View file

@ -15,6 +15,7 @@
package com.keylesspalace.tusky; package com.keylesspalace.tusky;
import android.app.NotificationManager;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@ -22,12 +23,12 @@ import android.graphics.PorterDuff;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle;
import android.os.PersistableBundle; import android.os.PersistableBundle;
import android.support.design.widget.FloatingActionButton; import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.TabLayout; import android.support.design.widget.TabLayout;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager;
import android.os.Bundle;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.Spanned; import android.text.Spanned;
import android.text.TextUtils; import android.text.TextUtils;
@ -187,6 +188,18 @@ public class MainActivity extends BaseActivity {
}); });
} }
@Override
protected void onResume() {
super.onResume();
SharedPreferences notificationPreferences = getApplicationContext().getSharedPreferences("Notifications", MODE_PRIVATE);
SharedPreferences.Editor editor = notificationPreferences.edit();
editor.putString("current", "[]");
editor.apply();
((NotificationManager) (getSystemService(NOTIFICATION_SERVICE))).cancel(MyFirebaseMessagingService.NOTIFY_ID);
}
@Override @Override
public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) { public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
ArrayList<Integer> pageHistoryList = new ArrayList<>(); ArrayList<Integer> pageHistoryList = new ArrayList<>();

View file

@ -22,6 +22,9 @@ import com.keylesspalace.tusky.entity.Notification;
import com.squareup.picasso.Picasso; import com.squareup.picasso.Picasso;
import com.squareup.picasso.Target; import com.squareup.picasso.Target;
import org.json.JSONArray;
import org.json.JSONException;
import java.io.IOException; import java.io.IOException;
import okhttp3.Interceptor; import okhttp3.Interceptor;
@ -36,6 +39,7 @@ import retrofit2.converter.gson.GsonConverterFactory;
public class MyFirebaseMessagingService extends FirebaseMessagingService { public class MyFirebaseMessagingService extends FirebaseMessagingService {
private MastodonAPI mastodonAPI; private MastodonAPI mastodonAPI;
private static final String TAG = "MyFirebaseMessagingService"; private static final String TAG = "MyFirebaseMessagingService";
public static final int NOTIFY_ID = 666;
@Override @Override
public void onMessageReceived(RemoteMessage remoteMessage) { public void onMessageReceived(RemoteMessage remoteMessage) {
@ -112,6 +116,34 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
private void buildNotification(Notification body) { private void buildNotification(Notification body) {
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
final SharedPreferences notificationPreferences = getApplicationContext().getSharedPreferences("Notifications", MODE_PRIVATE);
String rawCurrentNotifications = notificationPreferences.getString("current", "[]");
JSONArray currentNotifications;
try {
currentNotifications = new JSONArray(rawCurrentNotifications);
} catch (JSONException e) {
currentNotifications = new JSONArray();
}
boolean alreadyContains = false;
for(int i = 0; i < currentNotifications.length(); i++) {
try {
if (currentNotifications.getString(i).equals(body.account.displayName)) {
alreadyContains = true;
}
} catch (JSONException e) {
e.printStackTrace();
}
}
if (!alreadyContains) currentNotifications.put(body.account.displayName);
SharedPreferences.Editor editor = notificationPreferences.edit();
editor.putString("current", currentNotifications.toString());
editor.commit();
Intent resultIntent = new Intent(this, MainActivity.class); Intent resultIntent = new Intent(this, MainActivity.class);
resultIntent.putExtra("tab_position", 1); resultIntent.putExtra("tab_position", 1);
@ -122,73 +154,109 @@ public class MyFirebaseMessagingService extends FirebaseMessagingService {
final NotificationCompat.Builder builder = new NotificationCompat.Builder(this) final NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_notify) .setSmallIcon(R.drawable.ic_notify)
.setAutoCancel(true)
.setContentIntent(resultPendingIntent) .setContentIntent(resultPendingIntent)
.setDefaults(0); // So it doesn't ring twice, notify only in Target callback .setDefaults(0); // So it doesn't ring twice, notify only in Target callback
final Integer mId = (int)(System.currentTimeMillis() / 1000); if (currentNotifications.length() == 1) {
builder.setContentTitle(titleForType(body))
.setContentText(truncateWithEllipses(bodyForType(body), 40));
Target mTarget = new Target() { Target mTarget = new Target() {
@Override @Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
builder.setLargeIcon(bitmap); builder.setLargeIcon(bitmap);
if (preferences.getBoolean("notificationAlertSound", true)) { setupPreferences(preferences, builder);
builder.setSound(Settings.System.DEFAULT_NOTIFICATION_URI);
((NotificationManager) (getSystemService(NOTIFICATION_SERVICE))).notify(NOTIFY_ID, builder.build());
} }
if (preferences.getBoolean("notificationStyleVibrate", false)) { @Override
builder.setVibrate(new long[] { 500, 500 }); public void onBitmapFailed(Drawable errorDrawable) {
} }
if (preferences.getBoolean("notificationStyleLight", false)) { @Override
builder.setLights(0xFF00FF8F, 300, 1000); public void onPrepareLoad(Drawable placeHolderDrawable) {
} }
};
((NotificationManager) (getSystemService(NOTIFICATION_SERVICE))).notify(mId, builder.build()); Picasso.with(this)
.load(body.account.avatar)
.placeholder(R.drawable.avatar_default)
.transform(new RoundedTransformation(7, 0))
.into(mTarget);
} else {
setupPreferences(preferences, builder);
try {
builder.setContentTitle(String.format(getString(R.string.notification_title_summary), currentNotifications.length()))
.setContentText(truncateWithEllipses(joinNames(currentNotifications), 40));
} catch (JSONException e) {
e.printStackTrace();
} }
}
@Override
public void onBitmapFailed(Drawable errorDrawable) {
}
@Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
}
};
Picasso.with(this)
.load(body.account.avatar)
.placeholder(R.drawable.avatar_default)
.transform(new RoundedTransformation(7, 0))
.into(mTarget);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
builder.setVisibility(android.app.Notification.VISIBILITY_PRIVATE); builder.setVisibility(android.app.Notification.VISIBILITY_PRIVATE);
builder.setCategory(android.app.Notification.CATEGORY_SOCIAL); builder.setCategory(android.app.Notification.CATEGORY_SOCIAL);
} }
switch (body.type) { ((NotificationManager) (getSystemService(NOTIFICATION_SERVICE))).notify(NOTIFY_ID, builder.build());
case MENTION: }
builder.setContentTitle(String.format(getString(R.string.notification_mention_format), body.account.getDisplayName()))
.setContentText(truncateWithEllipses(body.status.content.toString(), 40)); private void setupPreferences(SharedPreferences preferences, NotificationCompat.Builder builder) {
break; if (preferences.getBoolean("notificationAlertSound", true)) {
case FOLLOW: builder.setSound(Settings.System.DEFAULT_NOTIFICATION_URI);
builder.setContentTitle(String.format(getString(R.string.notification_follow_format), body.account.getDisplayName()))
.setContentText(truncateWithEllipses(body.account.username, 40));
break;
case FAVOURITE:
builder.setContentTitle(String.format(getString(R.string.notification_favourite_format), body.account.getDisplayName()))
.setContentText(truncateWithEllipses(body.status.content.toString(), 40));
break;
case REBLOG:
builder.setContentTitle(String.format(getString(R.string.notification_reblog_format), body.account.getDisplayName()))
.setContentText(truncateWithEllipses(body.status.content.toString(), 40));
break;
} }
((NotificationManager) (getSystemService(NOTIFICATION_SERVICE))).notify(mId, builder.build()); if (preferences.getBoolean("notificationStyleVibrate", false)) {
builder.setVibrate(new long[] { 500, 500 });
}
if (preferences.getBoolean("notificationStyleLight", false)) {
builder.setLights(0xFF00FF8F, 300, 1000);
}
}
private String joinNames(JSONArray array) throws JSONException {
if (array.length() > 3) {
return String.format(getString(R.string.notification_summary_large), array.get(0), array.get(1), array.get(2), array.length() - 3);
} else if (array.length() == 3) {
return String.format(getString(R.string.notification_summary_medium), array.get(0), array.get(1), array.get(2));
} else if (array.length() == 2) {
return String.format(getString(R.string.notification_summary_small), array.get(0), array.get(1));
}
return null;
}
private String titleForType(Notification notification) {
switch (notification.type) {
case MENTION:
return String.format(getString(R.string.notification_mention_format), notification.account.getDisplayName());
case FOLLOW:
return String.format(getString(R.string.notification_follow_format), notification.account.getDisplayName());
case FAVOURITE:
return String.format(getString(R.string.notification_favourite_format), notification.account.getDisplayName());
case REBLOG:
return String.format(getString(R.string.notification_reblog_format), notification.account.getDisplayName());
}
return null;
}
private String bodyForType(Notification notification) {
switch (notification.type) {
case FOLLOW:
return notification.account.username;
case MENTION:
case FAVOURITE:
case REBLOG:
return notification.status.content.toString();
}
return null;
} }
} }

View file

@ -48,6 +48,7 @@ public class NotificationsFragment extends SFragment implements
private EndlessOnScrollListener scrollListener; private EndlessOnScrollListener scrollListener;
private NotificationsAdapter adapter; private NotificationsAdapter adapter;
private TabLayout.OnTabSelectedListener onTabSelectedListener; private TabLayout.OnTabSelectedListener onTabSelectedListener;
private Call<List<Notification>> listCall;
public static NotificationsFragment newInstance() { public static NotificationsFragment newInstance() {
NotificationsFragment fragment = new NotificationsFragment(); NotificationsFragment fragment = new NotificationsFragment();
@ -122,6 +123,12 @@ public class NotificationsFragment extends SFragment implements
sendFetchNotificationsRequest(); sendFetchNotificationsRequest();
} }
@Override
public void onDestroy() {
super.onDestroy();
if (listCall != null) listCall.cancel();
}
@Override @Override
public void onDestroyView() { public void onDestroyView() {
TabLayout tabLayout = (TabLayout) getActivity().findViewById(R.id.tab_layout); TabLayout tabLayout = (TabLayout) getActivity().findViewById(R.id.tab_layout);
@ -137,7 +144,9 @@ public class NotificationsFragment extends SFragment implements
private void sendFetchNotificationsRequest(final String fromId, String uptoId) { private void sendFetchNotificationsRequest(final String fromId, String uptoId) {
MastodonAPI api = ((BaseActivity) getActivity()).mastodonAPI; MastodonAPI api = ((BaseActivity) getActivity()).mastodonAPI;
api.notifications(fromId, uptoId, null).enqueue(new Callback<List<Notification>>() { listCall = api.notifications(fromId, uptoId, null);
listCall.enqueue(new Callback<List<Notification>>() {
@Override @Override
public void onResponse(Call<List<Notification>> call, retrofit2.Response<List<Notification>> response) { public void onResponse(Call<List<Notification>> call, retrofit2.Response<List<Notification>> response) {
if (response.isSuccessful()) { if (response.isSuccessful()) {

View file

@ -20,8 +20,9 @@ import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction;
import android.support.v7.widget.PopupMenu; import android.support.v7.widget.PopupMenu;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.text.Spanned; import android.text.Spanned;
@ -47,7 +48,6 @@ import retrofit2.Callback;
public class SFragment extends Fragment { public class SFragment extends Fragment {
protected String loggedInAccountId; protected String loggedInAccountId;
protected String loggedInUsername; protected String loggedInUsername;
private MastodonAPI api;
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
@ -57,7 +57,10 @@ public class SFragment extends Fragment {
getString(R.string.preferences_file_key), Context.MODE_PRIVATE); getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
loggedInAccountId = preferences.getString("loggedInAccountId", null); loggedInAccountId = preferences.getString("loggedInAccountId", null);
loggedInUsername = preferences.getString("loggedInAccountUsername", null); loggedInUsername = preferences.getString("loggedInAccountUsername", null);
api = ((BaseActivity) getActivity()).mastodonAPI; }
public MastodonAPI getApi() {
return ((BaseActivity) getActivity()).mastodonAPI;
} }
protected void reply(Status status) { protected void reply(Status status) {
@ -85,6 +88,11 @@ public class SFragment extends Fragment {
public void onResponse(Call<Status> call, retrofit2.Response<Status> response) { public void onResponse(Call<Status> call, retrofit2.Response<Status> response) {
if (response.isSuccessful()) { if (response.isSuccessful()) {
status.reblogged = reblog; status.reblogged = reblog;
if (status.reblog != null) {
status.reblog.reblogged = reblog;
}
adapter.notifyItemChanged(position); adapter.notifyItemChanged(position);
} }
} }
@ -96,9 +104,9 @@ public class SFragment extends Fragment {
}; };
if (reblog) { if (reblog) {
api.reblogStatus(id).enqueue(cb); getApi().reblogStatus(id).enqueue(cb);
} else { } else {
api.unreblogStatus(id).enqueue(cb); getApi().unreblogStatus(id).enqueue(cb);
} }
} }
@ -111,6 +119,11 @@ public class SFragment extends Fragment {
public void onResponse(Call<Status> call, retrofit2.Response<Status> response) { public void onResponse(Call<Status> call, retrofit2.Response<Status> response) {
if (response.isSuccessful()) { if (response.isSuccessful()) {
status.favourited = favourite; status.favourited = favourite;
if (status.reblog != null) {
status.reblog.favourited = favourite;
}
adapter.notifyItemChanged(position); adapter.notifyItemChanged(position);
} }
} }
@ -122,14 +135,14 @@ public class SFragment extends Fragment {
}; };
if (favourite) { if (favourite) {
api.favouriteStatus(id).enqueue(cb); getApi().favouriteStatus(id).enqueue(cb);
} else { } else {
api.unfavouriteStatus(id).enqueue(cb); getApi().unfavouriteStatus(id).enqueue(cb);
} }
} }
private void block(String id) { private void block(String id) {
api.blockAccount(id).enqueue(new Callback<Relationship>() { getApi().blockAccount(id).enqueue(new Callback<Relationship>() {
@Override @Override
public void onResponse(Call<Relationship> call, retrofit2.Response<Relationship> response) { public void onResponse(Call<Relationship> call, retrofit2.Response<Relationship> response) {
@ -143,7 +156,7 @@ public class SFragment extends Fragment {
} }
private void delete(String id) { private void delete(String id) {
api.deleteStatus(id).enqueue(new Callback<ResponseBody>() { getApi().deleteStatus(id).enqueue(new Callback<ResponseBody>() {
@Override @Override
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) { public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
@ -206,13 +219,9 @@ public class SFragment extends Fragment {
protected void viewMedia(String url, Status.MediaAttachment.Type type) { protected void viewMedia(String url, Status.MediaAttachment.Type type) {
switch (type) { switch (type) {
case IMAGE: { case IMAGE: {
Fragment newFragment = ViewMediaFragment.newInstance(url); DialogFragment newFragment = ViewMediaFragment.newInstance(url);
FragmentTransaction ft = getFragmentManager().beginTransaction();
FragmentManager manager = getFragmentManager(); newFragment.show(ft, "view_media");
manager.beginTransaction()
.add(R.id.overlay_fragment_container, newFragment)
.addToBackStack(null)
.commit();
break; break;
} }
case GIFV: case GIFV:

View file

@ -39,6 +39,8 @@ public class TimelineFragment extends SFragment implements
SwipeRefreshLayout.OnRefreshListener, StatusActionListener { SwipeRefreshLayout.OnRefreshListener, StatusActionListener {
private static final String TAG = "Timeline"; // logging tag private static final String TAG = "Timeline"; // logging tag
private Call<List<Status>> listCall;
enum Kind { enum Kind {
HOME, HOME,
PUBLIC, PUBLIC,
@ -144,6 +146,12 @@ public class TimelineFragment extends SFragment implements
sendFetchTimelineRequest(); sendFetchTimelineRequest();
} }
@Override
public void onDestroy() {
super.onDestroy();
if (listCall != null) listCall.cancel();
}
@Override @Override
public void onDestroyView() { public void onDestroyView() {
if (jumpToTopAllowed()) { if (jumpToTopAllowed()) {
@ -184,23 +192,28 @@ public class TimelineFragment extends SFragment implements
switch (kind) { switch (kind) {
default: default:
case HOME: { case HOME: {
api.homeTimeline(fromId, uptoId, null).enqueue(cb); listCall = api.homeTimeline(fromId, uptoId, null);
listCall.enqueue(cb);
break; break;
} }
case PUBLIC: { case PUBLIC: {
api.publicTimeline(null, fromId, uptoId, null).enqueue(cb); listCall = api.publicTimeline(null, fromId, uptoId, null);
listCall.enqueue(cb);
break; break;
} }
case TAG: { case TAG: {
api.hashtagTimeline(hashtagOrId, null, fromId, uptoId, null).enqueue(cb); listCall = api.hashtagTimeline(hashtagOrId, null, fromId, uptoId, null);
listCall.enqueue(cb);
break; break;
} }
case USER: { case USER: {
api.accountStatuses(hashtagOrId, fromId, uptoId, null).enqueue(cb); listCall = api.accountStatuses(hashtagOrId, fromId, uptoId, null);
listCall.enqueue(cb);
break; break;
} }
case FAVOURITES: { case FAVOURITES: {
api.favourites(fromId, uptoId, null).enqueue(cb); listCall = api.favourites(fromId, uptoId, null);
listCall.enqueue(cb);
break; break;
} }
} }

View file

@ -16,21 +16,27 @@
package com.keylesspalace.tusky; package com.keylesspalace.tusky;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment; import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowManager;
import com.squareup.picasso.Callback; import com.squareup.picasso.Callback;
import com.squareup.picasso.Picasso; import com.squareup.picasso.Picasso;
import butterknife.BindView;
import butterknife.ButterKnife;
import uk.co.senab.photoview.PhotoView; import uk.co.senab.photoview.PhotoView;
import uk.co.senab.photoview.PhotoViewAttacher; import uk.co.senab.photoview.PhotoViewAttacher;
public class ViewMediaFragment extends Fragment { public class ViewMediaFragment extends DialogFragment {
private PhotoViewAttacher attacher; private PhotoViewAttacher attacher;
@BindView(R.id.view_media_image) PhotoView photoView;
public static ViewMediaFragment newInstance(String url) { public static ViewMediaFragment newInstance(String url) {
Bundle arguments = new Bundle(); Bundle arguments = new Bundle();
ViewMediaFragment fragment = new ViewMediaFragment(); ViewMediaFragment fragment = new ViewMediaFragment();
@ -39,14 +45,29 @@ public class ViewMediaFragment extends Fragment {
return fragment; return fragment;
} }
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(DialogFragment.STYLE_NORMAL, R.style.Dialog_FullScreen);
}
@Override
public void onResume() {
ViewGroup.LayoutParams params = getDialog().getWindow().getAttributes();
params.width = WindowManager.LayoutParams.MATCH_PARENT;
params.height = WindowManager.LayoutParams.MATCH_PARENT;
getDialog().getWindow().setAttributes((android.view.WindowManager.LayoutParams) params);
super.onResume();
}
@Override @Override
public View onCreateView(LayoutInflater inflater, final ViewGroup container, public View onCreateView(LayoutInflater inflater, final ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_view_media, container, false); View rootView = inflater.inflate(R.layout.fragment_view_media, container, false);
ButterKnife.bind(this, rootView);
Bundle arguments = getArguments(); Bundle arguments = getArguments();
String url = arguments.getString("url"); String url = arguments.getString("url");
PhotoView photoView = (PhotoView) rootView.findViewById(R.id.view_media_image);
attacher = new PhotoViewAttacher(photoView); attacher = new PhotoViewAttacher(photoView);
@ -84,8 +105,4 @@ public class ViewMediaFragment extends Fragment {
attacher.cleanup(); attacher.cleanup();
super.onDestroyView(); super.onDestroyView();
} }
private void dismiss() {
getFragmentManager().popBackStack();
}
} }

View file

@ -78,14 +78,30 @@
android:textColor="?android:textColorPrimary" android:textColor="?android:textColorPrimary"
android:textSize="18sp" /> android:textSize="18sp" />
<TextView <RelativeLayout
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content">
android:maxLines="1" <TextView
android:ellipsize="end" android:layout_width="wrap_content"
android:textColor="?android:textColorSecondary" android:layout_height="wrap_content"
android:id="@+id/account_username" /> android:maxLines="1"
android:ellipsize="end"
android:textColor="?android:textColorSecondary"
android:id="@+id/account_username" />
<ImageView
android:id="@+id/account_locked"
android:visibility="gone"
android:layout_centerVertical="true"
android:layout_width="16sp"
android:layout_height="16sp"
android:layout_marginLeft="4dp"
android:layout_marginStart="4dp"
android:layout_toEndOf="@id/account_username"
app:srcCompat="@drawable/reblog_disabled_light"
android:tint="?android:textColorSecondary"
android:layout_toRightOf="@id/account_username" />
</RelativeLayout>
</LinearLayout> </LinearLayout>
</RelativeLayout> </RelativeLayout>

View file

@ -1,11 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:clickable="true" android:clickable="true"
android:background="#60000000"> android:layout_gravity="center"
android:background="@android:color/black">
<uk.co.senab.photoview.PhotoView <uk.co.senab.photoview.PhotoView
android:id="@+id/view_media_image" android:id="@+id/view_media_image"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />
</RelativeLayout> </FrameLayout>

View file

@ -102,12 +102,8 @@
<string name="visibility_unlisted">Everyone can see, but not on public timelines</string> <string name="visibility_unlisted">Everyone can see, but not on public timelines</string>
<string name="visibility_private">Only followers and mentions can see</string> <string name="visibility_private">Only followers and mentions can see</string>
<string name="notification_service_description">Allows Tusky to check for Mastodon notifications.</string>
<string name="notification_service_several_mentions">%d new mentions</string>
<string name="notification_service_one_mention">Mention from %s</string>
<string name="pref_title_notification_settings">Notifications</string> <string name="pref_title_notification_settings">Notifications</string>
<string name="pref_title_pull_notifications">Enable pull notifcations</string> <string name="pref_title_pull_notifications">Enable pull notifications</string>
<string name="pref_summary_pull_notifications">Check for notifications periodically</string> <string name="pref_summary_pull_notifications">Check for notifications periodically</string>
<string name="pref_title_pull_notification_check_interval">Check interval</string> <string name="pref_title_pull_notification_check_interval">Check interval</string>
<string name="pref_summary_pull_notification_check_interval">How often to pull</string> <string name="pref_summary_pull_notification_check_interval">How often to pull</string>
@ -131,5 +127,10 @@
<string name="action_mention">Mention</string> <string name="action_mention">Mention</string>
<string name="tusky_api_url">https://tuskynotifier.keylesspalace.com</string> <string name="tusky_api_url">https://tuskynotifier.keylesspalace.com</string>
<string name="notification_mention_format">%s mentioned you</string> <string name="notification_mention_format">%s mentioned you</string>
<string name="error_invalid_domain">Invalid domain entered</string>
<string name="error_failed_app_registration">This app could not obtain authentication from that server instance.</string>
<string name="notification_summary_large">%1$s, %2$s, %3$s and %4$d others</string>
<string name="notification_summary_medium">%1$s, %2$s, and %3$s</string>
<string name="notification_summary_small">%1$s and %2$s</string>
<string name="notification_title_summary">%d new interactions</string>
</resources> </resources>

View file

@ -92,6 +92,11 @@
<item name="windowActionBarOverlay">true</item> <item name="windowActionBarOverlay">true</item>
</style> </style>
<style name="Dialog.FullScreen" parent="Theme.AppCompat.Dialog">
<item name="android:padding">0dp</item>
<item name="android:windowBackground">@android:color/black</item>
</style>
<!--Light Application Theme Styles--> <!--Light Application Theme Styles-->
<style name="AppTheme.Light" parent="Theme.AppCompat.Light.NoActionBar"> <style name="AppTheme.Light" parent="Theme.AppCompat.Light.NoActionBar">