diff --git a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java index df7afd39..63d16be6 100644 --- a/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/AccountActivity.java @@ -33,7 +33,6 @@ import android.support.v4.view.ViewCompat; import android.support.v4.view.ViewPager; import android.support.v7.app.ActionBar; import android.support.v7.widget.Toolbar; -import android.text.method.LinkMovementMethod; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -206,9 +205,21 @@ public class AccountActivity extends BaseActivity implements SFragment.OnUserRem displayName.setText(account.getDisplayName()); - note.setText(account.note); - note.setLinksClickable(true); - note.setMovementMethod(LinkMovementMethod.getInstance()); + LinkHelper.setClickableText(note, account.note, null, new LinkListener() { + @Override + public void onViewTag(String tag) { + Intent intent = new Intent(AccountActivity.this, ViewTagActivity.class); + intent.putExtra("hashtag", tag); + startActivity(intent); + } + + @Override + public void onViewAccount(String id) { + Intent intent = new Intent(AccountActivity.this, AccountActivity.class); + intent.putExtra("id", id); + startActivity(intent); + } + }); if (account.locked) { accountLockedView.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/com/keylesspalace/tusky/BaseFragment.java b/app/src/main/java/com/keylesspalace/tusky/BaseFragment.java index 79569e82..0a9dcbcf 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BaseFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/BaseFragment.java @@ -15,6 +15,8 @@ package com.keylesspalace.tusky; +import android.content.Context; +import android.content.SharedPreferences; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; @@ -40,4 +42,9 @@ public class BaseFragment extends Fragment { } super.onDestroy(); } + + protected SharedPreferences getPrivatePreferences() { + return getContext().getSharedPreferences( + getString(R.string.preferences_file_key), Context.MODE_PRIVATE); + } } diff --git a/app/src/main/java/com/keylesspalace/tusky/CustomTabURLSpan.java b/app/src/main/java/com/keylesspalace/tusky/CustomTabURLSpan.java index dce7d45c..7af9a84f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/CustomTabURLSpan.java +++ b/app/src/main/java/com/keylesspalace/tusky/CustomTabURLSpan.java @@ -54,7 +54,7 @@ class CustomTabURLSpan extends URLSpan { customTabsIntent.launchUrl(context, uri); } } catch (ActivityNotFoundException e) { - android.util.Log.w("URLSpan", "Activity was not found for intent, " + customTabsIntent.toString()); + Log.w("URLSpan", "Activity was not found for intent, " + customTabsIntent.toString()); } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/DownsizeImageTask.java b/app/src/main/java/com/keylesspalace/tusky/DownsizeImageTask.java index e9fcc11a..4e0d77db 100644 --- a/app/src/main/java/com/keylesspalace/tusky/DownsizeImageTask.java +++ b/app/src/main/java/com/keylesspalace/tusky/DownsizeImageTask.java @@ -21,6 +21,7 @@ import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.net.Uri; import android.os.AsyncTask; +import android.support.annotation.Nullable; import android.support.media.ExifInterface; import java.io.ByteArrayOutputStream; @@ -42,6 +43,7 @@ class DownsizeImageTask extends AsyncTask { this.listener = listener; } + @Nullable private static Bitmap reorientBitmap(Bitmap bitmap, int orientation) { Matrix matrix = new Matrix(); switch (orientation) { diff --git a/app/src/main/java/com/keylesspalace/tusky/LinkHelper.java b/app/src/main/java/com/keylesspalace/tusky/LinkHelper.java new file mode 100644 index 00000000..e2b2fedc --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/LinkHelper.java @@ -0,0 +1,67 @@ +package com.keylesspalace.tusky; + +import android.preference.PreferenceManager; +import android.support.annotation.Nullable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.method.LinkMovementMethod; +import android.text.style.ClickableSpan; +import android.text.style.URLSpan; +import android.view.View; +import android.widget.TextView; + +import com.keylesspalace.tusky.entity.Status; + +class LinkHelper { + static void setClickableText(TextView view, Spanned content, + @Nullable Status.Mention[] mentions, + final LinkListener listener) { + SpannableStringBuilder builder = new SpannableStringBuilder(content); + boolean useCustomTabs = PreferenceManager.getDefaultSharedPreferences(view.getContext()) + .getBoolean("customTabs", true); + URLSpan[] urlSpans = content.getSpans(0, content.length(), URLSpan.class); + for (URLSpan span : urlSpans) { + int start = builder.getSpanStart(span); + int end = builder.getSpanEnd(span); + int flags = builder.getSpanFlags(span); + CharSequence text = builder.subSequence(start, end); + if (text.charAt(0) == '#') { + final String tag = text.subSequence(1, text.length()).toString(); + ClickableSpan newSpan = new ClickableSpan() { + @Override + public void onClick(View widget) { + listener.onViewTag(tag); + } + }; + builder.removeSpan(span); + builder.setSpan(newSpan, start, end, flags); + } else if (text.charAt(0) == '@' && mentions != null) { + final String accountUsername = text.subSequence(1, text.length()).toString(); + String id = null; + for (Status.Mention mention : mentions) { + if (mention.localUsername.equals(accountUsername)) { + id = mention.id; + } + } + if (id != null) { + final String accountId = id; + ClickableSpan newSpan = new ClickableSpan() { + @Override + public void onClick(View widget) { + listener.onViewAccount(accountId); + } + }; + builder.removeSpan(span); + builder.setSpan(newSpan, start, end, flags); + } + } else if (useCustomTabs) { + ClickableSpan newSpan = new CustomTabURLSpan(span.getURL()); + builder.removeSpan(span); + builder.setSpan(newSpan, start, end, flags); + } + } + view.setText(builder); + view.setLinksClickable(true); + view.setMovementMethod(LinkMovementMethod.getInstance()); + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/LinkListener.java b/app/src/main/java/com/keylesspalace/tusky/LinkListener.java new file mode 100644 index 00000000..de0242bc --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/LinkListener.java @@ -0,0 +1,6 @@ +package com.keylesspalace.tusky; + +interface LinkListener { + void onViewTag(String tag); + void onViewAccount(String id); +} diff --git a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java index 100bb01e..3854a48e 100644 --- a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java @@ -16,6 +16,7 @@ package com.keylesspalace.tusky; import android.app.AlertDialog; +import android.content.ActivityNotFoundException; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -24,6 +25,8 @@ import android.net.Uri; import android.os.Bundle; import android.preference.PreferenceManager; import android.support.annotation.NonNull; +import android.support.customtabs.CustomTabsIntent; +import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.text.method.LinkMovementMethod; import android.view.View; @@ -224,6 +227,36 @@ public class LoginActivity extends AppCompatActivity { return s.toString(); } + private static boolean openInCustomTab(Uri uri, Context context) { + boolean lightTheme = PreferenceManager.getDefaultSharedPreferences(context) + .getBoolean("lightTheme", false); + int toolbarColorRes; + if (lightTheme) { + toolbarColorRes = R.color.custom_tab_toolbar_light; + } else { + toolbarColorRes = R.color.custom_tab_toolbar_dark; + } + int toolbarColor = ContextCompat.getColor(context, toolbarColorRes); + CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder(); + builder.setToolbarColor(toolbarColor); + CustomTabsIntent customTabsIntent = builder.build(); + try { + String packageName = CustomTabsHelper.getPackageNameToUse(context); + /* If we cant find a package name, it means theres no browser that supports + * Chrome Custom Tabs installed. So, we fallback to the webview */ + if (packageName == null) { + return false; + } else { + customTabsIntent.intent.setPackage(packageName); + customTabsIntent.launchUrl(context, uri); + } + } catch (ActivityNotFoundException e) { + Log.w("URLSpan", "Activity was not found for intent, " + customTabsIntent.toString()); + return false; + } + return true; + } + private void redirectUserToAuthorizeAndLogin(EditText editText) { /* To authorize this app and log in it's necessary to redirect to the domain given, * activity_login there, and the server will redirect back to the app with its response. */ @@ -235,11 +268,14 @@ public class LoginActivity extends AppCompatActivity { parameters.put("response_type", "code"); parameters.put("scope", OAUTH_SCOPES); String url = "https://" + domain + endpoint + "?" + toQueryString(parameters); - Intent viewIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - if (viewIntent.resolveActivity(getPackageManager()) != null) { - startActivity(viewIntent); - } else { - editText.setError(getString(R.string.error_no_web_browser_found)); + Uri uri = Uri.parse(url); + if (!openInCustomTab(uri, this)) { + Intent viewIntent = new Intent(Intent.ACTION_VIEW, uri); + if (viewIntent.resolveActivity(getPackageManager()) != null) { + startActivity(viewIntent); + } else { + editText.setError(getString(R.string.error_no_web_browser_found)); + } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/SFragment.java b/app/src/main/java/com/keylesspalace/tusky/SFragment.java index 3fb124b2..3af5cce0 100644 --- a/app/src/main/java/com/keylesspalace/tusky/SFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/SFragment.java @@ -15,7 +15,6 @@ package com.keylesspalace.tusky; -import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; @@ -60,8 +59,7 @@ public abstract class SFragment extends BaseFragment { public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - SharedPreferences preferences = getContext().getSharedPreferences( - getString(R.string.preferences_file_key), Context.MODE_PRIVATE); + SharedPreferences preferences = getPrivatePreferences(); loggedInAccountId = preferences.getString("loggedInAccountId", null); loggedInUsername = preferences.getString("loggedInAccountUsername", null); } diff --git a/app/src/main/java/com/keylesspalace/tusky/StatusActionListener.java b/app/src/main/java/com/keylesspalace/tusky/StatusActionListener.java index 9d14ed96..1004bb82 100644 --- a/app/src/main/java/com/keylesspalace/tusky/StatusActionListener.java +++ b/app/src/main/java/com/keylesspalace/tusky/StatusActionListener.java @@ -19,13 +19,11 @@ import android.view.View; import com.keylesspalace.tusky.entity.Status; -interface StatusActionListener { +interface StatusActionListener extends LinkListener { void onReply(int position); void onReblog(final boolean reblog, final int position); void onFavourite(final boolean favourite, final int position); void onMore(View view, final int position); void onViewMedia(String url, Status.MediaAttachment.Type type); void onViewThread(int position); - void onViewTag(String tag); - void onViewAccount(String id); } diff --git a/app/src/main/java/com/keylesspalace/tusky/StatusViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/StatusViewHolder.java index 68215528..5f260b6d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/StatusViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/StatusViewHolder.java @@ -102,57 +102,10 @@ class StatusViewHolder extends RecyclerView.ViewHolder { } private void setContent(Spanned content, Status.Mention[] mentions, - final StatusActionListener listener) { + StatusActionListener listener) { /* Redirect URLSpan's in the status content to the listener for viewing tag pages and * account pages. */ - SpannableStringBuilder builder = new SpannableStringBuilder(content); - boolean useCustomTabs = PreferenceManager.getDefaultSharedPreferences(container.getContext()).getBoolean("customTabs", true); - URLSpan[] urlSpans = content.getSpans(0, content.length(), URLSpan.class); - for (URLSpan span : urlSpans) { - int start = builder.getSpanStart(span); - int end = builder.getSpanEnd(span); - int flags = builder.getSpanFlags(span); - CharSequence text = builder.subSequence(start, end); - if (text.charAt(0) == '#') { - final String tag = text.subSequence(1, text.length()).toString(); - ClickableSpan newSpan = new ClickableSpan() { - @Override - public void onClick(View widget) { - listener.onViewTag(tag); - } - }; - builder.removeSpan(span); - builder.setSpan(newSpan, start, end, flags); - } else if (text.charAt(0) == '@') { - final String accountUsername = text.subSequence(1, text.length()).toString(); - String id = null; - for (Status.Mention mention: mentions) { - if (mention.username.equals(accountUsername)) { - id = mention.id; - } - } - if (id != null) { - final String accountId = id; - ClickableSpan newSpan = new ClickableSpan() { - @Override - public void onClick(View widget) { - listener.onViewAccount(accountId); - } - }; - builder.removeSpan(span); - builder.setSpan(newSpan, start, end, flags); - } - } else if (useCustomTabs) { - ClickableSpan newSpan = new CustomTabURLSpan(span.getURL()); - builder.removeSpan(span); - builder.setSpan(newSpan, start, end, flags); - } - } - // Set the contents. - this.content.setText(builder); - // Make links clickable. - this.content.setLinksClickable(true); - this.content.setMovementMethod(LinkMovementMethod.getInstance()); + LinkHelper.setClickableText(this.content, content, mentions, listener); } private void setAvatar(String url) { diff --git a/app/src/main/java/com/keylesspalace/tusky/TimelineFragment.java b/app/src/main/java/com/keylesspalace/tusky/TimelineFragment.java index 6973524a..d23bbed3 100644 --- a/app/src/main/java/com/keylesspalace/tusky/TimelineFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/TimelineFragment.java @@ -39,7 +39,10 @@ import retrofit2.Call; import retrofit2.Callback; public class TimelineFragment extends SFragment implements - SwipeRefreshLayout.OnRefreshListener, StatusActionListener, StatusRemoveListener, SharedPreferences.OnSharedPreferenceChangeListener { + SwipeRefreshLayout.OnRefreshListener, + StatusActionListener, + StatusRemoveListener, + SharedPreferences.OnSharedPreferenceChangeListener { private static final String TAG = "Timeline"; // logging tag private Call> listCall; diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java b/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java index 0536330c..9bce54a1 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/ViewTagActivity.java @@ -26,7 +26,9 @@ import android.view.MenuItem; import butterknife.BindView; import butterknife.ButterKnife; -public class ViewTagActivity extends BaseActivity { +public class ViewTagActivity extends BaseActivity implements SFragment.OnUserRemovedListener { + private Fragment timelineFragment; + @BindView(R.id.toolbar) Toolbar toolbar; @Override @@ -51,6 +53,8 @@ public class ViewTagActivity extends BaseActivity { Fragment fragment = TimelineFragment.newInstance(TimelineFragment.Kind.TAG, hashtag); fragmentTransaction.add(R.id.fragment_container, fragment); fragmentTransaction.commit(); + + timelineFragment = fragment; } @Override @@ -63,4 +67,10 @@ public class ViewTagActivity extends BaseActivity { } return super.onOptionsItemSelected(item); } + + @Override + public void onUserRemoved(String accountId) { + StatusRemoveListener listener = (StatusRemoveListener) timelineFragment; + listener.removePostsByUser(accountId); + } } diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java b/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java index cee1d244..9dcd5efa 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/ViewThreadActivity.java @@ -24,7 +24,9 @@ import android.support.v7.widget.Toolbar; import android.view.Menu; import android.view.MenuItem; -public class ViewThreadActivity extends BaseActivity { +public class ViewThreadActivity extends BaseActivity implements SFragment.OnUserRemovedListener { + Fragment viewThreadFragment; + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -44,6 +46,8 @@ public class ViewThreadActivity extends BaseActivity { Fragment fragment = ViewThreadFragment.newInstance(id); fragmentTransaction.add(R.id.fragment_container, fragment); fragmentTransaction.commit(); + + viewThreadFragment = fragment; } @Override @@ -62,4 +66,12 @@ public class ViewThreadActivity extends BaseActivity { } return super.onOptionsItemSelected(item); } + + @Override + public void onUserRemoved(String accountId) { + if (viewThreadFragment instanceof StatusRemoveListener) { + StatusRemoveListener listener = (StatusRemoveListener) viewThreadFragment; + listener.removePostsByUser(accountId); + } + } } diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewThreadFragment.java b/app/src/main/java/com/keylesspalace/tusky/ViewThreadFragment.java index 39baf8ee..0cf47600 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ViewThreadFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/ViewThreadFragment.java @@ -36,7 +36,7 @@ import retrofit2.Call; import retrofit2.Callback; public class ViewThreadFragment extends SFragment implements - SwipeRefreshLayout.OnRefreshListener, StatusActionListener { + SwipeRefreshLayout.OnRefreshListener, StatusActionListener, StatusRemoveListener { private static final String TAG = "ViewThreadFragment"; private SwipeRefreshLayout swipeRefreshLayout; @@ -150,6 +150,11 @@ public class ViewThreadFragment extends SFragment implements } } + @Override + public void removePostsByUser(String accountId) { + adapter.removeAllByAccountId(accountId); + } + public void onRefresh() { sendStatusRequest(thisThreadsStatusId); sendThreadRequest(thisThreadsStatusId); diff --git a/app/src/main/java/com/keylesspalace/tusky/entity/Status.java b/app/src/main/java/com/keylesspalace/tusky/entity/Status.java index c793e239..4e147358 100644 --- a/app/src/main/java/com/keylesspalace/tusky/entity/Status.java +++ b/app/src/main/java/com/keylesspalace/tusky/entity/Status.java @@ -146,5 +146,8 @@ public class Status { @SerializedName("acct") public String username; + + @SerializedName("username") + public String localUsername; } }