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:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|screenSize|fontScale"
|
||||||
android:launchMode="singleTask"/>
|
android:launchMode="singleTask"/>
|
||||||
|
|
||||||
<activity android:name=".CountrySelectionActivity"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
|
||||||
|
|
||||||
<activity android:name=".InviteActivity"
|
<activity android:name=".InviteActivity"
|
||||||
android:theme="@style/TextSecure.HighlightTheme"
|
android:theme="@style/TextSecure.HighlightTheme"
|
||||||
android:windowSoftInputMode="stateHidden"
|
android:windowSoftInputMode="stateHidden"
|
||||||
|
@ -319,24 +316,12 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".registration.WelcomeActivity"
|
<activity android:name=".registration.RegistrationNavigationActivity"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||||
android:windowSoftInputMode="stateUnchanged"
|
android:windowSoftInputMode="stateUnchanged"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
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"
|
<activity android:name=".revealable.ViewOnceMessageActivity"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:theme="@style/TextSecure.FullScreenMedia"
|
android:theme="@style/TextSecure.FullScreenMedia"
|
||||||
|
|
|
@ -11,10 +11,12 @@ buildscript {
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.3.2'
|
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: 'com.android.application'
|
||||||
|
apply plugin: 'androidx.navigation.safeargs'
|
||||||
apply plugin: 'witness'
|
apply plugin: 'witness'
|
||||||
apply from: 'witness-verifications.gradle'
|
apply from: 'witness-verifications.gradle'
|
||||||
|
|
||||||
|
@ -66,8 +68,11 @@ dependencies {
|
||||||
implementation 'androidx.exifinterface:exifinterface:1.0.0'
|
implementation 'androidx.exifinterface:exifinterface:1.0.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
implementation 'androidx.multidex:multidex:2.0.1'
|
implementation 'androidx.multidex:multidex:2.0.1'
|
||||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
|
implementation 'androidx.navigation:navigation-fragment:2.1.0'
|
||||||
implementation 'androidx.lifecycle:lifecycle-common-java8:2.0.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-core:1.0.0-alpha04"
|
||||||
implementation "androidx.camera:camera-camera2: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_height="wrap_content"
|
||||||
android:layout_marginBottom="3dp"
|
android:layout_marginBottom="3dp"
|
||||||
android:src="@drawable/ic_add_white_original_24dp"
|
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
|
<EditText
|
||||||
android:id="@+id/input"
|
android:id="@+id/input"
|
||||||
|
@ -28,6 +30,8 @@
|
||||||
android:inputType="number"
|
android:inputType="number"
|
||||||
android:maxLength="3"
|
android:maxLength="3"
|
||||||
android:digits="1234567890"
|
android:digits="1234567890"
|
||||||
|
android:saveEnabled="false"
|
||||||
|
android:hint="@string/RegistrationActivity_country_code_description"
|
||||||
tools:text="123" />
|
tools:text="123" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
|
@ -1,28 +1,29 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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"
|
<TextView
|
||||||
android:layout_height="wrap_content"
|
android:id="@+id/country_name"
|
||||||
android:orientation="horizontal"
|
android:layout_width="0dp"
|
||||||
android:padding="8dip"
|
android:layout_height="wrap_content"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
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"
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/country_code"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_weight="1.0"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="18sp"
|
android:layout_marginStart="4dp"
|
||||||
android:textStyle="bold" />
|
android:layout_marginEnd="5dp"
|
||||||
|
android:textColor="@color/core_black"
|
||||||
<TextView android:id="@+id/country_code"
|
android:textSize="18sp"
|
||||||
android:layout_width="wrap_content"
|
tools:text="+1" />
|
||||||
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"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</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"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="16dp">
|
android:padding="16dp">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/restore_passphrase_input"
|
android:id="@+id/restore_passphrase_input"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="@string/enter_backup_passphrase_dialog__backup_passphrase"
|
android:fontFamily="monospace"
|
||||||
android:imeOptions="actionDone"
|
android:hint="@string/enter_backup_passphrase_dialog__backup_passphrase"
|
||||||
android:inputType="number" />
|
android:imeOptions="actionDone"
|
||||||
|
android:inputType="number"
|
||||||
|
android:maxLength="30"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
</FrameLayout>
|
</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"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout 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:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
|
android:id="@+id/image"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_marginStart="32dp"
|
android:layout_margin="16dp"
|
||||||
android:layout_marginTop="48dp"
|
android:importantForAccessibility="no"
|
||||||
android:layout_marginEnd="32dp"
|
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
android:src="@drawable/welcome"
|
android:src="@drawable/welcome"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/textView"
|
app:layout_constraintBottom_toTopOf="@+id/title"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/textView"
|
android:id="@+id/title"
|
||||||
style="@style/Signal.Text.Headline.Registration"
|
style="@style/Signal.Text.Headline.Registration"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="80dp"
|
android:layout_marginBottom="40dp"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:text="@string/RegistrationActivity_take_privacy_with_you_be_yourself_in_every_message"
|
android:text="@string/RegistrationActivity_take_privacy_with_you_be_yourself_in_every_message"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/welcome_terms_button"
|
app:layout_constraintBottom_toTopOf="@+id/welcome_terms_button"
|
||||||
|
@ -32,10 +30,10 @@
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/welcome_terms_button"
|
android:id="@+id/welcome_terms_button"
|
||||||
|
style="@style/Signal.Text.Body.Registration"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="24dp"
|
android:layout_marginBottom="24dp"
|
||||||
style="@style/Signal.Text.Body"
|
|
||||||
android:text="@string/RegistrationActivity_terms_and_privacy"
|
android:text="@string/RegistrationActivity_terms_and_privacy"
|
||||||
android:textColor="@color/signal_primary"
|
android:textColor="@color/signal_primary"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/welcome_continue_button"
|
app:layout_constraintBottom_toTopOf="@+id/welcome_continue_button"
|
||||||
|
@ -44,19 +42,12 @@
|
||||||
|
|
||||||
<com.dd.CircularProgressButton
|
<com.dd.CircularProgressButton
|
||||||
android:id="@+id/welcome_continue_button"
|
android:id="@+id/welcome_continue_button"
|
||||||
|
style="@style/Button.Registration"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:layout_marginStart="32dp"
|
android:layout_marginStart="32dp"
|
||||||
android:layout_marginEnd="32dp"
|
android:layout_marginEnd="32dp"
|
||||||
android:layout_marginBottom="54dp"
|
android:layout_marginBottom="@dimen/registration_button_bottom_margin"
|
||||||
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"
|
|
||||||
app:cpb_textIdle="@string/RegistrationActivity_continue"
|
app:cpb_textIdle="@string/RegistrationActivity_continue"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="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:background="@color/transparent"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:inputType="phone"
|
android:inputType="phone"
|
||||||
|
android:saveEnabled="false"
|
||||||
|
android:hint="@string/RegistrationActivity_phone_number_description"
|
||||||
tools:text="867-5309" />
|
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"?>
|
<?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"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="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:id="@+id/container_zero"
|
||||||
android:layout_width="48dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="54dp"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center"
|
android:background="@drawable/labeled_edit_text_background_inactive"
|
||||||
android:orientation="vertical"
|
app:layout_constraintDimensionRatio="w,1:1"
|
||||||
android:background="@drawable/labeled_edit_text_background_inactive">
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/code_zero"
|
android:id="@+id/code_zero"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="28dp"
|
android:layout_gravity="center"
|
||||||
tools:text="1" />
|
android:textColor="@color/core_black"
|
||||||
|
android:textSize="28sp"
|
||||||
|
tools:text="0" />
|
||||||
|
|
||||||
</LinearLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<FrameLayout
|
||||||
android:id="@+id/container_one"
|
android:id="@+id/container_one"
|
||||||
android:layout_width="48dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="54dp"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="5dp"
|
android:layout_marginStart="4dp"
|
||||||
android:gravity="center"
|
android:background="@drawable/labeled_edit_text_background_inactive"
|
||||||
android:orientation="vertical"
|
app:layout_constraintDimensionRatio="w,1:1"
|
||||||
android:background="@drawable/labeled_edit_text_background_inactive">
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/container_zero"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/code_one"
|
android:id="@+id/code_one"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="28dp"
|
android:layout_gravity="center"
|
||||||
tools:text="2" />
|
android:textColor="@color/core_black"
|
||||||
|
android:textSize="28sp"
|
||||||
|
tools:text="1" />
|
||||||
|
|
||||||
</LinearLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<FrameLayout
|
||||||
android:id="@+id/container_two"
|
android:id="@+id/container_two"
|
||||||
android:layout_width="48dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="54dp"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="5dp"
|
android:layout_marginStart="4dp"
|
||||||
android:gravity="center"
|
android:background="@drawable/labeled_edit_text_background_inactive"
|
||||||
android:orientation="vertical"
|
app:layout_constraintDimensionRatio="w,1:1"
|
||||||
android:background="@drawable/labeled_edit_text_background_inactive">
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/container_one"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/code_two"
|
android:id="@+id/code_two"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="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" />
|
tools:text="2" />
|
||||||
|
|
||||||
</LinearLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<TextView
|
||||||
android:id="@+id/separator_container"
|
android:id="@+id/separator"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="5dp"
|
|
||||||
android:gravity="center_horizontal"
|
|
||||||
android:layout_gravity="center"
|
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
|
<FrameLayout
|
||||||
android:id="@+id/separator"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="-"
|
|
||||||
android:textSize="28dp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/container_three"
|
android:id="@+id/container_three"
|
||||||
android:layout_width="48dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="54dp"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="5dp"
|
android:layout_marginStart="4dp"
|
||||||
android:gravity="center"
|
android:background="@drawable/labeled_edit_text_background_inactive"
|
||||||
android:orientation="vertical"
|
app:layout_constraintDimensionRatio="w,1:1"
|
||||||
android:background="@drawable/labeled_edit_text_background_inactive">
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/separator"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/code_three"
|
android:id="@+id/code_three"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="28dp"
|
android:layout_gravity="center"
|
||||||
tools:text="2" />
|
android:textColor="@color/core_black"
|
||||||
|
android:textSize="28sp"
|
||||||
|
tools:text="3" />
|
||||||
|
|
||||||
</LinearLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/container_four"
|
android:id="@+id/container_four"
|
||||||
android:layout_width="48dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="54dp"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="5dp"
|
android:layout_marginStart="4dp"
|
||||||
android:gravity="center"
|
android:background="@drawable/labeled_edit_text_background_inactive"
|
||||||
android:orientation="vertical"
|
app:layout_constraintDimensionRatio="w,1:1"
|
||||||
android:background="@drawable/labeled_edit_text_background_inactive">
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/container_three"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/code_four"
|
android:id="@+id/code_four"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="28dp"
|
android:layout_gravity="center"
|
||||||
tools:text="2" />
|
android:textColor="@color/core_black"
|
||||||
|
android:textSize="28sp"
|
||||||
|
tools:text="4" />
|
||||||
|
|
||||||
</LinearLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<FrameLayout
|
||||||
android:id="@+id/container_five"
|
android:id="@+id/container_five"
|
||||||
android:layout_width="48dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="54dp"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="5dp"
|
android:layout_marginStart="4dp"
|
||||||
android:gravity="center"
|
android:background="@drawable/labeled_edit_text_background_inactive"
|
||||||
android:orientation="vertical"
|
app:layout_constraintDimensionRatio="w,1:1"
|
||||||
android:background="@drawable/labeled_edit_text_background_inactive">
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/container_four"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/code_five"
|
android:id="@+id/code_five"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="28dp"
|
android:layout_gravity="center"
|
||||||
android:gravity="center"
|
android:textColor="@color/core_black"
|
||||||
tools:text="2" />
|
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>
|
</attr>
|
||||||
</declare-styleable>
|
</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">
|
<declare-styleable name="QuoteView">
|
||||||
<attr name="message_type" format="enum">
|
<attr name="message_type" format="enum">
|
||||||
<enum name="preview" value="0" />
|
<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_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_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_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>
|
<string name="RegistrationActivity_call">Call</string>
|
||||||
|
|
||||||
<!-- RevealableMessageView -->
|
<!-- 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_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_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_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_never">Never</string>
|
||||||
<string name="BackupUtil_unknown">Unknown</string>
|
<string name="BackupUtil_unknown">Unknown</string>
|
||||||
<string name="preferences_app_protection__screen_lock">Screen lock</string>
|
<string name="preferences_app_protection__screen_lock">Screen lock</string>
|
||||||
|
|
|
@ -248,10 +248,6 @@
|
||||||
<item name="android:textColor">@color/signal_primary</item>
|
<item name="android:textColor">@color/signal_primary</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="Button.Borderless.Registration" parent="Base.Widget.AppCompat.Button.Borderless">
|
|
||||||
<item name="android:textColor">@color/core_grey_60</item>
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<!-- RedPhone -->
|
<!-- RedPhone -->
|
||||||
|
|
||||||
<!-- Buttons in the main "button row" of the in-call onscreen touch UI. -->
|
<!-- Buttons in the main "button row" of the in-call onscreen touch UI. -->
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<style name="Signal.Text.Headline" parent="Base.TextAppearance.AppCompat.Headline">
|
<style name="Signal.Text.Headline" parent="Base.TextAppearance.AppCompat.Headline">
|
||||||
<item name="android:textSize">28sp</item>
|
<item name="android:textSize">28sp</item>
|
||||||
<item name="android:lineSpacingExtra">3sp</item>
|
<item name="android:lineSpacingExtra">3sp</item>
|
||||||
|
@ -7,10 +8,6 @@
|
||||||
<item name="android:letterSpacing" tools:ignore="NewApi">0</item>
|
<item name="android:letterSpacing" tools:ignore="NewApi">0</item>
|
||||||
</style>
|
</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">
|
<style name="Signal.Text.Body" parent="Base.TextAppearance.AppCompat.Body1">
|
||||||
<item name="android:textSize">16sp</item>
|
<item name="android:textSize">16sp</item>
|
||||||
<item name="android:lineSpacingExtra">2sp</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.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import androidx.annotation.IdRes;
|
import androidx.annotation.IdRes;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
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.crypto.MasterSecretUtil;
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.migrations.ApplicationMigrationActivity;
|
import org.thoughtcrime.securesms.migrations.ApplicationMigrationActivity;
|
||||||
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
||||||
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
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.service.KeyCachingService;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
|
||||||
|
@ -29,13 +29,12 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
|
||||||
|
|
||||||
public static final String LOCALE_EXTRA = "locale_extra";
|
public static final String LOCALE_EXTRA = "locale_extra";
|
||||||
|
|
||||||
private static final int STATE_NORMAL = 0;
|
private static final int STATE_NORMAL = 0;
|
||||||
private static final int STATE_CREATE_PASSPHRASE = 1;
|
private static final int STATE_CREATE_PASSPHRASE = 1;
|
||||||
private static final int STATE_PROMPT_PASSPHRASE = 2;
|
private static final int STATE_PROMPT_PASSPHRASE = 2;
|
||||||
private static final int STATE_UI_BLOCKING_UPGRADE = 3;
|
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 = 4;
|
||||||
private static final int STATE_EXPERIENCE_UPGRADE = 5;
|
private static final int STATE_WELCOME_PUSH_SCREEN = 5;
|
||||||
private static final int STATE_WELCOME_SCREEN = 6;
|
|
||||||
|
|
||||||
private SignalServiceNetworkAccess networkAccess;
|
private SignalServiceNetworkAccess networkAccess;
|
||||||
private BroadcastReceiver clearKeyReceiver;
|
private BroadcastReceiver clearKeyReceiver;
|
||||||
|
@ -133,13 +132,12 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
|
||||||
Log.i(TAG, "routeApplicationState(), state: " + state);
|
Log.i(TAG, "routeApplicationState(), state: " + state);
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case STATE_CREATE_PASSPHRASE: return getCreatePassphraseIntent();
|
case STATE_CREATE_PASSPHRASE: return getCreatePassphraseIntent();
|
||||||
case STATE_PROMPT_PASSPHRASE: return getPromptPassphraseIntent();
|
case STATE_PROMPT_PASSPHRASE: return getPromptPassphraseIntent();
|
||||||
case STATE_UI_BLOCKING_UPGRADE: return getUiBlockingUpgradeIntent();
|
case STATE_UI_BLOCKING_UPGRADE: return getUiBlockingUpgradeIntent();
|
||||||
case STATE_WELCOME_SCREEN: return getWelcomeIntent();
|
case STATE_WELCOME_PUSH_SCREEN: return getPushRegistrationIntent();
|
||||||
case STATE_PROMPT_PUSH_REGISTRATION: return getPushRegistrationIntent();
|
case STATE_EXPERIENCE_UPGRADE: return getExperienceUpgradeIntent();
|
||||||
case STATE_EXPERIENCE_UPGRADE: return getExperienceUpgradeIntent();
|
default: return null;
|
||||||
default: return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,10 +148,8 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
|
||||||
return STATE_PROMPT_PASSPHRASE;
|
return STATE_PROMPT_PASSPHRASE;
|
||||||
} else if (ApplicationMigrations.isUpdate(this) && ApplicationMigrations.isUiBlockingMigrationRunning()) {
|
} else if (ApplicationMigrations.isUpdate(this) && ApplicationMigrations.isUiBlockingMigrationRunning()) {
|
||||||
return STATE_UI_BLOCKING_UPGRADE;
|
return STATE_UI_BLOCKING_UPGRADE;
|
||||||
} else if (!TextSecurePreferences.hasSeenWelcomeScreen(this)) {
|
|
||||||
return STATE_WELCOME_SCREEN;
|
|
||||||
} else if (!TextSecurePreferences.hasPromptedPushRegistration(this)) {
|
} else if (!TextSecurePreferences.hasPromptedPushRegistration(this)) {
|
||||||
return STATE_PROMPT_PUSH_REGISTRATION;
|
return STATE_WELCOME_PUSH_SCREEN;
|
||||||
} else if (ExperienceUpgradeActivity.isUpdate(this)) {
|
} else if (ExperienceUpgradeActivity.isUpdate(this)) {
|
||||||
return STATE_EXPERIENCE_UPGRADE;
|
return STATE_EXPERIENCE_UPGRADE;
|
||||||
} else {
|
} else {
|
||||||
|
@ -180,16 +176,8 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
|
||||||
return getRoutedIntent(ExperienceUpgradeActivity.class, getIntent());
|
return getRoutedIntent(ExperienceUpgradeActivity.class, getIntent());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Intent getWelcomeIntent() {
|
|
||||||
return getRoutedIntent(WelcomeActivity.class, getPushRegistrationIntent());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Intent getPushRegistrationIntent() {
|
private Intent getPushRegistrationIntent() {
|
||||||
return getRoutedIntent(RegistrationActivity.class, getCreateProfileIntent());
|
return RegistrationNavigationActivity.newIntentForNewRegistration(this);
|
||||||
}
|
|
||||||
|
|
||||||
private Intent getCreateProfileIntent() {
|
|
||||||
return getRoutedIntent(CreateProfileActivity.class, getConversationListIntent());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Intent getRoutedIntent(Class<?> destination, @Nullable Intent nextIntent) {
|
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
|
border.setBackgroundResource(hasFocus ? R.drawable.labeled_edit_text_background_active
|
||||||
: R.drawable.labeled_edit_text_background_inactive);
|
: 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;
|
package org.thoughtcrime.securesms.components.registration;
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
|
|
||||||
public class CallMeCountDownView extends androidx.appcompat.widget.AppCompatButton {
|
public class CallMeCountDownView extends androidx.appcompat.widget.AppCompatButton {
|
||||||
|
|
||||||
private int countDown;
|
private int countDown;
|
||||||
|
@Nullable
|
||||||
|
private Listener listener;
|
||||||
|
|
||||||
public CallMeCountDownView(Context context) {
|
public CallMeCountDownView(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
|
@ -44,12 +45,25 @@ public class CallMeCountDownView extends androidx.appcompat.widget.AppCompatButt
|
||||||
countDown--;
|
countDown--;
|
||||||
|
|
||||||
int minutesRemaining = countDown / 60;
|
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));
|
setText(getResources().getString(R.string.RegistrationActivity_call_me_instead_available_in, minutesRemaining, secondsRemaining));
|
||||||
|
|
||||||
|
if (listener != null) {
|
||||||
|
listener.onRemaining(this, countDown);
|
||||||
|
}
|
||||||
|
|
||||||
postDelayed(this::updateCountDown, 1000);
|
postDelayed(this::updateCountDown, 1000);
|
||||||
} else if (countDown == 0) {
|
} else if (countDown == 0) {
|
||||||
setCallEnabled();
|
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;
|
package org.thoughtcrime.securesms.components.registration;
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.TypedArray;
|
|
||||||
import android.graphics.Color;
|
|
||||||
import android.os.Build;
|
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.AttributeSet;
|
||||||
import android.util.TypedValue;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.animation.AlphaAnimation;
|
import android.view.animation.AlphaAnimation;
|
||||||
import android.view.animation.Animation;
|
import android.view.animation.Animation;
|
||||||
|
@ -20,6 +12,11 @@ import android.view.animation.TranslateAnimation;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.TextView;
|
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.Collectors;
|
||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
|
|
||||||
|
@ -28,66 +25,51 @@ import org.thoughtcrime.securesms.R;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
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<TextView> codes = new ArrayList<>(6);
|
||||||
private final List<View> containers = new ArrayList<>(6);
|
private final List<View> containers = new ArrayList<>(6);
|
||||||
|
|
||||||
private OnCodeEnteredListener listener;
|
private OnCodeEnteredListener listener;
|
||||||
private int index = 0;
|
private int index;
|
||||||
|
|
||||||
public VerificationCodeView(Context context) {
|
public VerificationCodeView(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
initialize(context, null);
|
initialize(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public VerificationCodeView(Context context, @Nullable AttributeSet attrs) {
|
public VerificationCodeView(Context context, @Nullable AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
initialize(context, attrs);
|
initialize(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public VerificationCodeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
public VerificationCodeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||||
super(context, attrs, defStyleAttr);
|
super(context, attrs, defStyleAttr);
|
||||||
initialize(context, attrs);
|
initialize(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||||
public VerificationCodeView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
public VerificationCodeView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
super(context, attrs, defStyleAttr, 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);
|
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 {
|
containers.add(findViewById(R.id.container_zero));
|
||||||
TextView separator = findViewById(R.id.separator);
|
containers.add(findViewById(R.id.container_one));
|
||||||
|
containers.add(findViewById(R.id.container_two));
|
||||||
this.codes.add(findViewById(R.id.code_zero));
|
containers.add(findViewById(R.id.container_three));
|
||||||
this.codes.add(findViewById(R.id.code_one));
|
containers.add(findViewById(R.id.container_four));
|
||||||
this.codes.add(findViewById(R.id.code_two));
|
containers.add(findViewById(R.id.container_five));
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainThread
|
@MainThread
|
||||||
|
@ -143,11 +125,11 @@ public class VerificationCodeView extends FrameLayout {
|
||||||
setInactive(containers);
|
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));
|
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);
|
container.setBackgroundResource(R.drawable.labeled_edit_text_background_active);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
package org.thoughtcrime.securesms.components.reminder;
|
package org.thoughtcrime.securesms.components.reminder;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
|
||||||
import android.view.View.OnClickListener;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.RegistrationActivity;
|
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
|
||||||
public class PushRegistrationReminder extends Reminder {
|
public class PushRegistrationReminder extends Reminder {
|
||||||
|
@ -14,13 +12,7 @@ public class PushRegistrationReminder extends Reminder {
|
||||||
super(context.getString(R.string.reminder_header_push_title),
|
super(context.getString(R.string.reminder_header_push_title),
|
||||||
context.getString(R.string.reminder_header_push_text));
|
context.getString(R.string.reminder_header_push_text));
|
||||||
|
|
||||||
final OnClickListener okListener = v -> {
|
setOkListener(v -> context.startActivity(RegistrationNavigationActivity.newIntentForReRegistration(context)));
|
||||||
Intent intent = new Intent(context, RegistrationActivity.class);
|
|
||||||
intent.putExtra(RegistrationActivity.RE_REGISTRATION_EXTRA, true);
|
|
||||||
context.startActivity(intent);
|
|
||||||
};
|
|
||||||
|
|
||||||
setOkListener(okListener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
package org.thoughtcrime.securesms.components.reminder;
|
package org.thoughtcrime.securesms.components.reminder;
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.RegistrationActivity;
|
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
|
||||||
public class UnauthorizedReminder extends Reminder {
|
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));
|
context.getString(R.string.UnauthorizedReminder_this_is_likely_because_you_registered_your_phone_number_with_Signal_on_a_different_device));
|
||||||
|
|
||||||
setOkListener(v -> {
|
setOkListener(v -> {
|
||||||
Intent intent = new Intent(context, RegistrationActivity.class);
|
context.startActivity(RegistrationNavigationActivity.newIntentForReRegistration(context));
|
||||||
intent.putExtra(RegistrationActivity.RE_REGISTRATION_EXTRA, true);
|
|
||||||
context.startActivity(intent);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -93,7 +93,6 @@ import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
|
||||||
import org.thoughtcrime.securesms.PromptMmsActivity;
|
import org.thoughtcrime.securesms.PromptMmsActivity;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.RecipientPreferenceActivity;
|
import org.thoughtcrime.securesms.RecipientPreferenceActivity;
|
||||||
import org.thoughtcrime.securesms.RegistrationActivity;
|
|
||||||
import org.thoughtcrime.securesms.ShortcutLauncherActivity;
|
import org.thoughtcrime.securesms.ShortcutLauncherActivity;
|
||||||
import org.thoughtcrime.securesms.TransportOption;
|
import org.thoughtcrime.securesms.TransportOption;
|
||||||
import org.thoughtcrime.securesms.VerifyIdentityActivity;
|
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.RecipientExporter;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
|
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity;
|
||||||
import org.thoughtcrime.securesms.search.model.MessageResult;
|
import org.thoughtcrime.securesms.search.model.MessageResult;
|
||||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||||
|
@ -929,9 +929,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleRegisterForSignal() {
|
private void handleRegisterForSignal() {
|
||||||
Intent intent = new Intent(this, RegistrationActivity.class);
|
startActivity(RegistrationNavigationActivity.newIntentForReRegistration(this));
|
||||||
intent.putExtra(RegistrationActivity.RE_REGISTRATION_EXTRA, true);
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleInviteLink() {
|
private void handleInviteLink() {
|
||||||
|
|
|
@ -9,23 +9,24 @@ import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.ContactsContract;
|
import android.provider.ContactsContract;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.preference.CheckBoxPreference;
|
import androidx.preference.CheckBoxPreference;
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.google.firebase.iid.FirebaseInstanceId;
|
import com.google.firebase.iid.FirebaseInstanceId;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
|
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
|
||||||
import org.thoughtcrime.securesms.LogSubmitActivity;
|
import org.thoughtcrime.securesms.LogSubmitActivity;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.RegistrationActivity;
|
|
||||||
import org.thoughtcrime.securesms.contacts.ContactAccessor;
|
import org.thoughtcrime.securesms.contacts.ContactAccessor;
|
||||||
import org.thoughtcrime.securesms.contacts.ContactIdentityManager;
|
import org.thoughtcrime.securesms.contacts.ContactIdentityManager;
|
||||||
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.push.AccountManagerFactory;
|
import org.thoughtcrime.securesms.push.AccountManagerFactory;
|
||||||
|
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
@ -220,12 +221,7 @@ public class AdvancedPreferenceFragment extends CorrectedPreferenceFragment {
|
||||||
});
|
});
|
||||||
builder.show();
|
builder.show();
|
||||||
} else {
|
} else {
|
||||||
Intent nextIntent = new Intent(getActivity(), ApplicationPreferencesActivity.class);
|
startActivity(RegistrationNavigationActivity.newIntentForReRegistration(requireContext()));
|
||||||
|
|
||||||
Intent intent = new Intent(getActivity(), RegistrationActivity.class);
|
|
||||||
intent.putExtra(RegistrationActivity.RE_REGISTRATION_EXTRA, true);
|
|
||||||
intent.putExtra("next_intent", nextIntent);
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
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 {
|
dependencyVerification {
|
||||||
verify = [
|
verify = [
|
||||||
'androidx.activity:activity:0d6bafb56a72da893f3990ca5d819214d047f5f6b5c5f822ed97971c05eeb85a',
|
'androidx.activity:activity:d1bc9842455c2e534415d88c44df4d52413b478db9093a1ba36324f705f44c3d',
|
||||||
'androidx.annotation:annotation:d38d63edb30f1467818d50aaf05f8a692dea8b31392a049bfa991b159ad5b692',
|
'androidx.annotation:annotation:d38d63edb30f1467818d50aaf05f8a692dea8b31392a049bfa991b159ad5b692',
|
||||||
'androidx.appcompat:appcompat-resources:53c0a33d07c4bab48d4c8169bf30053aa14965af4a775b56092a9fc7079802b1',
|
'androidx.appcompat:appcompat-resources:53c0a33d07c4bab48d4c8169bf30053aa14965af4a775b56092a9fc7079802b1',
|
||||||
'androidx.appcompat:appcompat:49ad229add44f822fcb3c8405c3fddbd72660da6a839ce29e13158f5980514fd',
|
'androidx.appcompat:appcompat:49ad229add44f822fcb3c8405c3fddbd72660da6a839ce29e13158f5980514fd',
|
||||||
'androidx.arch.core:core-common:fe1237bf029d063e7f29fe39aeaf73ef74c8b0a3658486fc29d3c54326653889',
|
'androidx.arch.core:core-common:fe1237bf029d063e7f29fe39aeaf73ef74c8b0a3658486fc29d3c54326653889',
|
||||||
'androidx.arch.core:core-runtime:87e65fc767c712b437649c7cee2431ebb4bed6daef82e501d4125b3ed3f65f8e',
|
'androidx.arch.core:core-runtime:dd77615bd3dd275afb11b62df25bae46b10b4a117cd37943af45bdcbf8755852',
|
||||||
'androidx.asynclayoutinflater:asynclayoutinflater:f7eab60c57addd94bb06275832fe7600611beaaae1a1ec597c231956faf96c8b',
|
'androidx.asynclayoutinflater:asynclayoutinflater:f7eab60c57addd94bb06275832fe7600611beaaae1a1ec597c231956faf96c8b',
|
||||||
'androidx.camera:camera-camera2:b7897230aec96365d675712c92f5edcb8b464badfd61788c8f956ec2d6e49bfe',
|
'androidx.camera:camera-camera2:b7897230aec96365d675712c92f5edcb8b464badfd61788c8f956ec2d6e49bfe',
|
||||||
'androidx.camera:camera-core:e1c70de55600a0caf826eb4f8a75c96c5ff8f0b626bf08413d31e80ffa55f8ba',
|
'androidx.camera:camera-core:e1c70de55600a0caf826eb4f8a75c96c5ff8f0b626bf08413d31e80ffa55f8ba',
|
||||||
|
@ -17,13 +17,13 @@ dependencyVerification {
|
||||||
'androidx.constraintlayout:constraintlayout-solver:965c177e64fbd81bd1d27b402b66ef9d7bc7b5cb5f718044bf7a453abc542045',
|
'androidx.constraintlayout:constraintlayout-solver:965c177e64fbd81bd1d27b402b66ef9d7bc7b5cb5f718044bf7a453abc542045',
|
||||||
'androidx.constraintlayout:constraintlayout:5ff864def9d41cd04e08348d69591143bae3ceff4284cf8608bceb98c36ac830',
|
'androidx.constraintlayout:constraintlayout:5ff864def9d41cd04e08348d69591143bae3ceff4284cf8608bceb98c36ac830',
|
||||||
'androidx.coordinatorlayout:coordinatorlayout:e508c695489493374d942bf7b4ee02abf7571d25aac4c622e57d6cd5cd29eb73',
|
'androidx.coordinatorlayout:coordinatorlayout:e508c695489493374d942bf7b4ee02abf7571d25aac4c622e57d6cd5cd29eb73',
|
||||||
'androidx.core:core:45c7a50ad1f366e62db496d8cef7730d5ee1681215007d1a19e6b6d800a12842',
|
'androidx.core:core:76c7cfbe596fe3c09a6983bf1c89e889299c08ac9a3b52ce5182a088d056647e',
|
||||||
'androidx.cursoradapter:cursoradapter:a81c8fe78815fa47df5b749deb52727ad11f9397da58b16017f4eb2c11e28564',
|
'androidx.cursoradapter:cursoradapter:a81c8fe78815fa47df5b749deb52727ad11f9397da58b16017f4eb2c11e28564',
|
||||||
'androidx.customview:customview:20e5b8f6526a34595a604f56718da81167c0b40a7a94a57daa355663f2594df2',
|
'androidx.customview:customview:20e5b8f6526a34595a604f56718da81167c0b40a7a94a57daa355663f2594df2',
|
||||||
'androidx.documentfile:documentfile:865a061ef2fad16522f8433536b8d47208c46ff7c7745197dfa1eeb481869487',
|
'androidx.documentfile:documentfile:865a061ef2fad16522f8433536b8d47208c46ff7c7745197dfa1eeb481869487',
|
||||||
'androidx.drawerlayout:drawerlayout:9402442cdc5a43cf62fb14f8cf98c63342d4d9d9b805c8033c6cf7e802749ac1',
|
'androidx.drawerlayout:drawerlayout:9402442cdc5a43cf62fb14f8cf98c63342d4d9d9b805c8033c6cf7e802749ac1',
|
||||||
'androidx.exifinterface:exifinterface:ee48be10aab8f54efff4c14b77d11e10b9eeee4379d5ef6bf297a2923c55cc11',
|
'androidx.exifinterface:exifinterface:ee48be10aab8f54efff4c14b77d11e10b9eeee4379d5ef6bf297a2923c55cc11',
|
||||||
'androidx.fragment:fragment:9656d81c472b5142bbc3471ef7259fbc93905dc38e823c63a99e48819881b6e7',
|
'androidx.fragment:fragment:a14c8b8f2153f128e800fbd266a6beab1c283982a29ec570d2cc05d307d81496',
|
||||||
'androidx.gridlayout:gridlayout:a7e5dc6f39dbc3dc6ac6d57b02a9c6fd792e80f0e45ddb3bb08e8f03d23c8755',
|
'androidx.gridlayout:gridlayout:a7e5dc6f39dbc3dc6ac6d57b02a9c6fd792e80f0e45ddb3bb08e8f03d23c8755',
|
||||||
'androidx.interpolator:interpolator:33193135a64fe21fa2c35eec6688f1a76e512606c0fc83dc1b689e37add7732a',
|
'androidx.interpolator:interpolator:33193135a64fe21fa2c35eec6688f1a76e512606c0fc83dc1b689e37add7732a',
|
||||||
'androidx.legacy:legacy-preference-v14:d6d11913e56b8f2d14fd560bd1ad6d7fd5624a15dd4ec073b2d9188205f86280',
|
'androidx.legacy:legacy-preference-v14:d6d11913e56b8f2d14fd560bd1ad6d7fd5624a15dd4ec073b2d9188205f86280',
|
||||||
|
@ -31,29 +31,34 @@ dependencyVerification {
|
||||||
'androidx.legacy:legacy-support-core-utils:a7edcf01d5b52b3034073027bc4775b78a4764bb6202bb91d61c829add8dd1c7',
|
'androidx.legacy:legacy-support-core-utils:a7edcf01d5b52b3034073027bc4775b78a4764bb6202bb91d61c829add8dd1c7',
|
||||||
'androidx.legacy:legacy-support-v13:65f5fcb57644d381d471a00fdf50f90b808be6b48a8ae57fb4ea39b7da8cca86',
|
'androidx.legacy:legacy-support-v13:65f5fcb57644d381d471a00fdf50f90b808be6b48a8ae57fb4ea39b7da8cca86',
|
||||||
'androidx.legacy:legacy-support-v4:78fec1485f0f388a4749022dd51416857127cd2544ae1c3fd0b16589055480b0',
|
'androidx.legacy:legacy-support-v4:78fec1485f0f388a4749022dd51416857127cd2544ae1c3fd0b16589055480b0',
|
||||||
'androidx.lifecycle:lifecycle-common-java8:9edc2d4f589656d470ef03b9c6ece62d335971294b033ec7d9ceb6e361e9aafa',
|
'androidx.lifecycle:lifecycle-common-java8:a1ec63c1bb973443cb731d78ec336c5e20e7ee35c89cbb32d36f92c55bb02542',
|
||||||
'androidx.lifecycle:lifecycle-common:76db6be533bd730fb361c2feb12a2c26d9952824746847da82601ef81f082643',
|
'androidx.lifecycle:lifecycle-common:63898dabf7cfe5ec5d7ed8b8c2564c1427be876e1496ead95c2703cf59d3734b',
|
||||||
'androidx.lifecycle:lifecycle-extensions:8d4072201b6231d67e4192d608d46b1f5c920845106c9831632c2e3ffe706117',
|
'androidx.lifecycle:lifecycle-extensions:bd53c64b038585215b4959c1a388437a3ad525608a31c58e4283c3e371727d4d',
|
||||||
'androidx.lifecycle:lifecycle-livedata-core:fde334ec7e22744c0f5bfe7caf1a84c9d717327044400577bdf9bd921ec4f7bc',
|
'androidx.lifecycle:lifecycle-livedata-core:6df2bcbf3be50a5fa29e9aa09d39437f2d61d17c2c46ef618d65bbac4a4a99fc',
|
||||||
'androidx.lifecycle:lifecycle-livedata:c82609ced8c498f0a701a30fb6771bb7480860daee84d82e0a81ee86edf7ba39',
|
'androidx.lifecycle:lifecycle-livedata:242e446bed3db36f0df0aab0cb7f91060bd2dab7adcad1117adf54e724cd1d26',
|
||||||
'androidx.lifecycle:lifecycle-process:d8ff6fd844559743050c9ae010a6df230f2a3dbdf3e14498316f30bd8df836b5',
|
'androidx.lifecycle:lifecycle-process:8cddd0c7f4927bbf71fb71fca000786df82cc597c99463d6916ccbe4a205a9ac',
|
||||||
'androidx.lifecycle:lifecycle-runtime:7e6d414d03bb184f3015dacc6233eeaded45fa23f0cf4c1f6d3395d6495fa41c',
|
'androidx.lifecycle:lifecycle-runtime:e5173897b965e870651e83d9d5af1742d3f532d58863223a390ce3a194c8312b',
|
||||||
'androidx.lifecycle:lifecycle-service:cb2b15bb0cf14134e953ed8ead96f94265018643f519367d51fd837f7311e9f8',
|
'androidx.lifecycle:lifecycle-service:23516745f34f16ff7850bb1eadd55cf193dd789cba428de4bca120433e3bfd69',
|
||||||
'androidx.lifecycle:lifecycle-viewmodel:9f2efb59328027fa9f0c413d4d5910aab68d149b139ca8ce432135105b74833a',
|
'androidx.lifecycle:lifecycle-viewmodel-savedstate:f503b53f50c4e6c1f9a3d698c4733df6e7a44049fe477ad0b85cc2f460401fbc',
|
||||||
|
'androidx.lifecycle:lifecycle-viewmodel:7725715491963440ee483e46526cd4f83af1c758e072e97b3eab2115c6f4db35',
|
||||||
'androidx.loader:loader:11f735cb3b55c458d470bed9e25254375b518b4b1bad6926783a7026db0f5025',
|
'androidx.loader:loader:11f735cb3b55c458d470bed9e25254375b518b4b1bad6926783a7026db0f5025',
|
||||||
'androidx.localbroadcastmanager:localbroadcastmanager:e71c328ceef5c4a7d76f2d86df1b65d65fe2acf868b1a4efd84a3f34336186d8',
|
'androidx.localbroadcastmanager:localbroadcastmanager:e71c328ceef5c4a7d76f2d86df1b65d65fe2acf868b1a4efd84a3f34336186d8',
|
||||||
'androidx.media:media:b23b527b2bac870c4a7451e6982d7132e413e88d7f27dbeb1fc7640a720cd9ee',
|
'androidx.media:media:b23b527b2bac870c4a7451e6982d7132e413e88d7f27dbeb1fc7640a720cd9ee',
|
||||||
'androidx.multidex:multidex:42dd32ff9f97f85771b82a20003a8d70f68ab7b4ba328964312ce0732693db09',
|
'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.preference:preference:ea9fde25606eb456210ffe9f7e51048abd776b55a34c0cc6608282b5699122d1',
|
||||||
'androidx.print:print:1d5c7f3135a1bba661fc373fd72e11eb0a4adbb3396787826dd8e4190d5d9edd',
|
'androidx.print:print:1d5c7f3135a1bba661fc373fd72e11eb0a4adbb3396787826dd8e4190d5d9edd',
|
||||||
'androidx.recyclerview:recyclerview:06956fb1ac014027ca9d2b40469a4b42aa61b4957bb11848e1ff352701ab4548',
|
'androidx.recyclerview:recyclerview:06956fb1ac014027ca9d2b40469a4b42aa61b4957bb11848e1ff352701ab4548',
|
||||||
'androidx.savedstate:savedstate:115ac7313095b2d159565d2bc851a7722e43fc00347fc828214ff8917799b5f0',
|
'androidx.savedstate:savedstate:2510a5619c37579c9ce1a04574faaf323cd0ffe2fc4e20fa8f8f01e5bb402e83',
|
||||||
'androidx.slidingpanelayout:slidingpanelayout:76bffb7cefbf780794d8817002dad1562f3e27c0a9f746d62401c8edb30aeede',
|
'androidx.slidingpanelayout:slidingpanelayout:76bffb7cefbf780794d8817002dad1562f3e27c0a9f746d62401c8edb30aeede',
|
||||||
'androidx.swiperefreshlayout:swiperefreshlayout:9761b3a809c9b093fd06a3c4bbc645756dec0e95b5c9da419bc9f2a3f3026e8d',
|
'androidx.swiperefreshlayout:swiperefreshlayout:9761b3a809c9b093fd06a3c4bbc645756dec0e95b5c9da419bc9f2a3f3026e8d',
|
||||||
'androidx.transition:transition:a00a0f763f401abcecda9b0eafcb738929c5801b111a9a414b81a193d0f4008d',
|
'androidx.transition:transition:c374bef04f01580ba76447e759ea560079727779ff882ad55735fd445edca8b4',
|
||||||
'androidx.vectordrawable:vectordrawable-animated:f1613c47f1e6d2cd02ec9a42925f1a964fa63d1d028d34d884364cc3b9ffcb8f',
|
'androidx.vectordrawable:vectordrawable-animated:f1613c47f1e6d2cd02ec9a42925f1a964fa63d1d028d34d884364cc3b9ffcb8f',
|
||||||
'androidx.vectordrawable:vectordrawable:b632152304edb506bf7eacb329ef41e43b80164bf5be4c7bb132a249a65cbc26',
|
'androidx.vectordrawable:vectordrawable:b632152304edb506bf7eacb329ef41e43b80164bf5be4c7bb132a249a65cbc26',
|
||||||
'androidx.versionedparcelable:versionedparcelable:948c751f6352d4c0f93f15fa1bf506c59083bc7754264dd9a325a6da0e2eec05',
|
'androidx.versionedparcelable:versionedparcelable:9a1d77140ac222b7866b5054ee7d159bc1800987ed2d46dd6afdd145abb710c1',
|
||||||
'androidx.viewpager:viewpager:147af4e14a1984010d8f155e5e19d781f03c1d70dfed02a8e0d18428b8fc8682',
|
'androidx.viewpager:viewpager:147af4e14a1984010d8f155e5e19d781f03c1d70dfed02a8e0d18428b8fc8682',
|
||||||
'cn.carbswang.android:NumberPickerView:18b3c316d62c7c277978a8d4ed57a5b8f4e943762264960f579a8a549c756729',
|
'cn.carbswang.android:NumberPickerView:18b3c316d62c7c277978a8d4ed57a5b8f4e943762264960f579a8a549c756729',
|
||||||
'com.airbnb.android:lottie:6819ff968eb768096133c7873d63351705fd4ac424a0917d86c4145f5035097d',
|
'com.airbnb.android:lottie:6819ff968eb768096133c7873d63351705fd4ac424a0917d86c4145f5035097d',
|
||||||
|
|
Loading…
Reference in New Issue