diff --git a/jami-android/app/src/main/java/cx/ring/account/HomeAccountCreationFragment.kt b/jami-android/app/src/main/java/cx/ring/account/HomeAccountCreationFragment.kt index 06e567a52a36e5a81912bc33c11412727ee11e54..9a05251d7751aaba7192d09514f967861d706aa0 100644 --- a/jami-android/app/src/main/java/cx/ring/account/HomeAccountCreationFragment.kt +++ b/jami-android/app/src/main/java/cx/ring/account/HomeAccountCreationFragment.kt @@ -98,6 +98,7 @@ class HomeAccountCreationFragment : override fun onDestroyView() { super.onDestroyView() + mCompositeDisposable.dispose() binding = null } @@ -111,6 +112,10 @@ class HomeAccountCreationFragment : .launch(Intent(requireContext(), LinkDeviceImportSideActivity::class.java)) } + override fun goToBackupAccountLink() { + TODO("Not yet implemented") + } + override fun goToAccountConnect() { replaceFragmentWithSlide(JamiAccountConnectFragment(), JamiAccountConnectFragment.TAG, R.id.wizard_container) } diff --git a/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountWizard.kt b/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountWizard.kt index 907995e47d54fb5141cca9d97be52fd645ee8b79..dcf0f35d9d955ddd1e3bd320ec55a55b1de86a89 100644 --- a/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountWizard.kt +++ b/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountWizard.kt @@ -83,6 +83,8 @@ class TVAccountWizard : BaseActivity<AccountWizardPresenter>(), AccountWizardVie if (!model.managementServer.isNullOrEmpty()) { presenter.initJamiAccountConnect(model, defaultAccountName) mJamsAccount = true + } else if (model.archive != null) { + presenter.initJamiAccountBackup(model, getText(R.string.ring_account_default_name).toString()) } else { presenter.initJamiAccountCreation(model, defaultAccountName) mJamsAccount = false diff --git a/jami-android/app/src/main/java/cx/ring/tv/account/TVHomeAccountCreationFragment.kt b/jami-android/app/src/main/java/cx/ring/tv/account/TVHomeAccountCreationFragment.kt index b3c76a15f48d3a52959a01e3490cdea9286a0ac8..d2cc16b2db42c9865d400575473ed0b51b8d0e0b 100644 --- a/jami-android/app/src/main/java/cx/ring/tv/account/TVHomeAccountCreationFragment.kt +++ b/jami-android/app/src/main/java/cx/ring/tv/account/TVHomeAccountCreationFragment.kt @@ -17,41 +17,85 @@ package cx.ring.tv.account import android.app.Activity +import android.app.Instrumentation import android.content.Intent import android.os.Bundle +import android.util.Log import androidx.fragment.app.activityViewModels import androidx.leanback.widget.GuidanceStylist.Guidance import androidx.leanback.widget.GuidedAction import cx.ring.R import cx.ring.account.AccountCreationViewModel import dagger.hilt.android.AndroidEntryPoint +import androidx.activity.result.contract.ActivityResultContracts +import com.google.android.material.snackbar.Snackbar +import cx.ring.account.HomeAccountCreationFragment +import cx.ring.account.JamiImportBackupFragment +import cx.ring.utils.AndroidFileUtils.getCacheFile +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.disposables.CompositeDisposable import net.jami.account.HomeAccountCreationPresenter import net.jami.account.HomeAccountCreationView import net.jami.model.AccountCreationModel +import java.io.File @AndroidEntryPoint class TVHomeAccountCreationFragment : JamiGuidedStepFragment<HomeAccountCreationPresenter, HomeAccountCreationView>(), HomeAccountCreationView { private val model: AccountCreationViewModel by activityViewModels() + private val mCompositeDisposable = CompositeDisposable() + + private val startForResult = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == Activity.RESULT_OK) { + finish() + } + } + + private val selectFile = registerForActivityResult(ActivityResultContracts.GetContent()) { uri -> + Log.w(TAG, "Selected file: $uri") + if (uri == null) { + return@registerForActivityResult + } + getCacheFile(requireContext(), uri) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ file: File -> + model.model.archive = file + Log.w(TAG, "Loaded file: $file") + presenter.clickOnBackupAccountLink() + }) { e: Throwable -> + Log.e(HomeAccountCreationFragment.Companion.TAG, "Error importing archive", e) + view?.let { view -> + Snackbar.make( + view, + getString(R.string.import_archive_error), + Snackbar.LENGTH_LONG + ).show() + } + }.let { mCompositeDisposable.add(it) } + } + + private fun finish(){ + activity?.finish() + } override fun goToAccountCreation() { add(parentFragmentManager, TVJamiAccountCreationFragment()) } override fun goToAccountLink() { - startActivityForResult(Intent(requireContext(), TVImportWizard::class.java), 56) - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode == 56 && resultCode == Activity.RESULT_OK) { - activity?.finish() - } + startForResult.launch(Intent(requireContext(), TVImportWizard::class.java)) } override fun goToAccountConnect() { add(parentFragmentManager, TVJamiAccountConnectFragment()) } + override fun goToBackupAccountLink() { + Log.w(TAG, "goToBackupAccountLink") + add(parentFragmentManager, TVJamiLinkAccountFragment()) + } + override fun goToSIPAccountCreation() { //TODO } @@ -68,17 +112,16 @@ class TVHomeAccountCreationFragment : JamiGuidedStepFragment<HomeAccountCreation override fun onCreateActions(actions: MutableList<GuidedAction>, savedInstanceState: Bundle?) { val context = requireContext() - addAction(context, actions, LINK_ACCOUNT, getString(R.string.account_link_button), "", true) addAction(context, actions, CREATE_ACCOUNT, getString(R.string.account_create_title), "", true) - addAction( - context, actions, CREATE_JAMS_ACCOUNT, - getString(R.string.account_connect_server_button), "", true - ) + addAction(context, actions, LINK_ACCOUNT, getString(R.string.account_link_device), "", true) + addAction(context, actions, LINK_BACKUP_ACCOUNT, getString(R.string.account_link_archive_button), "", true) + addAction(context, actions, CREATE_JAMS_ACCOUNT, getString(R.string.account_connect_server_button), "", true) } override fun onGuidedActionClicked(action: GuidedAction) { when (action.id) { LINK_ACCOUNT -> presenter.clickOnLinkAccount() + LINK_BACKUP_ACCOUNT -> selectFile.launch("*/*") CREATE_ACCOUNT -> presenter.clickOnCreateAccount() CREATE_JAMS_ACCOUNT -> presenter.clickOnConnectAccount() else -> requireActivity().finish() @@ -86,8 +129,13 @@ class TVHomeAccountCreationFragment : JamiGuidedStepFragment<HomeAccountCreation } companion object { + private const val TAG = "TVHomeAccountCreationFragment" + private const val LINK_ACCOUNT = 0L - private const val CREATE_ACCOUNT = 1L - private const val CREATE_JAMS_ACCOUNT = 2L + private const val LINK_BACKUP_ACCOUNT = 1L + private const val CREATE_ACCOUNT = 2L + private const val CREATE_JAMS_ACCOUNT = 3L + + private const val REQUEST_CODE_IMPORT = 56 } } \ No newline at end of file diff --git a/jami-android/app/src/main/java/cx/ring/tv/account/TVImportWizard.kt b/jami-android/app/src/main/java/cx/ring/tv/account/TVImportWizard.kt index 9dcc10d76af1ca438bcb98d1d1f6819d979a2e42..215a5b74158e7aba8dcf633c75925dd46ec5e131 100644 --- a/jami-android/app/src/main/java/cx/ring/tv/account/TVImportWizard.kt +++ b/jami-android/app/src/main/java/cx/ring/tv/account/TVImportWizard.kt @@ -16,7 +16,6 @@ */ package cx.ring.tv.account -import android.app.Activity import android.os.Bundle import android.util.Log import androidx.appcompat.app.AppCompatActivity diff --git a/jami-android/app/src/main/java/cx/ring/tv/account/TVJamiLinkAccountFragment.kt b/jami-android/app/src/main/java/cx/ring/tv/account/TVJamiLinkAccountFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..2f7dca7bd024721854a824c31f79d4c15500b65b --- /dev/null +++ b/jami-android/app/src/main/java/cx/ring/tv/account/TVJamiLinkAccountFragment.kt @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2004-2025 Savoir-faire Linux Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package cx.ring.tv.account + +import android.graphics.Bitmap +import android.os.Bundle +import android.view.View +import androidx.fragment.app.activityViewModels +import androidx.leanback.widget.GuidanceStylist.Guidance +import androidx.leanback.widget.GuidedAction +import cx.ring.R +import cx.ring.account.AccountCreationViewModel +import dagger.hilt.android.AndroidEntryPoint +import net.jami.account.JamiLinkAccountPresenter +import net.jami.account.JamiLinkAccountView +import net.jami.utils.StringUtils.toPassword + +@AndroidEntryPoint +class TVJamiLinkAccountFragment : JamiGuidedStepFragment<JamiLinkAccountPresenter, JamiLinkAccountView>(), + JamiLinkAccountView { + private val model: AccountCreationViewModel by activityViewModels() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val m = model.model + presenter.init(m) + if (m.photo != null) { + guidanceStylist.iconView?.setImageBitmap(m.photo as Bitmap?) + } + } + + override fun onCreateGuidance(savedInstanceState: Bundle?): Guidance = Guidance( + getString(R.string.account_link_archive_button), + getString(R.string.help_password_enter), + "", + requireContext().getDrawable(R.drawable.ic_contact_picture_fallback) + ) + + override fun onCreateActions(actions: MutableList<GuidedAction>, savedInstanceState: Bundle?) { + val context = requireContext() + addPasswordAction(context, actions, PASSWORD, getString(R.string.account_enter_password), "", "") + addDisabledAction(context, actions, LINK, getString(R.string.import_side_main_title), "", null, true) + } + + override fun onProvideTheme(): Int = R.style.Theme_Ring_Leanback_GuidedStep_First + + override fun onGuidedActionClicked(action: GuidedAction) { + if (action.id == LINK) { + presenter.linkClicked() + } + } + + override fun enableLinkButton(enable: Boolean) { + findActionPositionById(LINK).takeUnless { it == -1 }?.also { position -> + actions[position]?.isEnabled = enable + notifyActionChanged(position) + } + } + + override fun showPin(show: Boolean) {} + + override fun createAccount() { + (activity as TVAccountWizard?)?.createAccount() + } + + override fun onGuidedActionEditedAndProceed(action: GuidedAction): Long { + val password = action.editDescription.toString() + action.description = if (password.isNotEmpty()) toPassword(password) else getString(R.string.account_enter_password) + when (action.id) { + PASSWORD -> { + notifyActionChanged(findActionPositionById(PASSWORD)) + presenter.passwordChanged(password) + } + } + return GuidedAction.ACTION_ID_NEXT + } + + override fun cancel() { + activity?.onBackPressedDispatcher?.onBackPressed() + } + + companion object { + private const val PASSWORD = 1L + private const val LINK = 3L + } +} \ No newline at end of file diff --git a/jami-android/libjamiclient/src/main/kotlin/net/jami/account/AccountWizardPresenter.kt b/jami-android/libjamiclient/src/main/kotlin/net/jami/account/AccountWizardPresenter.kt index 7c071785887b4a62fedfee6614646361667bc8ec..4f4606e91ad89cc7916219fa18bfb32d6de81fdc 100644 --- a/jami-android/libjamiclient/src/main/kotlin/net/jami/account/AccountWizardPresenter.kt +++ b/jami-android/libjamiclient/src/main/kotlin/net/jami/account/AccountWizardPresenter.kt @@ -154,7 +154,7 @@ class AccountWizardPresenter @Inject constructor( mCreatingAccount = false view.displayGenericError() } else { - view.goToProfileCreation() + view.finish(true) } } }) { diff --git a/jami-android/libjamiclient/src/main/kotlin/net/jami/account/HomeAccountCreationPresenter.kt b/jami-android/libjamiclient/src/main/kotlin/net/jami/account/HomeAccountCreationPresenter.kt index 86906ee2f6b842be146047b5e10d5fc6c1a088c1..6b1145817901552fc8d0aac013ec1c65f138d6de 100644 --- a/jami-android/libjamiclient/src/main/kotlin/net/jami/account/HomeAccountCreationPresenter.kt +++ b/jami-android/libjamiclient/src/main/kotlin/net/jami/account/HomeAccountCreationPresenter.kt @@ -28,6 +28,10 @@ class HomeAccountCreationPresenter @Inject constructor() : RootPresenter<HomeAcc view?.goToAccountLink() } + fun clickOnBackupAccountLink() { + view?.goToBackupAccountLink() + } + fun clickOnConnectAccount() { view?.goToAccountConnect() } diff --git a/jami-android/libjamiclient/src/main/kotlin/net/jami/account/HomeAccountCreationView.kt b/jami-android/libjamiclient/src/main/kotlin/net/jami/account/HomeAccountCreationView.kt index c09e99ee83c99348c071890b09f7f6db57d0c4ca..601396e7d422c18d7dcebcbd533f9c458f0f6f51 100644 --- a/jami-android/libjamiclient/src/main/kotlin/net/jami/account/HomeAccountCreationView.kt +++ b/jami-android/libjamiclient/src/main/kotlin/net/jami/account/HomeAccountCreationView.kt @@ -19,6 +19,7 @@ package net.jami.account interface HomeAccountCreationView { fun goToAccountCreation() fun goToAccountLink() + fun goToBackupAccountLink() fun goToAccountConnect() fun goToSIPAccountCreation() } \ No newline at end of file diff --git a/jami-android/libjamiclient/src/main/kotlin/net/jami/account/JamiLinkAccountPresenter.kt b/jami-android/libjamiclient/src/main/kotlin/net/jami/account/JamiLinkAccountPresenter.kt new file mode 100644 index 0000000000000000000000000000000000000000..65197d14345e47ec6b987849fd919efa92419985 --- /dev/null +++ b/jami-android/libjamiclient/src/main/kotlin/net/jami/account/JamiLinkAccountPresenter.kt @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2004-2025 Savoir-faire Linux Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package net.jami.account + +import net.jami.model.AccountCreationModel +import net.jami.mvp.RootPresenter +import javax.inject.Inject + +class JamiLinkAccountPresenter @Inject constructor() : RootPresenter<JamiLinkAccountView>() { + private var mAccountCreationModel: AccountCreationModel? = null + + fun init(accountCreationModel: AccountCreationModel?) { + mAccountCreationModel = accountCreationModel + if (mAccountCreationModel == null) { + view?.cancel() + return + } + val hasArchive = mAccountCreationModel?.archive != null + val view = view + if (view != null) { + view.showPin(!hasArchive) + view.enableLinkButton(hasArchive) + } + } + + fun passwordChanged(password: String) { + mAccountCreationModel?.password = password + showHideLinkButton() + } + + fun pinChanged(pin: String) { + mAccountCreationModel?.pin = pin + showHideLinkButton() + } + + fun resetPin() { + mAccountCreationModel?.pin = "" + showHideLinkButton() + } + + fun linkClicked() { + if (isFormValid) { + view?.createAccount() + } + } + + private fun showHideLinkButton() { + view?.enableLinkButton(isFormValid) + } + + private val isFormValid: Boolean + get() = mAccountCreationModel?.archive != null || mAccountCreationModel!!.pin.isNotEmpty() +} \ No newline at end of file diff --git a/jami-android/libjamiclient/src/main/kotlin/net/jami/account/JamiLinkAccountView.kt b/jami-android/libjamiclient/src/main/kotlin/net/jami/account/JamiLinkAccountView.kt new file mode 100644 index 0000000000000000000000000000000000000000..57082b3f434848aa146a5edb36c533e27f5f7a4a --- /dev/null +++ b/jami-android/libjamiclient/src/main/kotlin/net/jami/account/JamiLinkAccountView.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2004-2025 Savoir-faire Linux Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ +package net.jami.account + +import net.jami.model.AccountCreationModel + +interface JamiLinkAccountView { + fun enableLinkButton(enable: Boolean) + fun showPin(show: Boolean) + fun createAccount() + fun cancel() +} \ No newline at end of file