Improved registration flow.
parent
a135e7efa2
commit
86d088bce2
|
@ -119,9 +119,6 @@
|
|||
android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|screenSize|fontScale"
|
||||
android:launchMode="singleTask"/>
|
||||
|
||||
<activity android:name=".CountrySelectionActivity"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".InviteActivity"
|
||||
android:theme="@style/TextSecure.HighlightTheme"
|
||||
android:windowSoftInputMode="stateHidden"
|
||||
|
@ -319,24 +316,12 @@
|
|||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".registration.WelcomeActivity"
|
||||
<activity android:name=".registration.RegistrationNavigationActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||
android:windowSoftInputMode="stateUnchanged"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".RegistrationActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||
android:windowSoftInputMode="stateUnchanged"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".registration.CaptchaActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/TextSecure.LightNoActionBar"
|
||||
android:windowSoftInputMode="stateUnchanged"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".revealable.ViewOnceMessageActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/TextSecure.FullScreenMedia"
|
||||
|
|
|
@ -11,10 +11,12 @@ buildscript {
|
|||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.3.2'
|
||||
classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.1.0'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'androidx.navigation.safeargs'
|
||||
apply plugin: 'witness'
|
||||
apply from: 'witness-verifications.gradle'
|
||||
|
||||
|
@ -66,8 +68,11 @@ dependencies {
|
|||
implementation 'androidx.exifinterface:exifinterface:1.0.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-common-java8:2.0.0'
|
||||
implementation 'androidx.navigation:navigation-fragment:2.1.0'
|
||||
implementation 'androidx.navigation:navigation-ui:2.1.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0-alpha05'
|
||||
implementation 'androidx.lifecycle:lifecycle-common-java8:2.1.0'
|
||||
implementation "androidx.camera:camera-core:1.0.0-alpha04"
|
||||
implementation "androidx.camera:camera-camera2:1.0.0-alpha04"
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".registration.RegistrationNavigationActivity">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/nav_host_fragment"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:defaultNavHost="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:navGraph="@navigation/registration" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -1,37 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/registration_captcha_title"
|
||||
style="@style/Signal.Text.Headline"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:text="@string/RegistrationActivity_we_need_to_verify_that_youre_human"
|
||||
android:gravity="center"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<WebView
|
||||
android:id="@+id/registration_captcha_web_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/registration_captcha_title" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -15,7 +15,9 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="3dp"
|
||||
android:src="@drawable/ic_add_white_original_24dp"
|
||||
android:tint="@color/core_grey_60"/>
|
||||
android:tint="@color/core_grey_60"
|
||||
android:contentDescription="+"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/input"
|
||||
|
@ -28,6 +30,8 @@
|
|||
android:inputType="number"
|
||||
android:maxLength="3"
|
||||
android:digits="1234567890"
|
||||
android:saveEnabled="false"
|
||||
android:hint="@string/RegistrationActivity_country_code_description"
|
||||
tools:text="123" />
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
@ -1,28 +1,29 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="8dp">
|
||||
|
||||
<LinearLayout android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="8dip"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<TextView
|
||||
android:id="@+id/country_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.0"
|
||||
android:textAlignment="viewStart"
|
||||
android:textColor="@color/core_black"
|
||||
android:textSize="18sp"
|
||||
tools:text="Canada" />
|
||||
|
||||
<TextView android:id="@+id/country_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.0"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView android:id="@+id/country_code"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="18.0sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#7b7b7b"
|
||||
android:gravity="right|center"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_gravity="right|center"/>
|
||||
<TextView
|
||||
android:id="@+id/country_code"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:textColor="@color/core_black"
|
||||
android:textSize="18sp"
|
||||
tools:text="+1" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<fragment android:id="@+id/fragment_content"
|
||||
android:name="org.thoughtcrime.securesms.CountrySelectionFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
</FrameLayout>
|
|
@ -1,20 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp">
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/restore_passphrase_input"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/enter_backup_passphrase_dialog__backup_passphrase"
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="number" />
|
||||
android:id="@+id/restore_passphrase_input"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="monospace"
|
||||
android:hint="@string/enter_backup_passphrase_dialog__backup_passphrase"
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="number"
|
||||
android:maxLength="30"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</FrameLayout>
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/signal_primary">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/watermark"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/icon_transparent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintVertical_bias="0.4"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,41 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/registration_captcha_title"
|
||||
style="@style/Signal.Text.Headline"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/RegistrationActivity_we_need_to_verify_that_youre_human"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<WebView
|
||||
android:id="@+id/registration_captcha_web_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/registration_captcha_title" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</ScrollView>
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="#ffffff">
|
||||
|
||||
<EditText android:id="@+id/country_search"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawableStart="@drawable/ic_menu_search_holo_light"
|
||||
android:hint="@string/country_selection_fragment__search" />
|
||||
|
||||
<ListView android:id="@android:id/list"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:divider="#ffdddddd"
|
||||
android:dividerHeight="1.0px"
|
||||
android:choiceMode="singleChoice" />
|
||||
|
||||
<TextView android:id="@android:id/empty"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:gravity="center"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:text="@string/country_selection_fragment__loading_countries" />
|
||||
</LinearLayout>
|
|
@ -0,0 +1,100 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context=".registration.fragments.EnterCodeFragment">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/scroll_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:fillViewport="true">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:context=".registration.fragments.EnterCodeFragment">
|
||||
|
||||
<Button
|
||||
android:id="@+id/wrong_number"
|
||||
style="@style/Button.Borderless.Registration"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_toEndOf="@id/call_me_count_down"
|
||||
android:text="@string/RegistrationActivity_wrong_number"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/call_me_count_down"
|
||||
app:layout_constraintTop_toBottomOf="@+id/code" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/no_code"
|
||||
style="@style/Button.Borderless.Registration"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_toEndOf="@id/call_me_count_down"
|
||||
android:text="@string/RegistrationActivity_contact_signal_support"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/call_me_count_down"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/wrong_number"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/verify_header"
|
||||
style="@style/Signal.Text.Headline.Registration"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="40dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:gravity="center"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@string/RegistrationActivity_enter_the_code_we_sent_to_s" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.registration.CallMeCountDownView
|
||||
android:id="@+id/call_me_count_down"
|
||||
style="@style/Button.Borderless.Registration"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/code"
|
||||
app:layout_constraintEnd_toStartOf="@+id/wrong_number"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/wrong_number"
|
||||
tools:text="@string/RegistrationActivity_call" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.registration.VerificationCodeView
|
||||
android:id="@+id/code"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="32dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/verify_header" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<org.thoughtcrime.securesms.components.registration.VerificationPinKeyboard
|
||||
android:id="@+id/keyboard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,134 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/scroll_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true"
|
||||
tools:context=".registration.fragments.EnterPhoneNumberFragment">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/country_spinner_frame"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:background="@drawable/labeled_edit_text_background_inactive"
|
||||
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/verify_subheader">
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/country_spinner"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:textAlignment="viewStart" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linearLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layoutDirection="ltr"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/country_spinner_frame">
|
||||
|
||||
<org.thoughtcrime.securesms.components.LabeledEditText
|
||||
android:id="@+id/country_code"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_weight="1"
|
||||
app:labeledEditText_background="@color/white"
|
||||
app:labeledEditText_textLayout="@layout/country_code_text" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.LabeledEditText
|
||||
android:id="@+id/number"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="3"
|
||||
app:labeledEditText_background="@color/white"
|
||||
app:labeledEditText_label="Phone Number"
|
||||
app:labeledEditText_textLayout="@layout/phone_text" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/verify_header"
|
||||
style="@style/Signal.Text.Headline.Registration"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="40dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/RegistrationActivity_enter_your_phone_number_to_get_started"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/verify_subheader"
|
||||
style="@style/Signal.Text.Body.Registration"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/RegistrationActivity_you_will_receive_a_verification_code"
|
||||
app:layout_constraintTop_toBottomOf="@+id/verify_header"
|
||||
tools:layout_editor_absoluteX="0dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/cancel_button"
|
||||
style="@style/Button.Borderless.Registration"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@android:string/cancel"
|
||||
android:textColor="@color/core_grey_60"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/registerButton"
|
||||
app:layout_constraintVertical_bias="0" />
|
||||
|
||||
<com.dd.CircularProgressButton
|
||||
android:id="@+id/registerButton"
|
||||
style="@style/Button.Registration"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cpb_textIdle="@string/RegistrationActivity_next"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/linearLayout"
|
||||
app:layout_constraintVertical_bias="0" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</ScrollView>
|
|
@ -0,0 +1,113 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true"
|
||||
tools:context=".registration.fragments.RegistrationLockFragment">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/clarification_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/registration_activity__the_registration_lock_pin_is_not_the_same_as_the_sms_verification_code_you_just_received_please_enter_the_pin_you_previously_configured_in_the_application"
|
||||
android:textColor="#73B7F0"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/verify_header"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/verify_header"
|
||||
style="@style/Signal.Text.Headline.Registration"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="40dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/RegistrationActivity_enter_your_phone_number_to_get_started"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/verify_subheader"
|
||||
style="@style/Signal.Text.Body.Registration"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/RegistrationActivity_you_will_receive_a_verification_code"
|
||||
app:layout_constraintTop_toBottomOf="@+id/verify_header"
|
||||
tools:layout_editor_absoluteX="0dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/forgot_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="16dp"
|
||||
android:padding="16dp"
|
||||
android:text="@string/registration_activity__forgot_pin"
|
||||
android:textColor="@color/blue_400"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/pinButton" />
|
||||
|
||||
<com.dd.CircularProgressButton
|
||||
android:id="@+id/pinButton"
|
||||
style="@style/Button.Registration"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:cpb_textIdle="@string/RegistrationActivity_continue"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/textInputLayout" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/textInputLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/clarification_label">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/pin"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:hint="@string/registration_activity__registration_lock_pin"
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="numberPassword" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</ScrollView>
|
|
@ -0,0 +1,107 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true"
|
||||
tools:context=".registration.fragments.RestoreBackupFragment">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/skip_restore_button"
|
||||
style="@style/Button.Borderless.Registration"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="16dp"
|
||||
android:paddingStart="30dp"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingEnd="30dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:text="@string/registration_activity__skip"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/restore_button" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/verify_subheader"
|
||||
style="@style/Signal.Text.Body.Registration"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/RegistrationActivity_restore_your_messages_and_media_from_a_local_backup"
|
||||
app:layout_constraintTop_toBottomOf="@+id/verify_header"
|
||||
tools:layout_editor_absoluteX="0dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/backup_created_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
app:layout_constraintStart_toStartOf="@+id/verify_subheader"
|
||||
app:layout_constraintTop_toBottomOf="@+id/verify_subheader"
|
||||
tools:text="Backup created: 1 min ago" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/backup_size_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintStart_toStartOf="@+id/backup_created_text"
|
||||
app:layout_constraintTop_toBottomOf="@+id/backup_created_text"
|
||||
tools:text="Backup size: 899 KB" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/backup_progress_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/backup_size_text"
|
||||
tools:text="100 messages so far..." />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/verify_header"
|
||||
style="@style/Signal.Text.Headline.Registration"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="40dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/RegistrationActivity_restore_from_backup"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.dd.CircularProgressButton
|
||||
android:id="@+id/restore_button"
|
||||
style="@style/Button.Registration"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:cpb_textIdle="@string/registration_activity__restore_backup"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/backup_progress_text" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</ScrollView>
|
|
@ -1,29 +1,27 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="48dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_margin="16dp"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/welcome"
|
||||
app:layout_constraintBottom_toTopOf="@+id/textView"
|
||||
app:layout_constraintBottom_toTopOf="@+id/title"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView"
|
||||
android:id="@+id/title"
|
||||
style="@style/Signal.Text.Headline.Registration"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="80dp"
|
||||
android:layout_marginBottom="40dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/RegistrationActivity_take_privacy_with_you_be_yourself_in_every_message"
|
||||
app:layout_constraintBottom_toTopOf="@+id/welcome_terms_button"
|
||||
|
@ -32,10 +30,10 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/welcome_terms_button"
|
||||
style="@style/Signal.Text.Body.Registration"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
style="@style/Signal.Text.Body"
|
||||
android:text="@string/RegistrationActivity_terms_and_privacy"
|
||||
android:textColor="@color/signal_primary"
|
||||
app:layout_constraintBottom_toTopOf="@+id/welcome_continue_button"
|
||||
|
@ -44,19 +42,12 @@
|
|||
|
||||
<com.dd.CircularProgressButton
|
||||
android:id="@+id/welcome_continue_button"
|
||||
style="@style/Button.Registration"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="54dp"
|
||||
android:background="@color/signal_primary"
|
||||
android:textColor="@color/white"
|
||||
android:elevation="4dp"
|
||||
app:cpb_colorIndicator="@color/white"
|
||||
app:cpb_colorProgress="@color/textsecure_primary"
|
||||
app:cpb_cornerRadius="4dp"
|
||||
app:cpb_selectorIdle="@drawable/progress_button_state"
|
||||
android:layout_marginBottom="@dimen/registration_button_bottom_margin"
|
||||
app:cpb_textIdle="@string/RegistrationActivity_continue"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/signal_primary">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/watermark"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/icon_transparent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintVertical_bias="0.4"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -12,4 +12,6 @@
|
|||
android:background="@color/transparent"
|
||||
android:singleLine="true"
|
||||
android:inputType="phone"
|
||||
android:saveEnabled="false"
|
||||
android:hint="@string/RegistrationActivity_phone_number_description"
|
||||
tools:text="867-5309" />
|
||||
|
|
|
@ -1,333 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:background="@color/white"
|
||||
android:fillViewport="true"
|
||||
tools:context=".RegistrationActivity">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="40dp"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/verify_header"
|
||||
style="@style/Signal.Text.Headline.Registration"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:paddingBottom="16dp"
|
||||
android:text="@string/RegistrationActivity_enter_your_phone_number_to_get_started" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/verify_subheader"
|
||||
style="@style/Signal.Text.Body"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:paddingBottom="25dp"
|
||||
android:text="@string/RegistrationActivity_you_will_receive_a_verification_code" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/restore_container"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/header"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="0dp"
|
||||
android:visibility="invisible"
|
||||
tools:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/backup_created_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="Backup created: 1 min ago" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/backup_size_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
tools:text="Backup size: 899 KB" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/backup_progress_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="16dp"
|
||||
tools:text="100 messages so far..." />
|
||||
|
||||
<com.dd.CircularProgressButton
|
||||
android:id="@+id/restore_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="20dp"
|
||||
android:background="@color/signal_primary"
|
||||
android:textColor="@color/white"
|
||||
app:cpb_colorIndicator="@color/white"
|
||||
app:cpb_colorProgress="@color/textsecure_primary"
|
||||
app:cpb_cornerRadius="4dp"
|
||||
app:cpb_selectorIdle="@drawable/progress_button_state"
|
||||
app:cpb_textIdle="@string/registration_activity__restore_backup" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/skip_restore_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="13dp"
|
||||
android:paddingStart="30dp"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingEnd="30dp"
|
||||
android:paddingBottom="10dp"
|
||||
style="@style/Button.Borderless.Registration"
|
||||
android:text="@string/registration_activity__skip" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/registration_container"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/header"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:animateLayoutChanges="true"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="0dp"
|
||||
android:clipToPadding="false"
|
||||
android:clipChildren="false"
|
||||
tools:visibility="gone">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="@drawable/labeled_edit_text_background_inactive">
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/country_spinner"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layoutDirection="ltr"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<org.thoughtcrime.securesms.components.LabeledEditText
|
||||
android:id="@+id/country_code"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="12dp"
|
||||
app:labeledEditText_background="@color/white"
|
||||
app:labeledEditText_textLayout="@layout/country_code_text" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.LabeledEditText
|
||||
android:id="@+id/number"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="3"
|
||||
app:labeledEditText_background="@color/white"
|
||||
app:labeledEditText_label="Phone Number"
|
||||
app:labeledEditText_textLayout="@layout/phone_text"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.dd.CircularProgressButton
|
||||
android:id="@+id/registerButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="20dp"
|
||||
android:background="@color/signal_primary"
|
||||
android:textColor="@color/white"
|
||||
app:cpb_colorIndicator="@color/white"
|
||||
app:cpb_colorProgress="@color/textsecure_primary"
|
||||
app:cpb_cornerRadius="4dp"
|
||||
app:cpb_selectorIdle="@drawable/progress_button_state"
|
||||
app:cpb_textIdle="@string/RegistrationActivity_next" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/skip_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="@android:string/cancel"
|
||||
android:textColor="@color/core_grey_60" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/verification_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/header"
|
||||
android:visibility="invisible"
|
||||
tools:visibility="visible">
|
||||
|
||||
<org.thoughtcrime.securesms.components.registration.VerificationCodeView
|
||||
android:id="@+id/code"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:vcv_inputColor="@color/core_black"
|
||||
app:vcv_inputWidth="30dp"
|
||||
app:vcv_spacing="10dp"
|
||||
app:vcv_textColor="@color/core_black" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.registration.CallMeCountDownView
|
||||
android:id="@+id/call_me_count_down"
|
||||
style="@style/Button.Borderless.Registration"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/code"
|
||||
app:layout_constraintTop_toTopOf="@+id/wrong_number"
|
||||
app:layout_constraintStart_toStartOf="@+id/code" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/wrong_number"
|
||||
style="@style/Button.Borderless.Registration"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:layout_toEndOf="@id/call_me_count_down"
|
||||
android:text="@string/RegistrationActivity_wrong_number"
|
||||
app:layout_constraintEnd_toEndOf="@+id/code"
|
||||
app:layout_constraintTop_toBottomOf="@+id/code" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.registration.VerificationPinKeyboard
|
||||
android:id="@+id/keyboard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/pin_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/header"
|
||||
android:animateLayoutChanges="true"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="30dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingBottom="0dp"
|
||||
android:visibility="invisible"
|
||||
tools:visibility="gone">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/pin_clarification_container"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/clarification_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginTop="-2dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:text="@string/registration_activity__the_registration_lock_pin_is_not_the_same_as_the_sms_verification_code_you_just_received_please_enter_the_pin_you_previously_configured_in_the_application"
|
||||
android:textColor="#73B7F0" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="15dp"
|
||||
android:paddingStart="100dp"
|
||||
android:paddingEnd="100dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/pin"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/registration_activity__registration_lock_pin"
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="numberPassword" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.dd.CircularProgressButton
|
||||
android:id="@+id/pinButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="40dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:background="@color/signal_primary"
|
||||
android:textColor="@color/white"
|
||||
app:cpb_colorIndicator="@color/white"
|
||||
app:cpb_colorProgress="@color/textsecure_primary"
|
||||
app:cpb_cornerRadius="4dp"
|
||||
app:cpb_selectorIdle="@drawable/progress_button_state"
|
||||
app:cpb_textIdle="@string/RegistrationActivity_continue" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/forgot_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginTop="20dp"
|
||||
android:padding="15dp"
|
||||
android:text="@string/registration_activity__forgot_pin"
|
||||
android:textColor="@color/blue_400" />
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</ScrollView>
|
|
@ -1,135 +1,154 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center">
|
||||
android:layout_gravity="center"
|
||||
android:layoutDirection="ltr">
|
||||
|
||||
<LinearLayout
|
||||
<FrameLayout
|
||||
android:id="@+id/container_zero"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="54dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/labeled_edit_text_background_inactive">
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/labeled_edit_text_background_inactive"
|
||||
app:layout_constraintDimensionRatio="w,1:1"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/code_zero"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="28dp"
|
||||
tools:text="1" />
|
||||
android:layout_gravity="center"
|
||||
android:textColor="@color/core_black"
|
||||
android:textSize="28sp"
|
||||
tools:text="0" />
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
<FrameLayout
|
||||
android:id="@+id/container_one"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="54dp"
|
||||
android:layout_marginStart="5dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/labeled_edit_text_background_inactive">
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:background="@drawable/labeled_edit_text_background_inactive"
|
||||
app:layout_constraintDimensionRatio="w,1:1"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/container_zero"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/code_one"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="28dp"
|
||||
tools:text="2" />
|
||||
android:layout_gravity="center"
|
||||
android:textColor="@color/core_black"
|
||||
android:textSize="28sp"
|
||||
tools:text="1" />
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
<FrameLayout
|
||||
android:id="@+id/container_two"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="54dp"
|
||||
android:layout_marginStart="5dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/labeled_edit_text_background_inactive">
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:background="@drawable/labeled_edit_text_background_inactive"
|
||||
app:layout_constraintDimensionRatio="w,1:1"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/container_one"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/code_two"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="28dp"
|
||||
android:layout_gravity="center"
|
||||
android:textColor="@color/core_black"
|
||||
android:textSize="28sp"
|
||||
tools:text="2" />
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/separator_container"
|
||||
<TextView
|
||||
android:id="@+id/separator"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="5dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:layout_gravity="center"
|
||||
android:orientation="vertical">
|
||||
android:layout_marginStart="4dp"
|
||||
android:text="-"
|
||||
android:textColor="@color/core_black"
|
||||
android:textSize="28sp"
|
||||
app:layout_constraintStart_toEndOf="@+id/container_two"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/separator"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="-"
|
||||
android:textSize="28dp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
<FrameLayout
|
||||
android:id="@+id/container_three"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="54dp"
|
||||
android:layout_marginStart="5dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/labeled_edit_text_background_inactive">
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:background="@drawable/labeled_edit_text_background_inactive"
|
||||
app:layout_constraintDimensionRatio="w,1:1"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/separator"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/code_three"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="28dp"
|
||||
tools:text="2" />
|
||||
android:layout_gravity="center"
|
||||
android:textColor="@color/core_black"
|
||||
android:textSize="28sp"
|
||||
tools:text="3" />
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
<FrameLayout
|
||||
android:id="@+id/container_four"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="54dp"
|
||||
android:layout_marginStart="5dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/labeled_edit_text_background_inactive">
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:background="@drawable/labeled_edit_text_background_inactive"
|
||||
app:layout_constraintDimensionRatio="w,1:1"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/container_three"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/code_four"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="28dp"
|
||||
tools:text="2" />
|
||||
android:layout_gravity="center"
|
||||
android:textColor="@color/core_black"
|
||||
android:textSize="28sp"
|
||||
tools:text="4" />
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
<FrameLayout
|
||||
android:id="@+id/container_five"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="54dp"
|
||||
android:layout_marginStart="5dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/labeled_edit_text_background_inactive">
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:background="@drawable/labeled_edit_text_background_inactive"
|
||||
app:layout_constraintDimensionRatio="w,1:1"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/container_four"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/code_five"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="28dp"
|
||||
android:gravity="center"
|
||||
tools:text="2" />
|
||||
android:layout_gravity="center"
|
||||
android:textColor="@color/core_black"
|
||||
android:textSize="28sp"
|
||||
tools:text="5" />
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,202 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/signup"
|
||||
app:startDestination="@id/welcomeFragment">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/welcomeFragment"
|
||||
android:name="org.thoughtcrime.securesms.registration.fragments.WelcomeFragment"
|
||||
android:label="fragment_welcome"
|
||||
tools:layout="@layout/fragment_registration_welcome">
|
||||
|
||||
<action
|
||||
android:id="@+id/action_restore"
|
||||
app:destination="@id/restoreBackupFragment"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
||||
|
||||
<action
|
||||
android:id="@+id/action_skip_restore"
|
||||
app:destination="@id/enterPhoneNumberFragment"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/enterPhoneNumberFragment"
|
||||
android:name="org.thoughtcrime.securesms.registration.fragments.EnterPhoneNumberFragment"
|
||||
android:label="fragment_enter_phone_number"
|
||||
tools:layout="@layout/fragment_registration_enter_phone_number">
|
||||
|
||||
<action
|
||||
android:id="@+id/action_pickCountry"
|
||||
app:destination="@id/countryPickerFragment"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:launchSingleTop="true"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
||||
|
||||
<action
|
||||
android:id="@+id/action_enterVerificationCode"
|
||||
app:destination="@id/enterCodeFragment"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
||||
|
||||
<action
|
||||
android:id="@+id/action_requestCaptcha"
|
||||
app:destination="@id/captchaFragment"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/countryPickerFragment"
|
||||
android:name="org.thoughtcrime.securesms.registration.fragments.CountryPickerFragment"
|
||||
android:label="fragment_country_picker"
|
||||
tools:layout="@layout/fragment_registration_country_picker">
|
||||
|
||||
<action
|
||||
android:id="@+id/action_countrySelected"
|
||||
app:popUpTo="@id/countryPickerFragment"
|
||||
app:popUpToInclusive="true" />
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/enterCodeFragment"
|
||||
android:name="org.thoughtcrime.securesms.registration.fragments.EnterCodeFragment"
|
||||
android:label="fragment_enter_code"
|
||||
tools:layout="@layout/fragment_registration_enter_code">
|
||||
|
||||
<action
|
||||
android:id="@+id/action_requireRegistrationLockPin"
|
||||
app:destination="@id/registrationLockFragment"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim"
|
||||
app:popUpTo="@+id/welcomeFragment"
|
||||
app:popUpToInclusive="true" />
|
||||
|
||||
<action
|
||||
android:id="@+id/action_wrongNumber"
|
||||
app:popUpTo="@id/enterCodeFragment"
|
||||
app:popUpToInclusive="true" />
|
||||
|
||||
<action
|
||||
android:id="@+id/action_requestCaptcha"
|
||||
app:destination="@id/captchaFragment"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
||||
|
||||
<action
|
||||
android:id="@+id/action_successfulRegistration"
|
||||
app:destination="@id/registrationCompletePlaceHolderFragment"
|
||||
app:popUpTo="@+id/welcomeFragment"
|
||||
app:popUpToInclusive="true" />
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/registrationLockFragment"
|
||||
android:name="org.thoughtcrime.securesms.registration.fragments.RegistrationLockFragment"
|
||||
android:label="fragment_registration_lock"
|
||||
tools:layout="@layout/fragment_registration_lock">
|
||||
|
||||
<action
|
||||
android:id="@+id/action_successfulRegistration"
|
||||
app:destination="@id/registrationCompletePlaceHolderFragment"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim"
|
||||
app:popUpTo="@+id/welcomeFragment"
|
||||
app:popUpToInclusive="true" />
|
||||
|
||||
<argument
|
||||
android:name="timeRemaining"
|
||||
app:argType="long" />
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/captchaFragment"
|
||||
android:name="org.thoughtcrime.securesms.registration.fragments.CaptchaFragment"
|
||||
android:label="fragment_captcha"
|
||||
tools:layout="@layout/fragment_registration_captcha">
|
||||
|
||||
<action
|
||||
android:id="@+id/action_captchaComplete"
|
||||
app:popUpTo="@id/captchaFragment"
|
||||
app:popUpToInclusive="true" />
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/restoreBackupFragment"
|
||||
android:name="org.thoughtcrime.securesms.registration.fragments.RestoreBackupFragment"
|
||||
android:label="fragment_restore_backup"
|
||||
tools:layout="@layout/fragment_registration_restore_backup">
|
||||
|
||||
<action
|
||||
android:id="@+id/action_backupRestored"
|
||||
app:destination="@id/enterPhoneNumberFragment"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim"
|
||||
app:popUpTo="@id/restoreBackupFragment"
|
||||
app:popUpToInclusive="true" />
|
||||
|
||||
<action
|
||||
android:id="@+id/action_skip"
|
||||
app:destination="@id/enterPhoneNumberFragment"
|
||||
app:enterAnim="@anim/slide_from_end"
|
||||
app:exitAnim="@anim/slide_to_start"
|
||||
app:popEnterAnim="@anim/slide_from_start"
|
||||
app:popExitAnim="@anim/slide_to_end" />
|
||||
|
||||
<action
|
||||
android:id="@+id/action_noBackupFound"
|
||||
app:destination="@id/enterPhoneNumberFragment"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim"
|
||||
app:popUpTo="@id/restoreBackupFragment"
|
||||
app:popUpToInclusive="true" />
|
||||
|
||||
<action
|
||||
android:id="@+id/action_skip_no_return"
|
||||
app:destination="@id/enterPhoneNumberFragment"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim"
|
||||
app:popUpTo="@+id/restoreBackupFragment"
|
||||
app:popUpToInclusive="true" />
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/registrationCompletePlaceHolderFragment"
|
||||
android:name="org.thoughtcrime.securesms.registration.fragments.RegistrationCompleteFragment"
|
||||
android:label="fragment_registration_complete_place_holder"
|
||||
tools:layout="@layout/fragment_registration_blank" />
|
||||
|
||||
</navigation>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<dimen name="registration_headline_text">28sp</dimen>
|
||||
<dimen name="registration_body_text">16sp</dimen>
|
||||
<dimen name="registration_button_bottom_margin">54dp</dimen>
|
||||
|
||||
</resources>
|
|
@ -315,15 +315,6 @@
|
|||
</attr>
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="VerificationCodeView">
|
||||
<attr name="vcv_spacing" format="dimension"/>
|
||||
<attr name="vcv_inputWidth" format="dimension"/>
|
||||
<attr name="vcv_inputHeight" format="dimension"/>
|
||||
<attr name="vcv_inputColor" format="color"/>
|
||||
<attr name="vcv_textSize" format="dimension"/>
|
||||
<attr name="vcv_textColor" format="color"/>
|
||||
</declare-styleable>
|
||||
|
||||
<declare-styleable name="QuoteView">
|
||||
<attr name="message_type" format="enum">
|
||||
<enum name="preview" value="0" />
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<dimen name="registration_headline_text">18sp</dimen>
|
||||
<dimen name="registration_body_text">12sp</dimen>
|
||||
<dimen name="registration_button_bottom_margin">32dp</dimen>
|
||||
|
||||
</resources>
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<style name="Button.Registration" parent="Button.Borderless">
|
||||
<item name="android:textSize">@dimen/registration_body_text</item>
|
||||
<item name="android:textColor">@color/white</item>
|
||||
<item name="android:elevation" tools:ignore="NewApi">4dp</item>
|
||||
<item name="cpb_cornerRadius">4dp</item>
|
||||
<item name="cpb_colorIndicator">@color/white</item>
|
||||
<item name="cpb_colorProgress">@color/textsecure_primary</item>
|
||||
<item name="cpb_selectorIdle">@drawable/progress_button_state</item>
|
||||
</style>
|
||||
|
||||
<style name="Signal.Text.Headline.Registration">
|
||||
<item name="android:fontFamily">sans-serif-medium</item>
|
||||
</style>
|
||||
|
||||
<style name="Signal.Text.Body.Registration">
|
||||
<item name="android:textSize">@dimen/registration_body_text</item>
|
||||
</style>
|
||||
|
||||
<style name="Button.Borderless.Registration">
|
||||
<item name="android:textColor">@color/core_grey_60</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
|
@ -669,6 +669,9 @@
|
|||
<string name="RegistrationActivity_enter_your_phone_number_to_get_started">Enter your phone number to get started</string>
|
||||
<string name="RegistrationActivity_you_will_receive_a_verification_code">You will receive a verification code. Carrier rates may apply.</string>
|
||||
<string name="RegistrationActivity_enter_the_code_we_sent_to_s">Enter the code we sent to %s</string>
|
||||
|
||||
<string name="RegistrationActivity_phone_number_description">Phone number</string>
|
||||
<string name="RegistrationActivity_country_code_description">Country code</string>
|
||||
<string name="RegistrationActivity_call">Call</string>
|
||||
|
||||
<!-- RevealableMessageView -->
|
||||
|
@ -1581,6 +1584,10 @@
|
|||
<string name="RegistrationActivity_please_enter_the_verification_code_sent_to_s">Please enter the verification code sent to %s.</string>
|
||||
<string name="RegistrationActivity_wrong_number">Wrong number</string>
|
||||
<string name="RegistrationActivity_call_me_instead_available_in">Call me instead \n (Available in %1$02d:%2$02d)</string>
|
||||
<string name="RegistrationActivity_contact_signal_support">Contact Signal Support</string>
|
||||
<string name="RegistrationActivity_support_email" translatable="false">support@signal.org</string>
|
||||
<string name="RegistrationActivity_code_support_subject">Signal Registration - Verification Code for Android</string>
|
||||
<string name="RegistrationActivity_code_support_body">Subject: Signal Registration - Verification Code for Android\nDevice info: %1$s\nAndroid version: %2$s\nSignal version: %3$s\nLocale: %4$s</string>
|
||||
<string name="BackupUtil_never">Never</string>
|
||||
<string name="BackupUtil_unknown">Unknown</string>
|
||||
<string name="preferences_app_protection__screen_lock">Screen lock</string>
|
||||
|
|
|
@ -248,10 +248,6 @@
|
|||
<item name="android:textColor">@color/signal_primary</item>
|
||||
</style>
|
||||
|
||||
<style name="Button.Borderless.Registration" parent="Base.Widget.AppCompat.Button.Borderless">
|
||||
<item name="android:textColor">@color/core_grey_60</item>
|
||||
</style>
|
||||
|
||||
<!-- RedPhone -->
|
||||
|
||||
<!-- Buttons in the main "button row" of the in-call onscreen touch UI. -->
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<style name="Signal.Text.Headline" parent="Base.TextAppearance.AppCompat.Headline">
|
||||
<item name="android:textSize">28sp</item>
|
||||
<item name="android:lineSpacingExtra">3sp</item>
|
||||
|
@ -7,10 +8,6 @@
|
|||
<item name="android:letterSpacing" tools:ignore="NewApi">0</item>
|
||||
</style>
|
||||
|
||||
<style name="Signal.Text.Headline.Registration" parent="Signal.Text.Headline">
|
||||
<item name="android:fontFamily">sans-serif-medium</item>
|
||||
</style>
|
||||
|
||||
<style name="Signal.Text.Body" parent="Base.TextAppearance.AppCompat.Body1">
|
||||
<item name="android:textSize">16sp</item>
|
||||
<item name="android:lineSpacingExtra">2sp</item>
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
package org.thoughtcrime.securesms;
|
||||
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
public class CountrySelectionActivity extends BaseActivity
|
||||
implements CountrySelectionFragment.CountrySelectedListener
|
||||
|
||||
{
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
this.setContentView(R.layout.country_selection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void countrySelected(String countryName, int countryCode) {
|
||||
Intent result = getIntent();
|
||||
result.putExtra("country_name", countryName);
|
||||
result.putExtra("country_code", countryCode);
|
||||
|
||||
this.setResult(RESULT_OK, result);
|
||||
this.finish();
|
||||
}
|
||||
}
|
|
@ -5,20 +5,20 @@ import android.content.Context;
|
|||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.migrations.ApplicationMigrationActivity;
|
||||
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
||||
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
||||
import org.thoughtcrime.securesms.registration.WelcomeActivity;
|
||||
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
|
@ -29,13 +29,12 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
|
|||
|
||||
public static final String LOCALE_EXTRA = "locale_extra";
|
||||
|
||||
private static final int STATE_NORMAL = 0;
|
||||
private static final int STATE_CREATE_PASSPHRASE = 1;
|
||||
private static final int STATE_PROMPT_PASSPHRASE = 2;
|
||||
private static final int STATE_UI_BLOCKING_UPGRADE = 3;
|
||||
private static final int STATE_PROMPT_PUSH_REGISTRATION = 4;
|
||||
private static final int STATE_EXPERIENCE_UPGRADE = 5;
|
||||
private static final int STATE_WELCOME_SCREEN = 6;
|
||||
private static final int STATE_NORMAL = 0;
|
||||
private static final int STATE_CREATE_PASSPHRASE = 1;
|
||||
private static final int STATE_PROMPT_PASSPHRASE = 2;
|
||||
private static final int STATE_UI_BLOCKING_UPGRADE = 3;
|
||||
private static final int STATE_EXPERIENCE_UPGRADE = 4;
|
||||
private static final int STATE_WELCOME_PUSH_SCREEN = 5;
|
||||
|
||||
private SignalServiceNetworkAccess networkAccess;
|
||||
private BroadcastReceiver clearKeyReceiver;
|
||||
|
@ -133,13 +132,12 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
|
|||
Log.i(TAG, "routeApplicationState(), state: " + state);
|
||||
|
||||
switch (state) {
|
||||
case STATE_CREATE_PASSPHRASE: return getCreatePassphraseIntent();
|
||||
case STATE_PROMPT_PASSPHRASE: return getPromptPassphraseIntent();
|
||||
case STATE_UI_BLOCKING_UPGRADE: return getUiBlockingUpgradeIntent();
|
||||
case STATE_WELCOME_SCREEN: return getWelcomeIntent();
|
||||
case STATE_PROMPT_PUSH_REGISTRATION: return getPushRegistrationIntent();
|
||||
case STATE_EXPERIENCE_UPGRADE: return getExperienceUpgradeIntent();
|
||||
default: return null;
|
||||
case STATE_CREATE_PASSPHRASE: return getCreatePassphraseIntent();
|
||||
case STATE_PROMPT_PASSPHRASE: return getPromptPassphraseIntent();
|
||||
case STATE_UI_BLOCKING_UPGRADE: return getUiBlockingUpgradeIntent();
|
||||
case STATE_WELCOME_PUSH_SCREEN: return getPushRegistrationIntent();
|
||||
case STATE_EXPERIENCE_UPGRADE: return getExperienceUpgradeIntent();
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,10 +148,8 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
|
|||
return STATE_PROMPT_PASSPHRASE;
|
||||
} else if (ApplicationMigrations.isUpdate(this) && ApplicationMigrations.isUiBlockingMigrationRunning()) {
|
||||
return STATE_UI_BLOCKING_UPGRADE;
|
||||
} else if (!TextSecurePreferences.hasSeenWelcomeScreen(this)) {
|
||||
return STATE_WELCOME_SCREEN;
|
||||
} else if (!TextSecurePreferences.hasPromptedPushRegistration(this)) {
|
||||
return STATE_PROMPT_PUSH_REGISTRATION;
|
||||
return STATE_WELCOME_PUSH_SCREEN;
|
||||
} else if (ExperienceUpgradeActivity.isUpdate(this)) {
|
||||
return STATE_EXPERIENCE_UPGRADE;
|
||||
} else {
|
||||
|
@ -180,16 +176,8 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
|
|||
return getRoutedIntent(ExperienceUpgradeActivity.class, getIntent());
|
||||
}
|
||||
|
||||
private Intent getWelcomeIntent() {
|
||||
return getRoutedIntent(WelcomeActivity.class, getPushRegistrationIntent());
|
||||
}
|
||||
|
||||
private Intent getPushRegistrationIntent() {
|
||||
return getRoutedIntent(RegistrationActivity.class, getCreateProfileIntent());
|
||||
}
|
||||
|
||||
private Intent getCreateProfileIntent() {
|
||||
return getRoutedIntent(CreateProfileActivity.class, getConversationListIntent());
|
||||
return RegistrationNavigationActivity.newIntentForNewRegistration(this);
|
||||
}
|
||||
|
||||
private Intent getRoutedIntent(Class<?> destination, @Nullable Intent nextIntent) {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -84,4 +84,10 @@ public class LabeledEditText extends FrameLayout implements View.OnFocusChangeLi
|
|||
border.setBackgroundResource(hasFocus ? R.drawable.labeled_edit_text_background_active
|
||||
: R.drawable.labeled_edit_text_background_inactive);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
super.setEnabled(enabled);
|
||||
input.setEnabled(enabled);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
package org.thoughtcrime.securesms.components.registration;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
public class CallMeCountDownView extends androidx.appcompat.widget.AppCompatButton {
|
||||
|
||||
private int countDown;
|
||||
@Nullable
|
||||
private Listener listener;
|
||||
|
||||
public CallMeCountDownView(Context context) {
|
||||
super(context);
|
||||
|
@ -44,12 +45,25 @@ public class CallMeCountDownView extends androidx.appcompat.widget.AppCompatButt
|
|||
countDown--;
|
||||
|
||||
int minutesRemaining = countDown / 60;
|
||||
int secondsRemaining = countDown - (minutesRemaining * 60);
|
||||
int secondsRemaining = countDown % 60;
|
||||
|
||||
setText(getResources().getString(R.string.RegistrationActivity_call_me_instead_available_in, minutesRemaining, secondsRemaining));
|
||||
|
||||
if (listener != null) {
|
||||
listener.onRemaining(this, countDown);
|
||||
}
|
||||
|
||||
postDelayed(this::updateCountDown, 1000);
|
||||
} else if (countDown == 0) {
|
||||
setCallEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
public void setListener(@Nullable Listener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
void onRemaining(@NonNull CallMeCountDownView view, int remaining);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,8 @@
|
|||
package org.thoughtcrime.securesms.components.registration;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.view.animation.AlphaAnimation;
|
||||
import android.view.animation.Animation;
|
||||
|
@ -20,6 +12,11 @@ import android.view.animation.TranslateAnimation;
|
|||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.annimon.stream.Collectors;
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
|
@ -28,66 +25,51 @@ import org.thoughtcrime.securesms.R;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class VerificationCodeView extends FrameLayout {
|
||||
public final class VerificationCodeView extends FrameLayout {
|
||||
|
||||
private final List<TextView> codes = new ArrayList<>(6);
|
||||
private final List<View> containers = new ArrayList<>(6);
|
||||
|
||||
private OnCodeEnteredListener listener;
|
||||
private int index = 0;
|
||||
private int index;
|
||||
|
||||
public VerificationCodeView(Context context) {
|
||||
super(context);
|
||||
initialize(context, null);
|
||||
initialize(context);
|
||||
}
|
||||
|
||||
public VerificationCodeView(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialize(context, attrs);
|
||||
initialize(context);
|
||||
}
|
||||
|
||||
public VerificationCodeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
initialize(context, attrs);
|
||||
initialize(context);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
public VerificationCodeView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
initialize(context, attrs);
|
||||
initialize(context);
|
||||
}
|
||||
|
||||
private void initialize(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
private void initialize(@NonNull Context context) {
|
||||
inflate(context, R.layout.verification_code_view, this);
|
||||
|
||||
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.VerificationCodeView);
|
||||
codes.add(findViewById(R.id.code_zero));
|
||||
codes.add(findViewById(R.id.code_one));
|
||||
codes.add(findViewById(R.id.code_two));
|
||||
codes.add(findViewById(R.id.code_three));
|
||||
codes.add(findViewById(R.id.code_four));
|
||||
codes.add(findViewById(R.id.code_five));
|
||||
|
||||
try {
|
||||
TextView separator = findViewById(R.id.separator);
|
||||
|
||||
this.codes.add(findViewById(R.id.code_zero));
|
||||
this.codes.add(findViewById(R.id.code_one));
|
||||
this.codes.add(findViewById(R.id.code_two));
|
||||
this.codes.add(findViewById(R.id.code_three));
|
||||
this.codes.add(findViewById(R.id.code_four));
|
||||
this.codes.add(findViewById(R.id.code_five));
|
||||
|
||||
this.containers.add(findViewById(R.id.container_zero));
|
||||
this.containers.add(findViewById(R.id.container_one));
|
||||
this.containers.add(findViewById(R.id.container_two));
|
||||
this.containers.add(findViewById(R.id.container_three));
|
||||
this.containers.add(findViewById(R.id.container_four));
|
||||
this.containers.add(findViewById(R.id.container_five));
|
||||
|
||||
Stream.of(codes).forEach(textView -> textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, typedArray.getDimension(R.styleable.VerificationCodeView_vcv_textSize, 30)));
|
||||
Stream.of(codes).forEach(textView -> textView.setTextColor(typedArray.getColor(R.styleable.VerificationCodeView_vcv_textColor, Color.GRAY)));
|
||||
|
||||
separator.setTextSize(TypedValue.COMPLEX_UNIT_SP, typedArray.getDimension(R.styleable.VerificationCodeView_vcv_textSize, 30));
|
||||
separator.setTextColor(typedArray.getColor(R.styleable.VerificationCodeView_vcv_textColor, Color.GRAY));
|
||||
|
||||
} finally {
|
||||
if (typedArray != null) typedArray.recycle();
|
||||
}
|
||||
containers.add(findViewById(R.id.container_zero));
|
||||
containers.add(findViewById(R.id.container_one));
|
||||
containers.add(findViewById(R.id.container_two));
|
||||
containers.add(findViewById(R.id.container_three));
|
||||
containers.add(findViewById(R.id.container_four));
|
||||
containers.add(findViewById(R.id.container_five));
|
||||
}
|
||||
|
||||
@MainThread
|
||||
|
@ -143,11 +125,11 @@ public class VerificationCodeView extends FrameLayout {
|
|||
setInactive(containers);
|
||||
}
|
||||
|
||||
private void setInactive(List<View> views) {
|
||||
private static void setInactive(List<View> views) {
|
||||
Stream.of(views).forEach(c -> c.setBackgroundResource(R.drawable.labeled_edit_text_background_inactive));
|
||||
}
|
||||
|
||||
private void setActive(@NonNull View container) {
|
||||
private static void setActive(@NonNull View container) {
|
||||
container.setBackgroundResource(R.drawable.labeled_edit_text_background_active);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
package org.thoughtcrime.securesms.components.reminder;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.view.View.OnClickListener;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.RegistrationActivity;
|
||||
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
public class PushRegistrationReminder extends Reminder {
|
||||
|
@ -14,13 +12,7 @@ public class PushRegistrationReminder extends Reminder {
|
|||
super(context.getString(R.string.reminder_header_push_title),
|
||||
context.getString(R.string.reminder_header_push_text));
|
||||
|
||||
final OnClickListener okListener = v -> {
|
||||
Intent intent = new Intent(context, RegistrationActivity.class);
|
||||
intent.putExtra(RegistrationActivity.RE_REGISTRATION_EXTRA, true);
|
||||
context.startActivity(intent);
|
||||
};
|
||||
|
||||
setOkListener(okListener);
|
||||
setOkListener(v -> context.startActivity(RegistrationNavigationActivity.newIntentForReRegistration(context)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
package org.thoughtcrime.securesms.components.reminder;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.RegistrationActivity;
|
||||
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
public class UnauthorizedReminder extends Reminder {
|
||||
|
@ -15,9 +13,7 @@ public class UnauthorizedReminder extends Reminder {
|
|||
context.getString(R.string.UnauthorizedReminder_this_is_likely_because_you_registered_your_phone_number_with_Signal_on_a_different_device));
|
||||
|
||||
setOkListener(v -> {
|
||||
Intent intent = new Intent(context, RegistrationActivity.class);
|
||||
intent.putExtra(RegistrationActivity.RE_REGISTRATION_EXTRA, true);
|
||||
context.startActivity(intent);
|
||||
context.startActivity(RegistrationNavigationActivity.newIntentForReRegistration(context));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -93,7 +93,6 @@ import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
|
|||
import org.thoughtcrime.securesms.PromptMmsActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.RecipientPreferenceActivity;
|
||||
import org.thoughtcrime.securesms.RegistrationActivity;
|
||||
import org.thoughtcrime.securesms.ShortcutLauncherActivity;
|
||||
import org.thoughtcrime.securesms.TransportOption;
|
||||
import org.thoughtcrime.securesms.VerifyIdentityActivity;
|
||||
|
@ -193,6 +192,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
|||
import org.thoughtcrime.securesms.recipients.RecipientExporter;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity;
|
||||
import org.thoughtcrime.securesms.search.model.MessageResult;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
|
@ -929,9 +929,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||
}
|
||||
|
||||
private void handleRegisterForSignal() {
|
||||
Intent intent = new Intent(this, RegistrationActivity.class);
|
||||
intent.putExtra(RegistrationActivity.RE_REGISTRATION_EXTRA, true);
|
||||
startActivity(intent);
|
||||
startActivity(RegistrationNavigationActivity.newIntentForReRegistration(this));
|
||||
}
|
||||
|
||||
private void handleInviteLink() {
|
||||
|
|
|
@ -9,23 +9,24 @@ import android.net.Uri;
|
|||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.provider.ContactsContract;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.preference.CheckBoxPreference;
|
||||
import androidx.preference.Preference;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.firebase.iid.FirebaseInstanceId;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
|
||||
import org.thoughtcrime.securesms.LogSubmitActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.RegistrationActivity;
|
||||
import org.thoughtcrime.securesms.contacts.ContactAccessor;
|
||||
import org.thoughtcrime.securesms.contacts.ContactIdentityManager;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.push.AccountManagerFactory;
|
||||
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
@ -220,12 +221,7 @@ public class AdvancedPreferenceFragment extends CorrectedPreferenceFragment {
|
|||
});
|
||||
builder.show();
|
||||
} else {
|
||||
Intent nextIntent = new Intent(getActivity(), ApplicationPreferencesActivity.class);
|
||||
|
||||
Intent intent = new Intent(getActivity(), RegistrationActivity.class);
|
||||
intent.putExtra(RegistrationActivity.RE_REGISTRATION_EXTRA, true);
|
||||
intent.putExtra("next_intent", nextIntent);
|
||||
startActivity(intent);
|
||||
startActivity(RegistrationNavigationActivity.newIntentForReRegistration(requireContext()));
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
package org.thoughtcrime.securesms.registration;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
|
||||
import org.thoughtcrime.securesms.BaseActionBarActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
public class CaptchaActivity extends BaseActionBarActivity {
|
||||
|
||||
public static final String KEY_TOKEN = "token";
|
||||
public static final String KEY_IS_SMS = "is_sms";
|
||||
|
||||
private static final String SIGNAL_SCHEME = "signalcaptcha://";
|
||||
|
||||
public static Intent getIntent(@NonNull Context context, boolean isSms) {
|
||||
Intent intent = new Intent(context, CaptchaActivity.class);
|
||||
intent.putExtra(KEY_IS_SMS, isSms);
|
||||
return intent;
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.captcha_activity);
|
||||
|
||||
WebView webView = findViewById(R.id.registration_captcha_web_view);
|
||||
|
||||
webView.getSettings().setJavaScriptEnabled(true);
|
||||
webView.clearCache(true);
|
||||
|
||||
webView.setWebViewClient(new WebViewClient() {
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
if (url != null && url.startsWith(SIGNAL_SCHEME)) {
|
||||
handleToken(url.substring(SIGNAL_SCHEME.length()));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
webView.loadUrl("https://signalcaptchas.org/registration/generate.html");
|
||||
}
|
||||
|
||||
public void handleToken(String token) {
|
||||
if (!TextUtils.isEmpty(token)) {
|
||||
Intent result = new Intent();
|
||||
result.putExtra(KEY_TOKEN, token);
|
||||
result.putExtra(KEY_IS_SMS, getIntent().getBooleanExtra(KEY_IS_SMS, true));
|
||||
setResult(RESULT_OK, result);
|
||||
} else {
|
||||
setResult(RESULT_CANCELED);
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package org.thoughtcrime.securesms.registration;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public final class ReceivedSmsEvent {
|
||||
|
||||
private final @NonNull String code;
|
||||
|
||||
public ReceivedSmsEvent(@NonNull String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public @NonNull String getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package org.thoughtcrime.securesms.registration;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.google.android.gms.auth.api.phone.SmsRetriever;
|
||||
import com.google.android.gms.common.api.CommonStatusCodes;
|
||||
import com.google.android.gms.common.api.Status;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.service.VerificationCodeParser;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
public final class RegistrationNavigationActivity extends AppCompatActivity {
|
||||
|
||||
private static final String TAG = Log.tag(RegistrationNavigationActivity.class);
|
||||
|
||||
public static final String RE_REGISTRATION_EXTRA = "re_registration";
|
||||
|
||||
private SmsRetrieverReceiver smsRetrieverReceiver;
|
||||
|
||||
public static Intent newIntentForNewRegistration(@NonNull Context context) {
|
||||
Intent intent = new Intent(context, RegistrationNavigationActivity.class);
|
||||
intent.putExtra(RE_REGISTRATION_EXTRA, false);
|
||||
return intent;
|
||||
}
|
||||
|
||||
public static Intent newIntentForReRegistration(@NonNull Context context) {
|
||||
Intent intent = new Intent(context, RegistrationNavigationActivity.class);
|
||||
intent.putExtra(RE_REGISTRATION_EXTRA, true);
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_registration_navigation);
|
||||
initializeChallengeListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
shutdownChallengeListener();
|
||||
}
|
||||
|
||||
private void initializeChallengeListener() {
|
||||
smsRetrieverReceiver = new SmsRetrieverReceiver();
|
||||
|
||||
registerReceiver(smsRetrieverReceiver, new IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION));
|
||||
}
|
||||
|
||||
private void shutdownChallengeListener() {
|
||||
if (smsRetrieverReceiver != null) {
|
||||
unregisterReceiver(smsRetrieverReceiver);
|
||||
smsRetrieverReceiver = null;
|
||||
}
|
||||
}
|
||||
|
||||
private class SmsRetrieverReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.i(TAG, "SmsRetrieverReceiver received a broadcast...");
|
||||
|
||||
if (SmsRetriever.SMS_RETRIEVED_ACTION.equals(intent.getAction())) {
|
||||
Bundle extras = intent.getExtras();
|
||||
Status status = (Status) extras.get(SmsRetriever.EXTRA_STATUS);
|
||||
|
||||
switch (status.getStatusCode()) {
|
||||
case CommonStatusCodes.SUCCESS:
|
||||
Optional<String> code = VerificationCodeParser.parse(context, (String) extras.get(SmsRetriever.EXTRA_SMS_MESSAGE));
|
||||
if (code.isPresent()) {
|
||||
Log.i(TAG, "Received verification code.");
|
||||
handleVerificationCodeReceived(code.get());
|
||||
} else {
|
||||
Log.w(TAG, "Could not parse verification code.");
|
||||
}
|
||||
break;
|
||||
case CommonStatusCodes.TIMEOUT:
|
||||
Log.w(TAG, "Hit a timeout waiting for the SMS to arrive.");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "SmsRetrieverReceiver received the wrong action?");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleVerificationCodeReceived(@NonNull String code) {
|
||||
EventBus.getDefault().post(new ReceivedSmsEvent(code));
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
package org.thoughtcrime.securesms.registration;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.BaseActionBarActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
public class WelcomeActivity extends BaseActionBarActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.registration_welcome_activity);
|
||||
findViewById(R.id.welcome_terms_button).setOnClickListener(v -> onTermsClicked());
|
||||
findViewById(R.id.welcome_continue_button).setOnClickListener(v -> onContinueClicked());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
private void onTermsClicked() {
|
||||
CommunicationActions.openBrowserLink(this, "https://signal.org/legal");
|
||||
}
|
||||
|
||||
private void onContinueClicked() {
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.WRITE_CONTACTS, Manifest.permission.READ_CONTACTS,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.READ_PHONE_STATE)
|
||||
.ifNecessary()
|
||||
.withRationaleDialog(getString(R.string.RegistrationActivity_signal_needs_access_to_your_contacts_and_media_in_order_to_connect_with_friends),
|
||||
R.drawable.ic_contacts_white_48dp, R.drawable.ic_folder_white_48dp)
|
||||
.onAnyResult(() -> {
|
||||
TextSecurePreferences.setHasSeenWelcomeScreen(WelcomeActivity.this, true);
|
||||
|
||||
Intent nextIntent = getIntent().getParcelableExtra("next_intent");
|
||||
|
||||
if (nextIntent == null) {
|
||||
throw new IllegalStateException("Was not supplied a next_intent.");
|
||||
}
|
||||
|
||||
startActivity(nextIntent);
|
||||
overridePendingTransition(R.anim.slide_from_end, R.anim.fade_scale_out);
|
||||
finish();
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
package org.thoughtcrime.securesms.registration.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.lifecycle.SavedStateViewModelFactory;
|
||||
import androidx.lifecycle.ViewModelProviders;
|
||||
|
||||
import com.dd.CircularProgressButton;
|
||||
|
||||
import org.thoughtcrime.securesms.LogSubmitActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel;
|
||||
|
||||
import static org.thoughtcrime.securesms.registration.RegistrationNavigationActivity.RE_REGISTRATION_EXTRA;
|
||||
|
||||
abstract class BaseRegistrationFragment extends Fragment {
|
||||
|
||||
private RegistrationViewModel model;
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
this.model = getRegistrationViewModel(requireActivity());
|
||||
}
|
||||
|
||||
protected @NonNull RegistrationViewModel getModel() {
|
||||
return model;
|
||||
}
|
||||
|
||||
protected boolean isReregister() {
|
||||
Activity activity = getActivity();
|
||||
|
||||
if (activity == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return activity.getIntent().getBooleanExtra(RE_REGISTRATION_EXTRA, false);
|
||||
}
|
||||
|
||||
protected static RegistrationViewModel getRegistrationViewModel(@NonNull FragmentActivity activity) {
|
||||
SavedStateViewModelFactory savedStateViewModelFactory = new SavedStateViewModelFactory(activity.getApplication(), activity);
|
||||
|
||||
return ViewModelProviders.of(activity, savedStateViewModelFactory).get(RegistrationViewModel.class);
|
||||
}
|
||||
|
||||
protected static void setSpinning(@Nullable CircularProgressButton button) {
|
||||
if (button != null) {
|
||||
button.setClickable(false);
|
||||
button.setIndeterminateProgressMode(true);
|
||||
button.setProgress(50);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void cancelSpinning(@Nullable CircularProgressButton button) {
|
||||
if (button != null) {
|
||||
button.setProgress(0);
|
||||
button.setIndeterminateProgressMode(false);
|
||||
button.setClickable(true);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void hideKeyboard(@NonNull Context context, @NonNull View view) {
|
||||
InputMethodManager imm = (InputMethodManager) context.getSystemService(Activity.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets view up to allow log submitting after multiple taps.
|
||||
*/
|
||||
protected static void setDebugLogSubmitMultiTapView(@Nullable View view) {
|
||||
if (view == null) return;
|
||||
|
||||
view.setOnClickListener(new View.OnClickListener() {
|
||||
|
||||
private static final int DEBUG_TAP_TARGET = 8;
|
||||
private static final int DEBUG_TAP_ANNOUNCE = 4;
|
||||
|
||||
private int debugTapCounter;
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Context context = v.getContext();
|
||||
|
||||
debugTapCounter++;
|
||||
|
||||
if (debugTapCounter >= DEBUG_TAP_TARGET) {
|
||||
context.startActivity(new Intent(context, LogSubmitActivity.class));
|
||||
} else if (debugTapCounter >= DEBUG_TAP_ANNOUNCE) {
|
||||
int remaining = DEBUG_TAP_TARGET - debugTapCounter;
|
||||
|
||||
Toast.makeText(context, context.getResources().getQuantityString(R.plurals.RegistrationActivity_debug_log_hint, remaining, remaining), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package org.thoughtcrime.securesms.registration.fragments;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
/**
|
||||
* Fragment that displays a Captcha in a WebView.
|
||||
*/
|
||||
public final class CaptchaFragment extends BaseRegistrationFragment {
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_registration_captcha, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
WebView webView = view.findViewById(R.id.registration_captcha_web_view);
|
||||
|
||||
webView.getSettings().setJavaScriptEnabled(true);
|
||||
webView.clearCache(true);
|
||||
|
||||
webView.setWebViewClient(new WebViewClient() {
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
if (url != null && url.startsWith(RegistrationConstants.SIGNAL_CAPTCHA_SCHEME)) {
|
||||
handleToken(url.substring(RegistrationConstants.SIGNAL_CAPTCHA_SCHEME.length()));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
webView.loadUrl(RegistrationConstants.SIGNAL_CAPTCHA_URL);
|
||||
}
|
||||
|
||||
private void handleToken(@NonNull String token) {
|
||||
getModel().onCaptchaResponse(token);
|
||||
|
||||
Navigation.findNavController(requireView()).navigate(CaptchaFragmentDirections.actionCaptchaComplete());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
package org.thoughtcrime.securesms.registration.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ListView;
|
||||
import android.widget.SimpleAdapter;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.ListFragment;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.Loader;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.loaders.CountryListLoader;
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
|
||||
public final class CountryPickerFragment extends ListFragment implements LoaderManager.LoaderCallbacks<ArrayList<Map<String, String>>> {
|
||||
|
||||
private EditText countryFilter;
|
||||
private RegistrationViewModel model;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
|
||||
return inflater.inflate(R.layout.fragment_registration_country_picker, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
model = BaseRegistrationFragment.getRegistrationViewModel(requireActivity());
|
||||
|
||||
countryFilter = view.findViewById(R.id.country_search);
|
||||
|
||||
countryFilter.addTextChangedListener(new FilterWatcher());
|
||||
LoaderManager.getInstance(this).initLoader(0, null, this).forceLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListItemClick(@NonNull ListView listView, @NonNull View view, int position, long id) {
|
||||
Map<String, String> item = (Map<String, String>) getListAdapter().getItem(position);
|
||||
|
||||
int countryCode = Integer.parseInt(item.get("country_code").replace("+", ""));
|
||||
String countryName = item.get("country_name");
|
||||
|
||||
model.onCountrySelected(countryName, countryCode);
|
||||
|
||||
Navigation.findNavController(view).navigate(CountryPickerFragmentDirections.actionCountrySelected());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Loader<ArrayList<Map<String, String>>> onCreateLoader(int id, @Nullable Bundle args) {
|
||||
return new CountryListLoader(getActivity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(@NonNull Loader<ArrayList<Map<String, String>>> loader,
|
||||
@NonNull ArrayList<Map<String, String>> results)
|
||||
{
|
||||
String[] from = { "country_name", "country_code" };
|
||||
int[] to = { R.id.country_name, R.id.country_code };
|
||||
|
||||
setListAdapter(new SimpleAdapter(getActivity(), results, R.layout.country_list_item, from, to));
|
||||
|
||||
applyFilter(countryFilter.getText());
|
||||
}
|
||||
|
||||
private void applyFilter(@NonNull CharSequence text) {
|
||||
SimpleAdapter listAdapter = (SimpleAdapter) getListAdapter();
|
||||
|
||||
if (listAdapter != null) {
|
||||
listAdapter.getFilter().filter(text);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoaderReset(@NonNull Loader<ArrayList<Map<String, String>>> loader) {
|
||||
setListAdapter(null);
|
||||
}
|
||||
|
||||
private class FilterWatcher implements TextWatcher {
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
applyFilter(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,298 @@
|
|||
package org.thoughtcrime.securesms.registration.fragments;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.registration.CallMeCountDownView;
|
||||
import org.thoughtcrime.securesms.components.registration.VerificationCodeView;
|
||||
import org.thoughtcrime.securesms.components.registration.VerificationPinKeyboard;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.registration.ReceivedSmsEvent;
|
||||
import org.thoughtcrime.securesms.registration.service.CodeVerificationRequest;
|
||||
import org.thoughtcrime.securesms.registration.service.RegistrationCodeRequest;
|
||||
import org.thoughtcrime.securesms.registration.service.RegistrationService;
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel;
|
||||
import org.thoughtcrime.securesms.util.concurrent.AssertedSuccessListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
public final class EnterCodeFragment extends BaseRegistrationFragment {
|
||||
|
||||
private static final String TAG = Log.tag(EnterCodeFragment.class);
|
||||
|
||||
private ScrollView scrollView;
|
||||
private TextView header;
|
||||
private VerificationCodeView verificationCodeView;
|
||||
private VerificationPinKeyboard keyboard;
|
||||
private CallMeCountDownView callMeCountDown;
|
||||
private View wrongNumber;
|
||||
private View noCodeReceivedHelp;
|
||||
private boolean autoCompleting;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_registration_enter_code, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
setDebugLogSubmitMultiTapView(view.findViewById(R.id.verify_header));
|
||||
|
||||
scrollView = view.findViewById(R.id.scroll_view);
|
||||
header = view.findViewById(R.id.verify_header);
|
||||
verificationCodeView = view.findViewById(R.id.code);
|
||||
keyboard = view.findViewById(R.id.keyboard);
|
||||
callMeCountDown = view.findViewById(R.id.call_me_count_down);
|
||||
wrongNumber = view.findViewById(R.id.wrong_number);
|
||||
noCodeReceivedHelp = view.findViewById(R.id.no_code);
|
||||
|
||||
connectKeyboard(verificationCodeView, keyboard);
|
||||
|
||||
setOnCodeFullyEnteredListener(verificationCodeView);
|
||||
|
||||
wrongNumber.setOnClickListener(v -> Navigation.findNavController(view).navigate(EnterCodeFragmentDirections.actionWrongNumber()));
|
||||
|
||||
callMeCountDown.setOnClickListener(v -> handlePhoneCallRequest());
|
||||
|
||||
callMeCountDown.setListener((v, remaining) -> {
|
||||
if (remaining <= 30) {
|
||||
scrollView.smoothScrollTo(0, v.getBottom());
|
||||
callMeCountDown.setListener(null);
|
||||
}
|
||||
});
|
||||
|
||||
noCodeReceivedHelp.setOnClickListener(v -> sendEmailToSupport());
|
||||
|
||||
getModel().getSuccessfulCodeRequestAttempts().observe(this, (attempts) -> {
|
||||
if (attempts >= 3) {
|
||||
noCodeReceivedHelp.setVisibility(View.VISIBLE);
|
||||
scrollView.postDelayed(() -> scrollView.smoothScrollTo(0, noCodeReceivedHelp.getBottom()), 15000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setOnCodeFullyEnteredListener(VerificationCodeView verificationCodeView) {
|
||||
verificationCodeView.setOnCompleteListener(code -> {
|
||||
RegistrationViewModel model = getModel();
|
||||
|
||||
model.onVerificationCodeEntered(code);
|
||||
callMeCountDown.setVisibility(View.INVISIBLE);
|
||||
wrongNumber.setVisibility(View.INVISIBLE);
|
||||
keyboard.displayProgress();
|
||||
|
||||
RegistrationService registrationService = RegistrationService.getInstance(model.getNumber().getE164Number(), model.getRegistrationSecret());
|
||||
|
||||
registrationService.verifyAccount(requireActivity(), model.getFcmToken(), code, null,
|
||||
new CodeVerificationRequest.VerifyCallback() {
|
||||
|
||||
@Override
|
||||
public void onSuccessfulRegistration() {
|
||||
keyboard.displaySuccess().addListener(new AssertedSuccessListener<Boolean>() {
|
||||
@Override
|
||||
public void onSuccess(Boolean result) {
|
||||
handleSuccessfulRegistration();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIncorrectRegistrationLockPin(long timeRemaining) {
|
||||
keyboard.displayLocked().addListener(new AssertedSuccessListener<Boolean>() {
|
||||
@Override
|
||||
public void onSuccess(Boolean r) {
|
||||
Navigation.findNavController(requireView())
|
||||
.navigate(EnterCodeFragmentDirections.actionRequireRegistrationLockPin(timeRemaining));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTooManyAttempts() {
|
||||
keyboard.displayFailure().addListener(new AssertedSuccessListener<Boolean>() {
|
||||
@Override
|
||||
public void onSuccess(Boolean r) {
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.RegistrationActivity_too_many_attempts)
|
||||
.setMessage(R.string.RegistrationActivity_you_have_made_too_many_incorrect_registration_lock_pin_attempts_please_try_again_in_a_day)
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
callMeCountDown.setVisibility(View.VISIBLE);
|
||||
wrongNumber.setVisibility(View.VISIBLE);
|
||||
verificationCodeView.clear();
|
||||
keyboard.displayKeyboard();
|
||||
})
|
||||
.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError() {
|
||||
Toast.makeText(requireContext(), R.string.RegistrationActivity_error_connecting_to_service, Toast.LENGTH_LONG).show();
|
||||
keyboard.displayFailure().addListener(new AssertedSuccessListener<Boolean>() {
|
||||
@Override
|
||||
public void onSuccess(Boolean result) {
|
||||
callMeCountDown.setVisibility(View.VISIBLE);
|
||||
wrongNumber.setVisibility(View.VISIBLE);
|
||||
verificationCodeView.clear();
|
||||
keyboard.displayKeyboard();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void handleSuccessfulRegistration() {
|
||||
Navigation.findNavController(requireView()).navigate(EnterCodeFragmentDirections.actionSuccessfulRegistration());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
EventBus.getDefault().register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
EventBus.getDefault().unregister(this);
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onVerificationCodeReceived(@NonNull ReceivedSmsEvent event) {
|
||||
verificationCodeView.clear();
|
||||
|
||||
List<Integer> parsedCode = convertVerificationCodeToDigits(event.getCode());
|
||||
|
||||
autoCompleting = true;
|
||||
|
||||
final int size = parsedCode.size();
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
final int index = i;
|
||||
verificationCodeView.postDelayed(() -> {
|
||||
verificationCodeView.append(parsedCode.get(index));
|
||||
if (index == size - 1) {
|
||||
autoCompleting = false;
|
||||
}
|
||||
}, i * 200);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Integer> convertVerificationCodeToDigits(@Nullable String code) {
|
||||
if (code == null || code.length() != 6) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<Integer> result = new ArrayList<>(code.length());
|
||||
|
||||
try {
|
||||
for (int i = 0; i < code.length(); i++) {
|
||||
result.add(Integer.parseInt(Character.toString(code.charAt(i))));
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
Log.w(TAG, "Failed to convert code into digits.", e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void handlePhoneCallRequest() {
|
||||
callMeCountDown.startCountDown(RegistrationConstants.SUBSEQUENT_CALL_AVAILABLE_AFTER);
|
||||
|
||||
RegistrationViewModel model = getModel();
|
||||
String captcha = model.getCaptchaToken();
|
||||
model.clearCaptchaResponse();
|
||||
|
||||
NavController navController = Navigation.findNavController(callMeCountDown);
|
||||
|
||||
RegistrationService registrationService = RegistrationService.getInstance(model.getNumber().getE164Number(), model.getRegistrationSecret());
|
||||
|
||||
registrationService.requestVerificationCode(requireActivity(), RegistrationCodeRequest.Mode.PHONE_CALL, captcha,
|
||||
new RegistrationCodeRequest.SmsVerificationCodeCallback() {
|
||||
|
||||
@Override
|
||||
public void onNeedCaptcha() {
|
||||
navController.navigate(EnterCodeFragmentDirections.actionRequestCaptcha());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestSent(@Nullable String fcmToken) {
|
||||
model.setFcmToken(fcmToken);
|
||||
model.markASuccessfulAttempt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError() {
|
||||
Toast.makeText(requireContext(), R.string.RegistrationActivity_unable_to_connect_to_service, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void connectKeyboard(VerificationCodeView verificationCodeView, VerificationPinKeyboard keyboard) {
|
||||
keyboard.setOnKeyPressListener(key -> {
|
||||
if (!autoCompleting) {
|
||||
if (key >= 0) {
|
||||
verificationCodeView.append(key);
|
||||
} else {
|
||||
verificationCodeView.delete();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
getModel().getLiveNumber().observe(this, (s) -> header.setText(requireContext().getString(R.string.RegistrationActivity_enter_the_code_we_sent_to_s, s.getFullFormattedNumber())));
|
||||
|
||||
callMeCountDown.startCountDown(RegistrationConstants.FIRST_CALL_AVAILABLE_AFTER);
|
||||
}
|
||||
|
||||
private void sendEmailToSupport() {
|
||||
Intent intent = new Intent(Intent.ACTION_SENDTO);
|
||||
intent.setData(Uri.parse("mailto:"));
|
||||
intent.putExtra(Intent.EXTRA_EMAIL, new String[]{ getString(R.string.RegistrationActivity_support_email) });
|
||||
intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.RegistrationActivity_code_support_subject));
|
||||
intent.putExtra(Intent.EXTRA_TEXT, getString(R.string.RegistrationActivity_code_support_body,
|
||||
getDevice(),
|
||||
getAndroidVersion(),
|
||||
BuildConfig.VERSION_NAME,
|
||||
Locale.getDefault()));
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private static String getDevice() {
|
||||
return String.format("%s %s (%s)", Build.MANUFACTURER, Build.MODEL, Build.PRODUCT);
|
||||
}
|
||||
|
||||
private static String getAndroidVersion() {
|
||||
return String.format("%s (%s, %s)", Build.VERSION.RELEASE, Build.VERSION.INCREMENTAL, Build.DISPLAY);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,389 @@
|
|||
package org.thoughtcrime.securesms.registration.fragments;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.dd.CircularProgressButton;
|
||||
import com.google.android.gms.auth.api.phone.SmsRetriever;
|
||||
import com.google.android.gms.auth.api.phone.SmsRetrieverClient;
|
||||
import com.google.android.gms.common.ConnectionResult;
|
||||
import com.google.android.gms.common.GoogleApiAvailability;
|
||||
import com.google.android.gms.tasks.Task;
|
||||
import com.google.i18n.phonenumbers.AsYouTypeFormatter;
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.LabeledEditText;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.registration.service.RegistrationCodeRequest;
|
||||
import org.thoughtcrime.securesms.registration.service.RegistrationService;
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.NumberViewState;
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel;
|
||||
import org.thoughtcrime.securesms.util.Dialogs;
|
||||
import org.thoughtcrime.securesms.util.PlayServicesUtil;
|
||||
|
||||
public final class EnterPhoneNumberFragment extends BaseRegistrationFragment {
|
||||
|
||||
private static final String TAG = Log.tag(EnterPhoneNumberFragment.class);
|
||||
|
||||
private LabeledEditText countryCode;
|
||||
private LabeledEditText number;
|
||||
private ArrayAdapter<String> countrySpinnerAdapter;
|
||||
private AsYouTypeFormatter countryFormatter;
|
||||
private CircularProgressButton register;
|
||||
private Spinner countrySpinner;
|
||||
private View cancel;
|
||||
private ScrollView scrollView;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_registration_enter_phone_number, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
setDebugLogSubmitMultiTapView(view.findViewById(R.id.verify_header));
|
||||
|
||||
countryCode = view.findViewById(R.id.country_code);
|
||||
number = view.findViewById(R.id.number);
|
||||
countrySpinner = view.findViewById(R.id.country_spinner);
|
||||
cancel = view.findViewById(R.id.cancel_button);
|
||||
scrollView = view.findViewById(R.id.scroll_view);
|
||||
register = view.findViewById(R.id.registerButton);
|
||||
|
||||
initializeSpinner(countrySpinner);
|
||||
|
||||
setUpNumberInput();
|
||||
|
||||
register.setOnClickListener(v -> handleRegister(requireContext()));
|
||||
|
||||
if (isReregister()) {
|
||||
cancel.setVisibility(View.VISIBLE);
|
||||
cancel.setOnClickListener(v -> Navigation.findNavController(v).navigateUp());
|
||||
} else {
|
||||
cancel.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
RegistrationViewModel model = getModel();
|
||||
NumberViewState number = model.getNumber();
|
||||
|
||||
initNumber(number);
|
||||
|
||||
countryCode.getInput().addTextChangedListener(new CountryCodeChangedListener());
|
||||
|
||||
if (model.hasCaptchaToken()) {
|
||||
handleRegister(requireContext());
|
||||
}
|
||||
|
||||
countryCode.getInput().setImeOptions(EditorInfo.IME_ACTION_NEXT);
|
||||
}
|
||||
|
||||
private void setUpNumberInput() {
|
||||
EditText numberInput = number.getInput();
|
||||
|
||||
numberInput.addTextChangedListener(new NumberChangedListener());
|
||||
|
||||
number.setOnFocusChangeListener((v, hasFocus) -> {
|
||||
if (hasFocus) {
|
||||
scrollView.postDelayed(() -> scrollView.smoothScrollTo(0, register.getBottom()), 250);
|
||||
}
|
||||
});
|
||||
|
||||
numberInput.setImeOptions(EditorInfo.IME_ACTION_DONE);
|
||||
numberInput.setOnEditorActionListener((v, actionId, event) -> {
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
hideKeyboard(requireContext(), v);
|
||||
handleRegister(requireContext());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
private void handleRegister(@NonNull Context context) {
|
||||
if (TextUtils.isEmpty(countryCode.getText())) {
|
||||
Toast.makeText(context, getString(R.string.RegistrationActivity_you_must_specify_your_country_code), Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(this.number.getText())) {
|
||||
Toast.makeText(context, getString(R.string.RegistrationActivity_you_must_specify_your_phone_number), Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
final NumberViewState number = getModel().getNumber();
|
||||
final String e164number = number.getE164Number();
|
||||
|
||||
if (!number.isValid()) {
|
||||
Dialogs.showAlertDialog(context,
|
||||
getString(R.string.RegistrationActivity_invalid_number),
|
||||
String.format(getString(R.string.RegistrationActivity_the_number_you_specified_s_is_invalid), e164number));
|
||||
return;
|
||||
}
|
||||
|
||||
PlayServicesUtil.PlayServicesStatus fcmStatus = PlayServicesUtil.getPlayServicesStatus(context);
|
||||
|
||||
if (fcmStatus == PlayServicesUtil.PlayServicesStatus.SUCCESS) {
|
||||
handleRequestVerification(context, e164number, true);
|
||||
} else if (fcmStatus == PlayServicesUtil.PlayServicesStatus.MISSING) {
|
||||
handlePromptForNoPlayServices(context, e164number);
|
||||
} else if (fcmStatus == PlayServicesUtil.PlayServicesStatus.NEEDS_UPDATE) {
|
||||
GoogleApiAvailability.getInstance().getErrorDialog(requireActivity(), ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED, 0).show();
|
||||
} else {
|
||||
Dialogs.showAlertDialog(context, getString(R.string.RegistrationActivity_play_services_error),
|
||||
getString(R.string.RegistrationActivity_google_play_services_is_updating_or_unavailable));
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRequestVerification(@NonNull Context context, @NonNull String e164number, boolean fcmSupported) {
|
||||
setSpinning(register);
|
||||
disableAllEntries();
|
||||
|
||||
if (fcmSupported) {
|
||||
SmsRetrieverClient client = SmsRetriever.getClient(context);
|
||||
Task<Void> task = client.startSmsRetriever();
|
||||
|
||||
task.addOnSuccessListener(none -> {
|
||||
Log.i(TAG, "Successfully registered SMS listener.");
|
||||
requestVerificationCode(e164number, RegistrationCodeRequest.Mode.SMS_FCM_WITH_LISTENER);
|
||||
});
|
||||
|
||||
task.addOnFailureListener(e -> {
|
||||
Log.w(TAG, "Failed to register SMS listener.", e);
|
||||
requestVerificationCode(e164number, RegistrationCodeRequest.Mode.SMS_FCM_NO_LISTENER);
|
||||
});
|
||||
} else {
|
||||
requestVerificationCode(e164number, RegistrationCodeRequest.Mode.SMS_NO_FCM);
|
||||
}
|
||||
}
|
||||
|
||||
private void disableAllEntries() {
|
||||
countryCode.setEnabled(false);
|
||||
number.setEnabled(false);
|
||||
countrySpinner.setEnabled(false);
|
||||
cancel.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void enableAllEntries() {
|
||||
countryCode.setEnabled(true);
|
||||
number.setEnabled(true);
|
||||
countrySpinner.setEnabled(true);
|
||||
if (isReregister()) {
|
||||
cancel.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
private void requestVerificationCode(String e164number, @NonNull RegistrationCodeRequest.Mode mode) {
|
||||
RegistrationViewModel model = getModel();
|
||||
String captcha = model.getCaptchaToken();
|
||||
model.clearCaptchaResponse();
|
||||
|
||||
NavController navController = Navigation.findNavController(register);
|
||||
|
||||
RegistrationService registrationService = RegistrationService.getInstance(e164number, model.getRegistrationSecret());
|
||||
|
||||
registrationService.requestVerificationCode(requireActivity(), mode, captcha,
|
||||
new RegistrationCodeRequest.SmsVerificationCodeCallback() {
|
||||
|
||||
@Override
|
||||
public void onNeedCaptcha() {
|
||||
navController.navigate(EnterPhoneNumberFragmentDirections.actionRequestCaptcha());
|
||||
cancelSpinning(register);
|
||||
enableAllEntries();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestSent(@Nullable String fcmToken) {
|
||||
model.setFcmToken(fcmToken);
|
||||
model.markASuccessfulAttempt();
|
||||
navController.navigate(EnterPhoneNumberFragmentDirections.actionEnterVerificationCode());
|
||||
cancelSpinning(register);
|
||||
enableAllEntries();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError() {
|
||||
Toast.makeText(register.getContext(), R.string.RegistrationActivity_unable_to_connect_to_service, Toast.LENGTH_LONG).show();
|
||||
cancelSpinning(register);
|
||||
enableAllEntries();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initializeSpinner(Spinner countrySpinner) {
|
||||
countrySpinnerAdapter = new ArrayAdapter<>(requireContext(), android.R.layout.simple_spinner_item);
|
||||
countrySpinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
|
||||
setCountryDisplay(getString(R.string.RegistrationActivity_select_your_country));
|
||||
|
||||
countrySpinner.setAdapter(countrySpinnerAdapter);
|
||||
countrySpinner.setOnTouchListener((view, event) -> {
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
pickCountry(view);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
countrySpinner.setOnKeyListener((view, keyCode, event) -> {
|
||||
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER && event.getAction() == KeyEvent.ACTION_UP) {
|
||||
pickCountry(view);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
private void pickCountry(@NonNull View view) {
|
||||
Navigation.findNavController(view).navigate(R.id.action_pickCountry);
|
||||
}
|
||||
|
||||
private void initNumber(@NonNull NumberViewState numberViewState) {
|
||||
int countryCode = numberViewState.getCountryCode();
|
||||
long number = numberViewState.getNationalNumber();
|
||||
String regionDisplayName = numberViewState.getCountryDisplayName();
|
||||
|
||||
this.countryCode.setText(String.valueOf(countryCode));
|
||||
|
||||
setCountryDisplay(regionDisplayName);
|
||||
|
||||
String regionCode = PhoneNumberUtil.getInstance().getRegionCodeForCountryCode(countryCode);
|
||||
setCountryFormatter(regionCode);
|
||||
|
||||
if (number != 0) {
|
||||
this.number.setText(String.valueOf(number));
|
||||
}
|
||||
}
|
||||
|
||||
private void setCountryDisplay(String regionDisplayName) {
|
||||
countrySpinnerAdapter.clear();
|
||||
if (regionDisplayName == null) {
|
||||
countrySpinnerAdapter.add(getString(R.string.RegistrationActivity_select_your_country));
|
||||
} else {
|
||||
countrySpinnerAdapter.add(regionDisplayName);
|
||||
}
|
||||
}
|
||||
|
||||
private class CountryCodeChangedListener implements TextWatcher {
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
if (TextUtils.isEmpty(s) || !TextUtils.isDigitsOnly(s)) {
|
||||
setCountryDisplay(null);
|
||||
countryFormatter = null;
|
||||
return;
|
||||
}
|
||||
|
||||
int countryCode = Integer.parseInt(s.toString());
|
||||
String regionCode = PhoneNumberUtil.getInstance().getRegionCodeForCountryCode(countryCode);
|
||||
|
||||
setCountryFormatter(regionCode);
|
||||
|
||||
if (!TextUtils.isEmpty(regionCode) && !regionCode.equals("ZZ")) {
|
||||
number.requestFocus();
|
||||
|
||||
int numberLength = number.getText().length();
|
||||
number.getInput().setSelection(numberLength, numberLength);
|
||||
}
|
||||
|
||||
RegistrationViewModel model = getModel();
|
||||
|
||||
model.onCountrySelected(null, countryCode);
|
||||
setCountryDisplay(model.getNumber().getCountryDisplayName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
}
|
||||
|
||||
private class NumberChangedListener implements TextWatcher {
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
String number = reformatText(s);
|
||||
|
||||
if (number == null) return;
|
||||
|
||||
RegistrationViewModel model = getModel();
|
||||
|
||||
model.setNationalNumber(Long.parseLong(number));
|
||||
|
||||
setCountryDisplay(model.getNumber().getCountryDisplayName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private String reformatText(Editable s) {
|
||||
if (countryFormatter == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (TextUtils.isEmpty(s)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
countryFormatter.clear();
|
||||
|
||||
String number = s.toString().replaceAll("[^\\d.]", "");
|
||||
String formattedNumber = null;
|
||||
|
||||
for (int i = 0; i < number.length(); i++) {
|
||||
formattedNumber = countryFormatter.inputDigit(number.charAt(i));
|
||||
}
|
||||
|
||||
if (formattedNumber != null && !s.toString().equals(formattedNumber)) {
|
||||
s.replace(0, s.length(), formattedNumber);
|
||||
}
|
||||
return number;
|
||||
}
|
||||
|
||||
private void setCountryFormatter(@Nullable String regionCode) {
|
||||
PhoneNumberUtil util = PhoneNumberUtil.getInstance();
|
||||
|
||||
countryFormatter = regionCode != null ? util.getAsYouTypeFormatter(regionCode) : null;
|
||||
|
||||
reformatText(number.getText());
|
||||
}
|
||||
|
||||
private void handlePromptForNoPlayServices(@NonNull Context context, @NonNull String e164number) {
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(R.string.RegistrationActivity_missing_google_play_services)
|
||||
.setMessage(R.string.RegistrationActivity_this_device_is_missing_google_play_services)
|
||||
.setPositiveButton(R.string.RegistrationActivity_i_understand, (dialog1, which) -> handleRequestVerification(context, e164number, false))
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package org.thoughtcrime.securesms.registration.fragments;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.navigation.ActivityNavigator;
|
||||
|
||||
import org.thoughtcrime.securesms.ConversationListActivity;
|
||||
import org.thoughtcrime.securesms.CreateProfileActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
public final class RegistrationCompleteFragment extends BaseRegistrationFragment {
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_registration_blank, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
FragmentActivity activity = requireActivity();
|
||||
|
||||
if (!isReregister()) {
|
||||
activity.startActivity(getRoutedIntent(activity, CreateProfileActivity.class, new Intent(activity, ConversationListActivity.class)));
|
||||
}
|
||||
|
||||
activity.finish();
|
||||
ActivityNavigator.applyPopAnimationsToPendingTransition(activity);
|
||||
}
|
||||
|
||||
private static Intent getRoutedIntent(@NonNull Context context, Class<?> destination, @Nullable Intent nextIntent) {
|
||||
final Intent intent = new Intent(context, destination);
|
||||
if (nextIntent != null) intent.putExtra("next_intent", nextIntent);
|
||||
return intent;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package org.thoughtcrime.securesms.registration.fragments;
|
||||
|
||||
final class RegistrationConstants {
|
||||
|
||||
private RegistrationConstants() {
|
||||
}
|
||||
|
||||
static final int FIRST_CALL_AVAILABLE_AFTER = 64;
|
||||
static final int SUBSEQUENT_CALL_AVAILABLE_AFTER = 300;
|
||||
static final String TERMS_AND_CONDITIONS_URL = "https://signal.org/legal";
|
||||
|
||||
static final String SIGNAL_CAPTCHA_URL = "https://signalcaptchas.org/registration/generate.html";
|
||||
static final String SIGNAL_CAPTCHA_SCHEME = "signalcaptcha://";
|
||||
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
package org.thoughtcrime.securesms.registration.fragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.dd.CircularProgressButton;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.registration.service.CodeVerificationRequest;
|
||||
import org.thoughtcrime.securesms.registration.service.RegistrationService;
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public final class RegistrationLockFragment extends BaseRegistrationFragment {
|
||||
|
||||
private EditText pinEntry;
|
||||
private CircularProgressButton pinButton;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_registration_lock, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
setDebugLogSubmitMultiTapView(view.findViewById(R.id.verify_header));
|
||||
|
||||
pinEntry = view.findViewById(R.id.pin);
|
||||
pinButton = view.findViewById(R.id.pinButton);
|
||||
|
||||
View clarificationLabel = view.findViewById(R.id.clarification_label);
|
||||
View subHeader = view.findViewById(R.id.verify_subheader);
|
||||
View pinForgotButton = view.findViewById(R.id.forgot_button);
|
||||
|
||||
String code = getModel().getTextCodeEntered();
|
||||
|
||||
long timeRemaining = RegistrationLockFragmentArgs.fromBundle(requireArguments()).getTimeRemaining();
|
||||
|
||||
pinForgotButton.setOnClickListener(v -> handleForgottenPin(timeRemaining));
|
||||
|
||||
pinEntry.addTextChangedListener(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 s) {
|
||||
boolean matchesTextCode = s != null && s.toString().equals(code);
|
||||
clarificationLabel.setVisibility(matchesTextCode ? View.VISIBLE : View.INVISIBLE);
|
||||
subHeader.setVisibility(matchesTextCode ? View.INVISIBLE : View.VISIBLE);
|
||||
}
|
||||
});
|
||||
|
||||
pinEntry.setImeOptions(EditorInfo.IME_ACTION_DONE);
|
||||
pinEntry.setOnEditorActionListener((v, actionId, event) -> {
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
hideKeyboard(requireContext(), v);
|
||||
handlePinEntry();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
pinButton.setOnClickListener((v) -> {
|
||||
hideKeyboard(requireContext(), pinEntry);
|
||||
handlePinEntry();
|
||||
});
|
||||
}
|
||||
|
||||
private void handlePinEntry() {
|
||||
final String pin = pinEntry.getText().toString();
|
||||
|
||||
if (TextUtils.isEmpty(pin) || TextUtils.isEmpty(pin.replace(" ", ""))) {
|
||||
Toast.makeText(requireContext(), R.string.RegistrationActivity_you_must_enter_your_registration_lock_PIN, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
RegistrationViewModel model = getModel();
|
||||
RegistrationService registrationService = RegistrationService.getInstance(model.getNumber().getE164Number(), model.getRegistrationSecret());
|
||||
|
||||
setSpinning(pinButton);
|
||||
|
||||
registrationService.verifyAccount(requireActivity(), model.getFcmToken(), model.getTextCodeEntered(), pin, new CodeVerificationRequest.VerifyCallback() {
|
||||
|
||||
@Override
|
||||
public void onSuccessfulRegistration() {
|
||||
cancelSpinning(pinButton);
|
||||
|
||||
Navigation.findNavController(requireView()).navigate(RegistrationLockFragmentDirections.actionSuccessfulRegistration());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIncorrectRegistrationLockPin(long timeRemaining) {
|
||||
cancelSpinning(pinButton);
|
||||
|
||||
pinEntry.setText("");
|
||||
Toast.makeText(requireContext(), R.string.RegistrationActivity_incorrect_registration_lock_pin, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTooManyAttempts() {
|
||||
cancelSpinning(pinButton);
|
||||
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.RegistrationActivity_too_many_attempts)
|
||||
.setMessage(R.string.RegistrationActivity_you_have_made_too_many_incorrect_registration_lock_pin_attempts_please_try_again_in_a_day)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError() {
|
||||
cancelSpinning(pinButton);
|
||||
|
||||
Toast.makeText(requireContext(), R.string.RegistrationActivity_error_connecting_to_service, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleForgottenPin(long timeRemainingMs) {
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.RegistrationActivity_oh_no)
|
||||
.setMessage(getString(R.string.RegistrationActivity_registration_of_this_phone_number_will_be_possible_without_your_registration_lock_pin_after_seven_days_have_passed, (TimeUnit.MILLISECONDS.toDays(timeRemainingMs) + 1)))
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,332 @@
|
|||
package org.thoughtcrime.securesms.registration.fragments;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextWatcher;
|
||||
import android.text.style.ReplacementSpan;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.dd.CircularProgressButton;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.backup.FullBackupBase;
|
||||
import org.thoughtcrime.securesms.backup.FullBackupImporter;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.NoExternalStorageException;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.util.BackupUtil;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
public final class RestoreBackupFragment extends BaseRegistrationFragment {
|
||||
|
||||
private static final String TAG = Log.tag(RestoreBackupFragment.class);
|
||||
|
||||
private TextView restoreBackupSize;
|
||||
private TextView restoreBackupTime;
|
||||
private TextView restoreBackupProgress;
|
||||
private CircularProgressButton restoreButton;
|
||||
private View skipRestoreButton;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.fragment_registration_restore_backup, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
setDebugLogSubmitMultiTapView(view.findViewById(R.id.verify_header));
|
||||
|
||||
Log.i(TAG, "Backup restore.");
|
||||
|
||||
restoreBackupSize = view.findViewById(R.id.backup_size_text);
|
||||
restoreBackupTime = view.findViewById(R.id.backup_created_text);
|
||||
restoreBackupProgress = view.findViewById(R.id.backup_progress_text);
|
||||
restoreButton = view.findViewById(R.id.restore_button);
|
||||
skipRestoreButton = view.findViewById(R.id.skip_restore_button);
|
||||
|
||||
skipRestoreButton.setOnClickListener((v) -> {
|
||||
Log.i(TAG, "User skipped backup restore.");
|
||||
Navigation.findNavController(view)
|
||||
.navigate(RestoreBackupFragmentDirections.actionSkip());
|
||||
});
|
||||
|
||||
if (isReregister()) {
|
||||
Log.i(TAG, "Skipping backup restore during re-register.");
|
||||
Navigation.findNavController(view)
|
||||
.navigate(RestoreBackupFragmentDirections.actionSkipNoReturn());
|
||||
return;
|
||||
}
|
||||
|
||||
if (TextSecurePreferences.isBackupEnabled(requireContext())) {
|
||||
Log.i(TAG, "Backups enabled, so a backup must have been previously restored.");
|
||||
Navigation.findNavController(view)
|
||||
.navigate(RestoreBackupFragmentDirections.actionSkipNoReturn());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Permissions.hasAll(requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
||||
Log.i(TAG, "Skipping backup detection. We don't have the permission.");
|
||||
Navigation.findNavController(view)
|
||||
.navigate(RestoreBackupFragmentDirections.actionSkipNoReturn());
|
||||
} else {
|
||||
initializeBackupDetection(view);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private void initializeBackupDetection(@NonNull View view) {
|
||||
searchForBackup(backup -> {
|
||||
Context context = getContext();
|
||||
if (context == null) {
|
||||
Log.i(TAG, "No context on fragment, must have navigated away.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (backup == null) {
|
||||
Log.i(TAG, "Skipping backup detection. No backup found, or permission revoked since.");
|
||||
Navigation.findNavController(view)
|
||||
.navigate(RestoreBackupFragmentDirections.actionNoBackupFound());
|
||||
} else {
|
||||
restoreBackupSize.setText(getString(R.string.RegistrationActivity_backup_size_s, Util.getPrettyFileSize(backup.getSize())));
|
||||
restoreBackupTime.setText(getString(R.string.RegistrationActivity_backup_timestamp_s, DateUtils.getExtendedRelativeTimeSpanString(requireContext(), Locale.US, backup.getTimestamp())));
|
||||
|
||||
restoreButton.setOnClickListener((v) -> handleRestore(v.getContext(), backup));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
interface OnBackupSearchResultListener {
|
||||
|
||||
@MainThread
|
||||
void run(@Nullable BackupUtil.BackupInfo backup);
|
||||
}
|
||||
|
||||
static void searchForBackup(@NonNull OnBackupSearchResultListener listener) {
|
||||
new AsyncTask<Void, Void, BackupUtil.BackupInfo>() {
|
||||
@Override
|
||||
protected @Nullable
|
||||
BackupUtil.BackupInfo doInBackground(Void... voids) {
|
||||
try {
|
||||
return BackupUtil.getLatestBackup();
|
||||
} catch (NoExternalStorageException e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(@Nullable BackupUtil.BackupInfo backup) {
|
||||
listener.run(backup);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
private void handleRestore(@NonNull Context context, @NonNull BackupUtil.BackupInfo backup) {
|
||||
View view = LayoutInflater.from(context).inflate(R.layout.enter_backup_passphrase_dialog, null);
|
||||
EditText prompt = view.findViewById(R.id.restore_passphrase_input);
|
||||
|
||||
prompt.addTextChangedListener(new PassphraseAsYouTypeFormatter());
|
||||
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(R.string.RegistrationActivity_enter_backup_passphrase)
|
||||
.setView(view)
|
||||
.setPositiveButton(R.string.RegistrationActivity_restore, (dialog, which) -> {
|
||||
InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
inputMethodManager.hideSoftInputFromWindow(prompt.getWindowToken(), 0);
|
||||
|
||||
setSpinning(restoreButton);
|
||||
skipRestoreButton.setVisibility(View.INVISIBLE);
|
||||
|
||||
String passphrase = prompt.getText().toString();
|
||||
|
||||
restoreAsynchronously(context, backup, passphrase);
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
|
||||
Log.i(TAG, "Prompt for backup passphrase shown to user.");
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private void restoreAsynchronously(@NonNull Context context,
|
||||
@NonNull BackupUtil.BackupInfo backup,
|
||||
@NonNull String passphrase)
|
||||
{
|
||||
new AsyncTask<Void, Void, BackupImportResult>() {
|
||||
@Override
|
||||
protected BackupImportResult doInBackground(Void... voids) {
|
||||
try {
|
||||
Log.i(TAG, "Starting backup restore.");
|
||||
|
||||
SQLiteDatabase database = DatabaseFactory.getBackupDatabase(context);
|
||||
|
||||
FullBackupImporter.importFile(context,
|
||||
AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(),
|
||||
database,
|
||||
backup.getFile(),
|
||||
passphrase);
|
||||
|
||||
DatabaseFactory.upgradeRestored(context, database);
|
||||
NotificationChannels.restoreContactNotificationChannels(context);
|
||||
|
||||
TextSecurePreferences.setBackupEnabled(context, true);
|
||||
TextSecurePreferences.setBackupPassphrase(context, passphrase);
|
||||
|
||||
Log.i(TAG, "Backup restore complete.");
|
||||
return BackupImportResult.SUCCESS;
|
||||
} catch (FullBackupImporter.DatabaseDowngradeException e) {
|
||||
Log.w(TAG, "Failed due to the backup being from a newer version of Signal.", e);
|
||||
return BackupImportResult.FAILURE_VERSION_DOWNGRADE;
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
return BackupImportResult.FAILURE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(@NonNull BackupImportResult result) {
|
||||
cancelSpinning(restoreButton);
|
||||
skipRestoreButton.setVisibility(View.VISIBLE);
|
||||
|
||||
restoreBackupProgress.setText("");
|
||||
|
||||
switch (result) {
|
||||
case SUCCESS:
|
||||
Log.i(TAG, "Successful backup restore.");
|
||||
break;
|
||||
case FAILURE_VERSION_DOWNGRADE:
|
||||
Toast.makeText(context, R.string.RegistrationActivity_backup_failure_downgrade, Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
case FAILURE_UNKNOWN:
|
||||
Toast.makeText(context, R.string.RegistrationActivity_incorrect_backup_passphrase, Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
EventBus.getDefault().register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
EventBus.getDefault().unregister(this);
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onEvent(@NonNull FullBackupBase.BackupEvent event) {
|
||||
int count = event.getCount();
|
||||
|
||||
if (count == 0) {
|
||||
restoreBackupProgress.setText(R.string.RegistrationActivity_checking);
|
||||
} else {
|
||||
restoreBackupProgress.setText(getString(R.string.RegistrationActivity_d_messages_so_far, count));
|
||||
}
|
||||
|
||||
setSpinning(restoreButton);
|
||||
skipRestoreButton.setVisibility(View.INVISIBLE);
|
||||
|
||||
if (event.getType() == FullBackupBase.BackupEvent.Type.FINISHED) {
|
||||
Navigation.findNavController(requireView())
|
||||
.navigate(RestoreBackupFragmentDirections.actionBackupRestored());
|
||||
}
|
||||
}
|
||||
|
||||
private enum BackupImportResult {
|
||||
SUCCESS,
|
||||
FAILURE_VERSION_DOWNGRADE,
|
||||
FAILURE_UNKNOWN
|
||||
}
|
||||
|
||||
private static class PassphraseAsYouTypeFormatter implements TextWatcher {
|
||||
|
||||
private static final int GROUP_SIZE = 5;
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
removeSpans(editable);
|
||||
|
||||
addSpans(editable);
|
||||
}
|
||||
|
||||
private void removeSpans(Editable editable) {
|
||||
SpaceSpan[] paddingSpans = editable.getSpans(0, editable.length(), SpaceSpan.class);
|
||||
|
||||
for (SpaceSpan span : paddingSpans) {
|
||||
editable.removeSpan(span);
|
||||
}
|
||||
}
|
||||
|
||||
private void addSpans(Editable editable) {
|
||||
final int length = editable.length();
|
||||
|
||||
for (int i = GROUP_SIZE; i < length; i += GROUP_SIZE) {
|
||||
editable.setSpan(new SpaceSpan(), i - 1, i, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link ReplacementSpan} adds a small space after a single character.
|
||||
* Based on https://stackoverflow.com/a/51949578
|
||||
*/
|
||||
private static class SpaceSpan extends ReplacementSpan {
|
||||
|
||||
@Override
|
||||
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
|
||||
return (int) (paint.measureText(text, start, end) * 1.7f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) {
|
||||
canvas.drawText(text.subSequence(start, end).toString(), x, y, paint);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
package org.thoughtcrime.securesms.registration.fragments;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.navigation.ActivityNavigator;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import com.dd.CircularProgressButton;
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||
import com.google.i18n.phonenumbers.Phonenumber;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel;
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
public final class WelcomeFragment extends BaseRegistrationFragment {
|
||||
|
||||
private static final String TAG = Log.tag(WelcomeFragment.class);
|
||||
|
||||
private CircularProgressButton continueButton;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
return inflater.inflate(isReregister() ? R.layout.fragment_registration_blank
|
||||
: R.layout.fragment_registration_welcome,
|
||||
container,
|
||||
false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
if (isReregister()) {
|
||||
RegistrationViewModel model = getModel();
|
||||
|
||||
if (model.hasRestoreFlowBeenShown()) {
|
||||
Log.i(TAG, "We've come back to the home fragment on a restore, user must be backing out");
|
||||
if (!Navigation.findNavController(view).popBackStack()) {
|
||||
FragmentActivity activity = requireActivity();
|
||||
activity.finish();
|
||||
ActivityNavigator.applyPopAnimationsToPendingTransition(activity);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
initializeNumber();
|
||||
|
||||
Log.i(TAG, "Skipping restore because this is a reregistration.");
|
||||
model.setWelcomeSkippedOnRestore();
|
||||
Navigation.findNavController(view)
|
||||
.navigate(WelcomeFragmentDirections.actionSkipRestore());
|
||||
|
||||
} else {
|
||||
|
||||
setDebugLogSubmitMultiTapView(view.findViewById(R.id.image));
|
||||
setDebugLogSubmitMultiTapView(view.findViewById(R.id.title));
|
||||
|
||||
continueButton = view.findViewById(R.id.welcome_continue_button);
|
||||
continueButton.setOnClickListener(this::continueClicked);
|
||||
|
||||
view.findViewById(R.id.welcome_terms_button).setOnClickListener(v -> onTermsClicked());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
private void continueClicked(@NonNull View view) {
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.WRITE_CONTACTS,
|
||||
Manifest.permission.READ_CONTACTS,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.READ_PHONE_STATE)
|
||||
.ifNecessary()
|
||||
.withRationaleDialog(getString(R.string.RegistrationActivity_signal_needs_access_to_your_contacts_and_media_in_order_to_connect_with_friends),
|
||||
R.drawable.ic_contacts_white_48dp, R.drawable.ic_folder_white_48dp)
|
||||
.onAnyResult(() -> {
|
||||
gatherInformationAndContinue(continueButton);
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
private void gatherInformationAndContinue(@NonNull View view) {
|
||||
setSpinning(continueButton);
|
||||
|
||||
RestoreBackupFragment.searchForBackup(backup -> {
|
||||
Context context = getContext();
|
||||
if (context == null) {
|
||||
Log.i(TAG, "No context on fragment, must have navigated away.");
|
||||
return;
|
||||
}
|
||||
|
||||
TextSecurePreferences.setHasSeenWelcomeScreen(requireContext(), true);
|
||||
|
||||
initializeNumber();
|
||||
|
||||
cancelSpinning(continueButton);
|
||||
|
||||
if (backup == null) {
|
||||
Log.i(TAG, "Skipping backup. No backup found, or no permission to look.");
|
||||
Navigation.findNavController(view)
|
||||
.navigate(WelcomeFragmentDirections.actionSkipRestore());
|
||||
} else {
|
||||
Navigation.findNavController(view)
|
||||
.navigate(WelcomeFragmentDirections.actionRestore());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
private void initializeNumber() {
|
||||
Optional<Phonenumber.PhoneNumber> localNumber = Optional.absent();
|
||||
|
||||
if (Permissions.hasAll(requireContext(), Manifest.permission.READ_PHONE_STATE)) {
|
||||
localNumber = Util.getDeviceNumber(requireContext());
|
||||
}
|
||||
|
||||
if (localNumber.isPresent()) {
|
||||
getModel().onNumberDetected(localNumber.get().getCountryCode(), localNumber.get().getNationalNumber());
|
||||
} else {
|
||||
Optional<String> simCountryIso = Util.getSimCountryIso(requireContext());
|
||||
|
||||
if (simCountryIso.isPresent() && !TextUtils.isEmpty(simCountryIso.get())) {
|
||||
getModel().onNumberDetected(PhoneNumberUtil.getInstance().getCountryCodeForRegion(simCountryIso.get()), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onTermsClicked() {
|
||||
CommunicationActions.openBrowserLink(requireContext(), RegistrationConstants.TERMS_AND_CONDITIONS_URL);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
package org.thoughtcrime.securesms.registration.service;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.PreKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SessionUtil;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
|
||||
import org.thoughtcrime.securesms.jobs.RotateCertificateJob;
|
||||
import org.thoughtcrime.securesms.lock.RegistrationLockReminders;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.push.AccountManagerFactory;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
|
||||
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.libsignal.IdentityKeyPair;
|
||||
import org.whispersystems.libsignal.state.PreKeyRecord;
|
||||
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
|
||||
import org.whispersystems.libsignal.util.KeyHelper;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.RateLimitException;
|
||||
import org.whispersystems.signalservice.internal.push.LockedException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
public final class CodeVerificationRequest {
|
||||
|
||||
private static final String TAG = Log.tag(CodeVerificationRequest.class);
|
||||
|
||||
private enum Result {
|
||||
SUCCESS,
|
||||
PIN_LOCKED,
|
||||
RATE_LIMITED,
|
||||
ERROR
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously verify the account via the code.
|
||||
*
|
||||
* @param fcmToken The FCM token for the device.
|
||||
* @param code The code that was delivered to the user.
|
||||
* @param pin The users registration pin.
|
||||
* @param callback Exactly one method on this callback will be called.
|
||||
*/
|
||||
static void verifyAccount(@NonNull Context context,
|
||||
@NonNull Credentials credentials,
|
||||
@Nullable String fcmToken,
|
||||
@NonNull String code,
|
||||
@Nullable String pin,
|
||||
@NonNull VerifyCallback callback)
|
||||
{
|
||||
new AsyncTask<Void, Void, Result>() {
|
||||
|
||||
private volatile long timeRemaining;
|
||||
|
||||
@Override
|
||||
protected Result doInBackground(Void... voids) {
|
||||
try {
|
||||
verifyAccount(context, credentials, code, pin, fcmToken);
|
||||
return Result.SUCCESS;
|
||||
} catch (LockedException e) {
|
||||
Log.w(TAG, e);
|
||||
timeRemaining = e.getTimeRemaining();
|
||||
return Result.PIN_LOCKED;
|
||||
} catch (RateLimitException e) {
|
||||
Log.w(TAG, e);
|
||||
return Result.RATE_LIMITED;
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
return Result.ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Result result) {
|
||||
if (result == Result.SUCCESS) {
|
||||
|
||||
handleSuccessfulRegistration(context, pin);
|
||||
|
||||
callback.onSuccessfulRegistration();
|
||||
} else if (result == Result.PIN_LOCKED) {
|
||||
callback.onIncorrectRegistrationLockPin(timeRemaining);
|
||||
} else if (result == Result.RATE_LIMITED) {
|
||||
callback.onTooManyAttempts();
|
||||
} else if (result == Result.ERROR) {
|
||||
callback.onError();
|
||||
}
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
private static void handleSuccessfulRegistration(@NonNull Context context, @Nullable String pin) {
|
||||
TextSecurePreferences.setRegistrationLockPin(context, pin);
|
||||
TextSecurePreferences.setRegistrationtLockEnabled(context, pin != null);
|
||||
|
||||
if (pin != null) {
|
||||
TextSecurePreferences.setRegistrationLockLastReminderTime(context, System.currentTimeMillis());
|
||||
TextSecurePreferences.setRegistrationLockNextReminderInterval(context, RegistrationLockReminders.INITIAL_INTERVAL);
|
||||
}
|
||||
|
||||
JobManager jobManager = ApplicationDependencies.getJobManager();
|
||||
jobManager.add(new DirectoryRefreshJob(false));
|
||||
jobManager.add(new RotateCertificateJob(context));
|
||||
|
||||
DirectoryRefreshListener.schedule(context);
|
||||
RotateSignedPreKeyListener.schedule(context);
|
||||
}
|
||||
|
||||
private static void verifyAccount(@NonNull Context context, @NonNull Credentials credentials, @NonNull String code, @Nullable String pin, @Nullable String fcmToken) throws IOException {
|
||||
int registrationId = KeyHelper.generateRegistrationId(false);
|
||||
byte[] unidentifiedAccessKey = UnidentifiedAccessUtil.getSelfUnidentifiedAccessKey(context);
|
||||
boolean universalUnidentifiedAccess = TextSecurePreferences.isUniversalUnidentifiedAccess(context);
|
||||
|
||||
TextSecurePreferences.setLocalRegistrationId(context, registrationId);
|
||||
SessionUtil.archiveAllSessions(context);
|
||||
|
||||
SignalServiceAccountManager accountManager = AccountManagerFactory.createManager(context, credentials.getE164number(), credentials.getPassword());
|
||||
|
||||
boolean present = fcmToken != null;
|
||||
|
||||
accountManager.verifyAccountWithCode(code, null, registrationId, !present, pin,
|
||||
unidentifiedAccessKey, universalUnidentifiedAccess);
|
||||
|
||||
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context);
|
||||
List<PreKeyRecord> records = PreKeyUtil.generatePreKeys(context);
|
||||
SignedPreKeyRecord signedPreKey = PreKeyUtil.generateSignedPreKey(context, identityKey, true);
|
||||
|
||||
accountManager.setPreKeys(identityKey.getPublicKey(), signedPreKey, records);
|
||||
|
||||
if (present) {
|
||||
accountManager.setGcmId(Optional.fromNullable(fcmToken));
|
||||
}
|
||||
|
||||
TextSecurePreferences.setFcmToken(context, fcmToken);
|
||||
TextSecurePreferences.setFcmDisabled(context, !present);
|
||||
TextSecurePreferences.setWebsocketRegistered(context, true);
|
||||
TextSecurePreferences.setLocalNumber(context, credentials.getE164number());
|
||||
|
||||
DatabaseFactory.getIdentityDatabase(context)
|
||||
.saveIdentity(Recipient.self().getId(),
|
||||
identityKey.getPublicKey(), IdentityDatabase.VerifiedStatus.VERIFIED,
|
||||
true, System.currentTimeMillis(), true);
|
||||
|
||||
TextSecurePreferences.setVerifying(context, false);
|
||||
TextSecurePreferences.setPushRegistered(context, true);
|
||||
TextSecurePreferences.setPushServerPassword(context, credentials.getPassword());
|
||||
TextSecurePreferences.setSignedPreKeyRegistered(context, true);
|
||||
TextSecurePreferences.setPromptedPushRegistration(context, true);
|
||||
TextSecurePreferences.setUnauthorizedReceived(context, false);
|
||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(Recipient.self().getId(), true);
|
||||
DatabaseFactory.getRecipientDatabase(context).setRegistered(Recipient.self().getId(), RecipientDatabase.RegisteredState.REGISTERED);
|
||||
}
|
||||
|
||||
public interface VerifyCallback {
|
||||
|
||||
void onSuccessfulRegistration();
|
||||
|
||||
/**
|
||||
* @param timeRemaining Time until pin expires and number can be reused.
|
||||
*/
|
||||
void onIncorrectRegistrationLockPin(long timeRemaining);
|
||||
|
||||
void onTooManyAttempts();
|
||||
|
||||
void onError();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package org.thoughtcrime.securesms.registration.service;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public final class Credentials {
|
||||
|
||||
private final String e164number;
|
||||
private final String password;
|
||||
|
||||
public Credentials(@NonNull String e164number, @NonNull String password) {
|
||||
this.e164number = e164number;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public @NonNull String getE164number() {
|
||||
return e164number;
|
||||
}
|
||||
|
||||
public @NonNull String getPassword() {
|
||||
return password;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,154 @@
|
|||
package org.thoughtcrime.securesms.registration.service;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.gcm.FcmUtil;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.push.AccountManagerFactory;
|
||||
import org.thoughtcrime.securesms.registration.PushChallengeRequest;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
import org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
public final class RegistrationCodeRequest {
|
||||
|
||||
private static final long PUSH_REQUEST_TIMEOUT_MS = 5000L;
|
||||
|
||||
private static final String TAG = Log.tag(RegistrationCodeRequest.class);
|
||||
|
||||
/**
|
||||
* Request a verification code to be sent according to the specified {@param mode}.
|
||||
*
|
||||
* The request will fire asynchronously, and exactly one of the methods on the {@param callback}
|
||||
* will be called.
|
||||
*/
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
static void requestSmsVerificationCode(@NonNull Context context, @NonNull Credentials credentials, @Nullable String captchaToken, @NonNull Mode mode, @NonNull SmsVerificationCodeCallback callback) {
|
||||
Log.d(TAG, String.format("SMS Verification requested for %s captcha %s", credentials.getE164number(), captchaToken));
|
||||
|
||||
new AsyncTask<Void, Void, VerificationRequestResult>() {
|
||||
@Override
|
||||
protected @NonNull
|
||||
VerificationRequestResult doInBackground(Void... voids) {
|
||||
try {
|
||||
markAsVerifying(context);
|
||||
|
||||
Optional<String> fcmToken;
|
||||
|
||||
if (mode.isFcm()) {
|
||||
fcmToken = FcmUtil.getToken();
|
||||
} else {
|
||||
fcmToken = Optional.absent();
|
||||
}
|
||||
|
||||
SignalServiceAccountManager accountManager = AccountManagerFactory.createManager(context, credentials.getE164number(), credentials.getPassword());
|
||||
|
||||
Optional<String> pushChallenge = PushChallengeRequest.getPushChallengeBlocking(accountManager, fcmToken, credentials.getE164number(), PUSH_REQUEST_TIMEOUT_MS);
|
||||
|
||||
if (mode == Mode.PHONE_CALL) {
|
||||
accountManager.requestVoiceVerificationCode(Locale.getDefault(), Optional.fromNullable(captchaToken), pushChallenge);
|
||||
} else {
|
||||
accountManager.requestSmsVerificationCode(mode.isSmsRetrieverSupported(), Optional.fromNullable(captchaToken), pushChallenge);
|
||||
}
|
||||
|
||||
return new VerificationRequestResult(fcmToken.orNull(), Optional.absent());
|
||||
} catch (IOException e) {
|
||||
org.thoughtcrime.securesms.logging.Log.w(TAG, "Error during account registration", e);
|
||||
return new VerificationRequestResult(null, Optional.of(e));
|
||||
}
|
||||
}
|
||||
|
||||
protected void onPostExecute(@NonNull VerificationRequestResult result) {
|
||||
if (result.exception.isPresent() && result.exception.get() instanceof CaptchaRequiredException) {
|
||||
callback.onNeedCaptcha();
|
||||
} else if (result.exception.isPresent()) {
|
||||
callback.onError();
|
||||
} else {
|
||||
callback.requestSent(result.fcmToken);
|
||||
}
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
|
||||
private static void markAsVerifying(Context context) {
|
||||
TextSecurePreferences.setVerifying(context, true);
|
||||
|
||||
TextSecurePreferences.setPushRegistered(context, false);
|
||||
}
|
||||
|
||||
private static class VerificationRequestResult {
|
||||
private final @Nullable String fcmToken;
|
||||
private final Optional<IOException> exception;
|
||||
|
||||
private VerificationRequestResult(@Nullable String fcmToken, Optional<IOException> exception) {
|
||||
this.fcmToken = fcmToken;
|
||||
this.exception = exception;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The mode by which a code is being requested.
|
||||
*/
|
||||
public enum Mode {
|
||||
|
||||
/**
|
||||
* Device supports FCM and SMS retrieval.
|
||||
*
|
||||
* The SMS sent will be formatted for automatic SMS retrieval.
|
||||
*/
|
||||
SMS_FCM_WITH_LISTENER(true, true),
|
||||
|
||||
/**
|
||||
* Device supports FCM but not SMS retrieval.
|
||||
*
|
||||
* The SMS sent will be not be specially formatted for automatic SMS retrieval.
|
||||
*/
|
||||
SMS_FCM_NO_LISTENER(true, false),
|
||||
|
||||
/**
|
||||
* Device does not support FCM and so also not SMS retrieval.
|
||||
*/
|
||||
SMS_NO_FCM(false, false),
|
||||
|
||||
/**
|
||||
* Device is requesting a phone call.
|
||||
*
|
||||
* Neither FCM or SMS retrieval is relevant in this mode.
|
||||
*/
|
||||
PHONE_CALL(false, false);
|
||||
|
||||
private final boolean fcm;
|
||||
private final boolean smsRetrieverSupported;
|
||||
|
||||
Mode(boolean fcm, boolean smsRetrieverSupported) {
|
||||
this.fcm = fcm;
|
||||
this.smsRetrieverSupported = smsRetrieverSupported;
|
||||
}
|
||||
|
||||
public boolean isFcm() {
|
||||
return fcm;
|
||||
}
|
||||
|
||||
public boolean isSmsRetrieverSupported() {
|
||||
return smsRetrieverSupported;
|
||||
}
|
||||
}
|
||||
|
||||
public interface SmsVerificationCodeCallback {
|
||||
|
||||
void onNeedCaptcha();
|
||||
|
||||
void requestSent(@Nullable String fcmToken);
|
||||
|
||||
void onError();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package org.thoughtcrime.securesms.registration.service;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public final class RegistrationService {
|
||||
|
||||
private final Credentials credentials;
|
||||
|
||||
private RegistrationService(@NonNull Credentials credentials) {
|
||||
this.credentials = credentials;
|
||||
}
|
||||
|
||||
public static RegistrationService getInstance(@NonNull String e164number, @NonNull String password) {
|
||||
return new RegistrationService(new Credentials(e164number, password));
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link RegistrationCodeRequest}.
|
||||
*/
|
||||
public void requestVerificationCode(@NonNull Activity activity,
|
||||
@NonNull RegistrationCodeRequest.Mode mode,
|
||||
@Nullable String captchaToken,
|
||||
@NonNull RegistrationCodeRequest.SmsVerificationCodeCallback callback)
|
||||
{
|
||||
RegistrationCodeRequest.requestSmsVerificationCode(activity, credentials, captchaToken, mode, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link CodeVerificationRequest}.
|
||||
*/
|
||||
public void verifyAccount(@NonNull Activity activity,
|
||||
@Nullable String fcmToken,
|
||||
@NonNull String code,
|
||||
@Nullable String pin,
|
||||
@NonNull CodeVerificationRequest.VerifyCallback callback)
|
||||
{
|
||||
CodeVerificationRequest.verifyAccount(activity, credentials, fcmToken, code, pin, callback);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
package org.thoughtcrime.securesms.registration.viewmodel;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.i18n.phonenumbers.NumberParseException;
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||
import com.google.i18n.phonenumbers.Phonenumber;
|
||||
|
||||
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public final class NumberViewState implements Parcelable {
|
||||
|
||||
public static final NumberViewState INITIAL = new Builder().build();
|
||||
|
||||
private final String selectedCountryName;
|
||||
private final int countryCode;
|
||||
private final long nationalNumber;
|
||||
|
||||
private NumberViewState(Builder builder) {
|
||||
this.selectedCountryName = builder.countryDisplayName;
|
||||
this.countryCode = builder.countryCode;
|
||||
this.nationalNumber = builder.nationalNumber;
|
||||
}
|
||||
|
||||
public Builder toBuilder() {
|
||||
return new Builder().countryCode(countryCode)
|
||||
.selectedCountryDisplayName(selectedCountryName)
|
||||
.nationalNumber(nationalNumber);
|
||||
}
|
||||
|
||||
public int getCountryCode() {
|
||||
return countryCode;
|
||||
}
|
||||
|
||||
public long getNationalNumber() {
|
||||
return nationalNumber;
|
||||
}
|
||||
|
||||
public String getCountryDisplayName() {
|
||||
if (selectedCountryName != null) {
|
||||
return selectedCountryName;
|
||||
}
|
||||
|
||||
PhoneNumberUtil util = PhoneNumberUtil.getInstance();
|
||||
|
||||
if (isValid()) {
|
||||
String actualCountry = getActualCountry(util, getE164Number());
|
||||
|
||||
if (actualCountry != null) {
|
||||
return actualCountry;
|
||||
}
|
||||
}
|
||||
|
||||
String regionCode = util.getRegionCodeForCountryCode(countryCode);
|
||||
return PhoneNumberFormatter.getRegionDisplayName(regionCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds actual name of region from a valid number. So for example +1 might map to US or Canada or other territories.
|
||||
*/
|
||||
private static @Nullable String getActualCountry(@NonNull PhoneNumberUtil util, @NonNull String e164Number) {
|
||||
try {
|
||||
Phonenumber.PhoneNumber phoneNumber = getPhoneNumber(util, e164Number);
|
||||
String regionCode = util.getRegionCodeForNumber(phoneNumber);
|
||||
|
||||
if (regionCode != null) {
|
||||
return PhoneNumberFormatter.getRegionDisplayName(regionCode);
|
||||
}
|
||||
|
||||
} catch (NumberParseException e) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return PhoneNumberFormatter.isValidNumber(getE164Number(), Integer.toString(getCountryCode()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = countryCode;
|
||||
hash *= 31;
|
||||
hash += (int) (nationalNumber ^ (nationalNumber >>> 32));
|
||||
hash *= 31;
|
||||
hash += selectedCountryName != null ? selectedCountryName.hashCode() : 0;
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (obj == null) return false;
|
||||
if (obj.getClass() != getClass()) return false;
|
||||
|
||||
NumberViewState other = (NumberViewState) obj;
|
||||
|
||||
return other.countryCode == countryCode &&
|
||||
other.nationalNumber == nationalNumber &&
|
||||
Objects.equals(other.selectedCountryName, selectedCountryName);
|
||||
}
|
||||
|
||||
public String getE164Number() {
|
||||
return getConfiguredE164Number(countryCode, nationalNumber);
|
||||
}
|
||||
|
||||
public String getFullFormattedNumber() {
|
||||
return formatNumber(PhoneNumberUtil.getInstance(), getE164Number());
|
||||
}
|
||||
|
||||
private static String formatNumber(@NonNull PhoneNumberUtil util, @NonNull String e164Number) {
|
||||
try {
|
||||
Phonenumber.PhoneNumber number = getPhoneNumber(util, e164Number);
|
||||
return util.format(number, PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL);
|
||||
} catch (NumberParseException e) {
|
||||
return e164Number;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getConfiguredE164Number(int countryCode, long number) {
|
||||
return PhoneNumberFormatter.formatE164(String.valueOf(countryCode), String.valueOf(number));
|
||||
}
|
||||
|
||||
private static Phonenumber.PhoneNumber getPhoneNumber(@NonNull PhoneNumberUtil util, @NonNull String e164Number)
|
||||
throws NumberParseException
|
||||
{
|
||||
return util.parse(e164Number, null);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private String countryDisplayName;
|
||||
private int countryCode;
|
||||
private long nationalNumber;
|
||||
|
||||
public Builder countryCode(int countryCode) {
|
||||
this.countryCode = countryCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder selectedCountryDisplayName(String countryDisplayName) {
|
||||
this.countryDisplayName = countryDisplayName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder nationalNumber(long nationalNumber) {
|
||||
this.nationalNumber = nationalNumber;
|
||||
return this;
|
||||
}
|
||||
|
||||
public NumberViewState build() {
|
||||
return new NumberViewState(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int i) {
|
||||
parcel.writeString(selectedCountryName);
|
||||
parcel.writeInt(countryCode);
|
||||
parcel.writeLong(nationalNumber);
|
||||
}
|
||||
|
||||
public static final Creator<NumberViewState> CREATOR = new Creator<NumberViewState>() {
|
||||
@Override
|
||||
public NumberViewState createFromParcel(Parcel in) {
|
||||
return new Builder().selectedCountryDisplayName(in.readString())
|
||||
.countryCode(in.readInt())
|
||||
.nationalNumber(in.readLong())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NumberViewState[] newArray(int size) {
|
||||
return new NumberViewState[size];
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
package org.thoughtcrime.securesms.registration.viewmodel;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.SavedStateHandle;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
public final class RegistrationViewModel extends ViewModel {
|
||||
|
||||
private final String secret;
|
||||
private final MutableLiveData<NumberViewState> number;
|
||||
private final MutableLiveData<String> textCodeEntered;
|
||||
private final MutableLiveData<String> captchaToken;
|
||||
private final MutableLiveData<String> fcmToken;
|
||||
private final MutableLiveData<Boolean> restoreFlowShown;
|
||||
private final MutableLiveData<Integer> successfulCodeRequestAttempts;
|
||||
|
||||
public RegistrationViewModel(@NonNull SavedStateHandle savedStateHandle) {
|
||||
secret = loadValue(savedStateHandle, "REGISTRATION_SECRET", Util.getSecret(18));
|
||||
|
||||
number = savedStateHandle.getLiveData("NUMBER", NumberViewState.INITIAL);
|
||||
textCodeEntered = savedStateHandle.getLiveData("TEXT_CODE_ENTERED", "");
|
||||
captchaToken = savedStateHandle.getLiveData("CAPTCHA");
|
||||
fcmToken = savedStateHandle.getLiveData("FCM_TOKEN");
|
||||
restoreFlowShown = savedStateHandle.getLiveData("RESTORE_FLOW_SHOWN", false);
|
||||
successfulCodeRequestAttempts = savedStateHandle.getLiveData("SUCCESSFUL_CODE_REQUEST_ATTEMPTS", 0);
|
||||
}
|
||||
|
||||
private static <T> T loadValue(@NonNull SavedStateHandle savedStateHandle, @NonNull String key, @NonNull T initialValue) {
|
||||
if (!savedStateHandle.contains(key)) {
|
||||
savedStateHandle.set(key, initialValue);
|
||||
}
|
||||
return savedStateHandle.get(key);
|
||||
}
|
||||
|
||||
public @NonNull NumberViewState getNumber() {
|
||||
//noinspection ConstantConditions Live data was given an initial value
|
||||
return number.getValue();
|
||||
}
|
||||
|
||||
public @NonNull LiveData<NumberViewState> getLiveNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public @NonNull String getTextCodeEntered() {
|
||||
//noinspection ConstantConditions Live data was given an initial value
|
||||
return textCodeEntered.getValue();
|
||||
}
|
||||
|
||||
public String getCaptchaToken() {
|
||||
return captchaToken.getValue();
|
||||
}
|
||||
|
||||
public boolean hasCaptchaToken() {
|
||||
return getCaptchaToken() != null;
|
||||
}
|
||||
|
||||
public String getRegistrationSecret() {
|
||||
return secret;
|
||||
}
|
||||
|
||||
public void onCaptchaResponse(String captchaToken) {
|
||||
this.captchaToken.setValue(captchaToken);
|
||||
}
|
||||
|
||||
public void clearCaptchaResponse() {
|
||||
captchaToken.setValue(null);
|
||||
}
|
||||
|
||||
public void onCountrySelected(@Nullable String selectedCountryName, int countryCode) {
|
||||
setViewState(getNumber().toBuilder()
|
||||
.selectedCountryDisplayName(selectedCountryName)
|
||||
.countryCode(countryCode).build());
|
||||
}
|
||||
|
||||
public void setNationalNumber(long number) {
|
||||
NumberViewState numberViewState = getNumber().toBuilder().nationalNumber(number).build();
|
||||
setViewState(numberViewState);
|
||||
}
|
||||
|
||||
private void setViewState(NumberViewState numberViewState) {
|
||||
if (!numberViewState.equals(getNumber())) {
|
||||
number.setValue(numberViewState);
|
||||
}
|
||||
}
|
||||
|
||||
@MainThread
|
||||
public void onVerificationCodeEntered(String code) {
|
||||
textCodeEntered.setValue(code);
|
||||
}
|
||||
|
||||
public void onNumberDetected(int countryCode, long nationalNumber) {
|
||||
setViewState(getNumber().toBuilder()
|
||||
.countryCode(countryCode)
|
||||
.nationalNumber(nationalNumber)
|
||||
.build());
|
||||
}
|
||||
|
||||
public String getFcmToken() {
|
||||
return fcmToken.getValue();
|
||||
}
|
||||
|
||||
@MainThread
|
||||
public void setFcmToken(@Nullable String fcmToken) {
|
||||
this.fcmToken.setValue(fcmToken);
|
||||
}
|
||||
|
||||
public void setWelcomeSkippedOnRestore() {
|
||||
restoreFlowShown.setValue(true);
|
||||
}
|
||||
|
||||
public boolean hasRestoreFlowBeenShown() {
|
||||
//noinspection ConstantConditions Live data was given an initial value
|
||||
return restoreFlowShown.getValue();
|
||||
}
|
||||
|
||||
public void markASuccessfulAttempt() {
|
||||
//noinspection ConstantConditions Live data was given an initial value
|
||||
successfulCodeRequestAttempts.setValue(successfulCodeRequestAttempts.getValue() + 1);
|
||||
}
|
||||
|
||||
public LiveData<Integer> getSuccessfulCodeRequestAttempts() {
|
||||
return successfulCodeRequestAttempts;
|
||||
}
|
||||
}
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
dependencyVerification {
|
||||
verify = [
|
||||
'androidx.activity:activity:0d6bafb56a72da893f3990ca5d819214d047f5f6b5c5f822ed97971c05eeb85a',
|
||||
'androidx.activity:activity:d1bc9842455c2e534415d88c44df4d52413b478db9093a1ba36324f705f44c3d',
|
||||
'androidx.annotation:annotation:d38d63edb30f1467818d50aaf05f8a692dea8b31392a049bfa991b159ad5b692',
|
||||
'androidx.appcompat:appcompat-resources:53c0a33d07c4bab48d4c8169bf30053aa14965af4a775b56092a9fc7079802b1',
|
||||
'androidx.appcompat:appcompat:49ad229add44f822fcb3c8405c3fddbd72660da6a839ce29e13158f5980514fd',
|
||||
'androidx.arch.core:core-common:fe1237bf029d063e7f29fe39aeaf73ef74c8b0a3658486fc29d3c54326653889',
|
||||
'androidx.arch.core:core-runtime:87e65fc767c712b437649c7cee2431ebb4bed6daef82e501d4125b3ed3f65f8e',
|
||||
'androidx.arch.core:core-runtime:dd77615bd3dd275afb11b62df25bae46b10b4a117cd37943af45bdcbf8755852',
|
||||
'androidx.asynclayoutinflater:asynclayoutinflater:f7eab60c57addd94bb06275832fe7600611beaaae1a1ec597c231956faf96c8b',
|
||||
'androidx.camera:camera-camera2:b7897230aec96365d675712c92f5edcb8b464badfd61788c8f956ec2d6e49bfe',
|
||||
'androidx.camera:camera-core:e1c70de55600a0caf826eb4f8a75c96c5ff8f0b626bf08413d31e80ffa55f8ba',
|
||||
|
@ -17,13 +17,13 @@ dependencyVerification {
|
|||
'androidx.constraintlayout:constraintlayout-solver:965c177e64fbd81bd1d27b402b66ef9d7bc7b5cb5f718044bf7a453abc542045',
|
||||
'androidx.constraintlayout:constraintlayout:5ff864def9d41cd04e08348d69591143bae3ceff4284cf8608bceb98c36ac830',
|
||||
'androidx.coordinatorlayout:coordinatorlayout:e508c695489493374d942bf7b4ee02abf7571d25aac4c622e57d6cd5cd29eb73',
|
||||
'androidx.core:core:45c7a50ad1f366e62db496d8cef7730d5ee1681215007d1a19e6b6d800a12842',
|
||||
'androidx.core:core:76c7cfbe596fe3c09a6983bf1c89e889299c08ac9a3b52ce5182a088d056647e',
|
||||
'androidx.cursoradapter:cursoradapter:a81c8fe78815fa47df5b749deb52727ad11f9397da58b16017f4eb2c11e28564',
|
||||
'androidx.customview:customview:20e5b8f6526a34595a604f56718da81167c0b40a7a94a57daa355663f2594df2',
|
||||
'androidx.documentfile:documentfile:865a061ef2fad16522f8433536b8d47208c46ff7c7745197dfa1eeb481869487',
|
||||
'androidx.drawerlayout:drawerlayout:9402442cdc5a43cf62fb14f8cf98c63342d4d9d9b805c8033c6cf7e802749ac1',
|
||||
'androidx.exifinterface:exifinterface:ee48be10aab8f54efff4c14b77d11e10b9eeee4379d5ef6bf297a2923c55cc11',
|
||||
'androidx.fragment:fragment:9656d81c472b5142bbc3471ef7259fbc93905dc38e823c63a99e48819881b6e7',
|
||||
'androidx.fragment:fragment:a14c8b8f2153f128e800fbd266a6beab1c283982a29ec570d2cc05d307d81496',
|
||||
'androidx.gridlayout:gridlayout:a7e5dc6f39dbc3dc6ac6d57b02a9c6fd792e80f0e45ddb3bb08e8f03d23c8755',
|
||||
'androidx.interpolator:interpolator:33193135a64fe21fa2c35eec6688f1a76e512606c0fc83dc1b689e37add7732a',
|
||||
'androidx.legacy:legacy-preference-v14:d6d11913e56b8f2d14fd560bd1ad6d7fd5624a15dd4ec073b2d9188205f86280',
|
||||
|
@ -31,29 +31,34 @@ dependencyVerification {
|
|||
'androidx.legacy:legacy-support-core-utils:a7edcf01d5b52b3034073027bc4775b78a4764bb6202bb91d61c829add8dd1c7',
|
||||
'androidx.legacy:legacy-support-v13:65f5fcb57644d381d471a00fdf50f90b808be6b48a8ae57fb4ea39b7da8cca86',
|
||||
'androidx.legacy:legacy-support-v4:78fec1485f0f388a4749022dd51416857127cd2544ae1c3fd0b16589055480b0',
|
||||
'androidx.lifecycle:lifecycle-common-java8:9edc2d4f589656d470ef03b9c6ece62d335971294b033ec7d9ceb6e361e9aafa',
|
||||
'androidx.lifecycle:lifecycle-common:76db6be533bd730fb361c2feb12a2c26d9952824746847da82601ef81f082643',
|
||||
'androidx.lifecycle:lifecycle-extensions:8d4072201b6231d67e4192d608d46b1f5c920845106c9831632c2e3ffe706117',
|
||||
'androidx.lifecycle:lifecycle-livedata-core:fde334ec7e22744c0f5bfe7caf1a84c9d717327044400577bdf9bd921ec4f7bc',
|
||||
'androidx.lifecycle:lifecycle-livedata:c82609ced8c498f0a701a30fb6771bb7480860daee84d82e0a81ee86edf7ba39',
|
||||
'androidx.lifecycle:lifecycle-process:d8ff6fd844559743050c9ae010a6df230f2a3dbdf3e14498316f30bd8df836b5',
|
||||
'androidx.lifecycle:lifecycle-runtime:7e6d414d03bb184f3015dacc6233eeaded45fa23f0cf4c1f6d3395d6495fa41c',
|
||||
'androidx.lifecycle:lifecycle-service:cb2b15bb0cf14134e953ed8ead96f94265018643f519367d51fd837f7311e9f8',
|
||||
'androidx.lifecycle:lifecycle-viewmodel:9f2efb59328027fa9f0c413d4d5910aab68d149b139ca8ce432135105b74833a',
|
||||
'androidx.lifecycle:lifecycle-common-java8:a1ec63c1bb973443cb731d78ec336c5e20e7ee35c89cbb32d36f92c55bb02542',
|
||||
'androidx.lifecycle:lifecycle-common:63898dabf7cfe5ec5d7ed8b8c2564c1427be876e1496ead95c2703cf59d3734b',
|
||||
'androidx.lifecycle:lifecycle-extensions:bd53c64b038585215b4959c1a388437a3ad525608a31c58e4283c3e371727d4d',
|
||||
'androidx.lifecycle:lifecycle-livedata-core:6df2bcbf3be50a5fa29e9aa09d39437f2d61d17c2c46ef618d65bbac4a4a99fc',
|
||||
'androidx.lifecycle:lifecycle-livedata:242e446bed3db36f0df0aab0cb7f91060bd2dab7adcad1117adf54e724cd1d26',
|
||||
'androidx.lifecycle:lifecycle-process:8cddd0c7f4927bbf71fb71fca000786df82cc597c99463d6916ccbe4a205a9ac',
|
||||
'androidx.lifecycle:lifecycle-runtime:e5173897b965e870651e83d9d5af1742d3f532d58863223a390ce3a194c8312b',
|
||||
'androidx.lifecycle:lifecycle-service:23516745f34f16ff7850bb1eadd55cf193dd789cba428de4bca120433e3bfd69',
|
||||
'androidx.lifecycle:lifecycle-viewmodel-savedstate:f503b53f50c4e6c1f9a3d698c4733df6e7a44049fe477ad0b85cc2f460401fbc',
|
||||
'androidx.lifecycle:lifecycle-viewmodel:7725715491963440ee483e46526cd4f83af1c758e072e97b3eab2115c6f4db35',
|
||||
'androidx.loader:loader:11f735cb3b55c458d470bed9e25254375b518b4b1bad6926783a7026db0f5025',
|
||||
'androidx.localbroadcastmanager:localbroadcastmanager:e71c328ceef5c4a7d76f2d86df1b65d65fe2acf868b1a4efd84a3f34336186d8',
|
||||
'androidx.media:media:b23b527b2bac870c4a7451e6982d7132e413e88d7f27dbeb1fc7640a720cd9ee',
|
||||
'androidx.multidex:multidex:42dd32ff9f97f85771b82a20003a8d70f68ab7b4ba328964312ce0732693db09',
|
||||
'androidx.navigation:navigation-common:f968fcaa2fd94b0d1275ce175ecfb4773678732ead9b4d81993ffd5bc3fe3c7c',
|
||||
'androidx.navigation:navigation-fragment:776ba1be826f8de7cb262f55ece262c5eb9947758cbd2e902298750521404dd2',
|
||||
'androidx.navigation:navigation-runtime:499029c016345a2a2130ee7a32670871757e5fc7e6d1b93be8962bb59fa5ce9d',
|
||||
'androidx.navigation:navigation-ui:1ec0558d692982c5bcfcca6de5b5972723e6b4a9870aa7fc1eddf5e869f116ed',
|
||||
'androidx.preference:preference:ea9fde25606eb456210ffe9f7e51048abd776b55a34c0cc6608282b5699122d1',
|
||||
'androidx.print:print:1d5c7f3135a1bba661fc373fd72e11eb0a4adbb3396787826dd8e4190d5d9edd',
|
||||
'androidx.recyclerview:recyclerview:06956fb1ac014027ca9d2b40469a4b42aa61b4957bb11848e1ff352701ab4548',
|
||||
'androidx.savedstate:savedstate:115ac7313095b2d159565d2bc851a7722e43fc00347fc828214ff8917799b5f0',
|
||||
'androidx.savedstate:savedstate:2510a5619c37579c9ce1a04574faaf323cd0ffe2fc4e20fa8f8f01e5bb402e83',
|
||||
'androidx.slidingpanelayout:slidingpanelayout:76bffb7cefbf780794d8817002dad1562f3e27c0a9f746d62401c8edb30aeede',
|
||||
'androidx.swiperefreshlayout:swiperefreshlayout:9761b3a809c9b093fd06a3c4bbc645756dec0e95b5c9da419bc9f2a3f3026e8d',
|
||||
'androidx.transition:transition:a00a0f763f401abcecda9b0eafcb738929c5801b111a9a414b81a193d0f4008d',
|
||||
'androidx.transition:transition:c374bef04f01580ba76447e759ea560079727779ff882ad55735fd445edca8b4',
|
||||
'androidx.vectordrawable:vectordrawable-animated:f1613c47f1e6d2cd02ec9a42925f1a964fa63d1d028d34d884364cc3b9ffcb8f',
|
||||
'androidx.vectordrawable:vectordrawable:b632152304edb506bf7eacb329ef41e43b80164bf5be4c7bb132a249a65cbc26',
|
||||
'androidx.versionedparcelable:versionedparcelable:948c751f6352d4c0f93f15fa1bf506c59083bc7754264dd9a325a6da0e2eec05',
|
||||
'androidx.versionedparcelable:versionedparcelable:9a1d77140ac222b7866b5054ee7d159bc1800987ed2d46dd6afdd145abb710c1',
|
||||
'androidx.viewpager:viewpager:147af4e14a1984010d8f155e5e19d781f03c1d70dfed02a8e0d18428b8fc8682',
|
||||
'cn.carbswang.android:NumberPickerView:18b3c316d62c7c277978a8d4ed57a5b8f4e943762264960f579a8a549c756729',
|
||||
'com.airbnb.android:lottie:6819ff968eb768096133c7873d63351705fd4ac424a0917d86c4145f5035097d',
|
||||
|
|
Loading…
Reference in New Issue