diff --git a/app/build.gradle b/app/build.gradle index 786685f0..bb8cac27 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -29,10 +29,11 @@ dependencies { compile('com.mikepenz:materialdrawer:5.8.2@aar') { transitive = true } - compile 'com.android.support:appcompat-v7:25.2.0' - compile 'com.android.support:recyclerview-v7:25.2.0' - compile 'com.android.support:support-v13:25.2.0' - compile 'com.android.support:design:25.2.0' + compile 'com.android.support:appcompat-v7:25.3.1' + compile 'com.android.support:customtabs:25.3.1' + compile 'com.android.support:recyclerview-v7:25.3.1' + compile 'com.android.support:support-v13:25.3.1' + compile 'com.android.support:design:25.3.1' compile 'com.squareup.picasso:picasso:2.5.2' compile 'com.pkmmte.view:circularimageview:1.1' compile 'com.github.peter9870:sparkbutton:master' diff --git a/app/src/main/java/com/keylesspalace/tusky/CustomTabURLSpan.java b/app/src/main/java/com/keylesspalace/tusky/CustomTabURLSpan.java new file mode 100644 index 00000000..dce7d45c --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/CustomTabURLSpan.java @@ -0,0 +1,60 @@ +package com.keylesspalace.tusky; + +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; +import android.preference.PreferenceManager; +import android.support.customtabs.CustomTabsIntent; +import android.support.v4.content.ContextCompat; +import android.text.style.URLSpan; +import android.view.View; + +class CustomTabURLSpan extends URLSpan { + CustomTabURLSpan(String url) { + super(url); + } + + private CustomTabURLSpan(Parcel src) { + super(src); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + + @Override + public CustomTabURLSpan createFromParcel(Parcel source) { + return new CustomTabURLSpan(source); + } + + @Override + public CustomTabURLSpan[] newArray(int size) { + return new CustomTabURLSpan[size]; + } + }; + + @Override + public void onClick(View widget) { + Uri uri = Uri.parse(getURL()); + Context context = widget.getContext(); + boolean lightTheme = PreferenceManager.getDefaultSharedPreferences(context).getBoolean("lightTheme", false); + int toolbarColor = ContextCompat.getColor(context, lightTheme ? R.color.custom_tab_toolbar_light : R.color.custom_tab_toolbar_dark); + 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) { + super.onClick(widget); + } else { + customTabsIntent.intent.setPackage(packageName); + customTabsIntent.launchUrl(context, uri); + } + } catch (ActivityNotFoundException e) { + android.util.Log.w("URLSpan", "Activity was not found for intent, " + customTabsIntent.toString()); + } + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/CustomTabsHelper.java b/app/src/main/java/com/keylesspalace/tusky/CustomTabsHelper.java new file mode 100644 index 00000000..45670863 --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/CustomTabsHelper.java @@ -0,0 +1,122 @@ +package com.keylesspalace.tusky; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.net.Uri; +import android.text.TextUtils; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +/** + * stolen from https://github.com/GoogleChrome/custom-tabs-client/blob/master/shared/src/main/java/org/chromium/customtabsclient/shared/CustomTabsHelper.java + */ + +public class CustomTabsHelper { + private static final String TAG = "CustomTabsHelper"; + static final String STABLE_PACKAGE = "com.android.chrome"; + static final String BETA_PACKAGE = "com.chrome.beta"; + static final String DEV_PACKAGE = "com.chrome.dev"; + static final String LOCAL_PACKAGE = "com.google.android.apps.chrome"; + private static final String EXTRA_CUSTOM_TABS_KEEP_ALIVE = + "android.support.customtabs.extra.KEEP_ALIVE"; + private static final String ACTION_CUSTOM_TABS_CONNECTION = + "android.support.customtabs.action.CustomTabsService"; + + private static String sPackageNameToUse; + + private CustomTabsHelper() {} + + /** + * Goes through all apps that handle VIEW intents and have a warmup service. Picks + * the one chosen by the user if there is one, otherwise makes a best effort to return a + * valid package name. + * + * This is not threadsafe. + * + * @param context {@link Context} to use for accessing {@link PackageManager}. + * @return The package name recommended to use for connecting to custom tabs related components. + */ + public static String getPackageNameToUse(Context context) { + if (sPackageNameToUse != null) return sPackageNameToUse; + + PackageManager pm = context.getPackageManager(); + // Get default VIEW intent handler. + Intent activityIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.example.com")); + ResolveInfo defaultViewHandlerInfo = pm.resolveActivity(activityIntent, 0); + String defaultViewHandlerPackageName = null; + if (defaultViewHandlerInfo != null) { + defaultViewHandlerPackageName = defaultViewHandlerInfo.activityInfo.packageName; + } + + // Get all apps that can handle VIEW intents. + List resolvedActivityList = pm.queryIntentActivities(activityIntent, 0); + List packagesSupportingCustomTabs = new ArrayList<>(); + for (ResolveInfo info : resolvedActivityList) { + Intent serviceIntent = new Intent(); + serviceIntent.setAction(ACTION_CUSTOM_TABS_CONNECTION); + serviceIntent.setPackage(info.activityInfo.packageName); + if (pm.resolveService(serviceIntent, 0) != null) { + packagesSupportingCustomTabs.add(info.activityInfo.packageName); + } + } + + // Now packagesSupportingCustomTabs contains all apps that can handle both VIEW intents + // and service calls. + if (packagesSupportingCustomTabs.isEmpty()) { + sPackageNameToUse = null; + } else if (packagesSupportingCustomTabs.size() == 1) { + sPackageNameToUse = packagesSupportingCustomTabs.get(0); + } else if (!TextUtils.isEmpty(defaultViewHandlerPackageName) + && !hasSpecializedHandlerIntents(context, activityIntent) + && packagesSupportingCustomTabs.contains(defaultViewHandlerPackageName)) { + sPackageNameToUse = defaultViewHandlerPackageName; + } else if (packagesSupportingCustomTabs.contains(STABLE_PACKAGE)) { + sPackageNameToUse = STABLE_PACKAGE; + } else if (packagesSupportingCustomTabs.contains(BETA_PACKAGE)) { + sPackageNameToUse = BETA_PACKAGE; + } else if (packagesSupportingCustomTabs.contains(DEV_PACKAGE)) { + sPackageNameToUse = DEV_PACKAGE; + } else if (packagesSupportingCustomTabs.contains(LOCAL_PACKAGE)) { + sPackageNameToUse = LOCAL_PACKAGE; + } + return sPackageNameToUse; + } + + /** + * Used to check whether there is a specialized handler for a given intent. + * @param intent The intent to check with. + * @return Whether there is a specialized handler for the given intent. + */ + private static boolean hasSpecializedHandlerIntents(Context context, Intent intent) { + try { + PackageManager pm = context.getPackageManager(); + List handlers = pm.queryIntentActivities( + intent, + PackageManager.GET_RESOLVED_FILTER); + if (handlers == null || handlers.size() == 0) { + return false; + } + for (ResolveInfo resolveInfo : handlers) { + IntentFilter filter = resolveInfo.filter; + if (filter == null) continue; + if (filter.countDataAuthorities() == 0 || filter.countDataPaths() == 0) continue; + if (resolveInfo.activityInfo == null) continue; + return true; + } + } catch (RuntimeException e) { + Log.e(TAG, "Runtime exception while getting specialized handlers"); + } + return false; + } + + /** + * @return All possible chrome package names that provide custom tabs feature. + */ + public static String[] getPackages() { + return new String[]{"", STABLE_PACKAGE, BETA_PACKAGE, DEV_PACKAGE, LOCAL_PACKAGE}; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/keylesspalace/tusky/StatusViewHolder.java b/app/src/main/java/com/keylesspalace/tusky/StatusViewHolder.java index 8f5fdedb..5dd1449d 100644 --- a/app/src/main/java/com/keylesspalace/tusky/StatusViewHolder.java +++ b/app/src/main/java/com/keylesspalace/tusky/StatusViewHolder.java @@ -16,6 +16,8 @@ package com.keylesspalace.tusky; import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; import android.text.SpannableStringBuilder; @@ -105,6 +107,7 @@ class StatusViewHolder extends RecyclerView.ViewHolder { /* 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); @@ -140,6 +143,10 @@ class StatusViewHolder extends RecyclerView.ViewHolder { 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. diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index c055d723..d1006dab 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -35,6 +35,7 @@ #AFBFCF #000000 #2F2F2F + #363c4b #dfdfdf #8f8f8f @@ -68,4 +69,6 @@ #2F5F6F #EFEFEF #9F9F9F + #ffffff + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 24fd9ca9..730fa649 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -107,6 +107,7 @@ Only followers and mentions can see Notifications + Edit Notifications Push notifications Alerts Notify with a sound @@ -119,6 +120,8 @@ my posts are favourited Appearance Use the Light Theme + Browser + Use Chrome Custom Tabs %s mentioned you %1$s, %2$s, %3$s and %4$d others diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index f1771636..ea10731d 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -10,8 +10,14 @@ android:defaultValue="false" /> - - + + + + + - + diff --git a/build.gradle b/build.gradle index 22983d58..f8f4d274 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.0' + classpath 'com.android.tools.build:gradle:2.3.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files