From a5f28360ec0933d9007704db7605d070699e8fb4 Mon Sep 17 00:00:00 2001
From: Taras Smakula <tarassmakula@gmail.com>
Date: Tue, 12 Sep 2023 16:01:57 +0300
Subject: [PATCH] Add possibility to decrypt with raw key

---
 .../feature/log_in/EnterPassPhraseDialog.kt   |  71 -----------
 .../log_in/recovery/EnterPassPhraseDialog.kt  |  80 ++++++++++++
 .../recovery/EnterPassPhraseDialogListener.kt |  12 ++
 .../log_in/stages/LogInStagesFragment.kt      |  12 +-
 .../restore/RestoreBackupDataSource.kt        |  11 ++
 .../res/layout/dialog_enter_passphrase.xml    | 115 ++++++++----------
 auth/src/main/res/values/strings.xml          |   5 +-
 7 files changed, 164 insertions(+), 142 deletions(-)
 delete mode 100644 auth/src/main/java/org/futo/circles/auth/feature/log_in/EnterPassPhraseDialog.kt
 create mode 100644 auth/src/main/java/org/futo/circles/auth/feature/log_in/recovery/EnterPassPhraseDialog.kt
 create mode 100644 auth/src/main/java/org/futo/circles/auth/feature/log_in/recovery/EnterPassPhraseDialogListener.kt

