From bc9c378e4359a8a7e804c80eddde8aaf9864a0fa Mon Sep 17 00:00:00 2001 From: Acclorite <140836141+Acclorite@users.noreply.github.com> Date: Sat, 28 Dec 2024 16:02:16 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A0=EF=B8=8F=20Fix=20not=20thread-safe?= =?UTF-8?q?=20=5Fstate.update()=20calls?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fixed multiple issues with update calls being executed simultaneously --- .../book_story/ui/about/AboutModel.kt | 17 +- .../book_story/ui/book_info/BookInfoModel.kt | 35 ++-- .../book_story/ui/browse/BrowseModel.kt | 119 ++++++----- .../book_story/ui/history/HistoryModel.kt | 67 +++--- .../book_story/ui/library/LibraryModel.kt | 77 ++++--- .../acclorite/book_story/ui/main/MainModel.kt | 11 +- .../book_story/ui/reader/ReaderModel.kt | 36 ++-- .../book_story/ui/settings/SettingsModel.kt | 11 +- .../book_story/ui/start/StartModel.kt | 197 ++++++++++-------- 9 files changed, 344 insertions(+), 226 deletions(-) diff --git a/app/src/main/java/ua/acclorite/book_story/ui/about/AboutModel.kt b/app/src/main/java/ua/acclorite/book_story/ui/about/AboutModel.kt index a3ae604c..9a6be206 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/about/AboutModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/about/AboutModel.kt @@ -10,8 +10,9 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import ua.acclorite.book_story.R import ua.acclorite.book_story.domain.use_case.remote.CheckForUpdates @@ -25,6 +26,8 @@ class AboutModel @Inject constructor( private val checkForUpdates: CheckForUpdates ) : ViewModel() { + private val mutex = Mutex() + private val _state = MutableStateFlow(AboutState()) val state = _state.asStateFlow() @@ -100,10 +103,18 @@ class AboutModel @Inject constructor( } is AboutEvent.OnDismissDialog -> { - _state.update { - it.copy(dialog = null) + viewModelScope.launch { + _state.update { + it.copy(dialog = null) + } } } } } + + private suspend inline fun MutableStateFlow.update(function: (T) -> T) { + mutex.withLock { + this.value = function(this.value) + } + } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/ui/book_info/BookInfoModel.kt b/app/src/main/java/ua/acclorite/book_story/ui/book_info/BookInfoModel.kt index 7ef7a0d8..036e3f79 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/book_info/BookInfoModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/book_info/BookInfoModel.kt @@ -18,8 +18,9 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import kotlinx.coroutines.yield import ua.acclorite.book_story.R @@ -55,6 +56,8 @@ class BookInfoModel @Inject constructor( private val updateBookWithText: UpdateBookWithText ) : ViewModel() { + private val mutex = Mutex() + private val _state = MutableStateFlow(BookInfoState()) val state = _state.asStateFlow() @@ -384,12 +387,13 @@ class BookInfoModel @Inject constructor( is BookInfoEvent.OnCheckCoverReset -> { launch(Dispatchers.IO) { - _state.update { - it.copy( - canResetCover = canResetCover.execute( - _state.value.book.id + if (_state.value.book.id == -1) return@launch + canResetCover.execute(_state.value.book.id).apply { + _state.update { + it.copy( + canResetCover = this ) - ) + } } } } @@ -780,16 +784,16 @@ class BookInfoModel @Inject constructor( return@launch } + eventJob.cancel() + eventJob.join() + eventJob = SupervisorJob() + _state.update { BookInfoState( book = book ) } - eventJob.cancel() - eventJob.join() - eventJob = SupervisorJob() - if (startUpdate) { onEvent( BookInfoEvent.OnCheckForTextUpdate( @@ -804,13 +808,14 @@ class BookInfoModel @Inject constructor( fun resetScreen() { viewModelScope.launch(Dispatchers.Main) { - _state.update { - BookInfoState() - } - eventJob.cancel() - eventJob.join() eventJob = SupervisorJob() } } + + private suspend inline fun MutableStateFlow.update(function: (T) -> T) { + mutex.withLock { + this.value = function(this.value) + } + } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/ui/browse/BrowseModel.kt b/app/src/main/java/ua/acclorite/book_story/ui/browse/BrowseModel.kt index 40bfb30d..4d4c5780 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/browse/BrowseModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/browse/BrowseModel.kt @@ -18,8 +18,9 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.receiveAsFlow -import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import kotlinx.coroutines.yield import ua.acclorite.book_story.R @@ -47,6 +48,8 @@ class BrowseModel @Inject constructor( private val insertBook: InsertBook ) : ViewModel() { + private val mutex = Mutex() + private val _state = MutableStateFlow(BrowseState()) val state = _state.asStateFlow() @@ -119,27 +122,31 @@ class BrowseModel @Inject constructor( } is BrowseEvent.OnRequestFocus -> { - if (!_state.value.hasFocused) { - event.focusRequester.requestFocus() - _state.update { - it.copy( - hasFocused = true - ) + viewModelScope.launch(Dispatchers.Main) { + if (!_state.value.hasFocused) { + event.focusRequester.requestFocus() + _state.update { + it.copy( + hasFocused = true + ) + } } } } is BrowseEvent.OnSearchQueryChange -> { - _state.update { - it.copy( - searchQuery = event.query - ) - } - changeSearchQueryJob?.cancel() - changeSearchQueryJob = viewModelScope.launch(Dispatchers.IO) { - delay(500) - yield() - onEvent(BrowseEvent.OnSearch) + viewModelScope.launch { + _state.update { + it.copy( + searchQuery = event.query + ) + } + changeSearchQueryJob?.cancel() + changeSearchQueryJob = launch(Dispatchers.IO) { + delay(500) + yield() + onEvent(BrowseEvent.OnSearch) + } } } @@ -292,18 +299,22 @@ class BrowseModel @Inject constructor( } is BrowseEvent.OnShowFilterBottomSheet -> { - _state.update { - it.copy( - bottomSheet = BrowseScreen.FILTER_BOTTOM_SHEET - ) + viewModelScope.launch { + _state.update { + it.copy( + bottomSheet = BrowseScreen.FILTER_BOTTOM_SHEET + ) + } } } is BrowseEvent.OnDismissBottomSheet -> { - _state.update { - it.copy( - bottomSheet = null - ) + viewModelScope.launch { + _state.update { + it.copy( + bottomSheet = null + ) + } } } @@ -413,20 +424,22 @@ class BrowseModel @Inject constructor( } is BrowseEvent.OnDismissPermissionDialog -> { - val legacyPermission = Build.VERSION.SDK_INT < Build.VERSION_CODES.R - val isPermissionGranted = if (!legacyPermission) { - Environment.isExternalStorageManager() - } else event.storagePermissionState.status.isGranted + viewModelScope.launch { + val legacyPermission = Build.VERSION.SDK_INT < Build.VERSION_CODES.R + val isPermissionGranted = if (!legacyPermission) { + Environment.isExternalStorageManager() + } else event.storagePermissionState.status.isGranted - storagePermissionJob?.cancel() - _state.update { it.copy(dialog = null) } + storagePermissionJob?.cancel() + _state.update { it.copy(dialog = null) } - if (isPermissionGranted) { - viewModelScope.launch(Dispatchers.IO) { - getFilesFromDownloads() + if (isPermissionGranted) { + viewModelScope.launch(Dispatchers.IO) { + getFilesFromDownloads() + } + } else { + _state.update { it.copy(isError = true) } } - } else { - _state.update { it.copy(isError = true) } } } @@ -484,12 +497,14 @@ class BrowseModel @Inject constructor( } is BrowseEvent.OnDismissAddDialog -> { - _state.update { - it.copy( - dialog = null - ) + viewModelScope.launch { + _state.update { + it.copy( + dialog = null + ) + } + getAddDialogBooksJob?.cancel() } - getAddDialogBooksJob?.cancel() } is BrowseEvent.OnActionAddDialog -> { @@ -574,10 +589,12 @@ class BrowseModel @Inject constructor( } is BrowseEvent.OnDismissDialog -> { - _state.update { - it.copy( - dialog = null - ) + viewModelScope.launch { + _state.update { + it.copy( + dialog = null + ) + } } } } @@ -601,8 +618,10 @@ class BrowseModel @Inject constructor( } fun resetScreen() { - storagePermissionJob?.cancel() - _state.update { it.copy(isError = false) } + viewModelScope.launch { + storagePermissionJob?.cancel() + _state.update { it.copy(isError = false) } + } } fun filterList( @@ -710,4 +729,10 @@ class BrowseModel @Inject constructor( ) ) } + + private suspend inline fun MutableStateFlow.update(function: (T) -> T) { + mutex.withLock { + this.value = function(this.value) + } + } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/ui/history/HistoryModel.kt b/app/src/main/java/ua/acclorite/book_story/ui/history/HistoryModel.kt index ceecf8e8..fac6a296 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/history/HistoryModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/history/HistoryModel.kt @@ -11,8 +11,9 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.receiveAsFlow -import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import kotlinx.coroutines.yield import ua.acclorite.book_story.R @@ -42,6 +43,8 @@ class HistoryModel @Inject constructor( private val deleteWholeHistory: DeleteWholeHistory ) : ViewModel() { + private val mutex = Mutex() + private val _state = MutableStateFlow(HistoryState()) val state = _state.asStateFlow() @@ -136,27 +139,31 @@ class HistoryModel @Inject constructor( } is HistoryEvent.OnRequestFocus -> { - if (!_state.value.hasFocused) { - event.focusRequester.requestFocus() - _state.update { - it.copy( - hasFocused = true - ) + viewModelScope.launch(Dispatchers.Main) { + if (!_state.value.hasFocused) { + event.focusRequester.requestFocus() + _state.update { + it.copy( + hasFocused = true + ) + } } } } is HistoryEvent.OnSearchQueryChange -> { - _state.update { - it.copy( - searchQuery = event.query - ) - } - searchQueryChange?.cancel() - searchQueryChange = viewModelScope.launch(Dispatchers.IO) { - delay(500) - yield() - onEvent(HistoryEvent.OnSearch) + viewModelScope.launch { + _state.update { + it.copy( + searchQuery = event.query + ) + } + searchQueryChange?.cancel() + searchQueryChange = launch(Dispatchers.IO) { + delay(500) + yield() + onEvent(HistoryEvent.OnSearch) + } } } @@ -209,10 +216,12 @@ class HistoryModel @Inject constructor( } is HistoryEvent.OnShowDeleteWholeHistoryDialog -> { - _state.update { - it.copy( - dialog = HistoryScreen.DELETE_WHOLE_HISTORY_DIALOG - ) + viewModelScope.launch { + _state.update { + it.copy( + dialog = HistoryScreen.DELETE_WHOLE_HISTORY_DIALOG + ) + } } } @@ -238,10 +247,12 @@ class HistoryModel @Inject constructor( } is HistoryEvent.OnDismissDialog -> { - _state.update { - it.copy( - dialog = null - ) + viewModelScope.launch { + _state.update { + it.copy( + dialog = null + ) + } } } } @@ -314,4 +325,10 @@ class HistoryModel @Inject constructor( ) } } + + private suspend inline fun MutableStateFlow.update(function: (T) -> T) { + mutex.withLock { + this.value = function(this.value) + } + } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/ui/library/LibraryModel.kt b/app/src/main/java/ua/acclorite/book_story/ui/library/LibraryModel.kt index ab58b7b0..3869a733 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/library/LibraryModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/library/LibraryModel.kt @@ -10,8 +10,9 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.receiveAsFlow -import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import kotlinx.coroutines.yield import ua.acclorite.book_story.R @@ -32,6 +33,8 @@ class LibraryModel @Inject constructor( private val moveBooks: UpdateBook ) : ViewModel() { + private val mutex = Mutex() + private val _state = MutableStateFlow(LibraryState()) val state = _state.asStateFlow() @@ -104,16 +107,18 @@ class LibraryModel @Inject constructor( } is LibraryEvent.OnSearchQueryChange -> { - _state.update { - it.copy( - searchQuery = event.query - ) - } - searchQueryChange?.cancel() - searchQueryChange = viewModelScope.launch(Dispatchers.IO) { - delay(500) - yield() - onEvent(LibraryEvent.OnSearch) + viewModelScope.launch { + _state.update { + it.copy( + searchQuery = event.query + ) + } + searchQueryChange?.cancel() + searchQueryChange = launch(Dispatchers.IO) { + delay(500) + yield() + onEvent(LibraryEvent.OnSearch) + } } } @@ -124,12 +129,14 @@ class LibraryModel @Inject constructor( } is LibraryEvent.OnRequestFocus -> { - if (!_state.value.hasFocused) { - event.focusRequester.requestFocus() - _state.update { - it.copy( - hasFocused = true - ) + viewModelScope.launch(Dispatchers.Main) { + if (!_state.value.hasFocused) { + event.focusRequester.requestFocus() + _state.update { + it.copy( + hasFocused = true + ) + } } } } @@ -163,10 +170,12 @@ class LibraryModel @Inject constructor( } is LibraryEvent.OnShowMoveDialog -> { - _state.update { - it.copy( - dialog = LibraryScreen.MOVE_DIALOG - ) + viewModelScope.launch { + _state.update { + it.copy( + dialog = LibraryScreen.MOVE_DIALOG + ) + } } } @@ -213,10 +222,12 @@ class LibraryModel @Inject constructor( } is LibraryEvent.OnShowDeleteDialog -> { - _state.update { - it.copy( - dialog = LibraryScreen.DELETE_DIALOG - ) + viewModelScope.launch { + _state.update { + it.copy( + dialog = LibraryScreen.DELETE_DIALOG + ) + } } } @@ -249,10 +260,12 @@ class LibraryModel @Inject constructor( } is LibraryEvent.OnDismissDialog -> { - _state.update { - it.copy( - dialog = null - ) + viewModelScope.launch { + _state.update { + it.copy( + dialog = null + ) + } } } } @@ -273,4 +286,10 @@ class LibraryModel @Inject constructor( ) } } + + private suspend inline fun MutableStateFlow.update(function: (T) -> T) { + mutex.withLock { + this.value = function(this.value) + } + } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/ui/main/MainModel.kt b/app/src/main/java/ua/acclorite/book_story/ui/main/MainModel.kt index aa910bcc..543c6f40 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/main/MainModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/main/MainModel.kt @@ -11,8 +11,9 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import ua.acclorite.book_story.domain.browse.toBrowseFilesStructure import ua.acclorite.book_story.domain.browse.toBrowseLayout @@ -45,6 +46,8 @@ class MainModel @Inject constructor( private val getAllSettings: GetAllSettings ) : ViewModel() { + private val mutex = Mutex() + private val _isReady = MutableStateFlow(false) val isReady = _isReady.asStateFlow() @@ -506,4 +509,10 @@ class MainModel @Inject constructor( } } } + + private suspend inline fun MutableStateFlow.update(function: (T) -> T) { + mutex.withLock { + this.value = function(this.value) + } + } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderModel.kt b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderModel.kt index 20f735c3..e2aaba77 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/reader/ReaderModel.kt @@ -22,8 +22,9 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import kotlinx.coroutines.yield import ua.acclorite.book_story.R @@ -54,6 +55,8 @@ class ReaderModel @Inject constructor( private val checkForTextUpdate: CheckForTextUpdate ) : ViewModel() { + private val mutex = Mutex() + private val _state = MutableStateFlow(ReaderState()) val state = _state.asStateFlow() @@ -550,13 +553,14 @@ class ReaderModel @Inject constructor( return@launch } + eventJob.cancel() + eventJob.join() + eventJob = SupervisorJob() + _state.update { ReaderState(book = book) } - eventJob.cancel() - eventJob = SupervisorJob() - onEvent( ReaderEvent.OnMenuVisibility( show = false, @@ -606,12 +610,14 @@ class ReaderModel @Inject constructor( } private fun updateChapter(index: Int) { - val (currentChapter, currentChapterProgress) = calculateCurrentChapter(index) - _state.update { - it.copy( - currentChapter = currentChapter, - currentChapterProgress = currentChapterProgress - ) + viewModelScope.launch { + val (currentChapter, currentChapterProgress) = calculateCurrentChapter(index) + _state.update { + it.copy( + currentChapter = currentChapter, + currentChapterProgress = currentChapterProgress + ) + } } } @@ -672,12 +678,14 @@ class ReaderModel @Inject constructor( fun resetScreen() { viewModelScope.launch(Dispatchers.Main) { - _state.update { - ReaderState() - } - eventJob.cancel() eventJob = SupervisorJob() } } + + private suspend inline fun MutableStateFlow.update(function: (T) -> T) { + mutex.withLock { + this.value = function(this.value) + } + } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/ui/settings/SettingsModel.kt b/app/src/main/java/ua/acclorite/book_story/ui/settings/SettingsModel.kt index 58eac220..d9c6edc2 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/settings/SettingsModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/settings/SettingsModel.kt @@ -15,8 +15,9 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import kotlinx.coroutines.yield import ua.acclorite.book_story.R @@ -42,6 +43,8 @@ class SettingsModel @Inject constructor( private val deleteColorPreset: DeleteColorPreset ) : ViewModel() { + private val mutex = Mutex() + private val _state = MutableStateFlow(SettingsState()) val state = _state.asStateFlow() @@ -536,4 +539,10 @@ class SettingsModel @Inject constructor( updateColorColorPresetJob?.cancel() deleteColorPresetJob?.cancel() } + + private suspend inline fun MutableStateFlow.update(function: (T) -> T) { + mutex.withLock { + this.value = function(this.value) + } + } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/ui/start/StartModel.kt b/app/src/main/java/ua/acclorite/book_story/ui/start/StartModel.kt index fa47c304..93d5e39c 100644 --- a/app/src/main/java/ua/acclorite/book_story/ui/start/StartModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/ui/start/StartModel.kt @@ -16,8 +16,9 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.yield import ua.acclorite.book_story.presentation.core.util.launchActivity import javax.inject.Inject @@ -27,6 +28,8 @@ class StartModel @Inject constructor( ) : ViewModel() { + private val mutex = Mutex() + private val _state = MutableStateFlow(StartState()) val state = _state.asStateFlow() @@ -59,110 +62,114 @@ class StartModel @Inject constructor( } is StartEvent.OnStoragePermissionRequest -> { - val legacyStoragePermission = Build.VERSION.SDK_INT < Build.VERSION_CODES.R + viewModelScope.launch { + val legacyStoragePermission = Build.VERSION.SDK_INT < Build.VERSION_CODES.R - val isPermissionGranted = if (legacyStoragePermission) { - event.storagePermissionState.status.isGranted - } else Environment.isExternalStorageManager() - - if (isPermissionGranted) { - _state.update { - it.copy( - storagePermissionGranted = true - ) - } - return - } - - if (legacyStoragePermission) { - if (!event.storagePermissionState.status.shouldShowRationale) { - event.storagePermissionState.launchPermissionRequest() - } else { - val uri = Uri.parse("package:${event.activity.packageName}") - val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, uri) - - intent.launchActivity(event.activity) { - return - } - } - } - - if (!legacyStoragePermission) { - val uri = Uri.parse("package:${event.activity.packageName}") - val intent = Intent( - Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, - uri - ) - - intent.launchActivity(event.activity) { - return - } - } - - storagePermissionJob?.cancel() - storagePermissionJob = viewModelScope.launch { - while (true) { - val granted = if (legacyStoragePermission) { - event.storagePermissionState.status.isGranted - } else Environment.isExternalStorageManager() - - if (!granted) { - delay(1000) - yield() - continue - } - - yield() + val isPermissionGranted = if (legacyStoragePermission) { + event.storagePermissionState.status.isGranted + } else Environment.isExternalStorageManager() + if (isPermissionGranted) { _state.update { it.copy( storagePermissionGranted = true ) } - break + return@launch + } + + if (legacyStoragePermission) { + if (!event.storagePermissionState.status.shouldShowRationale) { + event.storagePermissionState.launchPermissionRequest() + } else { + val uri = Uri.parse("package:${event.activity.packageName}") + val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, uri) + + intent.launchActivity(event.activity) { + return@launch + } + } + } + + if (!legacyStoragePermission) { + val uri = Uri.parse("package:${event.activity.packageName}") + val intent = Intent( + Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, + uri + ) + + intent.launchActivity(event.activity) { + return@launch + } + } + + storagePermissionJob?.cancel() + storagePermissionJob = viewModelScope.launch { + while (true) { + val granted = if (legacyStoragePermission) { + event.storagePermissionState.status.isGranted + } else Environment.isExternalStorageManager() + + if (!granted) { + delay(1000) + yield() + continue + } + + yield() + + _state.update { + it.copy( + storagePermissionGranted = true + ) + } + break + } } } } is StartEvent.OnNotificationsPermissionRequest -> { - if (event.notificationsPermissionState.status.isGranted) { - _state.update { - it.copy( - notificationsPermissionGranted = true - ) - } - return - } - - if (!event.notificationsPermissionState.status.shouldShowRationale) { - event.notificationsPermissionState.launchPermissionRequest() - } else { - val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) - intent.putExtra(Settings.EXTRA_APP_PACKAGE, event.activity.packageName) - - intent.launchActivity(event.activity) { - return - } - } - - notificationsPermissionJob?.cancel() - notificationsPermissionJob = viewModelScope.launch { - while (true) { - if (!event.notificationsPermissionState.status.isGranted) { - delay(1000) - yield() - continue - } - - yield() - + viewModelScope.launch { + if (event.notificationsPermissionState.status.isGranted) { _state.update { it.copy( notificationsPermissionGranted = true ) } + return@launch + } - break + if (!event.notificationsPermissionState.status.shouldShowRationale) { + event.notificationsPermissionState.launchPermissionRequest() + } else { + val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) + intent.putExtra(Settings.EXTRA_APP_PACKAGE, event.activity.packageName) + + intent.launchActivity(event.activity) { + return@launch + } + } + + notificationsPermissionJob?.cancel() + notificationsPermissionJob = viewModelScope.launch { + while (true) { + if (!event.notificationsPermissionState.status.isGranted) { + delay(1000) + yield() + continue + } + + yield() + + _state.update { + it.copy( + notificationsPermissionGranted = true + ) + } + + break + } } } } @@ -170,11 +177,19 @@ class StartModel @Inject constructor( } fun resetScreen() { - _state.update { - storagePermissionJob?.cancel() - notificationsPermissionJob?.cancel() + viewModelScope.launch { + _state.update { + storagePermissionJob?.cancel() + notificationsPermissionJob?.cancel() - StartState() + StartState() + } + } + } + + private suspend inline fun MutableStateFlow.update(function: (T) -> T) { + mutex.withLock { + this.value = function(this.value) } } } \ No newline at end of file