diff --git a/jami-android/app/src/main/AndroidManifest.xml b/jami-android/app/src/main/AndroidManifest.xml index 06f21a7926f9cfe2ff61fda8f3984f8786cf871c..e999e2ac4e4169d64cadc998f5e41977dfc43dfd 100644 --- a/jami-android/app/src/main/AndroidManifest.xml +++ b/jami-android/app/src/main/AndroidManifest.xml @@ -312,7 +312,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. <service android:name=".service.ConnectionService" android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE" - android:exported="true"> + android:exported="true" + tools:targetApi="28"> <intent-filter> <action android:name="android.telecom.ConnectionService" /> </intent-filter> @@ -377,6 +378,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.TVImportWizard" + 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/linkdevice/view/ExportSideStep3Fragment.kt b/jami-android/app/src/main/java/cx/ring/linkdevice/view/ExportSideStep3Fragment.kt index d4c947e0fce7461ef3b76397dfc09c390a9bca02..6f84d0f92d65bc3b932da9fc4eca1754773e9588 100644 --- a/jami-android/app/src/main/java/cx/ring/linkdevice/view/ExportSideStep3Fragment.kt +++ b/jami-android/app/src/main/java/cx/ring/linkdevice/view/ExportSideStep3Fragment.kt @@ -16,7 +16,6 @@ */ package cx.ring.linkdevice.view -import cx.ring.linkdevice.viewmodel.AuthError import android.content.Context import android.os.Bundle import android.util.Log @@ -27,6 +26,7 @@ import android.view.ViewGroup import androidx.core.content.ContextCompat import cx.ring.R import cx.ring.databinding.FragmentExportSideStep3Binding +import net.jami.services.AccountService.AuthError class ExportSideStep3Fragment : Fragment() { private var _binding: FragmentExportSideStep3Binding? = null diff --git a/jami-android/app/src/main/java/cx/ring/linkdevice/view/ImportSideStep1Fragment.kt b/jami-android/app/src/main/java/cx/ring/linkdevice/view/ImportSideStep1Fragment.kt index d51d680cb1e56dfe1cada610c21d8c7082d6e03e..12df4819e18d5e3c23a97fb1207b3c8c2f41c66a 100644 --- a/jami-android/app/src/main/java/cx/ring/linkdevice/view/ImportSideStep1Fragment.kt +++ b/jami-android/app/src/main/java/cx/ring/linkdevice/view/ImportSideStep1Fragment.kt @@ -63,8 +63,8 @@ class ImportSideStep1Fragment : Fragment() { binding.share.setOnClickListener { ActionHelper.shareAuthenticationToken(requireContext(), currentAuthenticationToken) } - } + fun showOutput(token: String) { Log.i(TAG, "Show authentication token : $token") currentAuthenticationToken = token diff --git a/jami-android/app/src/main/java/cx/ring/linkdevice/view/ImportSideStep3Fragment.kt b/jami-android/app/src/main/java/cx/ring/linkdevice/view/ImportSideStep3Fragment.kt index b56bc8d2e33bc74f6d43215705b3c81c24a7a6a1..66945a3f2e080b332adfd2dc5f72ce967f54d145 100644 --- a/jami-android/app/src/main/java/cx/ring/linkdevice/view/ImportSideStep3Fragment.kt +++ b/jami-android/app/src/main/java/cx/ring/linkdevice/view/ImportSideStep3Fragment.kt @@ -16,7 +16,6 @@ */ package cx.ring.linkdevice.view -import cx.ring.linkdevice.viewmodel.AuthError import android.content.Context import android.os.Bundle import android.util.Log @@ -27,6 +26,7 @@ import android.view.ViewGroup import androidx.core.content.ContextCompat import cx.ring.R import cx.ring.databinding.FragmentImportSideStep3Binding +import net.jami.services.AccountService.AuthError class ImportSideStep3Fragment : Fragment() { private var _binding: FragmentImportSideStep3Binding? = null diff --git a/jami-android/app/src/main/java/cx/ring/linkdevice/view/LinkDeviceImportSideActivity.kt b/jami-android/app/src/main/java/cx/ring/linkdevice/view/LinkDeviceImportSideActivity.kt index 993b263ace5f8dd3a2ceb330844c32b8812a99fc..5b998481feb67d7c5eaffebc8cbeda3f4b3c8003 100644 --- a/jami-android/app/src/main/java/cx/ring/linkdevice/view/LinkDeviceImportSideActivity.kt +++ b/jami-android/app/src/main/java/cx/ring/linkdevice/view/LinkDeviceImportSideActivity.kt @@ -97,7 +97,7 @@ class LinkDeviceImportSideActivity : AppCompatActivity(), (adapter as ViewPagerAdapter).importSideStep2 .showAuthentication( it.needPassword, - it.jamiId, + it.id, it.registeredName, it.error ) diff --git a/jami-android/app/src/main/java/cx/ring/linkdevice/viewmodel/ExportSideViewModel.kt b/jami-android/app/src/main/java/cx/ring/linkdevice/viewmodel/ExportSideViewModel.kt index 1bd395d115c4bfc495f67c31854b6de53f28fd17..9a45ee21afc89028d9fe2e2ab1d5dc7e021af4b2 100644 --- a/jami-android/app/src/main/java/cx/ring/linkdevice/viewmodel/ExportSideViewModel.kt +++ b/jami-android/app/src/main/java/cx/ring/linkdevice/viewmodel/ExportSideViewModel.kt @@ -24,6 +24,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import net.jami.services.AccountService +import net.jami.services.AccountService.AuthError import net.jami.services.AccountService.AuthState import net.jami.services.AccountService.AuthResult import net.jami.utils.Log diff --git a/jami-android/app/src/main/java/cx/ring/linkdevice/viewmodel/ImportSideViewModel.kt b/jami-android/app/src/main/java/cx/ring/linkdevice/viewmodel/ImportSideViewModel.kt index 443a6c657c65ebed52a22df52367eb54f8c98c4f..ced4c5adcd37a3ed7a2d2adf821126e51c63d97f 100644 --- a/jami-android/app/src/main/java/cx/ring/linkdevice/viewmodel/ImportSideViewModel.kt +++ b/jami-android/app/src/main/java/cx/ring/linkdevice/viewmodel/ImportSideViewModel.kt @@ -30,6 +30,7 @@ import net.jami.model.Account import net.jami.model.AccountConfig import net.jami.model.ConfigKey import net.jami.services.AccountService +import net.jami.services.AccountService.AuthError import net.jami.utils.Log import javax.inject.Inject import javax.inject.Named @@ -40,9 +41,9 @@ sealed class AddDeviceImportState { data class TokenAvailable(val token: String) : AddDeviceImportState() data object Connecting : AddDeviceImportState() data class Authenticating( - val jamiId: String, + val id: String, val needPassword: Boolean, - val registeredName: String = "", + val registeredName: String? = null, val error: InputError? ) : AddDeviceImportState() @@ -78,9 +79,8 @@ class ImportSideViewModel @Inject constructor( accountService.authResultObservable.filter { it.accountId == account.accountId } } .observeOn(mUiScheduler) - .subscribe { it: AuthResult -> - updateDeviceAuthState(it) - }.apply { compositeDisposable.add(this) } + .subscribe(this::updateDeviceAuthState) + .apply { compositeDisposable.add(this) } } fun onAuthentication(password: String = "") { @@ -109,14 +109,14 @@ class ImportSideViewModel @Inject constructor( val authError = details[IMPORT_AUTH_ERROR_KEY]?.let { InputError.fromString(it) } - val jamiId = details[IMPORT_PEER_ID_KEY] ?: throw IllegalStateException("Jami ID not found") + val peerId = details[IMPORT_PEER_ID_KEY] ?: throw IllegalStateException("Jami ID not found") _tempAccount?.accountId?.apply { - accountService.findRegistrationByAddress(this, "", jamiId) + accountService.findRegistrationByAddress(this, "", peerId) .observeOn(mUiScheduler) .subscribe { registeredName -> Log.d(TAG, "Registered name: ${registeredName.name}") _uiState.value = AddDeviceImportState.Authenticating( - jamiId = jamiId, + id = peerId, needPassword = needPassword, registeredName = registeredName.name, error = authError @@ -124,7 +124,7 @@ class ImportSideViewModel @Inject constructor( }.apply { compositeDisposable.add(this) } } _uiState.value = AddDeviceImportState.Authenticating( - jamiId = jamiId, + id = peerId, needPassword = needPassword, error = authError ) diff --git a/jami-android/app/src/main/java/cx/ring/linkdevice/viewmodel/SharedTypes.kt b/jami-android/app/src/main/java/cx/ring/linkdevice/viewmodel/SharedTypes.kt index 20bea5d36f168728df6f95643889961514e987eb..0908580636087beaca9fa55ad92a122410ac9594 100644 --- a/jami-android/app/src/main/java/cx/ring/linkdevice/viewmodel/SharedTypes.kt +++ b/jami-android/app/src/main/java/cx/ring/linkdevice/viewmodel/SharedTypes.kt @@ -20,29 +20,9 @@ interface AuthStateListener { fun onInitSignal() { // Should not be received since there is nothing to do. throw UnsupportedOperationException() } - fun onTokenAvailableSignal(details: Map<String, String>) fun onConnectingSignal() fun onAuthenticatingSignal(details: Map<String, String>) fun onInProgressSignal() fun onDoneSignal(details: Map<String, String>) } - -enum class AuthError { - NETWORK, - AUTHENTICATION, - TIMEOUT, - CANCELED, - UNKNOWN; - - companion object { - fun fromString(value: String) = when (value) { - "network" -> NETWORK - "auth_error" -> AUTHENTICATION - "timeout" -> TIMEOUT - "canceled" -> CANCELED - "unknown" -> UNKNOWN - else -> UNKNOWN - } - } -} \ No newline at end of file diff --git a/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountImportStep1Fragment.kt b/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountImportStep1Fragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..68bcdefb9561f7909845e77e765e88ae1262049d --- /dev/null +++ b/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountImportStep1Fragment.kt @@ -0,0 +1,83 @@ +/* + * 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.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.graphics.createBitmap +import androidx.fragment.app.Fragment +import cx.ring.R +import cx.ring.databinding.FragmentImportSideStep1Binding +import cx.ring.utils.QRCodeLoaderUtils +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class TVAccountImportStep1Fragment : Fragment() { + + private var _binding: FragmentImportSideStep1Binding? = null + private val binding get() = _binding!! + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = + FragmentImportSideStep1Binding.inflate(inflater, container, false) + .apply { + _binding = this + share.visibility = View.GONE + showLoading() + }.root + + fun showLoading() { + binding.QRContainer.visibility = View.INVISIBLE + binding.loadingContainer.visibility = View.VISIBLE + binding.connecting.text = getText(R.string.import_side_step1_preparing_device) + } + + fun showToken(token: String) { + Log.w(TAG, "showToken: $token") + + QRCodeLoaderUtils.loadQRCodeData( + token, + requireContext().getColor(android.R.color.black), + requireContext().getColor(android.R.color.transparent) + ) { qrCodeData -> + binding.qrImage.setImageBitmap( + createBitmap(qrCodeData.width, qrCodeData.height).apply { + setPixels( + qrCodeData.data, 0, qrCodeData.width, + 0, 0, qrCodeData.width, qrCodeData.height + ) + } + ) + binding.code.text = token + binding.QRContainer.visibility = View.VISIBLE + binding.loadingContainer.visibility = View.INVISIBLE + binding.advice.visibility = View.VISIBLE + } + } + + fun showConnecting() { + binding.QRContainer.visibility = View.INVISIBLE + binding.loadingContainer.visibility = View.VISIBLE + binding.connecting.text = getText(R.string.import_side_step1_connecting) + } + + companion object { + private const val TAG = "TVAccountImportStep1Fragment" + } +} \ No newline at end of file diff --git a/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountImportStep2Fragment.kt b/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountImportStep2Fragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..dfdc1e78be21ef54a38ef691d24bba44f67391f4 --- /dev/null +++ b/jami-android/app/src/main/java/cx/ring/tv/account/TVAccountImportStep2Fragment.kt @@ -0,0 +1,197 @@ +/* + * 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.text.InputType +import android.util.Log +import androidx.leanback.app.GuidedStepSupportFragment +import androidx.leanback.widget.GuidanceStylist +import androidx.leanback.widget.GuidedAction +import cx.ring.R +import cx.ring.views.AvatarDrawable +import dagger.hilt.android.AndroidEntryPoint +import net.jami.services.ContactService +import net.jami.utils.StringUtils +import javax.inject.Inject +import javax.inject.Singleton + + +@AndroidEntryPoint +class TVAccountImportStep2Fragment : GuidedStepSupportFragment() { + + @Inject + @Singleton + lateinit var contactService: ContactService + + private var mPassword: String = "" + + override fun onCreateGuidance(savedInstanceState: Bundle?): GuidanceStylist.Guidance { + val args = arguments ?: return super.onCreateGuidance(savedInstanceState) + val id = args.getString("id") ?: "" + val name = args.getString("registeredName", id) + return GuidanceStylist.Guidance( + getString(R.string.import_side_main_title), + id, + null, + AvatarDrawable.Builder() + .withId(id) + .withName(name) + .withCircleCrop(true) + .build(requireContext()) + .apply { setInSize(resources.getDimensionPixelSize(R.dimen.tv_avatar_size)) } + ) + } + + fun update( + needPassword: Boolean, + id: String, + registeredName: String?, + error: String? + ) { + // update avatar: + guidanceStylist.iconView?.setImageDrawable(AvatarDrawable.Builder() + .withId(id) + .withName(registeredName) + .withCircleCrop(true) + .build(requireContext()) + .apply { setInSize(resources.getDimensionPixelSize(R.dimen.tv_avatar_size)) }) + + // update guidance: + guidanceStylist.titleView?.text = registeredName + + // update actions: + val position = findActionPositionById(ACTION_ID_CHECK) + if (position != -1) { + val action = actions[position] + action.title = error ?: "" + if (error != null) { + action.icon = requireContext().getDrawable(R.drawable.ic_error_red) + } + notifyActionChanged(position) + } + } + + override fun onCreateActions(actions: MutableList<GuidedAction>, savedInstanceState: Bundle?) { + val args = arguments ?: return + val needPassword = args.getBoolean("needPassword", false) + //val error = args.getString("error") ?: "" + if (needPassword) { + actions.add( + GuidedAction.Builder(requireContext()) + .id(ACTION_ID_PASSWORD) + .title(R.string.wizard_password_info) + .description(R.string.enter_password) + .descriptionEditInputType(InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD) + .descriptionEditable(true) + .editDescription("") + .inputType(InputType.TYPE_TEXT_VARIATION_PASSWORD) + .build() + ) + actions.add(GuidedAction.Builder(requireContext()) + .id(ACTION_ID_CHECK) + .enabled(false) + .focusable(false) + .infoOnly(true) + .build()) + } + actions.add( + GuidedAction.Builder(requireContext()) + .id(GuidedAction.ACTION_ID_CONTINUE) + .title(R.string.import_side_step2_password_import) + .enabled(!needPassword) + .build() + ) + actions.add( + GuidedAction.Builder(requireContext()) + .clickAction(GuidedAction.ACTION_ID_CANCEL) + .build() + ) + } + + override fun onGuidedActionEditedAndProceed(action: GuidedAction): Long { + if (action.id == ACTION_ID_PASSWORD) { + updatePasswordAction(action) + } + return GuidedAction.ACTION_ID_NEXT + } + + override fun onGuidedActionEditCanceled(action: GuidedAction) { + if (action.id == ACTION_ID_PASSWORD) { + updatePasswordAction(action) + } + } + + private fun updatePasswordAction(action: GuidedAction) { + val password = (action.editDescription?.toString() ?: "").apply { mPassword = this } + Log.w(TAG, "updatePasswordAction: $password") + action.description = if (password.isNotEmpty()) StringUtils.toPassword(password) else getString(R.string.enter_password) + notifyActionChanged(findActionPositionById(action.id)) + + // update continue action: + val continueAction = findActionPositionById(GuidedAction.ACTION_ID_CONTINUE) + if (continueAction != -1) { + actions[continueAction].isEnabled = password.isNotEmpty() + notifyActionChanged(continueAction) + } + } + + override fun onGuidedActionClicked(action: GuidedAction) { + val callback = (activity as? TVImportWizard) ?: return + val needPassword = arguments?.getBoolean("needPassword", false) == true + when (action.id) { + ACTION_ID_PASSWORD -> { + /*val password = action.editTitle.toString() + callback.onAuthentication(password)*/ + } + GuidedAction.ACTION_ID_CONTINUE -> { + if (needPassword) { + callback.onAuthentication(mPassword) + } else { + callback.onAuthentication("") + } + } + GuidedAction.ACTION_ID_CANCEL -> { + callback.onCancel() + } + } + } + + override fun onProvideTheme(): Int = R.style.Theme_Ring_Leanback_GuidedStep_First + + companion object { + const val TAG = "TVAccountImportStep2Fragment" + const val ACTION_ID_PASSWORD = 1L + const val ACTION_ID_CHECK = 2L + + fun build( + needPassword: Boolean, + id: String, + registeredName: String?, + error: String? + ): TVAccountImportStep2Fragment = + TVAccountImportStep2Fragment().apply { + arguments = Bundle().apply { + putBoolean("needPassword", needPassword) + putString("id", id) + putString("registeredName", registeredName) + putString("error", error) + } + } + } +} 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 474865ef46ef37e7eb98a4116109c8b0e233ee44..b3c76a15f48d3a52959a01e3490cdea9286a0ac8 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 @@ -16,6 +16,8 @@ */ package cx.ring.tv.account +import android.app.Activity +import android.content.Intent import android.os.Bundle import androidx.fragment.app.activityViewModels import androidx.leanback.widget.GuidanceStylist.Guidance @@ -36,8 +38,14 @@ class TVHomeAccountCreationFragment : JamiGuidedStepFragment<HomeAccountCreation add(parentFragmentManager, TVJamiAccountCreationFragment()) } - override fun goToAccountLink() { // Todo: Legacy code. Implement new UI instead of TVJamiLinkAccountFragment. -// add(parentFragmentManager, TVJamiLinkAccountFragment()) + 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() + } } override fun goToAccountConnect() { 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 new file mode 100644 index 0000000000000000000000000000000000000000..9dcc10d76af1ca438bcb98d1d1f6819d979a2e42 --- /dev/null +++ b/jami-android/app/src/main/java/cx/ring/tv/account/TVImportWizard.kt @@ -0,0 +1,162 @@ +/* + * 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.R +import cx.ring.application.JamiApplication +import cx.ring.linkdevice.view.ImportSideStep3Fragment +import cx.ring.linkdevice.view.ImportSideStep3Fragment.OnResultCallback +import cx.ring.linkdevice.viewmodel.AddDeviceImportState +import cx.ring.linkdevice.viewmodel.ImportSideViewModel +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch +import net.jami.services.AccountService + +@AndroidEntryPoint +class TVImportWizard : AppCompatActivity(), OnResultCallback { + private val importSideViewModel by lazy { ViewModelProvider(this)[ImportSideViewModel::class.java] } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + JamiApplication.instance?.startDaemon(this) + if (savedInstanceState == null) { + supportFragmentManager.beginTransaction() + .add(android.R.id.content, TVAccountImportStep1Fragment()) + .commitNow() + } + + lifecycleScope.launch { + importSideViewModel.uiState.collect { + Log.d(TAG, "UI state: $it") + when (it) { + is AddDeviceImportState.Init -> {} + is AddDeviceImportState.TokenAvailable -> showToken(it.token) + is AddDeviceImportState.Connecting -> showConnecting() + is AddDeviceImportState.Authenticating -> showAuthenticating( + it.needPassword, + it.id, + it.registeredName, + when (it.error) { + ImportSideViewModel.InputError.BAD_PASSWORD -> getString(R.string.link_device_error_bad_password) + ImportSideViewModel.InputError.UNKNOWN -> getString(R.string.link_device_error_unknown) + else -> null + } + ) + is AddDeviceImportState.InProgress -> showInProgress() + is AddDeviceImportState.Done -> showDone(it.error) + } + } + } + } + + private fun showToken(token: String) { + Log.w(TAG, "showToken: $token") + val fragment = + supportFragmentManager.findFragmentById(android.R.id.content) as? TVAccountImportStep1Fragment + ?: TVAccountImportStep1Fragment().also { + supportFragmentManager.beginTransaction() + .replace(android.R.id.content, it) + .commitNow() + } + if (token.isEmpty()) + fragment.showLoading() + else + fragment.showToken(token) + } + + private fun showConnecting() { + val fragment = + supportFragmentManager.findFragmentById(android.R.id.content) as? TVAccountImportStep1Fragment + fragment?.showConnecting() + } + + fun onAuthentication(password: String) { + importSideViewModel.onAuthentication(password) + } + + fun onCancel() { + //presenter.onCancel() + } + + private fun showAuthenticating( + needPassword: Boolean, + id: String, + registeredName: String?, + error: String? + ) { + Log.w(TAG, "showAuthenticating: $needPassword, $id, $registeredName, $error") + val fragment = + supportFragmentManager.findFragmentById(android.R.id.content) as? TVAccountImportStep2Fragment + if (fragment == null) { + GuidedStepSupportFragment.addAsRoot( + this, + TVAccountImportStep2Fragment.build(needPassword, id, registeredName, error), + android.R.id.content + ) + } else { + fragment.update(needPassword, id, registeredName, error) + } + } + + private fun showInProgress() { + Log.w(TAG, "showInProgress") + val fragment = + supportFragmentManager.findFragmentById(android.R.id.content) as? ImportSideStep3Fragment + ?: ImportSideStep3Fragment().also { + supportFragmentManager.beginTransaction() + .replace(android.R.id.content, it) + .commitNow() + } + fragment.showLoading() + } + + private fun showDone(error: AccountService.AuthError?) { + Log.w(TAG, "showDone $error") + val fragment = + supportFragmentManager.findFragmentById(android.R.id.content) as? ImportSideStep3Fragment + ?: ImportSideStep3Fragment().also { + supportFragmentManager.beginTransaction() + .replace(android.R.id.content, it) + .commitNow() + } + if (error == null) { + fragment.showDone() + } else { + fragment.showError(error) + } + } + + override fun onExit(returnCode: Int) { + Log.w(TAG, "showExit $returnCode") + setResult(when(returnCode) { + 0 -> RESULT_OK + else -> RESULT_CANCELED + }) + finish() + } + + companion object { + private const val TAG = "TVImportWizard" + } +} \ No newline at end of file diff --git a/jami-android/app/src/main/java/cx/ring/views/AvatarDrawable.kt b/jami-android/app/src/main/java/cx/ring/views/AvatarDrawable.kt index bbe5e9fb583288b595a3ba87578d3863ec54ef29..cabb6d95f141ada0dbff2c445e30d8026c36eef3 100644 --- a/jami-android/app/src/main/java/cx/ring/views/AvatarDrawable.kt +++ b/jami-android/app/src/main/java/cx/ring/views/AvatarDrawable.kt @@ -184,6 +184,10 @@ class AvatarDrawable : Drawable { this.id = uri.rawUriString return this } + fun withId(id: String): Builder { + this.id = id + return this + } fun withPhoto(photo: Bitmap?): Builder { photos = if (photo == null) null else mutableListOf(photo) // list elements must be mutable diff --git a/jami-android/libjamiclient/src/main/kotlin/net/jami/services/AccountService.kt b/jami-android/libjamiclient/src/main/kotlin/net/jami/services/AccountService.kt index 5459d3496406f5685a78431eb54d0e2941557bcd..cb0fda7d31fb91c728515d107e78b99463ec2830 100644 --- a/jami-android/libjamiclient/src/main/kotlin/net/jami/services/AccountService.kt +++ b/jami-android/libjamiclient/src/main/kotlin/net/jami/services/AccountService.kt @@ -1707,6 +1707,24 @@ class AccountService( val operationId: Long? = null ) + enum class AuthError { + NETWORK, + AUTHENTICATION, + TIMEOUT, + CANCELED, + UNKNOWN; + companion object { + fun fromString(value: String) = when (value) { + "network" -> NETWORK + "auth_error" -> AUTHENTICATION + "timeout" -> TIMEOUT + "canceled" -> CANCELED + "unknown" -> UNKNOWN + else -> UNKNOWN + } + } + } + /** * Related to add device feature (import side). * Updates the state of the account import. diff --git a/jami-android/libjamiclient/src/main/kotlin/net/jami/utils/StringUtils.kt b/jami-android/libjamiclient/src/main/kotlin/net/jami/utils/StringUtils.kt index b711d6200044193fe1645f89440f56c3268c85eb..5fa57995f3e7ae9afc73b068133afc4da1e06223 100644 --- a/jami-android/libjamiclient/src/main/kotlin/net/jami/utils/StringUtils.kt +++ b/jami-android/libjamiclient/src/main/kotlin/net/jami/utils/StringUtils.kt @@ -39,7 +39,7 @@ object StringUtils { fun toPassword(s: String): String { if (s.isEmpty()) return "" - return String(CharArray(s.length).apply { Arrays.fill(this, '*') }) + return String(CharArray(s.length).apply { Arrays.fill(this, '●') }) } fun toNumber(s: String?): String? = s?.replace("(", "")?.replace(")", "")?.replace("-", "")?.replace(" ", "")