created enter phone number activity

Daniel Gultsch created

Change summary

build.gradle                                                            |   2 
src/main/res/values/strings.xml                                         |   8 
src/quick/AndroidManifest.xml                                           |  13 
src/quick/java/eu/siacs/conversations/ui/EnterPhoneNumberActivity.java  | 107 
src/quick/java/eu/siacs/conversations/ui/drawable/TextDrawable.java     | 240 
src/quick/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java |  53 
src/quick/java/eu/siacs/conversations/utils/SignupUtils.java            |  10 
src/quick/res/drawable-hdpi/ic_arrow_drop_down_black_18dp.png           |   0 
src/quick/res/drawable-mdpi/ic_arrow_drop_down_black_18dp.png           |   0 
src/quick/res/drawable-xhdpi/ic_arrow_drop_down_black_18dp.png          |   0 
src/quick/res/drawable-xxhdpi/ic_arrow_drop_down_black_18dp.png         |   0 
src/quick/res/drawable-xxxhdpi/ic_arrow_drop_down_black_18dp.png        |   0 
src/quick/res/layout/activity_enter_number.xml                          |  91 
13 files changed, 522 insertions(+), 2 deletions(-)

Detailed changes

build.gradle 🔗

@@ -23,6 +23,7 @@ configurations {
     compatImplementation
     fullFreeCompatImplementation
     quickFreeCompatImplementation
+    quickImplementation
 }
 
 ext {
@@ -59,6 +60,7 @@ dependencies {
     implementation 'org.osmdroid:osmdroid-android:6.0.1'
     implementation 'org.hsluv:hsluv:0.2'
     implementation 'org.conscrypt:conscrypt-android:1.3.0'
+    quickImplementation 'io.michaelrocks:libphonenumber-android:8.9.14'
 }
 
 ext {

src/main/res/values/strings.xml 🔗

@@ -750,4 +750,12 @@
     <string name="cancelled">cancelled</string>
     <string name="already_drafting_message">You are already drafting a message.</string>
     <string name="feature_not_implemented">Feature not implemented</string>
+    <string name="invalid_country_code">Invalid country code</string>
+    <string name="choose_a_country">Choose a country</string>
+    <string name="phone_number">phone number</string>
+    <string name="verify_your_phone_number">Verify your phone number</string>
+    <string name="enter_country_code_and_phone_number">Quick Conversations will send an SMS message (carrier charges may apply) to verify your phone number. Enter your country code and phone number:</string>
+    <string name="we_will_be_verifying"><![CDATA[We will be verifying the phone number<br/><br/><b>%s</b><br/><br/>Is this OK, or would you like to edit the number?]]></string>
+    <string name="not_a_valid_phone_number">%s is not a valid phone number.</string>
+    <string name="please_enter_your_phone_number">Please enter your phone number.</string>
 </resources>

src/quick/AndroidManifest.xml 🔗

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="eu.siacs.conversations">
+
+    <application tools:ignore="GoogleAppIndexingWarning">
+        <activity
+            android:name=".ui.EnterPhoneNumberActivity"
+            android:label="@string/verify_your_phone_number"
+            android:launchMode="singleTask" />
+
+    </application>
+</manifest>

src/quick/java/eu/siacs/conversations/ui/EnterPhoneNumberActivity.java 🔗

@@ -0,0 +1,107 @@
+package eu.siacs.conversations.ui;
+
+import android.app.AlertDialog;
+import android.databinding.DataBindingUtil;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.text.Editable;
+import android.text.Html;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.View;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.databinding.ActivityEnterNumberBinding;
+import eu.siacs.conversations.ui.drawable.TextDrawable;
+import eu.siacs.conversations.utils.PhoneNumberUtilWrapper;
+import io.michaelrocks.libphonenumber.android.NumberParseException;
+import io.michaelrocks.libphonenumber.android.PhoneNumberUtil;
+import io.michaelrocks.libphonenumber.android.Phonenumber;
+
+public class EnterPhoneNumberActivity extends AppCompatActivity {
+
+    private ActivityEnterNumberBinding binding;
+    private String region = null;
+    private final TextWatcher countryCodeTextWatcher = new TextWatcher() {
+        @Override
+        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+
+        }
+
+        @Override
+        public void onTextChanged(CharSequence s, int start, int before, int count) {
+
+        }
+
+        @Override
+        public void afterTextChanged(Editable editable) {
+            final String text = editable.toString();
+            try {
+                final int code = Integer.parseInt(text);
+                region = PhoneNumberUtilWrapper.getInstance(EnterPhoneNumberActivity.this).getRegionCodeForCountryCode(code);
+                if ("ZZ".equals(region)) {
+                    binding.country.setText(TextUtils.isEmpty(text) ? R.string.choose_a_country : R.string.invalid_country_code);
+                } else {
+                    binding.number.requestFocus();
+                    binding.country.setText(PhoneNumberUtilWrapper.getCountryForCode(region));
+                }
+            } catch (NumberFormatException e) {
+                binding.country.setText(TextUtils.isEmpty(text) ? R.string.choose_a_country : R.string.invalid_country_code);
+            }
+        }
+    };
+
+    @Override
+    protected void onCreate(final Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        this.binding = DataBindingUtil.setContentView(this, R.layout.activity_enter_number);
+        this.binding.countryCode.setCompoundDrawables(new TextDrawable(this.binding.countryCode, "+"), null, null, null);
+        this.binding.country.setOnClickListener(this::onSelectCountryClick);
+        this.binding.next.setOnClickListener(this::onNextClick);
+        setSupportActionBar((Toolbar) this.binding.toolbar);
+
+
+        this.binding.countryCode.addTextChangedListener(this.countryCodeTextWatcher);
+        this.region = PhoneNumberUtilWrapper.getUserCountry(this);
+        this.binding.countryCode.setText(String.valueOf(PhoneNumberUtilWrapper.getInstance(this).getCountryCodeForRegion(this.region)));
+    }
+
+    private void onNextClick(View v) {
+        final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        try {
+            final Editable number = this.binding.number.getText();
+            final String input = number.toString();
+            final Phonenumber.PhoneNumber phoneNumber = PhoneNumberUtilWrapper.getInstance(this).parse(input, region);
+            this.binding.countryCode.setText(String.valueOf(phoneNumber.getCountryCode()));
+            number.clear();
+            number.append(String.valueOf(phoneNumber.getNationalNumber()));
+            final String formattedPhoneNumber = PhoneNumberUtilWrapper.getInstance(this).format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL);
+
+            if (PhoneNumberUtilWrapper.getInstance(this).isValidNumber(phoneNumber)) {
+                builder.setMessage(Html.fromHtml(getString(R.string.we_will_be_verifying, formattedPhoneNumber)));
+                builder.setNegativeButton(R.string.edit, null);
+                builder.setPositiveButton(R.string.ok, (dialog, which) -> onPhoneNumberEntered(phoneNumber));
+            } else {
+                builder.setMessage(getString(R.string.not_a_valid_phone_number, formattedPhoneNumber));
+                builder.setPositiveButton(R.string.ok, null);
+            }
+            Log.d(Config.LOGTAG, phoneNumber.toString());
+        } catch (NumberParseException e) {
+            builder.setMessage(R.string.please_enter_your_phone_number);
+            builder.setPositiveButton(R.string.ok, null);
+        }
+        builder.create().show();
+    }
+
+    private void onSelectCountryClick(View view) {
+
+    }
+
+    private void onPhoneNumberEntered(Phonenumber.PhoneNumber phoneNumber) {
+
+    }
+
+}

