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.

main
Vavassor 8 years ago
parent 3c655a25dc
commit 2cb0b96abd
  1. 17
      app/src/main/java/com/keylesspalace/tusky/ComposeActivity.java
  2. 45
      app/src/main/java/com/keylesspalace/tusky/ComposeOptionsFragment.java
  3. 47
      app/src/main/java/com/keylesspalace/tusky/LoginActivity.java
  4. 4
      app/src/main/res/values/donottranslate.xml
  5. 7
      app/src/main/res/values/strings.xml

@ -93,7 +93,7 @@ import okhttp3.RequestBody;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.Callback; 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 String TAG = "ComposeActivity"; // logging tag
private static final int STATUS_CHARACTER_LIMIT = 500; private static final int STATUS_CHARACTER_LIMIT = 500;
private static final int STATUS_MEDIA_SIZE_LIMIT = 4000000; // 4MB private static final int STATUS_MEDIA_SIZE_LIMIT = 4000000; // 4MB
@ -525,28 +525,17 @@ public class ComposeActivity extends BaseActivity {
private void showComposeOptions() { private void showComposeOptions() {
ComposeOptionsFragment fragment = ComposeOptionsFragment.newInstance( ComposeOptionsFragment fragment = ComposeOptionsFragment.newInstance(
statusVisibility, statusHideText, inReplyToId != null, statusVisibility, statusHideText, inReplyToId != null);
new ComposeOptionsFragment.Listener() { fragment.show(getSupportFragmentManager(), null);
@Override
public int describeContents() {
return 0;
} }
@Override
public void writeToParcel(Parcel dest, int flags) {}
@Override
public void onVisibilityChanged(String visibility) { public void onVisibilityChanged(String visibility) {
setStatusVisibility(visibility); setStatusVisibility(visibility);
} }
@Override
public void onContentWarningChanged(boolean hideText) { public void onContentWarningChanged(boolean hideText) {
showContentWarning(hideText); showContentWarning(hideText);
} }
});
fragment.show(getSupportFragmentManager(), null);
}
private void sendStatus() { private void sendStatus() {
if (statusAlreadyInFlight) { if (statusAlreadyInFlight) {

@ -1,5 +1,6 @@
package com.keylesspalace.tusky; package com.keylesspalace.tusky;
import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.os.Parcelable; import android.os.Parcelable;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
@ -12,19 +13,22 @@ import android.widget.CompoundButton;
import android.widget.RadioButton; import android.widget.RadioButton;
import android.widget.RadioGroup; import android.widget.RadioGroup;
import java.util.List;
public class ComposeOptionsFragment extends BottomSheetDialogFragment { public class ComposeOptionsFragment extends BottomSheetDialogFragment {
public interface Listener extends Parcelable { interface Listener {
void onVisibilityChanged(String visibility); void onVisibilityChanged(String visibility);
void onContentWarningChanged(boolean hideText); void onContentWarningChanged(boolean hideText);
} }
private RadioGroup radio;
private CheckBox hideText;
private Listener listener; private Listener listener;
public static ComposeOptionsFragment newInstance(String visibility, public static ComposeOptionsFragment newInstance(String visibility, boolean hideText,
boolean hideText, boolean isReply, Listener listener) { boolean isReply) {
Bundle arguments = new Bundle(); Bundle arguments = new Bundle();
ComposeOptionsFragment fragment = new ComposeOptionsFragment(); ComposeOptionsFragment fragment = new ComposeOptionsFragment();
arguments.putParcelable("listener", listener);
arguments.putString("visibility", visibility); arguments.putString("visibility", visibility);
arguments.putBoolean("hideText", hideText); arguments.putBoolean("hideText", hideText);
arguments.putBoolean("isReply", isReply); arguments.putBoolean("isReply", isReply);
@ -32,6 +36,12 @@ public class ComposeOptionsFragment extends BottomSheetDialogFragment {
return fragment; return fragment;
} }
@Override
public void onAttach(Context context) {
super.onAttach(context);
listener = (Listener) context;
}
@Nullable @Nullable
@Override @Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, 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); View rootView = inflater.inflate(R.layout.fragment_compose_options, container, false);
Bundle arguments = getArguments(); Bundle arguments = getArguments();
listener = arguments.getParcelable("listener");
String statusVisibility = arguments.getString("visibility"); String statusVisibility = arguments.getString("visibility");
boolean statusHideText = arguments.getBoolean("hideText"); boolean statusHideText = arguments.getBoolean("hideText");
boolean isReply = arguments.getBoolean("isReply"); boolean isReply = arguments.getBoolean("isReply");
RadioGroup radio = (RadioGroup) rootView.findViewById(R.id.radio_visibility); radio = (RadioGroup) rootView.findViewById(R.id.radio_visibility);
int radioCheckedId; int radioCheckedId;
if (!isReply) { if (!isReply) {
radioCheckedId = R.id.radio_public; radioCheckedId = R.id.radio_public;
@ -59,6 +68,21 @@ public class ComposeOptionsFragment extends BottomSheetDialogFragment {
} }
} }
radio.check(radioCheckedId); 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() { radio.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override @Override
public void onCheckedChanged(RadioGroup group, int checkedId) { public void onCheckedChanged(RadioGroup group, int checkedId) {
@ -81,20 +105,11 @@ public class ComposeOptionsFragment extends BottomSheetDialogFragment {
listener.onVisibilityChanged(visibility); 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() { hideText.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override @Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
listener.onContentWarningChanged(isChecked); listener.onContentWarningChanged(isChecked);
} }
}); });
return rootView;
} }
} }

@ -20,6 +20,7 @@ import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
@ -33,8 +34,6 @@ import android.widget.TextView;
import com.keylesspalace.tusky.entity.AccessToken; import com.keylesspalace.tusky.entity.AccessToken;
import com.keylesspalace.tusky.entity.AppCredentials; import com.keylesspalace.tusky.entity.AppCredentials;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -60,14 +59,6 @@ public class LoginActivity extends AppCompatActivity {
@BindView(R.id.button_login) Button button; @BindView(R.id.button_login) Button button;
@BindView(R.id.whats_an_instance) TextView whatsAnInstance; @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 * Chain together the key-value pairs into a query string, for either appending to a URL or
* as the content of an HTTP request. * as the content of an HTTP request.
@ -77,9 +68,9 @@ public class LoginActivity extends AppCompatActivity {
String between = ""; String between = "";
for (Map.Entry<String, String> entry : parameters.entrySet()) { for (Map.Entry<String, String> entry : parameters.entrySet()) {
s.append(between); s.append(between);
s.append(urlEncode(entry.getKey())); s.append(Uri.encode(entry.getKey()));
s.append("="); s.append("=");
s.append(urlEncode(entry.getValue())); s.append(Uri.encode(entry.getValue()));
between = "&"; between = "&";
} }
return s.toString(); return s.toString();
@ -98,7 +89,7 @@ public class LoginActivity extends AppCompatActivity {
return scheme + "://" + host + "/"; 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, /* 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. */ * activity_login there, and the server will redirect back to the app with its response. */
String endpoint = MastodonAPI.ENDPOINT_AUTHORIZE; String endpoint = MastodonAPI.ENDPOINT_AUTHORIZE;
@ -109,8 +100,12 @@ public class LoginActivity extends AppCompatActivity {
parameters.put("response_type", "code"); parameters.put("response_type", "code");
parameters.put("scope", OAUTH_SCOPES); parameters.put("scope", OAUTH_SCOPES);
String url = "https://" + domain + endpoint + "?" + toQueryString(parameters); String url = "https://" + domain + endpoint + "?" + toQueryString(parameters);
Intent viewIntent = new Intent("android.intent.action.VIEW", Uri.parse(url)); Intent viewIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
if (viewIntent.resolveActivity(getPackageManager()) != null) {
startActivity(viewIntent); startActivity(viewIntent);
} else {
editText.setError(getString(R.string.error_no_web_browser_found));
}
} }
private MastodonAPI getApiFor(String domain) { private MastodonAPI getApiFor(String domain) {
@ -139,7 +134,7 @@ public class LoginActivity extends AppCompatActivity {
if (prefClientId != null && prefClientSecret != null) { if (prefClientId != null && prefClientSecret != null) {
clientId = prefClientId; clientId = prefClientId;
clientSecret = prefClientSecret; clientSecret = prefClientSecret;
redirectUserToAuthorizeAndLogin(); redirectUserToAuthorizeAndLogin(editText);
} else { } else {
Callback<AppCredentials> callback = new Callback<AppCredentials>() { Callback<AppCredentials> callback = new Callback<AppCredentials>() {
@Override @Override
@ -156,7 +151,7 @@ public class LoginActivity extends AppCompatActivity {
editor.putString(domain + "/client_id", clientId); editor.putString(domain + "/client_id", clientId);
editor.putString(domain + "/client_secret", clientSecret); editor.putString(domain + "/client_secret", clientSecret);
editor.apply(); editor.apply();
redirectUserToAuthorizeAndLogin(); redirectUserToAuthorizeAndLogin(editText);
} }
@Override @Override
@ -225,6 +220,26 @@ public class LoginActivity extends AppCompatActivity {
textView.setMovementMethod(LinkMovementMethod.getInstance()); 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 @Override

@ -4,7 +4,7 @@
<string name="app_website">http://tusky.keylesspalace.com</string> <string name="app_website">http://tusky.keylesspalace.com</string>
<string name="tusky_api_url">https://tuskynotifier.keylesspalace.com</string> <string name="tusky_api_url">https://tuskynotifier.keylesspalace.com</string>
<string name="oauth_scheme">com.keylesspalace.tusky</string> <string name="oauth_scheme">oauth2redirect</string>
<string name="oauth_redirect_host">oauth2redirect</string> <string name="oauth_redirect_host">com.keylesspalace.tusky</string>
<string name="preferences_file_key">com.keylesspalace.tusky.PREFERENCES</string> <string name="preferences_file_key">com.keylesspalace.tusky.PREFERENCES</string>
</resources> </resources>

@ -1,8 +1,9 @@
<resources xmlns:tools="http://schemas.android.com/tools"> <resources>
<string name="error_no_web_browser_found">Couldn\'t find a web browser to use.</string>
<string name="error_authorization_unknown">An unidentified authorization error occurred.</string> <string name="error_authorization_unknown">An unidentified authorization error occurred.</string>
<string name="error_authorization_denied" tools:ignore="MissingTranslation">Authorization was denied.</string> <string name="error_authorization_denied">Authorization was denied.</string>
<string name="error_retrieving_oauth_token" tools:ignore="MissingTranslation">Failed getting a login token.</string> <string name="error_retrieving_oauth_token">Failed getting a login token.</string>
<string name="error_fetching_notifications">Notifications could not be fetched.</string> <string name="error_fetching_notifications">Notifications could not be fetched.</string>
<string name="error_compose_character_limit">The status is too long!</string> <string name="error_compose_character_limit">The status is too long!</string>
<string name="error_sending_status">The status failed to be sent.</string> <string name="error_sending_status">The status failed to be sent.</string>

Loading…
Cancel
Save