From 2cb0b96abda367015b453b31e4f1076225a2c052 Mon Sep 17 00:00:00 2001 From: Vavassor Date: Tue, 4 Apr 2017 22:29:15 -0400 Subject: [PATCH] Fixes a crash during authentication and another when opening the composer options. Also, sets up the next update to reset the redirect URI for the app during authorization. --- .../keylesspalace/tusky/ComposeActivity.java | 31 ++++-------- .../tusky/ComposeOptionsFragment.java | 45 +++++++++++------ .../keylesspalace/tusky/LoginActivity.java | 49 ++++++++++++------- app/src/main/res/values/donottranslate.xml | 4 +- app/src/main/res/values/strings.xml | 7 +-- 5 files changed, 78 insertions(+), 58 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java index 3bc2327ad..34ad44d43 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java @@ -93,7 +93,7 @@ import okhttp3.RequestBody; import retrofit2.Call; import retrofit2.Callback; -public class ComposeActivity extends BaseActivity { +public class ComposeActivity extends BaseActivity implements ComposeOptionsFragment.Listener { private static final String TAG = "ComposeActivity"; // logging tag private static final int STATUS_CHARACTER_LIMIT = 500; private static final int STATUS_MEDIA_SIZE_LIMIT = 4000000; // 4MB @@ -525,29 +525,18 @@ public class ComposeActivity extends BaseActivity { private void showComposeOptions() { ComposeOptionsFragment fragment = ComposeOptionsFragment.newInstance( - statusVisibility, statusHideText, inReplyToId != null, - new ComposeOptionsFragment.Listener() { - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel dest, int flags) {} - - @Override - public void onVisibilityChanged(String visibility) { - setStatusVisibility(visibility); - } - - @Override - public void onContentWarningChanged(boolean hideText) { - showContentWarning(hideText); - } - }); + statusVisibility, statusHideText, inReplyToId != null); fragment.show(getSupportFragmentManager(), null); } + public void onVisibilityChanged(String visibility) { + setStatusVisibility(visibility); + } + + public void onContentWarningChanged(boolean hideText) { + showContentWarning(hideText); + } + private void sendStatus() { if (statusAlreadyInFlight) { return; diff --git a/app/src/main/java/com/keylesspalace/tusky/ComposeOptionsFragment.java b/app/src/main/java/com/keylesspalace/tusky/ComposeOptionsFragment.java index 40680baee..888e5825f 100644 --- a/app/src/main/java/com/keylesspalace/tusky/ComposeOptionsFragment.java +++ b/app/src/main/java/com/keylesspalace/tusky/ComposeOptionsFragment.java @@ -1,5 +1,6 @@ package com.keylesspalace.tusky; +import android.content.Context; import android.os.Bundle; import android.os.Parcelable; import android.support.annotation.Nullable; @@ -12,19 +13,22 @@ import android.widget.CompoundButton; import android.widget.RadioButton; import android.widget.RadioGroup; +import java.util.List; + public class ComposeOptionsFragment extends BottomSheetDialogFragment { - public interface Listener extends Parcelable { + interface Listener { void onVisibilityChanged(String visibility); void onContentWarningChanged(boolean hideText); } + private RadioGroup radio; + private CheckBox hideText; private Listener listener; - public static ComposeOptionsFragment newInstance(String visibility, - boolean hideText, boolean isReply, Listener listener) { + public static ComposeOptionsFragment newInstance(String visibility, boolean hideText, + boolean isReply) { Bundle arguments = new Bundle(); ComposeOptionsFragment fragment = new ComposeOptionsFragment(); - arguments.putParcelable("listener", listener); arguments.putString("visibility", visibility); arguments.putBoolean("hideText", hideText); arguments.putBoolean("isReply", isReply); @@ -32,6 +36,12 @@ public class ComposeOptionsFragment extends BottomSheetDialogFragment { return fragment; } + @Override + public void onAttach(Context context) { + super.onAttach(context); + listener = (Listener) context; + } + @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @@ -39,12 +49,11 @@ public class ComposeOptionsFragment extends BottomSheetDialogFragment { View rootView = inflater.inflate(R.layout.fragment_compose_options, container, false); Bundle arguments = getArguments(); - listener = arguments.getParcelable("listener"); String statusVisibility = arguments.getString("visibility"); boolean statusHideText = arguments.getBoolean("hideText"); boolean isReply = arguments.getBoolean("isReply"); - RadioGroup radio = (RadioGroup) rootView.findViewById(R.id.radio_visibility); + radio = (RadioGroup) rootView.findViewById(R.id.radio_visibility); int radioCheckedId; if (!isReply) { radioCheckedId = R.id.radio_public; @@ -59,6 +68,21 @@ public class ComposeOptionsFragment extends BottomSheetDialogFragment { } } radio.check(radioCheckedId); + + if (isReply) { + RadioButton publicButton = (RadioButton) rootView.findViewById(R.id.radio_public); + publicButton.setEnabled(false); + } + + hideText = (CheckBox) rootView.findViewById(R.id.compose_hide_text); + hideText.setChecked(statusHideText); + + return rootView; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); radio.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup group, int checkedId) { @@ -81,20 +105,11 @@ public class ComposeOptionsFragment extends BottomSheetDialogFragment { listener.onVisibilityChanged(visibility); } }); - if (isReply) { - RadioButton publicButton = (RadioButton) rootView.findViewById(R.id.radio_public); - publicButton.setEnabled(false); - } - - CheckBox hideText = (CheckBox) rootView.findViewById(R.id.compose_hide_text); - hideText.setChecked(statusHideText); hideText.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { listener.onContentWarningChanged(isChecked); } }); - - return rootView; } } diff --git a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java index fdb4d622e..0db32a97b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/LoginActivity.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.preference.PreferenceManager; @@ -33,8 +34,6 @@ import android.widget.TextView; import com.keylesspalace.tusky.entity.AccessToken; import com.keylesspalace.tusky.entity.AppCredentials; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; import java.util.HashMap; import java.util.Map; @@ -60,14 +59,6 @@ public class LoginActivity extends AppCompatActivity { @BindView(R.id.button_login) Button button; @BindView(R.id.whats_an_instance) TextView whatsAnInstance; - private static String urlEncode(String string) { - try { - return URLEncoder.encode(string, "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new IllegalArgumentException("Failed to encode the string.", e); - } - } - /** * Chain together the key-value pairs into a query string, for either appending to a URL or * as the content of an HTTP request. @@ -77,9 +68,9 @@ public class LoginActivity extends AppCompatActivity { String between = ""; for (Map.Entry entry : parameters.entrySet()) { s.append(between); - s.append(urlEncode(entry.getKey())); + s.append(Uri.encode(entry.getKey())); s.append("="); - s.append(urlEncode(entry.getValue())); + s.append(Uri.encode(entry.getValue())); between = "&"; } return s.toString(); @@ -98,7 +89,7 @@ public class LoginActivity extends AppCompatActivity { return scheme + "://" + host + "/"; } - private void redirectUserToAuthorizeAndLogin() { + 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. */ String endpoint = MastodonAPI.ENDPOINT_AUTHORIZE; @@ -109,8 +100,12 @@ 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("android.intent.action.VIEW", Uri.parse(url)); - startActivity(viewIntent); + 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)); + } } private MastodonAPI getApiFor(String domain) { @@ -139,7 +134,7 @@ public class LoginActivity extends AppCompatActivity { if (prefClientId != null && prefClientSecret != null) { clientId = prefClientId; clientSecret = prefClientSecret; - redirectUserToAuthorizeAndLogin(); + redirectUserToAuthorizeAndLogin(editText); } else { Callback callback = new Callback() { @Override @@ -156,7 +151,7 @@ public class LoginActivity extends AppCompatActivity { editor.putString(domain + "/client_id", clientId); editor.putString(domain + "/client_secret", clientSecret); editor.apply(); - redirectUserToAuthorizeAndLogin(); + redirectUserToAuthorizeAndLogin(editText); } @Override @@ -225,6 +220,26 @@ public class LoginActivity extends AppCompatActivity { textView.setMovementMethod(LinkMovementMethod.getInstance()); } }); + + // Apply any updates needed. + int versionCode = 1; + try { + versionCode = getPackageManager().getPackageInfo(getPackageName(), 0).versionCode; + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "The app version was not found. " + e.getMessage()); + } + if (preferences.getInt("lastUpdate", 0) != versionCode) { + SharedPreferences.Editor editor = preferences.edit(); + if (versionCode == 14) { + /* This version switches the order of scheme and host in the OAuth redirect URI. + * But to fix it requires forcing the app to re-authenticate with servers. So, clear + * out the stored client id/secret pairs. The only other things that are lost are + * "rememberedVisibility", "loggedInUsername", and "loggedInAccountId". */ + editor.clear(); + } + editor.putInt("lastUpdate", versionCode); + editor.apply(); + } } @Override diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index dd336a03e..cc0029d12 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -4,7 +4,7 @@ http://tusky.keylesspalace.com https://tuskynotifier.keylesspalace.com - com.keylesspalace.tusky - oauth2redirect + oauth2redirect + com.keylesspalace.tusky com.keylesspalace.tusky.PREFERENCES \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 641726687..a23d4a311 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,8 +1,9 @@ - + + Couldn\'t find a web browser to use. An unidentified authorization error occurred. - Authorization was denied. - Failed getting a login token. + Authorization was denied. + Failed getting a login token. Notifications could not be fetched. The status is too long! The status failed to be sent.