src/quick/java/eu/siacs/conversations/ui/drawable/TextDrawable.java 🔗

@@ -0,0 +1,240 @@
+package eu.siacs.conversations.ui.drawable; /**
+ * Copyright 2016 Ali Muzaffar
+ * <p/>
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.widget.TextView;
+
+import java.lang.ref.WeakReference;
+
+public class TextDrawable extends Drawable implements TextWatcher {
+    private WeakReference<TextView> ref;
+    private String mText;
+    private Paint mPaint;
+    private Rect mHeightBounds;
+    private boolean mBindToViewPaint = false;
+    private float mPrevTextSize = 0;
+    private boolean mInitFitText = false;
+    private boolean mFitTextEnabled = false;
+
+    /**
+     * Create a TextDrawable using the given paint object and string
+     *
+     * @param paint
+     * @param s
+     */
+    public TextDrawable(Paint paint, String s) {
+        mText = s;
+        mPaint = new Paint(paint);
+        mHeightBounds = new Rect();
+        init();
+    }
+
+    /**
+     * Create a TextDrawable. This uses the given TextView to initialize paint and has initial text
+     * that will be drawn. Initial text can also be useful for reserving space that may otherwise
+     * not be available when setting compound drawables.
+     *
+     * @param tv               The TextView / EditText using to initialize this drawable
+     * @param initialText      Optional initial text to display
+     * @param bindToViewsText  Should this drawable mirror the text in the TextView
+     * @param bindToViewsPaint Should this drawable mirror changes to Paint in the TextView, like textColor, typeface, alpha etc.
+     *                         Note, this will override any changes made using setColorFilter or setAlpha.
+     */
+    public TextDrawable(TextView tv, String initialText, boolean bindToViewsText, boolean bindToViewsPaint) {
+        this(tv.getPaint(), initialText);
+        ref = new WeakReference<>(tv);
+        if (bindToViewsText || bindToViewsPaint) {
+            if (bindToViewsText) {
+                tv.addTextChangedListener(this);
+            }
+            mBindToViewPaint = bindToViewsPaint;
+        }
+    }
+
+    /**
+     * Create a TextDrawable. This uses the given TextView to initialize paint and the text that
+     * will be drawn.
+     *
+     * @param tv               The TextView / EditText using to initialize this drawable
+     * @param bindToViewsText  Should this drawable mirror the text in the TextView
+     * @param bindToViewsPaint Should this drawable mirror changes to Paint in the TextView, like textColor, typeface, alpha etc.
+     *                         Note, this will override any changes made using setColorFilter or setAlpha.
+     */
+    public TextDrawable(TextView tv, boolean bindToViewsText, boolean bindToViewsPaint) {
+        this(tv, tv.getText().toString(), false, false);
+    }
+
+    /**
+     * Use the provided TextView/EditText to initialize the drawable.
+     * The Drawable will copy the Text and the Paint properties, however it will from that
+     * point on be independant of the TextView.
+     *
+     * @param tv a TextView or EditText or any of their children.
+     */
+    public TextDrawable(TextView tv) {
+        this(tv, false, false);
+    }
+
+    /**
+     * Use the provided TextView/EditText to initialize the drawable.
+     * The Drawable will copy the Paint properties, and use the provided text to initialise itself.
+     *
+     * @param tv a TextView or EditText or any of their children.
+     * @param s  The String to draw
+     */
+    public TextDrawable(TextView tv, String s) {
+        this(tv, s, false, false);
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        if (mBindToViewPaint && ref.get() != null) {
+            Paint p = ref.get().getPaint();
+            canvas.drawText(mText, 0, getBounds().height(), p);
+        } else {
+            if (mInitFitText) {
+                fitTextAndInit();
+            }
+            canvas.drawText(mText, 0, getBounds().height(), mPaint);
+        }
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        mPaint.setAlpha(alpha);
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        mPaint.setColorFilter(colorFilter);
+    }
+
+    @Override
+    public int getOpacity() {
+        int alpha = mPaint.getAlpha();
+        if (alpha == 0) {
+            return PixelFormat.TRANSPARENT;
+        } else if (alpha == 255) {
+            return PixelFormat.OPAQUE;
+        } else {
+            return PixelFormat.TRANSLUCENT;
+        }
+    }
+
+    private void init() {
+        Rect bounds = getBounds();
+        //We want to use some character to determine the max height of the text.
+        //Otherwise if we draw something like "..." they will appear centered
+        //Here I'm just going to use the entire alphabet to determine max height.
+        mPaint.getTextBounds("1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+", 0, 1, mHeightBounds);
+        //This doesn't account for leading or training white spaces.
+        //mPaint.getTextBounds(mText, 0, mText.length(), bounds);
+        float width = mPaint.measureText(mText);
+        bounds.top = mHeightBounds.top;
+        bounds.bottom = mHeightBounds.bottom;
+        bounds.right = (int) width;
+        bounds.left = 0;
+        setBounds(bounds);
+    }
+
+    public void setPaint(Paint paint) {
+        mPaint = new Paint(paint);
+        //Since this can change the font used, we need to recalculate bounds.
+        if (mFitTextEnabled && !mInitFitText) {
+            fitTextAndInit();
+        } else {
+            init();
+        }
+        invalidateSelf();
+    }
+
+    public Paint getPaint() {
+        return mPaint;
+    }
+
+    public void setText(String text) {
+        mText = text;
+        //Since this can change the bounds of the text, we need to recalculate.
+        if (mFitTextEnabled && !mInitFitText) {
+            fitTextAndInit();
+        } else {
+            init();
+        }
+        invalidateSelf();
+    }
+
+    public String getText() {
+        return mText;
+    }
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+
+    }
+
+    @Override
+    public void onTextChanged(CharSequence s, int start, int before, int count) {
+
+    }
+
+    @Override
+    public void afterTextChanged(Editable s) {
+        setText(s.toString());
+    }
+
+    /**
+     * Make the TextDrawable match the width of the View it's associated with.
+     * <p/>
+     * Note: While this option will not work if bindToViewPaint is true.
+     *
+     * @param fitText
+     */
+    public void setFillText(boolean fitText) {
+        mFitTextEnabled = fitText;
+        if (fitText) {
+            mPrevTextSize = mPaint.getTextSize();
+            if (ref.get() != null) {
+                if (ref.get().getWidth() > 0) {
+                    fitTextAndInit();
+                } else {
+                    mInitFitText = true;
+                }
+            }
+        } else {
+            if (mPrevTextSize > 0) {
+                mPaint.setTextSize(mPrevTextSize);
+            }
+            init();
+        }
+    }
+
+    private void fitTextAndInit() {
+        float fitWidth = ref.get().getWidth();
+        float textWidth = mPaint.measureText(mText);
+        float multi = fitWidth / textWidth;
+        mPaint.setTextSize(mPaint.getTextSize() * multi);
+        mInitFitText = false;
+        init();
+    }
+
+}

