diff --git a/jami-android/app/src/main/AndroidManifest.xml b/jami-android/app/src/main/AndroidManifest.xml index e999e2ac4e4169d64cadc998f5e41977dfc43dfd..12a488a71ae8c6c497e8d6197c5ff30b0a3a9bb6 100644 --- a/jami-android/app/src/main/AndroidManifest.xml +++ b/jami-android/app/src/main/AndroidManifest.xml @@ -383,6 +383,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize" android:exported="false" android:theme="@style/AppTheme" /> + <activity + android:name=".tv.account.TVExportWizard" + android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize" + android:exported="false" + android:theme="@style/AppTheme" /> <activity android:name=".tv.search.SearchActivity" android:exported="false" diff --git a/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountExport.kt b/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountExport.kt deleted file mode 100644 index dfc36b9dbd589570379204633e134296d5ec96ed..0000000000000000000000000000000000000000 --- a/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountExport.kt +++ /dev/null @@ -1,126 +0,0 @@ -/* - * 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.app.AlertDialog -import android.app.DownloadManager -import android.content.Context -import android.os.Bundle -import android.view.View -import androidx.leanback.widget.GuidanceStylist.Guidance -import androidx.leanback.widget.GuidedAction -import cx.ring.R -import cx.ring.databinding.ItemProgressDialogBinding -import cx.ring.utils.AndroidFileUtils.getMimeType -import dagger.hilt.android.AndroidEntryPoint -import net.jami.account.JamiAccountSummaryPresenter -import net.jami.account.JamiAccountSummaryView -import net.jami.model.Account -import net.jami.model.Profile -import java.io.File - -@AndroidEntryPoint -class TVAccountExport : JamiGuidedStepFragment<JamiAccountSummaryPresenter, JamiAccountSummaryView>(), JamiAccountSummaryView { - private var mWaitDialog: AlertDialog? = null - private lateinit var mIdAccount: String - private var mHasPassword = false - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - presenter.setAccountId(mIdAccount) - } - - override fun onCreateGuidance(savedInstanceState: Bundle?): Guidance { - // Todo: finish to clean up. - val title = getString(R.string.account_export_title) - val breadcrumb = "" - val icon = requireContext().getDrawable(R.drawable.baseline_devices_24) - return Guidance(title, null, breadcrumb, icon) - } - - override fun onCreateActions(actions: MutableList<GuidedAction>, savedInstanceState: Bundle?) { - val context = requireContext() - // Todo: finish to clean up. -// if (mHasPassword) { -// addPasswordAction(context, actions, PASSWORD, getString(R.string.account_enter_password), "", "") -// } else { - addAction(context, actions, ACTION, R.string.account_start_export_button) -// } - } - - override fun onGuidedActionClicked(action: GuidedAction) { - // Todo: finish to clean up. -// presenter.startAccountExport("") - } - - override fun onGuidedActionEditedAndProceed(action: GuidedAction): Long { - // Todo: finish to clean up. -// presenter.startAccountExport(action.description.toString()) - return GuidedAction.ACTION_ID_NEXT - } - - override fun onProvideTheme(): Int { - return R.style.Theme_Ring_Leanback_GuidedStep_First - } - - override fun showExportingProgressDialog() { - mWaitDialog = AlertDialog.Builder(requireActivity()) - .setView(ItemProgressDialogBinding.inflate(layoutInflater).root) - .setTitle(R.string.export_account_wait_title) - .setMessage(R.string.export_account_wait_message) - .setCancelable(false) - .show() - } - - override fun showPasswordProgressDialog() {} - override fun accountChanged(account: Account, profile: Profile) {} - - override fun passwordChangeEnded(accountId: String, ok: Boolean, newPassword: String) {} - override fun displayCompleteArchive(dest: File) { - val downloadManager = requireContext().getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager - downloadManager.addCompletedDownload( - dest.name, - dest.name, - true, - getMimeType(dest.absolutePath), - dest.absolutePath, - dest.length(), - true - ) - } - - override fun gotToImageCapture() {} - override fun askCameraPermission() {} - override fun goToGallery() {} - override fun goToMedia(accountId: String) {} - override fun goToSystem(accountId: String) {} - override fun goToAdvanced(accountId: String) {} - override fun goToAccount(accountId: String) {} - override fun showRevokingProgressDialog() {} - override fun deviceRevocationEnded(device: String, status: Int) {} - override fun updateDeviceList(devices: Map<String, String>, currentDeviceId: String) {} - - companion object { - private const val PASSWORD = 1L - private const val ACTION = 2L - fun createInstance(idAccount: String, hasPassword: Boolean): TVAccountExport = - TVAccountExport().apply { - mIdAccount = idAccount - mHasPassword = hasPassword - } - } -} \ No newline at end of file diff --git a/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountExportStep1Fragment.kt b/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountExportStep1Fragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..6af3d6901c5c9b7a0b6d79922a110af3b3e8cf9b --- /dev/null +++ b/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountExportStep1Fragment.kt @@ -0,0 +1,187 @@ +/* + * 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.os.Bundle +import android.util.Log +import android.view.* +import androidx.fragment.app.Fragment +import com.google.zxing.BarcodeFormat +import com.google.zxing.ResultPoint +import com.journeyapps.barcodescanner.BarcodeCallback +import com.journeyapps.barcodescanner.BarcodeResult +import com.journeyapps.barcodescanner.DefaultDecoderFactory +import cx.ring.R +import cx.ring.databinding.FragmentExportSideStep1Binding +import cx.ring.linkdevice.viewmodel.ExportSideInputError +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class TVAccountExportStep1Fragment : Fragment() { + + private var _binding: FragmentExportSideStep1Binding? = null + private val binding get() = _binding!! + + private enum class InputMode { QR, CODE } + + private var currentMode = InputMode.QR + private var isLoading = false + + interface OnInputCallback { + fun onAuthenticationUri(authenticationUri: String) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View = FragmentExportSideStep1Binding.inflate(inflater, container, false) + .also { _binding = it } + .root + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + initializeBarcode() + showInputQr() + + binding.switchMode.setOnClickListener { + if (currentMode == InputMode.QR) showInputCode() + else showInputQr() + } + + binding.connect.setOnClickListener { + Log.i(TAG, "Connect clicked, authentication uri = ${binding.code.text}") + showInputCode(loading = true) + (requireActivity() as OnInputCallback) + .onAuthenticationUri(binding.code.text.toString()) + } + } + + override fun onResume() { + super.onResume() + if (currentMode == InputMode.QR) { + binding.barcodeScanner.resume() + } + } + + override fun onPause() { + super.onPause() + if (currentMode == InputMode.QR) { + binding.barcodeScanner.pause() + } + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + fun showError(error: ExportSideInputError?) { + if (currentMode == InputMode.QR) showInputQr(error = error) + else showInputCode(error = error) + } + + private fun initializeBarcode() { + binding.barcodeScanner.apply { + barcodeView.decoderFactory = DefaultDecoderFactory(listOf(BarcodeFormat.QR_CODE)) + decodeContinuous(onBarcodeResult) + } + } + + private fun showInputQr(loading: Boolean = false, error: ExportSideInputError? = null) { + binding.barcodeScanner.resume() + + isLoading = loading + currentMode = InputMode.QR + + binding.inputCode.visibility = View.INVISIBLE + binding.inputQr.visibility = View.VISIBLE + binding.switchMode.text = getText(R.string.export_side_step1_switch_to_code) + + if (loading) { + binding.barcodeScanner.visibility = View.INVISIBLE + binding.loadingQr.visibility = View.VISIBLE + binding.errorQr.visibility = View.INVISIBLE + binding.switchMode.isEnabled = false + } else if (error != null) { + initializeBarcode() + binding.barcodeScanner.resume() + binding.errorQr.text = when (error) { + ExportSideInputError.INVALID_INPUT -> + getString(R.string.export_side_step1_error_malformed) + } + binding.switchMode.isEnabled = true + binding.barcodeScanner.visibility = View.VISIBLE + binding.loadingQr.visibility = View.INVISIBLE + binding.errorQr.visibility = View.VISIBLE + binding.errorQr.visibility = View.VISIBLE + } else { + binding.barcodeScanner.resume() + binding.barcodeScanner.visibility = View.VISIBLE + binding.loadingQr.visibility = View.INVISIBLE + binding.errorQr.visibility = View.INVISIBLE + binding.switchMode.isEnabled = true + } + } + + private fun showInputCode(loading: Boolean = false, error: ExportSideInputError? = null) { + binding.barcodeScanner.pause() + + isLoading = loading + currentMode = InputMode.CODE + + binding.inputCode.visibility = View.VISIBLE + binding.inputQr.visibility = View.INVISIBLE + binding.switchMode.text = getText(R.string.export_side_step1_switch_to_qr) + + binding.codeLayout.error = when (error) { + ExportSideInputError.INVALID_INPUT -> getText(R.string.export_side_step1_error_malformed) + else -> null + } + + if (loading) { + binding.loadingCode.visibility = View.VISIBLE + binding.connect.isEnabled = false + binding.code.isEnabled = false + binding.switchMode.isEnabled = false + } else { + binding.connect.isEnabled = true + binding.code.isEnabled = true + binding.switchMode.isEnabled = true + binding.loadingCode.visibility = View.INVISIBLE + } + } + + private val onBarcodeResult = object : BarcodeCallback { + override fun barcodeResult(result: BarcodeResult) { + if (!result.text.startsWith(SCHEME)) return + Log.i(TAG, "QR scanned: ${result.text}") + binding.barcodeScanner.pause() + binding.barcodeScanner.barcodeView.stopDecoding() + showInputQr(loading = true) + (requireActivity() as OnInputCallback).onAuthenticationUri(result.text) + } + + override fun possibleResultPoints(resultPoints: List<ResultPoint>) {} + } + + companion object { + private const val TAG = "TVAccountExportStep1Fragment" + const val SCHEME = "jami-auth://" + } +} diff --git a/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountExportStep2Fragment.kt b/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountExportStep2Fragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..8d6d0b9e6be6fd3898880b31836416f74175d885 --- /dev/null +++ b/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountExportStep2Fragment.kt @@ -0,0 +1,277 @@ +/* + * 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.os.Bundle +import android.util.Log +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.ProgressBar +import androidx.leanback.app.GuidedStepSupportFragment +import androidx.leanback.widget.GuidanceStylist +import androidx.leanback.widget.GuidedAction +import androidx.leanback.widget.GuidedActionsStylist +import cx.ring.R +import cx.ring.views.AvatarDrawable +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class TVAccountExportStep2Fragment : GuidedStepSupportFragment() { + + private var peerAddress: String? = null + private lateinit var progressBar: ProgressBar + + interface OnReviewCallback { + fun onIdentityConfirmation(confirm: Boolean) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + val rootView = super.onCreateView(inflater, container, savedInstanceState) + + // Wrap GuidedStepSupportFragment view inside FrameLayout + val frameLayout = FrameLayout(requireContext()).apply { + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + } + + // Create and add ProgressBar + progressBar = ProgressBar(requireContext()).apply { + visibility = View.GONE + isIndeterminate = true + } + frameLayout.addView(rootView) + frameLayout.addView(progressBar, FrameLayout.LayoutParams( + FrameLayout.LayoutParams.WRAP_CONTENT, + FrameLayout.LayoutParams.WRAP_CONTENT + ).apply { + gravity = Gravity.CENTER + }) + + return frameLayout + } + + override fun onCreateGuidance(savedInstanceState: Bundle?): GuidanceStylist.Guidance { + val id = arguments?.getString(ARG_ACCOUNT_ID) ?: "" + val name = arguments?.getString(ARG_REGISTERED_NAME) ?: id + Log.d(TAG, "onCreateGuidance: id=$id, name=$name") + + return GuidanceStylist.Guidance( + getString(R.string.export_side_main_title), + id, + null, + AvatarDrawable.Builder() + .withId(id) + .withName(name) + .withCircleCrop(true) + .build(requireContext()) + .apply { + setInSize(resources.getDimensionPixelSize(R.dimen.tv_avatar_size)) + } + ) + } + + override fun onCreateActionsStylist(): GuidedActionsStylist { + return TVGuidedActionsStylist() + } + + override fun onCreateActions(actions: MutableList<GuidedAction>, savedInstanceState: Bundle?) { + peerAddress = arguments?.getString(ARG_PEER_ADDRESS) + val isPasswordMode = peerAddress.isNullOrEmpty() + + if (isPasswordMode) { + actions.add( + GuidedAction.Builder(requireContext()) + .id(ACTION_ID_PASSWORD_NOTICE) + .title(getString(R.string.export_side_step2_password)) + .infoOnly(true) + .enabled(false) + .focusable(false) + .build() + ) + } else { + actions.add( + GuidedAction.Builder(requireContext()) + .id(ACTION_ID_PEER_IP) + .title(getString(R.string.export_side_step2_advice_ip_only)) + .description(peerAddress ?: "") + .infoOnly(true) + .enabled(false) + .focusable(false) + .build() + ) + } + + actions.add( + GuidedAction.Builder(requireContext()) + .id(GuidedAction.ACTION_ID_CONTINUE) + .title(R.string.export_side_step2_confirm) + .build() + ) + + actions.add( + GuidedAction.Builder(requireContext()) + .id(GuidedAction.ACTION_ID_CANCEL) + .title(android.R.string.cancel) + .build() + ) + } + + override fun onGuidedActionClicked(action: GuidedAction) { + val callback = activity as? TVExportWizard ?: return + when (action.id) { + GuidedAction.ACTION_ID_CONTINUE -> { + showLoading() + callback.onIdentityConfirmation(true) + } + GuidedAction.ACTION_ID_CANCEL -> { + callback.onIdentityConfirmation(false) + } + } + } + + private fun showLoading() { + actions.forEach { it.isEnabled = false } + setActions(actions) + + (getGuidedActionsStylist() as? GuidedActionsStylist)?.actionsGridView?.apply { + animate() + .alpha(0f) + .setDuration(200) + .withEndAction { visibility = View.INVISIBLE } + .start() + } + + progressBar.visibility = View.VISIBLE + } + + fun update(peerAddress: String?) { + val isPasswordMode = peerAddress.isNullOrEmpty() + val isCurrentlyPasswordMode = findActionPositionById(ACTION_ID_PASSWORD_NOTICE) != -1 + val isCurrentlyIPMode = findActionPositionById(ACTION_ID_PEER_IP) != -1 + + if (isPasswordMode && !isCurrentlyPasswordMode) { + actions.clear() + setupPasswordModeActions() + } else if (!isPasswordMode && !isCurrentlyIPMode) { + actions.clear() + setupIPModeActions(peerAddress) + } else if (!isPasswordMode && isCurrentlyIPMode) { + val ipIndex = findActionPositionById(ACTION_ID_PEER_IP) + if (ipIndex != -1) { + actions[ipIndex].description = peerAddress + notifyActionChanged(ipIndex) + } + } + } + + private fun setupPasswordModeActions() { + actions.add( + GuidedAction.Builder(requireContext()) + .id(ACTION_ID_PASSWORD_NOTICE) + .title(getString(R.string.export_side_step2_password)) + .infoOnly(true) + .enabled(false) + .focusable(false) + .build() + ) + actions.add( + GuidedAction.Builder(requireContext()) + .id(GuidedAction.ACTION_ID_CONTINUE) + .title(R.string.export_side_step2_confirm) + .enabled(true) + .build() + ) + actions.add( + GuidedAction.Builder(requireContext()) + .id(GuidedAction.ACTION_ID_CANCEL) + .title(android.R.string.cancel) + .build() + ) + setActions(actions) + } + + private fun setupIPModeActions(peerAddress: String?) { + actions.add( + GuidedAction.Builder(requireContext()) + .id(ACTION_ID_PEER_IP) + .title(getString(R.string.export_side_step2_advice_ip_only)) + .description(peerAddress ?: "") + .infoOnly(true) + .enabled(false) + .focusable(false) + .build() + ) + actions.add( + GuidedAction.Builder(requireContext()) + .id(GuidedAction.ACTION_ID_CONTINUE) + .title(R.string.export_side_step2_confirm) + .enabled(true) + .build() + ) + actions.add( + GuidedAction.Builder(requireContext()) + .id(GuidedAction.ACTION_ID_CANCEL) + .title(android.R.string.cancel) + .build() + ) + setActions(actions) + } + + override fun onProvideTheme(): Int = R.style.Theme_Ring_Leanback_GuidedStep_First + + companion object { + private const val TAG = "TVAccountExportStep2Fragment" + private const val ACTION_ID_PASSWORD_NOTICE = 1L + private const val ACTION_ID_PEER_IP = 2L + private const val ARG_PEER_ADDRESS = "peerAddress" + private const val ARG_ACCOUNT_ID = "accountId" + private const val ARG_REGISTERED_NAME = "registeredName" + + fun build( + peerAddress: String?, accountId: String, registeredName: String + ): TVAccountExportStep2Fragment { + return TVAccountExportStep2Fragment().apply { + arguments = Bundle().apply { + putString(ARG_PEER_ADDRESS, peerAddress) + putString(ARG_ACCOUNT_ID, accountId) + putString(ARG_REGISTERED_NAME, registeredName) + } + } + } + } +} + +class TVGuidedActionsStylist : GuidedActionsStylist() { + override fun onBindViewHolder(viewHolder: ViewHolder, action: GuidedAction) { + super.onBindViewHolder(viewHolder, action) + + viewHolder.titleView?.apply { + maxLines = 5 + isSingleLine = false + ellipsize = null + } + } +} diff --git a/jami-android/app/src/main/java/cx/ring/tv/account/TVExportWizard.kt b/jami-android/app/src/main/java/cx/ring/tv/account/TVExportWizard.kt new file mode 100644 index 0000000000000000000000000000000000000000..423c34139c700ffd6e28f257aba0a5e981bc1088 --- /dev/null +++ b/jami-android/app/src/main/java/cx/ring/tv/account/TVExportWizard.kt @@ -0,0 +1,156 @@ +/* + * 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.app.Activity +import android.os.Bundle +import android.util.Log +import androidx.appcompat.app.AppCompatActivity +import androidx.leanback.app.GuidedStepSupportFragment +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope +import cx.ring.application.JamiApplication +import cx.ring.linkdevice.view.ExportSideStep3Fragment +import cx.ring.linkdevice.viewmodel.AddDeviceExportState +import cx.ring.linkdevice.viewmodel.ExportSideInputError +import cx.ring.linkdevice.viewmodel.ExportSideViewModel +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch +import net.jami.services.AccountService + +@AndroidEntryPoint +class TVExportWizard : AppCompatActivity(), + TVAccountExportStep1Fragment.OnInputCallback, + TVAccountExportStep2Fragment.OnReviewCallback, + ExportSideStep3Fragment.OnResultCallback { + + private val exportSideViewModel by lazy { + ViewModelProvider(this)[ExportSideViewModel::class.java] + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + JamiApplication.instance?.startDaemon(this) + + if (savedInstanceState == null) { + supportFragmentManager.beginTransaction() + .replace(android.R.id.content, TVAccountExportStep1Fragment()) + .commitNow() + } + + observeUiState() + } + + private fun observeUiState() { + lifecycleScope.launch { + exportSideViewModel.uiState.collect { state -> + try { + when (state) { + is AddDeviceExportState.Init -> showInit(state.error) + is AddDeviceExportState.TokenAvailable -> + throw UnsupportedOperationException() + is AddDeviceExportState.Connecting -> + Log.d(TAG, "State: Connecting") + is AddDeviceExportState.Authenticating -> + showAuthenticating(state.peerAddress) + is AddDeviceExportState.InProgress -> showInProgress() + is AddDeviceExportState.Done -> showDone(state.error) + } + } catch (e: Exception) { + Log.e(TAG, "Error while processing UI state.", e) + } + } + } + } + + private fun showInit(error: ExportSideInputError?) { + (supportFragmentManager.findFragmentById(android.R.id.content) + as? TVAccountExportStep1Fragment)?.showError(error) + } + + private fun showAuthenticating(peerAddress: String?) { + val accountId = intent.getStringExtra(EXTRA_ACCOUNT_ID) ?: "" + val registeredName = intent.getStringExtra(EXTRA_REGISTERED_NAME) ?: accountId + val fragment = supportFragmentManager + .findFragmentById(android.R.id.content) as? TVAccountExportStep2Fragment + + if (fragment == null) { + GuidedStepSupportFragment.addAsRoot( + this, + TVAccountExportStep2Fragment.build(peerAddress, accountId, registeredName), + android.R.id.content + ) + } else { + fragment.update(peerAddress) + } + } + + private fun showInProgress() { + val fragment = ExportSideStep3Fragment() + supportFragmentManager.beginTransaction() + .replace(android.R.id.content, fragment) + .commitNow() + + fragment.showLoading() + } + + private fun showDone(error: AccountService.AuthError?) { + val fragment = ExportSideStep3Fragment() + supportFragmentManager.beginTransaction() + .replace(android.R.id.content, fragment) + .commitNow() + + if (error != null) { + fragment.showError(error) + } else { + fragment.showDone() + } + } + + override fun onAuthenticationUri(authenticationUri: String) { + exportSideViewModel.onAuthenticationUri(authenticationUri) + } + + override fun onIdentityConfirmation(confirm: Boolean) { + if (!confirm) { + finish(1) + } else { + exportSideViewModel.onIdentityConfirmation() + } + } + + override fun onExit(returnCode: Int) { + finish(returnCode) + } + + private fun finish(returnCode: Int = 0) { + if (returnCode != 0) { + Log.w(TAG, "Account exportation failed.") + exportSideViewModel.onCancel() + } + setResult(if (returnCode == 0) Activity.RESULT_OK else Activity.RESULT_CANCELED) + finish() + } + + + companion object { + private const val TAG = "TVExportWizard" + const val EXTRA_ACCOUNT_ID = "account_id" + const val EXTRA_REGISTERED_NAME = "registered_name" + } +} diff --git a/jami-android/app/src/main/java/cx/ring/tv/main/MainFragment.kt b/jami-android/app/src/main/java/cx/ring/tv/main/MainFragment.kt index feb4ff5808e4b705162f3ba799592e7793f0d450..ce0f1e3db3df70742f8449f3f25bdf756629431c 100644 --- a/jami-android/app/src/main/java/cx/ring/tv/main/MainFragment.kt +++ b/jami-android/app/src/main/java/cx/ring/tv/main/MainFragment.kt @@ -34,7 +34,7 @@ import androidx.tvprovider.media.tv.ChannelLogoUtils import androidx.tvprovider.media.tv.PreviewProgram import androidx.tvprovider.media.tv.TvContractCompat import cx.ring.R -import cx.ring.tv.account.TVAccountExport +import cx.ring.tv.account.TVExportWizard import cx.ring.tv.account.TVProfileEditingFragment import cx.ring.tv.account.TVShareActivity import cx.ring.tv.call.TVCallActivity @@ -294,12 +294,18 @@ class MainFragment : BaseBrowseFragment<MainPresenter>(), MainView { } } - override fun showExportDialog(pAccountID: String, hasPassword: Boolean) { - val wizard: GuidedStepSupportFragment = - TVAccountExport.createInstance(pAccountID, hasPassword) - GuidedStepSupportFragment.add(parentFragmentManager, wizard, R.id.main_browse_fragment) + override fun showExportDialog(accountId: String, registeredName: String?) { + Log.d(TAG, "showExportDialog called with accountId: $accountId, name: $registeredName") + + val intent = Intent(requireContext(), TVExportWizard::class.java).apply { + putExtra(TVExportWizard.EXTRA_ACCOUNT_ID, accountId) + putExtra(TVExportWizard.EXTRA_REGISTERED_NAME, registeredName) + } + + startActivity(intent) } + override fun showProfileEditing() { GuidedStepSupportFragment.add( parentFragmentManager, diff --git a/jami-android/app/src/main/java/cx/ring/tv/main/MainPresenter.kt b/jami-android/app/src/main/java/cx/ring/tv/main/MainPresenter.kt index e3eb2547cabe4eb34fb193b0c343a6f18b82af6a..6c9b0f91ff88814bd1814927235de9c62f467b7c 100644 --- a/jami-android/app/src/main/java/cx/ring/tv/main/MainPresenter.kt +++ b/jami-android/app/src/main/java/cx/ring/tv/main/MainPresenter.kt @@ -62,7 +62,7 @@ class MainPresenter @Inject constructor( fun onExportClicked() { val account = accountService.currentAccount ?: return - view?.showExportDialog(account.accountId, account.hasPassword()) + view?.showExportDialog(account.accountId, account.displayUsername) } fun onEditProfileClicked() { diff --git a/jami-android/app/src/main/java/cx/ring/tv/main/MainView.kt b/jami-android/app/src/main/java/cx/ring/tv/main/MainView.kt index 7957b02b75f3e7c188153af3e88712c3f3206e40..3af1b40d03586e1395fcde6e937e33e3755b103b 100644 --- a/jami-android/app/src/main/java/cx/ring/tv/main/MainView.kt +++ b/jami-android/app/src/main/java/cx/ring/tv/main/MainView.kt @@ -26,7 +26,7 @@ interface MainView { fun showContactRequests(contactRequests: List<Conversation>) fun callContact(accountID: String, ringID: String) fun displayAccountInfo(viewModel: HomeNavigationViewModel) - fun showExportDialog(pAccountID: String, hasPassword: Boolean) + fun showExportDialog(accountId: String, registeredName: String?) fun showProfileEditing() fun showAccountShare() fun showSettings()