diff --git a/app/src/main/java/io/timelimit/android/ui/MainActivity.kt b/app/src/main/java/io/timelimit/android/ui/MainActivity.kt index cf64291..23ea8cc 100644 --- a/app/src/main/java/io/timelimit/android/ui/MainActivity.kt +++ b/app/src/main/java/io/timelimit/android/ui/MainActivity.kt @@ -31,6 +31,7 @@ import androidx.lifecycle.Observer import androidx.lifecycle.Transformations import androidx.lifecycle.ViewModelProviders import androidx.navigation.NavController +import androidx.navigation.NavDestination import androidx.navigation.fragment.NavHostFragment import io.timelimit.android.Application import io.timelimit.android.R @@ -51,13 +52,7 @@ import io.timelimit.android.ui.main.ActivityViewModelHolder import io.timelimit.android.ui.main.AuthenticatedUser import io.timelimit.android.ui.main.FragmentWithCustomTitle import io.timelimit.android.ui.manage.parent.ManageParentFragmentArgs -import io.timelimit.android.ui.manage.parent.link.LinkParentMailFragment -import io.timelimit.android.ui.manage.parent.password.restore.RestoreParentPasswordFragment -import io.timelimit.android.ui.overview.main.MainFragment -import io.timelimit.android.ui.parentmode.ParentModeFragment import io.timelimit.android.ui.payment.ActivityPurchaseModel -import io.timelimit.android.ui.setup.SetupTermsFragment -import io.timelimit.android.ui.setup.parent.SetupParentModeFragment import io.timelimit.android.ui.util.SyncStatusModel import java.security.SecureRandom @@ -101,7 +96,6 @@ class MainActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De } private val currentNavigatorFragment = MutableLiveData() - private val application: Application by lazy { getApplication() as Application } private val syncModel: SyncStatusModel by lazy { ViewModelProviders.of(this).get(SyncStatusModel::class.java) } @@ -147,13 +141,11 @@ class MainActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De } // up button - val shouldShowBackButtonForNavigatorFragment = currentNavigatorFragment.map { fragment -> - (!(fragment is MainFragment)) && (!(fragment is SetupTermsFragment)) && (!(fragment is ParentModeFragment)) - } - - val shouldShowUpButton = shouldShowBackButtonForNavigatorFragment - - shouldShowUpButton.observe(this, Observer { supportActionBar!!.setDisplayHomeAsUpEnabled(it) }) + getNavController().addOnDestinationChangedListener(object: NavController.OnDestinationChangedListener { + override fun onDestinationChanged(controller: NavController, destination: NavDestination, arguments: Bundle?) { + supportActionBar!!.setDisplayHomeAsUpEnabled(controller.previousBackStackEntry != null) + } + }) // init if not yet done DefaultAppLogic.with(this) @@ -183,6 +175,33 @@ class MainActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De syncModel.statusText.observe(this, Observer { supportActionBar!!.subtitle = it }) handleParameters(intent) + + val hasDeviceId = getActivityViewModel().logic.deviceId.map { it != null }.ignoreUnchanged() + val hasParentKey = getActivityViewModel().logic.database.config().getParentModeKeyLive().map { it != null }.ignoreUnchanged() + + hasDeviceId.observe(this) { + val rootDestination = getNavController().backQueue.getOrNull(1)?.destination?.id + + if (!it) getActivityViewModel().logOut() + + if ( + it && rootDestination != R.id.overviewFragment || + !it && rootDestination == R.id.overviewFragment + ) { + restartContent() + } + } + + hasParentKey.observe(this) { + val rootDestination = getNavController().backQueue.getOrNull(1)?.destination?.id + + if ( + it && rootDestination != R.id.parentModeFragment || + !it && rootDestination == R.id.parentModeFragment + ) { + restartContent() + } + } } override fun onOptionsItemSelected(item: MenuItem) = when { @@ -259,29 +278,26 @@ class MainActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De if (handleParameters(intent)) return - val currentFragment = currentNavigatorFragment.value - // at these screens, some users restart the App // if they want to continue after opening the mail // because they don't understand how to use the list of running Apps ... // Due to that, on the relevant screens, the App does not // go back to the start when opening it again - if ( - currentFragment is SetupParentModeFragment || - currentFragment is RestoreParentPasswordFragment || - currentFragment is LinkParentMailFragment - ) { - return + val isImportantScreen = when (getNavController().currentDestination?.id) { + R.id.setupParentModeFragment -> true + R.id.restoreParentPasswordFragment -> true + R.id.linkParentMailFragment -> true + else -> false } - getNavController().popBackStack(R.id.overviewFragment, true) - getNavController().handleDeepLink( - getNavController().createDeepLink() - .setDestination(R.id.overviewFragment) - .createTaskStackBuilder() - .intents - .first() - ) + if (!isImportantScreen) restartContent() + } + + private fun restartContent() { + while (getNavController().popBackStack()) {/* do nothing */} + + getNavController().clearBackStack(R.id.launchFragment) + getNavController().navigate(R.id.launchFragment) } override fun getActivityViewModel(): ActivityViewModel { @@ -296,15 +312,6 @@ class MainActivity : AppCompatActivity(), ActivityViewModelHolder, U2fManager.De return getNavHostFragment().navController } - override fun onBackPressed() { - if (currentNavigatorFragment.value is SetupTermsFragment || currentNavigatorFragment.value is ParentModeFragment) { - // hack to prevent the user from going to the launch screen of the App if it is not set up - finish() - } else { - super.onBackPressed() - } - } - override fun showAuthenticationScreen() { if (supportFragmentManager.findFragmentByTag(AUTH_DIALOG_TAG) == null) { NewLoginFragment().showSafe(supportFragmentManager, AUTH_DIALOG_TAG) diff --git a/app/src/main/java/io/timelimit/android/ui/launch/LaunchFragment.kt b/app/src/main/java/io/timelimit/android/ui/launch/LaunchFragment.kt new file mode 100644 index 0000000..8718e4c --- /dev/null +++ b/app/src/main/java/io/timelimit/android/ui/launch/LaunchFragment.kt @@ -0,0 +1,64 @@ +/* + * TimeLimit Copyright 2019 - 2022 Jonas Lochmann + * + * 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 version 3 of the License. + * + * 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 . + */ +package io.timelimit.android.ui.launch + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.navigation.Navigation +import io.timelimit.android.R +import io.timelimit.android.extensions.safeNavigate +import io.timelimit.android.ui.obsolete.ObsoleteDialogFragment +import io.timelimit.android.ui.overview.main.MainFragmentDirections + +class LaunchFragment: Fragment() { + private val model by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + ObsoleteDialogFragment.show(requireActivity(), false) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.circular_progress_indicator, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val navigation = Navigation.findNavController(view) + + model.action.observe(viewLifecycleOwner) { + when (it) { + LaunchModel.Action.Setup -> navigation.safeNavigate(LaunchFragmentDirections.actionLaunchFragmentToSetupTermsFragment(), R.id.launchFragment) + LaunchModel.Action.Overview -> navigation.safeNavigate(LaunchFragmentDirections.actionLaunchFragmentToOverviewFragment(), R.id.launchFragment) + is LaunchModel.Action.Child -> { + navigation.safeNavigate(LaunchFragmentDirections.actionLaunchFragmentToOverviewFragment(), R.id.launchFragment) + navigation.safeNavigate(MainFragmentDirections.actionOverviewFragmentToManageChildFragment(it.id, fromRedirect = true), R.id.overviewFragment) + } + LaunchModel.Action.DeviceSetup -> { + navigation.safeNavigate(LaunchFragmentDirections.actionLaunchFragmentToOverviewFragment(), R.id.launchFragment) + navigation.safeNavigate(MainFragmentDirections.actionOverviewFragmentToSetupDeviceFragment(), R.id.overviewFragment) + } + LaunchModel.Action.ParentMode -> navigation.safeNavigate(LaunchFragmentDirections.actionLaunchFragmentToParentModeFragment(), R.id.launchFragment) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/timelimit/android/ui/launch/LaunchModel.kt b/app/src/main/java/io/timelimit/android/ui/launch/LaunchModel.kt new file mode 100644 index 0000000..9b9c8e4 --- /dev/null +++ b/app/src/main/java/io/timelimit/android/ui/launch/LaunchModel.kt @@ -0,0 +1,68 @@ +/* + * TimeLimit Copyright 2019 - 2022 Jonas Lochmann + * + * 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 version 3 of the License. + * + * 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 . + */ +package io.timelimit.android.ui.launch + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import io.timelimit.android.async.Threads +import io.timelimit.android.coroutines.executeAndWait +import io.timelimit.android.data.model.UserType +import io.timelimit.android.livedata.castDown +import io.timelimit.android.livedata.waitUntilValueMatches +import io.timelimit.android.logic.DefaultAppLogic +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class LaunchModel(application: Application): AndroidViewModel(application) { + private val actionInternal = MutableLiveData() + private val logic = DefaultAppLogic.with(application) + + val action = actionInternal.castDown() + + init { + viewModelScope.launch { + withContext(Dispatchers.Main) { + logic.isInitialized.waitUntilValueMatches { it == true } + + actionInternal.value = Threads.database.executeAndWait { + val hasDeviceId = logic.database.config().getOwnDeviceIdSync() != null + val hasParentKey = logic.database.config().getParentModeKeySync() != null + + if (hasDeviceId) { + val config = logic.database.derivedDataDao().getUserAndDeviceRelatedDataSync() + + if (config?.userRelatedData?.user?.type == UserType.Child) Action.Child(config.userRelatedData.user.id) + else if (config?.userRelatedData == null) Action.DeviceSetup + else Action.Overview + } + else if (hasParentKey) Action.ParentMode + else Action.Setup + } + } + } + } + + sealed class Action { + object Setup: Action() + object Overview: Action() + data class Child(val id: String): Action() + object DeviceSetup: Action() + object ParentMode: Action() + } +} \ No newline at end of file diff --git a/app/src/main/java/io/timelimit/android/ui/overview/main/MainFragment.kt b/app/src/main/java/io/timelimit/android/ui/overview/main/MainFragment.kt index fdc811f..8675f4e 100644 --- a/app/src/main/java/io/timelimit/android/ui/overview/main/MainFragment.kt +++ b/app/src/main/java/io/timelimit/android/ui/overview/main/MainFragment.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 - 2021 Jonas Lochmann + * TimeLimit Copyright 2019 - 2022 Jonas Lochmann * * 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 @@ -19,30 +19,19 @@ import android.os.Bundle import android.view.Menu import android.view.MenuInflater import android.view.MenuItem -import android.view.View import androidx.fragment.app.Fragment import androidx.lifecycle.LiveData -import androidx.lifecycle.Observer import io.timelimit.android.R -import io.timelimit.android.async.Threads -import io.timelimit.android.coroutines.executeAndWait -import io.timelimit.android.coroutines.runAsync -import io.timelimit.android.data.model.UserType import io.timelimit.android.extensions.safeNavigate import io.timelimit.android.livedata.* -import io.timelimit.android.logic.AppLogic -import io.timelimit.android.logic.DefaultAppLogic import io.timelimit.android.ui.fragment.SingleFragmentWrapper import io.timelimit.android.ui.main.FragmentWithCustomTitle import io.timelimit.android.ui.manage.device.add.AddDeviceFragment -import io.timelimit.android.ui.obsolete.ObsoleteDialogFragment import io.timelimit.android.ui.overview.about.AboutFragmentParentHandlers import io.timelimit.android.ui.overview.overview.OverviewFragment import io.timelimit.android.ui.overview.overview.OverviewFragmentParentHandlers class MainFragment : SingleFragmentWrapper(), OverviewFragmentParentHandlers, AboutFragmentParentHandlers, FragmentWithCustomTitle { - private val logic: AppLogic by lazy { DefaultAppLogic.with(requireContext()) } - private var didRedirectToUserScreen = false override val showAuthButton: Boolean = true override fun createChildFragment(): Fragment = OverviewFragment() @@ -53,60 +42,6 @@ class MainFragment : SingleFragmentWrapper(), OverviewFragmentParentHandlers, Ab setHasOptionsMenu(true) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - logic.isInitialized.switchMap { isInitialized -> - if (isInitialized) { - logic.database.config().getOwnDeviceId().map { it == null } - } else { - liveDataFromNonNullValue(false) - } - }.observe(viewLifecycleOwner, Observer { shouldShowSetup -> - if (shouldShowSetup == true) { - runAsync { - val hasParentKey = Threads.database.executeAndWait { logic.database.config().getParentModeKeySync() != null } - - if (isAdded && !parentFragmentManager.isStateSaved) { - if (hasParentKey) { - logic.platformIntegration.disableDeviceAdmin() - - navigation.safeNavigate( - MainFragmentDirections.actionOverviewFragmentToParentModeFragment(), - R.id.overviewFragment - ) - } else { - navigation.safeNavigate( - MainFragmentDirections.actionOverviewFragmentToSetupTermsFragment(), - R.id.overviewFragment - ) - } - } - } - } else { - if (savedInstanceState == null && !didRedirectToUserScreen) { - didRedirectToUserScreen = true - - runAsync { - val user = logic.deviceUserEntry.waitForNullableValue() - - if (user?.type == UserType.Child) { - if (isAdded && !parentFragmentManager.isStateSaved) { - openManageChildScreen(user.id, fromRedirect = true) - } - } - - if (user != null) { - if (isAdded && !parentFragmentManager.isStateSaved) { - ObsoleteDialogFragment.show(requireActivity(), false) - } - } - } - } - } - }) - } - override fun openAddDeviceScreen() { AddDeviceFragment().show(parentFragmentManager) } @@ -118,12 +53,10 @@ class MainFragment : SingleFragmentWrapper(), OverviewFragmentParentHandlers, Ab ) } - override fun openManageChildScreen(childId: String) = openManageChildScreen(childId = childId, fromRedirect = false) - - private fun openManageChildScreen(childId: String, fromRedirect: Boolean) { + override fun openManageChildScreen(childId: String) { navigation.safeNavigate( - MainFragmentDirections.actionOverviewFragmentToManageChildFragment(childId = childId, fromRedirect = fromRedirect), - R.id.overviewFragment + MainFragmentDirections.actionOverviewFragmentToManageChildFragment(childId = childId, fromRedirect = false), + R.id.overviewFragment ) } diff --git a/app/src/main/java/io/timelimit/android/ui/overview/uninstall/UninstallFragment.kt b/app/src/main/java/io/timelimit/android/ui/overview/uninstall/UninstallFragment.kt index 1637e19..4ce1a22 100644 --- a/app/src/main/java/io/timelimit/android/ui/overview/uninstall/UninstallFragment.kt +++ b/app/src/main/java/io/timelimit/android/ui/overview/uninstall/UninstallFragment.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 - 2021 Jonas Lochmann + * TimeLimit Copyright 2019 - 2022 Jonas Lochmann * * 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 @@ -21,8 +21,6 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.lifecycle.LiveData -import androidx.navigation.NavController -import androidx.navigation.Navigation import io.timelimit.android.BuildConfig import io.timelimit.android.R import io.timelimit.android.databinding.FragmentUninstallBinding @@ -43,7 +41,6 @@ class UninstallFragment : Fragment(), FragmentWithCustomTitle { private val activity: ActivityViewModelHolder by lazy { getActivity() as ActivityViewModelHolder } private val auth: ActivityViewModel by lazy { activity.getActivityViewModel() } private var showBackdoorButton = false - private lateinit var navigation: NavController override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -52,8 +49,6 @@ class UninstallFragment : Fragment(), FragmentWithCustomTitle { } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - navigation = Navigation.findNavController(container!!) - val binding = FragmentUninstallBinding.inflate(inflater, container, false) binding.uninstall.isEnabled = binding.checkConfirm.isChecked @@ -74,10 +69,6 @@ class UninstallFragment : Fragment(), FragmentWithCustomTitle { binding.showBackdoorButton = showBackdoorButton - auth.logic.deviceId.observe(viewLifecycleOwner) { - if (it == null) { navigation.popBackStack() } - } - AuthenticationFab.manageAuthenticationFab( fab = binding.fab, fragment = this, diff --git a/app/src/main/java/io/timelimit/android/ui/setup/SetupLocalModeFragment.kt b/app/src/main/java/io/timelimit/android/ui/setup/SetupLocalModeFragment.kt index 8e7361a..385e00a 100644 --- a/app/src/main/java/io/timelimit/android/ui/setup/SetupLocalModeFragment.kt +++ b/app/src/main/java/io/timelimit/android/ui/setup/SetupLocalModeFragment.kt @@ -81,7 +81,6 @@ class SetupLocalModeFragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { val binding = FragmentSetupLocalModeBinding.inflate(inflater, container, false) - val navigation = Navigation.findNavController(container!!) binding.setPasswordView.allowNoPassword.value = true @@ -97,8 +96,6 @@ class SetupLocalModeFragment : Fragment() { model.status.observe(viewLifecycleOwner) { if (it == SetupLocalModeModel.Status.Done) { MustReadFragment.newInstance(R.string.must_read_child_manipulation).show(fragmentManager!!) - - navigation.popBackStack(R.id.overviewFragment, false) } } diff --git a/app/src/main/java/io/timelimit/android/ui/setup/SetupSelectModeFragment.kt b/app/src/main/java/io/timelimit/android/ui/setup/SetupSelectModeFragment.kt index 1e84dd9..037a8b5 100644 --- a/app/src/main/java/io/timelimit/android/ui/setup/SetupSelectModeFragment.kt +++ b/app/src/main/java/io/timelimit/android/ui/setup/SetupSelectModeFragment.kt @@ -140,8 +140,6 @@ class SetupSelectModeFragment : Fragment() { SetupSelectModeFragmentDirections.actionSetupSelectModeFragmentToSetupParentModeFragment(), R.id.setupSelectModeFragment ) - } else if (requestCode == REQUEST_SETUP_PARENT_MODE && resultCode == Activity.RESULT_OK) { - navigation.popBackStack(R.id.overviewFragment, false) } } } diff --git a/app/src/main/java/io/timelimit/android/ui/setup/child/SetupRemoteChildFragment.kt b/app/src/main/java/io/timelimit/android/ui/setup/child/SetupRemoteChildFragment.kt index 1259f8b..abcede1 100644 --- a/app/src/main/java/io/timelimit/android/ui/setup/child/SetupRemoteChildFragment.kt +++ b/app/src/main/java/io/timelimit/android/ui/setup/child/SetupRemoteChildFragment.kt @@ -1,5 +1,5 @@ /* - * TimeLimit Copyright 2019 - 2020 Jonas Lochmann + * TimeLimit Copyright 2019 - 2022 Jonas Lochmann * * 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 @@ -22,13 +22,10 @@ import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders -import androidx.navigation.Navigation import com.google.android.material.snackbar.Snackbar import io.timelimit.android.R import io.timelimit.android.databinding.SetupRemoteChildFragmentBinding -import io.timelimit.android.extensions.safeNavigate import io.timelimit.android.extensions.setOnEnterListenr -import io.timelimit.android.ui.overview.main.MainFragmentDirections class SetupRemoteChildFragment : Fragment() { private val model: SetupRemoteChildViewModel by lazy { @@ -65,18 +62,6 @@ class SetupRemoteChildFragment : Fragment() { }.let { } }) - model.isSetupDone.observe(this, Observer { - if (it!!) { - val navigation = Navigation.findNavController(binding.root) - - navigation.popBackStack(R.id.overviewFragment, false) - navigation.safeNavigate( - MainFragmentDirections.actionOverviewFragmentToSetupDeviceFragment(), - R.id.overviewFragment - ) - } - }) - return binding.root } } diff --git a/app/src/main/java/io/timelimit/android/ui/setup/parent/SetupParentModeFragment.kt b/app/src/main/java/io/timelimit/android/ui/setup/parent/SetupParentModeFragment.kt index b897127..358af8e 100644 --- a/app/src/main/java/io/timelimit/android/ui/setup/parent/SetupParentModeFragment.kt +++ b/app/src/main/java/io/timelimit/android/ui/setup/parent/SetupParentModeFragment.kt @@ -28,7 +28,6 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.observe -import androidx.navigation.Navigation import io.timelimit.android.R import io.timelimit.android.async.Threads import io.timelimit.android.coroutines.executeAndWait @@ -195,12 +194,6 @@ class SetupParentModeFragment : Fragment(), AuthenticateByMailFragmentListener { } } - model.isSetupDone.observe(this, Observer { - if (it!!) { - Navigation.findNavController(binding.root).popBackStack(R.id.overviewFragment, false) - } - }) - UpdateConsentCard.bind( view = binding.update, lifecycleOwner = viewLifecycleOwner, diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 69d2775..e585bac 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -16,7 +16,7 @@ + app:startDestination="@id/launchFragment"> - - + + + + +