src/quick/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java 🔗

@@ -0,0 +1,53 @@
+package eu.siacs.conversations.utils;
+
+import android.content.Context;
+import android.telephony.TelephonyManager;
+
+import java.util.Locale;
+
+import io.michaelrocks.libphonenumber.android.PhoneNumberUtil;
+
+public class PhoneNumberUtilWrapper {
+
+    private static volatile PhoneNumberUtil instance;
+
+
+    public static String getCountryForCode(String code) {
+        Locale locale = new Locale("", code);
+        return locale.getDisplayCountry();
+    }
+
+    public static String getUserCountry(Context context) {
+        try {
+            final TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+            final String simCountry = tm.getSimCountryIso();
+            if (simCountry != null && simCountry.length() == 2) { // SIM country code is available
+                return simCountry.toUpperCase(Locale.US);
+            } else if (tm.getPhoneType() != TelephonyManager.PHONE_TYPE_CDMA) { // device is not 3G (would be unreliable)
+                String networkCountry = tm.getNetworkCountryIso();
+                if (networkCountry != null && networkCountry.length() == 2) { // network country code is available
+                    return networkCountry.toUpperCase(Locale.US);
+                }
+            }
+        } catch (Exception e) {
+            // fallthrough
+        }
+        Locale locale = Locale.getDefault();
+        return locale.getCountry();
+    }
+
+    public static PhoneNumberUtil getInstance(final Context context) {
+        PhoneNumberUtil localInstance = instance;
+        if (localInstance == null) {
+            synchronized (PhoneNumberUtilWrapper.class){
+                localInstance = instance;
+                if (localInstance == null) {
+                    instance = localInstance = PhoneNumberUtil.createInstance(context);
+                }
+
+            }
+        }
+        return localInstance;
+    }
+
+}