diff --git a/auth/src/main/java/org/futo/circles/auth/feature/log_in/EnterPassPhraseDialog.kt b/auth/src/main/java/org/futo/circles/auth/feature/log_in/EnterPassPhraseDialog.kt
deleted file mode 100644
index e7f5f0514..000000000
--- a/auth/src/main/java/org/futo/circles/auth/feature/log_in/EnterPassPhraseDialog.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-package org.futo.circles.auth.feature.log_in
-
-import android.content.Context
-import android.net.Uri
-import android.os.Bundle
-import android.view.LayoutInflater
-import androidx.appcompat.app.AppCompatDialog
-import androidx.core.widget.doAfterTextChanged
-import org.futo.circles.auth.databinding.DialogEnterPassphraseBinding
-import org.futo.circles.core.extensions.getFilename
-import org.futo.circles.core.extensions.getText
-import org.futo.circles.core.extensions.gone
-import org.futo.circles.core.extensions.visible
-
-interface EnterPassPhraseDialogListener {
-    fun onRestoreBackup(passphrase: String)
-    fun onRestoreBackup(uri: Uri)
-    fun onDoNotRestore()
-    fun onSelectFileClicked()
-}
-
-class EnterPassPhraseDialog(context: Context, private val listener: EnterPassPhraseDialogListener) :
-    AppCompatDialog(context) {
-
-    private val binding = DialogEnterPassphraseBinding.inflate(LayoutInflater.from(context))
-
-    private var selectedFileUri: Uri? = null
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        setContentView(binding.root)
-        setCancelable(false)
-
-        with(binding) {
-            fileNameGroup.gone()
-            btnCancel.setOnClickListener {
-                listener.onDoNotRestore()
-                dismiss()
-            }
-            btnRestore.setOnClickListener {
-                selectedFileUri?.let {
-                    listener.onRestoreBackup(it)
-                } ?: listener.onRestoreBackup(tilPassphrase.getText())
-                dismiss()
-            }
-            tilPassphrase.editText?.doAfterTextChanged { handleRestoreButtonEnabled() }
-            ivRemoveFile.setOnClickListener {
-                selectedFileUri = null
-                binding.passPhraseGroup.visible()
-                binding.fileNameGroup.gone()
-                handleRestoreButtonEnabled()
-            }
-            btnUploadFile.setOnClickListener {
-                listener.onSelectFileClicked()
-            }
-        }
-    }
-
-    fun selectFile(uri: Uri) {
-        selectedFileUri = uri
-        binding.tvFileName.text = uri.getFilename(context) ?: uri.toString()
-        binding.passPhraseGroup.gone()
-        binding.fileNameGroup.visible()
-        handleRestoreButtonEnabled()
-    }
-
-    private fun handleRestoreButtonEnabled() {
-        binding.btnRestore.isEnabled =
-            binding.tilPassphrase.getText().isNotEmpty() || selectedFileUri != null
-    }
-}
\ No newline at end of file
diff --git a/auth/src/main/java/org/futo/circles/auth/feature/log_in/recovery/EnterPassPhraseDialog.kt b/auth/src/main/java/org/futo/circles/auth/feature/log_in/recovery/EnterPassPhraseDialog.kt
new file mode 100644
index 000000000..25f0035fe
--- /dev/null
+++ b/auth/src/main/java/org/futo/circles/auth/feature/log_in/recovery/EnterPassPhraseDialog.kt
@@ -0,0 +1,80 @@
+package org.futo.circles.auth.feature.log_in.recovery
+
+import android.app.ActionBar
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.InsetDrawable
+import android.net.Uri
+import android.os.Bundle
+import android.view.LayoutInflater
+import androidx.appcompat.app.AppCompatDialog
+import androidx.core.widget.doAfterTextChanged
+import org.futo.circles.auth.databinding.DialogEnterPassphraseBinding
+import org.futo.circles.core.extensions.getText
+import org.futo.circles.core.extensions.setIsVisible
+
+class EnterPassPhraseDialog(context: Context, private val listener: EnterPassPhraseDialogListener) :
+    AppCompatDialog(context) {
+
+    private val binding = DialogEnterPassphraseBinding.inflate(LayoutInflater.from(context))
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(binding.root)
+        setCancelable(false)
+        window?.apply {
+            setBackgroundDrawable(InsetDrawable(ColorDrawable(Color.TRANSPARENT), 20))
+            setLayout(ActionBar.LayoutParams.MATCH_PARENT, ActionBar.LayoutParams.WRAP_CONTENT)
+        }
+        with(binding) {
+            recoveryTypeGroup.setOnCheckedChangeListener { _, checkedId ->
+                tilPassphrase.setIsVisible(checkedId == btnPassphrase.id)
+                vRecoveryKey.setIsVisible(checkedId == btnRecoveryKey.id)
+                handleRestoreButtonEnabled()
+            }
+            vRecoveryKey.setup(
+                onUploadFileListener = { listener.onSelectFileClicked() },
+                onInputChanged = { handleRestoreButtonEnabled() }
+            )
+            btnCancel.setOnClickListener {
+                listener.onDoNotRestore()
+                dismiss()
+            }
+            btnRestore.setOnClickListener {
+                restoreBackup()
+                dismiss()
+            }
+            tilPassphrase.editText?.doAfterTextChanged { handleRestoreButtonEnabled() }
+        }
+    }
+
+    fun selectFile(uri: Uri) {
+        binding.vRecoveryKey.selectFile(uri)
+    }
+
+    private fun restoreBackup() {
+        if (isPassphraseTypeSelected()) {
+            listener.onRestoreBackupWithPassphrase(binding.tilPassphrase.getText())
+        } else {
+            val uri = binding.vRecoveryKey.getFileUri()
+            uri?.let {
+                listener.onRestoreBackup(it)
+            } ?: listener.onRestoreBackupWithRawKey(binding.vRecoveryKey.getRawKey() ?: "")
+        }
+    }
+
+    private fun isPassphraseTypeSelected() =
+        binding.recoveryTypeGroup.checkedRadioButtonId == binding.btnPassphrase.id
+
+    private fun handleRestoreButtonEnabled() {
+        binding.btnRestore.isEnabled = if (isPassphraseTypeSelected()) {
+            binding.tilPassphrase.getText().isNotEmpty()
+        } else {
+            val isRawKeyEntered = binding.vRecoveryKey.getRawKey()?.isNotEmpty() == true
+            val isFileKeyEntered = binding.vRecoveryKey.getFileUri() != null
+
+            isRawKeyEntered || isFileKeyEntered
+        }
+    }
+}
\ No newline at end of file
diff --git a/auth/src/main/java/org/futo/circles/auth/feature/log_in/recovery/EnterPassPhraseDialogListener.kt b/auth/src/main/java/org/futo/circles/auth/feature/log_in/recovery/EnterPassPhraseDialogListener.kt
new file mode 100644
index 000000000..9c750f912
--- /dev/null
+++ b/auth/src/main/java/org/futo/circles/auth/feature/log_in/recovery/EnterPassPhraseDialogListener.kt
@@ -0,0 +1,12 @@
+package org.futo.circles.auth.feature.log_in.recovery
+
+import android.net.Uri
+
+interface EnterPassPhraseDialogListener {
+    fun onRestoreBackupWithPassphrase(passphrase: String)
+
+    fun onRestoreBackupWithRawKey(key: String)
+    fun onRestoreBackup(uri: Uri)
+    fun onDoNotRestore()
+    fun onSelectFileClicked()
+}
\ No newline at end of file
diff --git a/auth/src/main/java/org/futo/circles/auth/feature/log_in/stages/LogInStagesFragment.kt b/auth/src/main/java/org/futo/circles/auth/feature/log_in/stages/LogInStagesFragment.kt
index 91ceb1411..7e940c2af 100644
--- a/auth/src/main/java/org/futo/circles/auth/feature/log_in/stages/LogInStagesFragment.kt
+++ b/auth/src/main/java/org/futo/circles/auth/feature/log_in/stages/LogInStagesFragment.kt
@@ -15,8 +15,8 @@ import dagger.hilt.android.AndroidEntryPoint
 import org.futo.circles.auth.R
 import org.futo.circles.auth.base.LoginStageNavigationEvent
 import org.futo.circles.auth.databinding.FragmentLoginStagesBinding
-import org.futo.circles.auth.feature.log_in.EnterPassPhraseDialog
-import org.futo.circles.auth.feature.log_in.EnterPassPhraseDialogListener
+import org.futo.circles.auth.feature.log_in.recovery.EnterPassPhraseDialog
+import org.futo.circles.auth.feature.log_in.recovery.EnterPassPhraseDialogListener
 import org.futo.circles.core.CirclesAppConfig
 import org.futo.circles.core.extensions.navigateSafe
 import org.futo.circles.core.extensions.observeData
@@ -98,8 +98,12 @@ class LogInStagesFragment : Fragment(R.layout.fragment_login_stages),
     private fun showPassPhraseDialog() {
         enterPassPhraseDialog =
             EnterPassPhraseDialog(requireContext(), object : EnterPassPhraseDialogListener {
-                override fun onRestoreBackup(passphrase: String) {
-                    viewModel.restoreBackup(passphrase)
+                override fun onRestoreBackupWithPassphrase(passphrase: String) {
+                    viewModel.restoreBackupWithPassPhrase(passphrase)
+                }
+
+                override fun onRestoreBackupWithRawKey(key: String) {
+                    viewModel.restoreBackupWithRawKey(key)
                 }
 
                 override fun onRestoreBackup(uri: Uri) {
diff --git a/auth/src/main/java/org/futo/circles/auth/feature/pass_phrase/restore/RestoreBackupDataSource.kt b/auth/src/main/java/org/futo/circles/auth/feature/pass_phrase/restore/RestoreBackupDataSource.kt
index 2ffc7bd5f..3d153f77e 100644
--- a/auth/src/main/java/org/futo/circles/auth/feature/pass_phrase/restore/RestoreBackupDataSource.kt
+++ b/auth/src/main/java/org/futo/circles/auth/feature/pass_phrase/restore/RestoreBackupDataSource.kt
@@ -121,6 +121,17 @@ class RestoreBackupDataSource @Inject constructor(
         loadingLiveData.postValue(LoadingData(isLoading = false))
     }
 
+    suspend fun restoreKeysWithRawKey(rawKey: String) {
+        try {
+            val keyData = ssssDataSource.getRecoveryKeyFromFileKey(rawKey, progressObserver)
+            restoreKeysWithRecoveryKey(keyData)
+        } catch (e: Throwable) {
+            loadingLiveData.postValue(LoadingData(isLoading = false))
+            throw e
+        }
+        loadingLiveData.postValue(LoadingData(isLoading = false))
+    }
+
     suspend fun restoreKeysWithRecoveryKey(uri: Uri) {
         try {
             val key = readRecoveryKeyFile(uri)
diff --git a/auth/src/main/res/layout/dialog_enter_passphrase.xml b/auth/src/main/res/layout/dialog_enter_passphrase.xml
index b11b3a0a7..cbb6fc808 100644
--- a/auth/src/main/res/layout/dialog_enter_passphrase.xml
+++ b/auth/src/main/res/layout/dialog_enter_passphrase.xml
@@ -1,7 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <RelativeLayout 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="wrap_content">
 
@@ -23,7 +22,7 @@
                 android:gravity="center"
                 android:lines="1"
                 android:paddingVertical="8dp"
-                android:text="@string/enter_passphrase"
+                android:text="@string/recovery"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toTopOf="parent" />
@@ -51,6 +50,40 @@
                 app:layout_constraintStart_toStartOf="parent"
                 app:layout_constraintTop_toBottomOf="@id/titleDivider" />
 
+            <RadioGroup
+                android:id="@+id/recoveryTypeGroup"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginHorizontal="16dp"
+                android:layout_marginTop="16dp"
+                android:gravity="center"
+                android:orientation="horizontal"
+                android:paddingVertical="4dp"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/tvSubtitle">
+
+                <com.google.android.material.radiobutton.MaterialRadioButton
+                    android:id="@+id/btnPassphrase"
+                    style="@style/body"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="0.5"
+                    android:checked="true"
+                    android:padding="8dp"
+                    android:text="@string/passphrase" />
+
+                <com.google.android.material.radiobutton.MaterialRadioButton
+                    android:id="@+id/btnRecoveryKey"
+                    style="@style/body"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="0.5"
+                    android:padding="8dp"
+                    android:text="@string/recovery_key" />
+
+            </RadioGroup>
+
             <com.google.android.material.textfield.TextInputLayout
                 android:id="@+id/tilPassphrase"
                 style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
@@ -62,7 +95,7 @@
                 android:hint="@string/passphrase"
                 app:layout_constraintEnd_toEndOf="parent"
                 app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toBottomOf="@id/tvSubtitle"
+                app:layout_constraintTop_toBottomOf="@id/recoveryTypeGroup"
                 app:passwordToggleEnabled="true">
 
                 <com.google.android.material.textfield.TextInputEditText
@@ -74,60 +107,24 @@
 
             </com.google.android.material.textfield.TextInputLayout>
 
-            <TextView
-                android:id="@+id/tvOr"
-                style="@style/body"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginTop="16dp"
-                android:text="@string/or"
-                app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toBottomOf="@id/tilPassphrase" />
-
-            <TextView
-                android:id="@+id/tvFileName"
-                style="@style/body"
-                android:layout_width="wrap_content"
+            <org.futo.circles.auth.view.EnterRecoveryKeyView
+                android:id="@+id/vRecoveryKey"
+                android:layout_width="0dp"
                 android:layout_height="wrap_content"
-                android:layout_marginStart="26dp"
+                android:layout_marginStart="16dp"
                 android:layout_marginTop="16dp"
-                android:layout_marginEnd="8dp"
-                android:ellipsize="end"
-                android:lines="1"
-                app:layout_constrainedWidth="true"
-                app:layout_constraintEnd_toStartOf="@id/ivRemoveFile"
-                app:layout_constraintHorizontal_chainStyle="packed"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toBottomOf="@id/tvOr"
-                tools:text="File name.txt" />
-
-            <ImageView
-                android:id="@+id/ivRemoveFile"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
                 android:layout_marginEnd="16dp"
-                android:background="?android:selectableItemBackgroundBorderless"
-                android:clickable="true"
-                android:focusable="true"
-                android:src="@drawable/ic_close"
-                app:layout_constraintBottom_toBottomOf="@id/tvFileName"
+                android:visibility="gone"
                 app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintStart_toEndOf="@id/tvFileName"
-                app:layout_constraintTop_toTopOf="@id/tvFileName"
-                app:tint="@color/blue" />
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/recoveryTypeGroup" />
 
-            <com.google.android.material.button.MaterialButton
-                android:id="@+id/btnUploadFile"
-                style="@style/PostButtonStyle"
-                android:layout_width="wrap_content"
+            <androidx.constraintlayout.widget.Barrier
+                android:id="@+id/barrier"
+                android:layout_width="match_parent"
                 android:layout_height="wrap_content"
-                android:layout_marginTop="16dp"
-                android:text="@string/upload_recovery_key"
-                app:icon="@drawable/ic_file"
-                app:layout_constraintEnd_toEndOf="parent"
-                app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toBottomOf="@id/tvFileName" />
+                app:barrierDirection="bottom"
+                app:constraint_referenced_ids="tilPassphrase,vRecoveryKey" />
 
             <com.google.android.material.button.MaterialButton
                 android:id="@+id/btnRestore"
@@ -153,27 +150,15 @@
                 android:layout_width="0dp"
                 android:layout_height="wrap_content"
                 android:layout_marginStart="16dp"
-                android:layout_marginTop="16dp"
+                android:layout_marginTop="24dp"
                 android:layout_marginEnd="8dp"
                 android:layout_marginBottom="8dp"
                 android:text="@string/cancel"
                 app:layout_constraintBottom_toBottomOf="parent"
                 app:layout_constraintEnd_toStartOf="@id/btnRestore"
                 app:layout_constraintStart_toStartOf="parent"
-                app:layout_constraintTop_toBottomOf="@id/btnUploadFile" />
-
+                app:layout_constraintTop_toBottomOf="@id/barrier" />
 
-            <androidx.constraintlayout.widget.Group
-                android:id="@+id/passPhraseGroup"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                app:constraint_referenced_ids="tvOr,tvSubtitle,tilPassphrase" />
-
-            <androidx.constraintlayout.widget.Group
-                android:id="@+id/fileNameGroup"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                app:constraint_referenced_ids="tvFileName,ivRemoveFile" />
 
         </androidx.constraintlayout.widget.ConstraintLayout>
 
diff --git a/auth/src/main/res/values/strings.xml b/auth/src/main/res/values/strings.xml
index 1ca60b490..08958efe5 100644
--- a/auth/src/main/res/values/strings.xml
+++ b/auth/src/main/res/values/strings.xml
@@ -75,10 +75,11 @@
     <string name="validate_your_token">Validate your token</string>
     <string name="sign_up_token_format">abcd–efgh–1234–5678</string>
     <string name="validate_token">Validate token</string>
-    <string name="enter_passphrase">Enter Passphrase</string>
+    <string name="recovery">Recovery</string>
+    <string name="recovery_key">Recovery Key</string>
     <string name="use_your_recovery_message">Use your Recovery Passphrase or Recovery Key to continue.</string>
     <string name="passphrase">Passphrase</string>
-    <string name="upload_recovery_key">Upload Recovery Key</string>
+    <string name="upload_recovery_key_file">Upload Recovery Key file</string>
     <string name="restore">Restore</string>
     <string name="validate_your_email">Validate your email</string>
     <string name="not_supported_navigation_event">Not supported navigation event</string>
-- 
GitLab