commit a34ee2213a1ba7c2be06798e50c2c17bc96789f2 Author: Benjamin Erhart berhart@netzarchitekten.com Date: Wed Apr 22 11:27:13 2020 +0200
Improved UI: show progress spinner, start request on keyboard enter. --- .../android/ui/onboarding/MoatActivity.java | 118 ++++++++++++++------- app/src/main/res/layout/activity_moat.xml | 23 +++- 2 files changed, 98 insertions(+), 43 deletions(-)
diff --git a/app/src/main/java/org/torproject/android/ui/onboarding/MoatActivity.java b/app/src/main/java/org/torproject/android/ui/onboarding/MoatActivity.java index e2f642fd..0b99477f 100644 --- a/app/src/main/java/org/torproject/android/ui/onboarding/MoatActivity.java +++ b/app/src/main/java/org/torproject/android/ui/onboarding/MoatActivity.java @@ -2,10 +2,9 @@ /* See LICENSE for licensing information */ package org.torproject.android.ui.onboarding;
-import android.app.Dialog; +import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.graphics.BitmapFactory; @@ -13,11 +12,17 @@ import android.os.Bundle; import android.text.TextUtils; import android.util.Base64; import android.util.Log; +import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; @@ -50,12 +55,14 @@ import org.torproject.android.service.util.Prefs; API description: https://github.com/NullHypothesis/bridgedb#accessing-the-moat-interface */ -public class MoatActivity extends AppCompatActivity implements View.OnClickListener { +public class MoatActivity extends AppCompatActivity implements View.OnClickListener, TextView.OnEditorActionListener {
private static String moatBaseUrl = "https://bridges.torproject.org/moat";
private ImageView mCaptchaIv; + private ProgressBar mProgressBar; private EditText mSolutionEt; + private Button mRequestBt;
private String mChallenge;
@@ -103,9 +110,14 @@ public class MoatActivity extends AppCompatActivity implements View.OnClickListe setTitle(getString(R.string.request_bridges));
mCaptchaIv = findViewById(R.id.captchaIv); + mProgressBar = findViewById(R.id.progressBar); mSolutionEt = findViewById(R.id.solutionEt); + mRequestBt = findViewById(R.id.requestBt);
- findViewById(R.id.requestBt).setOnClickListener(this); + mCaptchaIv.setVisibility(View.GONE); + mSolutionEt.setOnEditorActionListener(this); + mRequestBt.setEnabled(false); + mRequestBt.setOnClickListener(this);
LocalBroadcastManager.getInstance(this).registerReceiver(mBroadcastReceiver, new IntentFilter(TorServiceConstants.ACTION_STATUS)); @@ -159,6 +171,27 @@ public class MoatActivity extends AppCompatActivity implements View.OnClickListe return super.onOptionsItemSelected(item); }
+ + @Override + public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) { + Log.d(MoatActivity.class.getSimpleName(), "Editor Action: actionId=" + actionId + ", IME_ACTION_GO=" + EditorInfo.IME_ACTION_GO); + + if (keyEvent != null && keyEvent.getKeyCode() == KeyEvent.KEYCODE_ENTER) { + if (keyEvent.getAction() == KeyEvent.ACTION_UP) { + InputMethodManager imm = (InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE); + if (imm != null) { + imm.hideSoftInputFromWindow(textView.getWindowToken(), 0); + } + + onClick(mSolutionEt); + } + + return true; + } + + return false; + } + @Override protected void onDestroy() { super.onDestroy(); @@ -167,6 +200,10 @@ public class MoatActivity extends AppCompatActivity implements View.OnClickListe }
private void fetchCaptcha() { + mProgressBar.setVisibility(View.VISIBLE); + mCaptchaIv.setVisibility(View.GONE); + mRequestBt.setEnabled(false); + JsonObjectRequest request = buildRequest("fetch", ""type": "client-transports", "supported": ["obfs4"]", new Response.Listener<JSONObject>() { @@ -179,19 +216,14 @@ public class MoatActivity extends AppCompatActivity implements View.OnClickListe byte[] image = Base64.decode(data.getString("image"), Base64.DEFAULT); mCaptchaIv.setImageBitmap(BitmapFactory.decodeByteArray(image, 0, image.length));
+ mProgressBar.setVisibility(View.GONE); + mCaptchaIv.setVisibility(View.VISIBLE); + mRequestBt.setEnabled(true); + } catch (JSONException e) { - Log.d(MoatActivity.class.getSimpleName(), "Error decoding answer."); - - new AlertDialog.Builder(MoatActivity.this) - .setTitle(R.string.error) - .setMessage(e.getLocalizedMessage()) - .setNegativeButton(R.string.btn_cancel, new Dialog.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - //Do nothing. - } - }) - .show(); + Log.d(MoatActivity.class.getSimpleName(), "Error decoding answer: " + response.toString()); + + displayError(e, response); } } }); @@ -202,6 +234,9 @@ public class MoatActivity extends AppCompatActivity implements View.OnClickListe }
private void requestBridges(String solution) { + mProgressBar.setVisibility(View.VISIBLE); + mRequestBt.setEnabled(false); + JsonObjectRequest request = buildRequest("check", ""id": "2", "type": "moat-solution", "transport": "obfs4", "challenge": "" + mChallenge + "", "solution": "" + solution + "", "qrcode": "false"", @@ -222,21 +257,14 @@ public class MoatActivity extends AppCompatActivity implements View.OnClickListe Prefs.setBridgesList(sb.toString()); Prefs.putBridgesEnabled(true);
- MoatActivity.this.finish(); + mProgressBar.setVisibility(View.GONE);
- } catch (JSONException e) { + MoatActivity.this.finish(); + } + catch (JSONException e) { Log.d(MoatActivity.class.getSimpleName(), "Error decoding answer: " + response.toString());
- new AlertDialog.Builder(MoatActivity.this) - .setTitle(R.string.error) - .setMessage(e.getLocalizedMessage()) - .setNegativeButton(R.string.btn_cancel, new Dialog.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - //Do nothing. - } - }) - .show(); + displayError(e, response); } } }); @@ -267,16 +295,7 @@ public class MoatActivity extends AppCompatActivity implements View.OnClickListe public void onErrorResponse(VolleyError error) { Log.d(MoatActivity.class.getSimpleName(), "Error response.");
- new AlertDialog.Builder(MoatActivity.this) - .setTitle(R.string.error) - .setMessage(error.getLocalizedMessage()) - .setNegativeButton(R.string.btn_cancel, new Dialog.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - //Do nothing. - } - }) - .show(); + displayError(error, null); } } ) { @@ -322,4 +341,27 @@ public class MoatActivity extends AppCompatActivity implements View.OnClickListe sendIntentToService(TorServiceConstants.ACTION_STATUS); } } + + private void displayError(Exception exception, JSONObject response) { + + String detail = null; + + // Try to decode potential error response. + if (response != null) { + try { + detail = response.getJSONArray("errors").getJSONObject(0).getString("detail"); + } catch (JSONException e2) { + // Ignore. Show first exception instead. + } + } + + mProgressBar.setVisibility(View.GONE); + mRequestBt.setEnabled(mCaptchaIv.getVisibility() == View.VISIBLE); + + new AlertDialog.Builder(this) + .setTitle(R.string.error) + .setMessage(TextUtils.isEmpty(detail) ? exception.getLocalizedMessage() : detail) + .setNegativeButton(R.string.btn_cancel, null) + .show(); + } } diff --git a/app/src/main/res/layout/activity_moat.xml b/app/src/main/res/layout/activity_moat.xml index fe01873c..b0a2b42f 100644 --- a/app/src/main/res/layout/activity_moat.xml +++ b/app/src/main/res/layout/activity_moat.xml @@ -28,20 +28,33 @@ android:layout_margin="12dp" android:text="@string/solve_captcha_instruction" />
- <ImageView - android:id="@+id/captchaIv" + <RelativeLayout android:layout_width="match_parent" android:layout_height="240dp" - android:contentDescription="@string/captcha" - tools:srcCompat="@tools:sample/backgrounds/scenic" /> + android:layout_margin="12dp"> + + <ImageView + android:id="@+id/captchaIv" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:contentDescription="@string/captcha" + tools:srcCompat="@tools:sample/backgrounds/scenic" /> + + <ProgressBar + android:id="@+id/progressBar" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + + </RelativeLayout>
<EditText android:id="@+id/solutionEt" android:layout_width="match_parent" android:layout_height="wrap_content" android:autofillHints="" - android:hint="@string/enter_characters_from_image" android:ems="10" + android:hint="@string/enter_characters_from_image" + android:imeOptions="actionSend" android:inputType="textShortMessage|text" />
<Button