diff --git a/build.gradle b/build.gradle index f5c0071d3..aca9b99e0 100644 --- a/build.gradle +++ b/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 { diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index bd90c28ec..6e13f5af0 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -750,4 +750,12 @@ cancelled You are already drafting a message. Feature not implemented + Invalid country code + Choose a country + phone number + Verify your 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: +
%s

Is this OK, or would you like to edit the number?]]>
+ %s is not a valid phone number. + Please enter your phone number. diff --git a/src/quick/AndroidManifest.xml b/src/quick/AndroidManifest.xml new file mode 100644 index 000000000..4316f2f99 --- /dev/null +++ b/src/quick/AndroidManifest.xml @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/src/quick/java/eu/siacs/conversations/ui/EnterPhoneNumberActivity.java b/src/quick/java/eu/siacs/conversations/ui/EnterPhoneNumberActivity.java new file mode 100644 index 000000000..d247139ac --- /dev/null +++ b/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) { + + } + +} diff --git a/src/quick/java/eu/siacs/conversations/ui/drawable/TextDrawable.java b/src/quick/java/eu/siacs/conversations/ui/drawable/TextDrawable.java new file mode 100644 index 000000000..3b49962a6 --- /dev/null +++ b/src/quick/java/eu/siacs/conversations/ui/drawable/TextDrawable.java @@ -0,0 +1,240 @@ +package eu.siacs.conversations.ui.drawable; /** + * Copyright 2016 Ali Muzaffar + *

+ * 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 + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * 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 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. + *

+ * 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(); + } + +} \ No newline at end of file diff --git a/src/quick/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java b/src/quick/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java new file mode 100644 index 000000000..854c0770b --- /dev/null +++ b/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; + } + +} diff --git a/src/quick/java/eu/siacs/conversations/utils/SignupUtils.java b/src/quick/java/eu/siacs/conversations/utils/SignupUtils.java index 6a874e713..314a8655e 100644 --- a/src/quick/java/eu/siacs/conversations/utils/SignupUtils.java +++ b/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; } } \ No newline at end of file diff --git a/src/quick/res/drawable-hdpi/ic_arrow_drop_down_black_18dp.png b/src/quick/res/drawable-hdpi/ic_arrow_drop_down_black_18dp.png new file mode 100644 index 000000000..f15fe3a5b Binary files /dev/null and b/src/quick/res/drawable-hdpi/ic_arrow_drop_down_black_18dp.png differ diff --git a/src/quick/res/drawable-mdpi/ic_arrow_drop_down_black_18dp.png b/src/quick/res/drawable-mdpi/ic_arrow_drop_down_black_18dp.png new file mode 100644 index 000000000..68ad72ffe Binary files /dev/null and b/src/quick/res/drawable-mdpi/ic_arrow_drop_down_black_18dp.png differ diff --git a/src/quick/res/drawable-xhdpi/ic_arrow_drop_down_black_18dp.png b/src/quick/res/drawable-xhdpi/ic_arrow_drop_down_black_18dp.png new file mode 100644 index 000000000..2a5865da4 Binary files /dev/null and b/src/quick/res/drawable-xhdpi/ic_arrow_drop_down_black_18dp.png differ diff --git a/src/quick/res/drawable-xxhdpi/ic_arrow_drop_down_black_18dp.png b/src/quick/res/drawable-xxhdpi/ic_arrow_drop_down_black_18dp.png new file mode 100644 index 000000000..06cc5c9a5 Binary files /dev/null and b/src/quick/res/drawable-xxhdpi/ic_arrow_drop_down_black_18dp.png differ diff --git a/src/quick/res/drawable-xxxhdpi/ic_arrow_drop_down_black_18dp.png b/src/quick/res/drawable-xxxhdpi/ic_arrow_drop_down_black_18dp.png new file mode 100644 index 000000000..df2614b96 Binary files /dev/null and b/src/quick/res/drawable-xxxhdpi/ic_arrow_drop_down_black_18dp.png differ diff --git a/src/quick/res/layout/activity_enter_number.xml b/src/quick/res/layout/activity_enter_number.xml new file mode 100644 index 000000000..c91bb0a6a --- /dev/null +++ b/src/quick/res/layout/activity_enter_number.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + +