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.

This commit is contained in:
Vavassor 2017-04-04 22:29:15 -04:00
parent 3c655a25dc
commit 2cb0b96abd
5 changed files with 78 additions and 58 deletions

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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<String, String> 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<AppCredentials> callback = new Callback<AppCredentials>() {
@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

View File

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

View File

@ -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_denied" tools:ignore="MissingTranslation">Authorization was denied.</string>
<string name="error_retrieving_oauth_token" tools:ignore="MissingTranslation">Failed getting a login token.</string>
<string name="error_authorization_denied">Authorization was denied.</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_compose_character_limit">The status is too long!</string>
<string name="error_sending_status">The status failed to be sent.</string>