Loading app/src/main/java/foundation/e/apps/domain/install/GetAppDetailsUseCase.kt +1 −1 Original line number Diff line number Diff line Loading @@ -125,7 +125,7 @@ class GetAppDetailsUseCase @Inject constructor( getPWAAppDetails(packageName) } Source.SYSTEM_APP -> throw UninstallableAppException("System_app can't be installed through this") Source.SYSTEM_APP -> error("SYSTEM_APP should have been filtered before reaching this point") } private suspend fun getPlayStoreAppDetails(packageName: String): Application { Loading app/src/main/java/foundation/e/apps/feature/auth/toc/TocViewModel.kt +17 −0 Original line number Diff line number Diff line /* * Copyright (C) 2026 e Foundation * * 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 foundation.e.apps.feature.auth.toc import androidx.lifecycle.LiveData Loading app/src/main/java/foundation/e/apps/feature/main/MainActivity.kt +18 −15 Original line number Diff line number Diff line Loading @@ -127,10 +127,12 @@ class MainActivity : AppCompatActivity() { ParentalControlAuthenticator.initLauncher(parentalControlAuthenticatorLauncher) // Add an OnBackPressedCallback to handle the back press onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { onBackPressedDispatcher.addCallback( this, object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { if (isInitialScreen()) { resetIgnoreStatusForSessionRefresh() resetIgnoreSessionError() finish() } else { // Let the system handle the back press Loading @@ -138,7 +140,8 @@ class MainActivity : AppCompatActivity() { onBackPressedDispatcher.onBackPressed() } } }) } ) binding = ActivityMainBinding.inflate(layoutInflater) Loading Loading @@ -277,15 +280,15 @@ class MainActivity : AppCompatActivity() { ) } fun resetIgnoreStatusForSessionRefresh() { viewModel.shouldIgnoreSessionError = false fun resetIgnoreSessionError() { viewModel.resetIgnoreSessionError() } @Suppress("DEPRECATION") private fun setupBackPressHandlingForTiramisuAndAbove() { if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) { onBackInvokedDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT) { resetIgnoreStatusForSessionRefresh() resetIgnoreSessionError() finish() } } Loading Loading @@ -726,7 +729,7 @@ class MainActivity : AppCompatActivity() { } private fun handleRefreshSessionEvent() { val shouldShowDialog = !viewModel.shouldIgnoreSessionError val shouldShowDialog = !viewModel.shouldIgnoreSessionError() val isDialogShowing = supportFragmentManager.findFragmentByTag(SESSION_DIALOG_TAG) != null if (shouldShowDialog && !isDialogShowing) { showRefreshSessionDialog() Loading @@ -738,7 +741,7 @@ class MainActivity : AppCompatActivity() { } private fun onIgnoreSessionClick() { viewModel.shouldIgnoreSessionError = true viewModel.ignoreSessionError() } private fun setupBottomNavItemSelectedListener( Loading app/src/main/java/foundation/e/apps/feature/main/MainActivityStartupStateMachine.kt +3 −9 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package foundation.e.apps.feature.main import android.os.Looper import androidx.annotation.MainThread import foundation.e.apps.R import foundation.e.apps.domain.auth.AuthRefreshState Loading @@ -28,7 +29,7 @@ import foundation.e.apps.domain.startup.StartupDestination internal class MainActivityStartupStateMachine( private val resolveStartupDestinationUseCase: ResolveStartupDestinationUseCase, ) { private var ownerThreadId: Long? = null private val ownerThreadId: Long = Looper.getMainLooper().thread.id private var latestTocAcceptance: Boolean? = null private var latestAuthRefreshState: AuthRefreshState = AuthRefreshState.Pending private var latestDestinationId: Int? = null Loading Loading @@ -140,14 +141,7 @@ internal class MainActivityStartupStateMachine( } private fun verifySingleThreadAccess() { val currentThreadId = Thread.currentThread().id val expectedThreadId = ownerThreadId if (expectedThreadId == null) { ownerThreadId = currentThreadId return } check(expectedThreadId == currentThreadId) { check(ownerThreadId == Thread.currentThread().id) { "MainActivityStartupStateMachine must be mutated from a single owner thread" } } Loading app/src/main/java/foundation/e/apps/feature/main/MainActivityViewModel.kt +11 −1 Original line number Diff line number Diff line Loading @@ -94,7 +94,17 @@ class MainActivityViewModel @Inject constructor( startupStateMachine.closeAfterLogin = value } var shouldIgnoreSessionError = false private var ignoreSessionError = false fun shouldIgnoreSessionError(): Boolean = ignoreSessionError fun ignoreSessionError() { ignoreSessionError = true } fun resetIgnoreSessionError() { ignoreSessionError = false } private fun observeStartupInputs() { viewModelScope.launch { Loading Loading
app/src/main/java/foundation/e/apps/domain/install/GetAppDetailsUseCase.kt +1 −1 Original line number Diff line number Diff line Loading @@ -125,7 +125,7 @@ class GetAppDetailsUseCase @Inject constructor( getPWAAppDetails(packageName) } Source.SYSTEM_APP -> throw UninstallableAppException("System_app can't be installed through this") Source.SYSTEM_APP -> error("SYSTEM_APP should have been filtered before reaching this point") } private suspend fun getPlayStoreAppDetails(packageName: String): Application { Loading
app/src/main/java/foundation/e/apps/feature/auth/toc/TocViewModel.kt +17 −0 Original line number Diff line number Diff line /* * Copyright (C) 2026 e Foundation * * 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 foundation.e.apps.feature.auth.toc import androidx.lifecycle.LiveData Loading
app/src/main/java/foundation/e/apps/feature/main/MainActivity.kt +18 −15 Original line number Diff line number Diff line Loading @@ -127,10 +127,12 @@ class MainActivity : AppCompatActivity() { ParentalControlAuthenticator.initLauncher(parentalControlAuthenticatorLauncher) // Add an OnBackPressedCallback to handle the back press onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { onBackPressedDispatcher.addCallback( this, object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { if (isInitialScreen()) { resetIgnoreStatusForSessionRefresh() resetIgnoreSessionError() finish() } else { // Let the system handle the back press Loading @@ -138,7 +140,8 @@ class MainActivity : AppCompatActivity() { onBackPressedDispatcher.onBackPressed() } } }) } ) binding = ActivityMainBinding.inflate(layoutInflater) Loading Loading @@ -277,15 +280,15 @@ class MainActivity : AppCompatActivity() { ) } fun resetIgnoreStatusForSessionRefresh() { viewModel.shouldIgnoreSessionError = false fun resetIgnoreSessionError() { viewModel.resetIgnoreSessionError() } @Suppress("DEPRECATION") private fun setupBackPressHandlingForTiramisuAndAbove() { if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) { onBackInvokedDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT) { resetIgnoreStatusForSessionRefresh() resetIgnoreSessionError() finish() } } Loading Loading @@ -726,7 +729,7 @@ class MainActivity : AppCompatActivity() { } private fun handleRefreshSessionEvent() { val shouldShowDialog = !viewModel.shouldIgnoreSessionError val shouldShowDialog = !viewModel.shouldIgnoreSessionError() val isDialogShowing = supportFragmentManager.findFragmentByTag(SESSION_DIALOG_TAG) != null if (shouldShowDialog && !isDialogShowing) { showRefreshSessionDialog() Loading @@ -738,7 +741,7 @@ class MainActivity : AppCompatActivity() { } private fun onIgnoreSessionClick() { viewModel.shouldIgnoreSessionError = true viewModel.ignoreSessionError() } private fun setupBottomNavItemSelectedListener( Loading
app/src/main/java/foundation/e/apps/feature/main/MainActivityStartupStateMachine.kt +3 −9 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package foundation.e.apps.feature.main import android.os.Looper import androidx.annotation.MainThread import foundation.e.apps.R import foundation.e.apps.domain.auth.AuthRefreshState Loading @@ -28,7 +29,7 @@ import foundation.e.apps.domain.startup.StartupDestination internal class MainActivityStartupStateMachine( private val resolveStartupDestinationUseCase: ResolveStartupDestinationUseCase, ) { private var ownerThreadId: Long? = null private val ownerThreadId: Long = Looper.getMainLooper().thread.id private var latestTocAcceptance: Boolean? = null private var latestAuthRefreshState: AuthRefreshState = AuthRefreshState.Pending private var latestDestinationId: Int? = null Loading Loading @@ -140,14 +141,7 @@ internal class MainActivityStartupStateMachine( } private fun verifySingleThreadAccess() { val currentThreadId = Thread.currentThread().id val expectedThreadId = ownerThreadId if (expectedThreadId == null) { ownerThreadId = currentThreadId return } check(expectedThreadId == currentThreadId) { check(ownerThreadId == Thread.currentThread().id) { "MainActivityStartupStateMachine must be mutated from a single owner thread" } } Loading
app/src/main/java/foundation/e/apps/feature/main/MainActivityViewModel.kt +11 −1 Original line number Diff line number Diff line Loading @@ -94,7 +94,17 @@ class MainActivityViewModel @Inject constructor( startupStateMachine.closeAfterLogin = value } var shouldIgnoreSessionError = false private var ignoreSessionError = false fun shouldIgnoreSessionError(): Boolean = ignoreSessionError fun ignoreSessionError() { ignoreSessionError = true } fun resetIgnoreSessionError() { ignoreSessionError = false } private fun observeStartupInputs() { viewModelScope.launch { Loading