diff --git a/changelog.d/8410.bugfix b/changelog.d/8410.bugfix new file mode 100644 index 0000000000..f72d02e0b4 --- /dev/null +++ b/changelog.d/8410.bugfix @@ -0,0 +1 @@ +Fix crash when opening "Protect access" screen, and various other issue with `repeatOnLifecycle` diff --git a/vector/src/main/java/im/vector/app/core/extensions/TextInputLayout.kt b/vector/src/main/java/im/vector/app/core/extensions/TextInputLayout.kt index bb6f6a2cbb..e9a9cdb1cf 100644 --- a/vector/src/main/java/im/vector/app/core/extensions/TextInputLayout.kt +++ b/vector/src/main/java/im/vector/app/core/extensions/TextInputLayout.kt @@ -21,10 +21,9 @@ import android.text.Editable import android.view.View import android.view.inputmethod.EditorInfo import androidx.autofill.HintConstants -import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle +import androidx.lifecycle.withResumed import com.google.android.material.textfield.TextInputLayout import im.vector.app.core.platform.SimpleTextWatcher import kotlinx.coroutines.flow.launchIn @@ -88,7 +87,7 @@ fun TextInputLayout.setOnImeDoneListener(action: () -> Unit) { fun TextInputLayout.setOnFocusLostListener(lifecycleOwner: LifecycleOwner, action: () -> Unit) { editText().setOnFocusChangeListener { _, hasFocus -> when (hasFocus) { - false -> lifecycleOwner.lifecycleScope.launch { lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { action() } } + false -> lifecycleOwner.lifecycleScope.launch { lifecycleOwner.withResumed { action() } } else -> { // do nothing } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index 872f3f6dd5..42eb200887 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -30,9 +30,8 @@ import androidx.core.view.isVisible import androidx.drawerlayout.widget.DrawerLayout import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager -import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle +import androidx.lifecycle.withResumed import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.viewModel import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -402,7 +401,9 @@ class HomeActivity : private fun handleStartRecoverySetup() { // To avoid IllegalStateException in case the transaction was executed after onSaveInstanceState lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.RESUMED) { navigator.open4SSetup(this@HomeActivity, SetupMode.NORMAL) } + withResumed { + navigator.open4SSetup(this@HomeActivity, SetupMode.NORMAL) + } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index c8eafb40ef..feaad386cb 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -47,9 +47,8 @@ import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.core.view.updatePadding import androidx.fragment.app.setFragmentResultListener -import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle +import androidx.lifecycle.withResumed import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -1112,29 +1111,31 @@ class TimelineFragment : private fun updateJumpToReadMarkerViewVisibility() { if (isThreadTimeLine()) return viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { - val state = timelineViewModel.awaitState() - val showJumpToUnreadBanner = when (state.unreadState) { - UnreadState.Unknown, - UnreadState.HasNoUnread -> false - is UnreadState.ReadMarkerNotLoaded -> true - is UnreadState.HasUnread -> { - if (state.canShowJumpToReadMarker) { - val lastVisibleItem = layoutManager.findLastCompletelyVisibleItemPosition() - val positionOfReadMarker = withContext(Dispatchers.Default) { - timelineEventController.getPositionOfReadMarker() - } - if (positionOfReadMarker == null) { - false + withResumed { + viewLifecycleOwner.lifecycleScope.launch { + val state = timelineViewModel.awaitState() + val showJumpToUnreadBanner = when (state.unreadState) { + UnreadState.Unknown, + UnreadState.HasNoUnread -> false + is UnreadState.ReadMarkerNotLoaded -> true + is UnreadState.HasUnread -> { + if (state.canShowJumpToReadMarker) { + val lastVisibleItem = layoutManager.findLastCompletelyVisibleItemPosition() + val positionOfReadMarker = withContext(Dispatchers.Default) { + timelineEventController.getPositionOfReadMarker() + } + if (positionOfReadMarker == null) { + false + } else { + positionOfReadMarker > lastVisibleItem + } } else { - positionOfReadMarker > lastVisibleItem + false } - } else { - false } } + views.jumpToReadMarkerView.isVisible = showJumpToUnreadBanner } - views.jumpToReadMarkerView.isVisible = showJumpToUnreadBanner } } } @@ -1625,14 +1626,16 @@ class TimelineFragment : override fun onRoomCreateLinkClicked(url: String) { viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { - permalinkHandler - .launch(requireActivity(), url, object : NavigationInterceptor { - override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?, rootThreadEventId: String?): Boolean { - requireActivity().finish() - return false - } - }) + withResumed { + viewLifecycleOwner.lifecycleScope.launch { + permalinkHandler + .launch(requireActivity(), url, object : NavigationInterceptor { + override fun navToRoom(roomId: String?, eventId: String?, deepLink: Uri?, rootThreadEventId: String?): Boolean { + requireActivity().finish() + return false + } + }) + } } } } diff --git a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt index dea2e08a1f..d39dd42c5f 100644 --- a/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/LocationSharingFragment.kt @@ -26,9 +26,7 @@ import androidx.core.content.ContextCompat import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.fragment.app.setFragmentResultListener -import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -101,12 +99,10 @@ class LocationSharingFragment : views.mapView.onCreate(savedInstanceState) viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) { - views.mapView.initialize( - url = urlMapProvider.getMapUrl(), - locationTargetChangeListener = this@LocationSharingFragment - ) - } + views.mapView.initialize( + url = urlMapProvider.getMapUrl(), + locationTargetChangeListener = this@LocationSharingFragment + ) } initLocateButton() diff --git a/vector/src/main/java/im/vector/app/features/location/preview/LocationPreviewFragment.kt b/vector/src/main/java/im/vector/app/features/location/preview/LocationPreviewFragment.kt index aa4be84985..f796db5c7f 100644 --- a/vector/src/main/java/im/vector/app/features/location/preview/LocationPreviewFragment.kt +++ b/vector/src/main/java/im/vector/app/features/location/preview/LocationPreviewFragment.kt @@ -22,9 +22,7 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible -import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -81,9 +79,7 @@ class LocationPreviewFragment : views.mapView.onCreate(savedInstanceState) viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) { - views.mapView.initialize(urlMapProvider.getMapUrl()) - } + views.mapView.initialize(urlMapProvider.getMapUrl()) } observeViewEvents() diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPinFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPinFragment.kt index 840ae23115..f86095e12d 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPinFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPinFragment.kt @@ -16,9 +16,7 @@ package im.vector.app.features.settings -import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle import androidx.preference.Preference import androidx.preference.SwitchPreference import dagger.hilt.android.AndroidEntryPoint @@ -80,13 +78,13 @@ class VectorSettingsPinFragment : override fun onResume() { super.onResume() - updateBiometricPrefState(isPinCodeChecked = usePinCodePref.isChecked) + viewLifecycleOwner.lifecycleScope.launch { + refreshPinCodeStatus() + } } override fun bindPref() { - refreshPinCodeStatus() - usePinCodePref.setOnPreferenceChangeListener { _, value -> val isChecked = (value as? Boolean).orFalse() updateBiometricPrefState(isPinCodeChecked = isChecked) @@ -130,38 +128,34 @@ class VectorSettingsPinFragment : .onFailure { Timber.e(it) } } - private fun refreshPinCodeStatus() { - viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { - val hasPinCode = pinCodeStore.hasEncodedPin() - usePinCodePref.isChecked = hasPinCode - usePinCodePref.onPreferenceClickListener = Preference.OnPreferenceClickListener { - if (hasPinCode) { - lifecycleScope.launch { - pinCodeStore.deletePinCode() - refreshPinCodeStatus() - } - } else { - navigator.openPinCode( - requireContext(), - pinActivityResultLauncher, - PinMode.CREATE - ) - } - true - } - - changePinCodePref.onPreferenceClickListener = Preference.OnPreferenceClickListener { - if (hasPinCode) { - navigator.openPinCode( - requireContext(), - pinActivityResultLauncher, - PinMode.MODIFY - ) - } - true + private suspend fun refreshPinCodeStatus() { + val hasPinCode = pinCodeStore.hasEncodedPin() + usePinCodePref.isChecked = hasPinCode + usePinCodePref.onPreferenceClickListener = Preference.OnPreferenceClickListener { + if (hasPinCode) { + lifecycleScope.launch { + pinCodeStore.deletePinCode() + refreshPinCodeStatus() } + } else { + navigator.openPinCode( + requireContext(), + pinActivityResultLauncher, + PinMode.CREATE + ) } + true + } + + changePinCodePref.onPreferenceClickListener = Preference.OnPreferenceClickListener { + if (hasPinCode) { + navigator.openPinCode( + requireContext(), + pinActivityResultLauncher, + PinMode.MODIFY + ) + } + true } } @@ -170,6 +164,6 @@ class VectorSettingsPinFragment : } private val pinActivityResultLauncher = registerStartForActivityResult { - refreshPinCodeStatus() + // Nothing to do, refreshPinCodeStatus() will be called by `onResume` } } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt index 312fc0fa29..da0f2e46e2 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsSecurityPrivacyFragment.kt @@ -27,9 +27,8 @@ import android.view.View import android.view.ViewGroup import androidx.core.content.ContextCompat import androidx.core.view.isVisible -import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle +import androidx.lifecycle.withResumed import androidx.preference.Preference import androidx.preference.PreferenceCategory import androidx.preference.SwitchPreference @@ -190,6 +189,10 @@ class VectorSettingsSecurityPrivacyFragment : rawService .getElementWellknown(session.sessionParams) ?.isE2EByDefault() == false + + refreshXSigningStatus() + // My device name may have been updated + refreshMyDevice() } } @@ -288,19 +291,6 @@ class VectorSettingsSecurityPrivacyFragment : true } - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.RESUMED) { - refreshXSigningStatus() - } - } - - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.RESUMED) { - // My device name may have been updated - refreshMyDevice() - } - } - secureBackupPreference.icon = activity?.let { ThemeUtils.tintDrawable( it, @@ -429,16 +419,18 @@ class VectorSettingsSecurityPrivacyFragment : private fun openPinCodePreferenceScreen() { viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { - val hasPinCode = pinCodeStore.hasEncodedPin() - if (hasPinCode) { - navigator.openPinCode( - requireContext(), - pinActivityResultLauncher, - PinMode.AUTH - ) - } else { - doOpenPinCodePreferenceScreen() + withResumed { + viewLifecycleOwner.lifecycleScope.launch { + val hasPinCode = pinCodeStore.hasEncodedPin() + if (hasPinCode) { + navigator.openPinCode( + requireContext(), + pinActivityResultLauncher, + PinMode.AUTH + ) + } else { + doOpenPinCodePreferenceScreen() + } } } } diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt index 1fb4d25667..6904bf735b 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt @@ -24,9 +24,8 @@ import android.view.View import android.view.ViewGroup import android.widget.ScrollView import androidx.core.view.isVisible -import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope -import androidx.lifecycle.repeatOnLifecycle +import androidx.lifecycle.withResumed import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.args import com.airbnb.mvrx.withState @@ -178,7 +177,7 @@ class UserListFragment : // Scroll to the bottom when adding chips. When removing chips, do not scroll if (newNumberOfChips >= currentNumberOfChips) { viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { + withResumed { views.chipGroupScrollView.fullScroll(ScrollView.FOCUS_DOWN) } }