src/quick/java/eu/siacs/conversations/utils/SignupUtils.java 🔗

@@ -2,16 +2,22 @@ package eu.siacs.conversations.utils;
 
 import android.app.Activity;
 import android.content.Intent;
+import android.util.Log;
 
+import eu.siacs.conversations.Config;
 import eu.siacs.conversations.ui.ConversationsActivity;
+import eu.siacs.conversations.ui.EnterPhoneNumberActivity;
 
 public class SignupUtils {
 
     public static Intent getSignUpIntent(Activity activity) {
-        return null;
+        final Intent intent = new Intent(activity, EnterPhoneNumberActivity.class);
+        return intent;
     }
 
     public static Intent getRedirectionIntent(ConversationsActivity activity) {
-        return null;
+        final Intent intent = getSignUpIntent(activity);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        return intent;
     }
 }

src/quick/res/layout/activity_enter_number.xml 🔗

@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <include android:id="@+id/toolbar" layout="@layout/toolbar" />
+
+        <ScrollView
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:fillViewport="true">
+
+            <RelativeLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content">
+
+                <TextView
+                    android:id="@+id/instructions"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:padding="16dp"
+                    android:gravity="center_horizontal"
+                    android:textAppearance="@style/TextAppearance.Conversations.Body1"
+                    android:text="@string/enter_country_code_and_phone_number"/>
+
+                <LinearLayout
+                    android:id="@+id/phone_number_box"
+                    android:layout_width="256dp"
+                    android:layout_height="match_parent"
+                    android:layout_above="@+id/next"
+                    android:layout_below="@+id/instructions"
+                    android:layout_centerHorizontal="true"
+                    android:orientation="vertical">
+
+                    <EditText
+                        android:id="@+id/country"
+                        style="@style/Widget.Conversations.EditText"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:cursorVisible="false"
+                        android:drawableRight="@drawable/ic_arrow_drop_down_black_18dp"
+                        android:focusable="false"
+                        android:gravity="bottom|center_horizontal"
+                        android:longClickable="false" />
+
+                    <LinearLayout
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:orientation="horizontal">
+
+                        <EditText
+                            android:id="@+id/country_code"
+                            style="@style/Widget.Conversations.EditText"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="1"
+                            android:gravity="bottom|center_horizontal"
+                            android:inputType="number"
+                            android:longClickable="false"
+                            android:maxLength="3"
+                            android:maxLines="1" />
+
+                        <EditText
+                            android:id="@+id/number"
+                            style="@style/Widget.Conversations.EditText"
+                            android:layout_width="0dp"
+                            android:layout_height="wrap_content"
+                            android:layout_weight="3"
+                            android:hint="@string/phone_number"
+                            android:inputType="number"
+                            android:gravity="bottom|start"
+                            android:longClickable="false"
+                            android:maxLines="1" />
+                    </LinearLayout>
+                </LinearLayout>
+                <Button
+                    android:id="@+id/next"
+                    android:layout_alignParentBottom="true"
+                    android:layout_alignParentEnd="true"
+                    style="@style/Widget.Conversations.Button.Borderless"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/next"
+                    android:textColor="?colorAccent"/>
+            </RelativeLayout>
+        </ScrollView>
+    </LinearLayout>
+</layout>