mirror of
https://codeberg.org/timelimit/timelimit-android.git
synced 2025-10-03 01:39:22 +02:00
Squashed commit of the following:
commit 168ac15c6d07d01aa61f5d660e7f9118a002bee3 Author: Jonas L <jonas@determapp.de> Date: Mon Feb 25 00:00:00 2019 +0000 Hide sign in with google button when migrating to the connected mode commit e77f43017875633444ffb4f5a33304fc8366bf26 Author: Jonas L <jonas@determapp.de> Date: Mon Feb 25 00:00:00 2019 +0000 Improve strings commit 16d8ae49aecd79cdc46d1224b0e4e7b27f5eca53 Author: Jonas L <jonas@determapp.de> Date: Mon Feb 25 00:00:00 2019 +0000 Fix migration time limit rules commit 88368e16d726840db21177a1cc2a9bd0b4c8c7ae Author: Jonas L <jonas@determapp.de> Date: Mon Feb 25 00:00:00 2019 +0000 Handle empty name field commit d7bf7fe950eaf476fd49be8f233ebc39f6334f53 Author: Jonas L <jonas@determapp.de> Date: Mon Feb 25 00:00:00 2019 +0000 Implement basically migration to connected mode commit d05274d1a4cfa866ff1dafd764bbd90951bfcaa9 Author: Jonas L <jonas@determapp.de> Date: Mon Feb 25 00:00:00 2019 +0000 Add screen for migration commit 429ae8ce84ebe1121050b94c51f15da6030e8b25 Author: Jonas L <jonas@determapp.de> Date: Mon Feb 25 00:00:00 2019 +0000 Add entry point for migrating to the connected mode
This commit is contained in:
parent
43efaceb7e
commit
d85bf2a699
21 changed files with 812 additions and 8 deletions
|
@ -46,4 +46,7 @@ abstract class CategoryAppDao {
|
|||
|
||||
@Query("SELECT * FROM category_app LIMIT :pageSize OFFSET :offset")
|
||||
abstract fun getCategoryAppPageSync(offset: Int, pageSize: Int): List<CategoryApp>
|
||||
|
||||
@Query("SELECT * FROM category_app")
|
||||
abstract fun getAllCategoryAppSync(): List<CategoryApp>
|
||||
}
|
||||
|
|
|
@ -94,6 +94,9 @@ abstract class CategoryDao {
|
|||
|
||||
@Query("UPDATE category SET parent_category_id = :parentCategoryId WHERE id = :categoryId")
|
||||
abstract fun updateParentCategory(categoryId: String, parentCategoryId: String)
|
||||
|
||||
@Query("SELECT * FROM category")
|
||||
abstract fun getAllCategoriesSync(): List<Category>
|
||||
}
|
||||
|
||||
data class CategoryWithVersionNumbers(
|
||||
|
|
|
@ -56,4 +56,7 @@ abstract class TimeLimitRuleDao {
|
|||
|
||||
@Query("SELECT * FROM time_limit_rule LIMIT :pageSize OFFSET :offset")
|
||||
abstract fun getRulePageSync(offset: Int, pageSize: Int): List<TimeLimitRule>
|
||||
|
||||
@Query("SELECT * FROM time_limit_rule")
|
||||
abstract fun getAllRulesSync(): List<TimeLimitRule>
|
||||
}
|
||||
|
|
|
@ -68,4 +68,7 @@ abstract class UsedTimeDao {
|
|||
|
||||
@Query("SELECT * FROM used_time WHERE category_id IN (:categoryIds) AND day_of_epoch >= :startingDayOfEpoch AND day_of_epoch <= :endDayOfEpoch")
|
||||
abstract fun getUsedTimesByDayAndCategoryIds(categoryIds: List<String>, startingDayOfEpoch: Int, endDayOfEpoch: Int): LiveData<List<UsedTimeItem>>
|
||||
|
||||
@Query("SELECT * FROM used_time")
|
||||
abstract fun getAllUsedTimeItemsSync(): List<UsedTimeItem>
|
||||
}
|
||||
|
|
|
@ -34,17 +34,27 @@ import io.timelimit.android.ui.MainActivity
|
|||
class AuthenticateByMailFragment : Fragment() {
|
||||
companion object {
|
||||
private const val REQUEST_SIGN_IN_WITH_GOOGLE = 1
|
||||
private const val EXTRA_HIDE_SIGN_IN_WITH_GOOGLE_BUTTON = "hsiwgb"
|
||||
|
||||
fun newInstance(hideSignInWithGoogleButton: Boolean) = AuthenticateByMailFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putBoolean(EXTRA_HIDE_SIGN_IN_WITH_GOOGLE_BUTTON, hideSignInWithGoogleButton)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val listener: AuthenticateByMailFragmentListener by lazy { parentFragment as AuthenticateByMailFragmentListener }
|
||||
private val googleAuthUtil: GoogleSignInUtil by lazy { (activity as MainActivity).googleSignInUtil }
|
||||
private val model: AuthenticateByMailModel by lazy { ViewModelProviders.of(this).get(AuthenticateByMailModel::class.java) }
|
||||
private val hideSignInWithGoogleButton: Boolean by lazy {
|
||||
arguments?.getBoolean(EXTRA_HIDE_SIGN_IN_WITH_GOOGLE_BUTTON, false) ?: false
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
val binding = FragmentAuthenticateByMailBinding.inflate(layoutInflater, container, false)
|
||||
|
||||
model.usingDefaultServer.observe(this, Observer {
|
||||
binding.usingDefaultServer = it
|
||||
binding.showSignInWithGoogleButton = it && (!hideSignInWithGoogleButton)
|
||||
})
|
||||
|
||||
binding.signInWithGoogleButton.setOnClickListener {
|
||||
|
|
|
@ -166,6 +166,8 @@ class ActivityViewModel(application: Application): AndroidViewModel(application)
|
|||
authenticatedUserMetadata.value = user
|
||||
}
|
||||
|
||||
fun getAuthenticatedUser() = authenticatedUserMetadata.value
|
||||
|
||||
fun logOut() {
|
||||
authenticatedUserMetadata.value = null
|
||||
}
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* TimeLimit Copyright <C> 2019 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package io.timelimit.android.ui.migrate_to_connected
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.Fragment
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.navigation.Navigation
|
||||
import io.timelimit.android.R
|
||||
import io.timelimit.android.data.model.UserType
|
||||
import io.timelimit.android.databinding.MigrateToConnectedModeFragmentBinding
|
||||
import io.timelimit.android.ui.authentication.AuthenticateByMailFragment
|
||||
import io.timelimit.android.ui.authentication.AuthenticateByMailFragmentListener
|
||||
import io.timelimit.android.ui.main.ActivityViewModelHolder
|
||||
import io.timelimit.android.ui.main.getActivityViewModel
|
||||
|
||||
class MigrateToConnectedModeFragment : Fragment(), AuthenticateByMailFragmentListener {
|
||||
companion object {
|
||||
private const val PAGE_READY = 0
|
||||
private const val PAGE_AUTH = 1
|
||||
private const val PAGE_WORKING = 2
|
||||
private const val PAGE_EXISTING_ACCOUNT = 3
|
||||
private const val PAGE_DONE = 4
|
||||
}
|
||||
|
||||
private val model: MigrateToConnectedModeModel by lazy {
|
||||
ViewModelProviders.of(this).get(MigrateToConnectedModeModel::class.java)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { // Inflate the layout for this fragment
|
||||
val binding = MigrateToConnectedModeFragmentBinding.inflate(inflater, container, false)
|
||||
val navigation = Navigation.findNavController(container!!)
|
||||
val auth = getActivityViewModel(activity!!)
|
||||
|
||||
auth.authenticatedUser.observe(this, Observer {
|
||||
binding.isParentSignedIn = it?.second?.type == UserType.Parent
|
||||
})
|
||||
|
||||
binding.parentAuthButton.setOnClickListener {
|
||||
(activity as ActivityViewModelHolder).showAuthenticationScreen()
|
||||
}
|
||||
|
||||
binding.goButton.setOnClickListener {
|
||||
var name = binding.parentName.text.toString()
|
||||
|
||||
if (name.isBlank())
|
||||
name = getString(R.string.setup_username_parent)
|
||||
|
||||
model.doMigration(
|
||||
model = auth,
|
||||
parentFirstName = name
|
||||
)
|
||||
}
|
||||
|
||||
model.status.observe(this, Observer { status ->
|
||||
when (status) {
|
||||
LeaveScreenMigrationStatus -> {
|
||||
navigation.popBackStack()
|
||||
|
||||
null
|
||||
}
|
||||
WaitingForAuthMigrationStatus -> binding.flipper.displayedChild = PAGE_AUTH
|
||||
WaitingForConfirmationByParentMigrationStatus -> binding.flipper.displayedChild = PAGE_READY
|
||||
WorkingMigrationStatus -> binding.flipper.displayedChild = PAGE_WORKING
|
||||
DoneMigrationStatus -> binding.flipper.displayedChild = PAGE_DONE
|
||||
ConflictAlreadyHasAccountMigrationStatus -> binding.flipper.displayedChild = PAGE_EXISTING_ACCOUNT
|
||||
}.let { /* require handling all cases */ }
|
||||
})
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
childFragmentManager.beginTransaction()
|
||||
.replace(R.id.mail_auth_container, AuthenticateByMailFragment.newInstance(
|
||||
hideSignInWithGoogleButton = true
|
||||
))
|
||||
.commit()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLoginSucceeded(mailAuthToken: String) {
|
||||
model.onLoginSucceeded(mailAuthToken)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
/*
|
||||
* TimeLimit Copyright <C> 2019 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package io.timelimit.android.ui.migrate_to_connected
|
||||
|
||||
import android.app.Application
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import io.timelimit.android.BuildConfig
|
||||
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.backup.DatabaseBackup
|
||||
import io.timelimit.android.data.transaction
|
||||
import io.timelimit.android.livedata.castDown
|
||||
import io.timelimit.android.logic.DefaultAppLogic
|
||||
import io.timelimit.android.sync.ApplyServerDataStatus
|
||||
import io.timelimit.android.sync.actions.apply.ApplyActionParentPasswordAuthentication
|
||||
import io.timelimit.android.sync.network.ClientDataStatus
|
||||
import io.timelimit.android.sync.network.NewDeviceInfo
|
||||
import io.timelimit.android.sync.network.ParentPassword
|
||||
import io.timelimit.android.sync.network.StatusOfMailAddress
|
||||
import io.timelimit.android.sync.network.api.HttpError
|
||||
import io.timelimit.android.ui.main.ActivityViewModel
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlin.Exception
|
||||
|
||||
class MigrateToConnectedModeModel(application: Application): AndroidViewModel(application) {
|
||||
companion object {
|
||||
private const val LOG_TAG = "MigrateToConnected"
|
||||
}
|
||||
|
||||
private val logic = DefaultAppLogic.with(application)
|
||||
private val database = logic.database
|
||||
private var mailAuthToken: String? = null
|
||||
private val lock = Mutex()
|
||||
private val statusInternal = MutableLiveData<MigrateToConnectedModeStatus>().apply {
|
||||
value = WaitingForAuthMigrationStatus
|
||||
}
|
||||
|
||||
val status = statusInternal.castDown()
|
||||
|
||||
fun onLoginSucceeded(mailAuthToken: String) {
|
||||
this.mailAuthToken = mailAuthToken
|
||||
|
||||
runAsync {
|
||||
lock.withLock {
|
||||
try {
|
||||
statusInternal.value = WorkingMigrationStatus
|
||||
|
||||
val api = logic.serverLogic.getServerConfigCoroutine().api
|
||||
|
||||
val status = api.getStatusByMailToken(mailAuthToken).status
|
||||
|
||||
when (status) {
|
||||
StatusOfMailAddress.MailAddressWithFamily -> statusInternal.value = ConflictAlreadyHasAccountMigrationStatus
|
||||
StatusOfMailAddress.MailAddressWithoutFamily -> statusInternal.value = WaitingForConfirmationByParentMigrationStatus
|
||||
}.let { /* require handling all paths */ }
|
||||
} catch (ex: Exception) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.w(LOG_TAG, "error during checking mail", ex)
|
||||
}
|
||||
|
||||
Toast.makeText(
|
||||
getApplication(),
|
||||
if (ex is HttpError)
|
||||
R.string.error_server_rejected
|
||||
else
|
||||
R.string.error_network,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
|
||||
statusInternal.value = LeaveScreenMigrationStatus
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun doMigration(
|
||||
model: ActivityViewModel,
|
||||
parentFirstName: String
|
||||
) {
|
||||
runAsync {
|
||||
lock.withLock {
|
||||
try {
|
||||
statusInternal.value = WorkingMigrationStatus
|
||||
|
||||
if (!model.isParentAuthenticated()) {
|
||||
throw IllegalStateException()
|
||||
}
|
||||
|
||||
val auth = model.getAuthenticatedUser()!!
|
||||
|
||||
val currentConfig = Threads.database.executeAndWait {
|
||||
database.transaction().use {
|
||||
// check if not yet linked
|
||||
if (database.config().getDeviceAuthTokenSync() != "") {
|
||||
throw IllegalStateException("already linked")
|
||||
}
|
||||
|
||||
OfflineModeStatus.query(database)
|
||||
}
|
||||
}
|
||||
|
||||
// create family at server
|
||||
val server = logic.serverLogic.getServerConfigCoroutine()
|
||||
|
||||
val addDeviceResponse = server.api.createFamilyByMailToken(
|
||||
mailToken = mailAuthToken!!,
|
||||
parentPassword = ParentPassword(
|
||||
parentPasswordHash = auth.firstPasswordHash,
|
||||
parentPasswordSecondHash = auth.secondPasswordHash,
|
||||
parentPasswordSecondSalt = currentConfig.users.find { it.id == auth.userId }!!.secondPasswordSalt
|
||||
),
|
||||
parentDevice = NewDeviceInfo(model = currentConfig.device.model),
|
||||
deviceName = currentConfig.device.name,
|
||||
parentName = parentFirstName,
|
||||
timeZone = logic.timeApi.getSystemTimeZone().id
|
||||
)
|
||||
|
||||
// sync from server
|
||||
val clientStatusResponse = server.api.pullChanges(addDeviceResponse.deviceAuthToken, ClientDataStatus.empty)
|
||||
|
||||
val authentication = Threads.database.executeAndWait {
|
||||
logic.database.transaction().use { transaction ->
|
||||
val customServerUrl = logic.database.config().getCustomServerUrlSync()
|
||||
val database = logic.database
|
||||
|
||||
database.deleteAllData()
|
||||
|
||||
database.config().setCustomServerUrlSync(customServerUrl)
|
||||
database.config().setOwnDeviceIdSync(addDeviceResponse.ownDeviceId)
|
||||
database.config().setDeviceAuthTokenSync(addDeviceResponse.deviceAuthToken)
|
||||
|
||||
ApplyServerDataStatus.applyServerDataStatusSync(clientStatusResponse, logic.database, logic.platformIntegration)
|
||||
|
||||
val newParentUser = database.user().getParentUsersSync().first()
|
||||
val newParentUserAuth = ApplyActionParentPasswordAuthentication(
|
||||
parentUserId = newParentUser.id,
|
||||
secondPasswordHash = auth.secondPasswordHash
|
||||
)
|
||||
|
||||
transaction.setSuccess()
|
||||
|
||||
newParentUserAuth
|
||||
}
|
||||
}
|
||||
|
||||
currentConfig.apply(authentication, logic, addDeviceResponse.ownDeviceId)
|
||||
DatabaseBackup.with(getApplication()).tryCreateDatabaseBackupAsync()
|
||||
|
||||
statusInternal.value = DoneMigrationStatus
|
||||
} catch (ex: Exception) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.w(LOG_TAG, "error migration to connected mode", ex)
|
||||
}
|
||||
|
||||
Toast.makeText(getApplication(), R.string.error_general, Toast.LENGTH_SHORT).show()
|
||||
|
||||
statusInternal.value = LeaveScreenMigrationStatus
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class MigrateToConnectedModeStatus
|
||||
object WaitingForAuthMigrationStatus: MigrateToConnectedModeStatus()
|
||||
object LeaveScreenMigrationStatus: MigrateToConnectedModeStatus()
|
||||
object WaitingForConfirmationByParentMigrationStatus: MigrateToConnectedModeStatus()
|
||||
object ConflictAlreadyHasAccountMigrationStatus: MigrateToConnectedModeStatus()
|
||||
object WorkingMigrationStatus: MigrateToConnectedModeStatus()
|
||||
object DoneMigrationStatus: MigrateToConnectedModeStatus()
|
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
* TimeLimit Copyright <C> 2019 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package io.timelimit.android.ui.migrate_to_connected
|
||||
|
||||
import android.util.Log
|
||||
import io.timelimit.android.BuildConfig
|
||||
import io.timelimit.android.data.Database
|
||||
import io.timelimit.android.data.model.*
|
||||
import io.timelimit.android.logic.AppLogic
|
||||
import io.timelimit.android.sync.actions.*
|
||||
import io.timelimit.android.sync.actions.apply.ApplyActionParentPasswordAuthentication
|
||||
import io.timelimit.android.sync.actions.apply.ApplyActionUtil
|
||||
|
||||
data class OfflineModeStatus(
|
||||
val users: List<User>,
|
||||
val categories: List<Category>,
|
||||
val categoryApps: List<CategoryApp>,
|
||||
val device: Device,
|
||||
val rules: List<TimeLimitRule>,
|
||||
val usedTimes: List<UsedTimeItem>
|
||||
) {
|
||||
companion object {
|
||||
private const val LOG_TAG = "OfflineModeStatus"
|
||||
|
||||
fun query(database: Database): OfflineModeStatus = OfflineModeStatus(
|
||||
users = database.user().getAllUsersSync(),
|
||||
categories = database.category().getAllCategoriesSync(),
|
||||
categoryApps = database.categoryApp().getAllCategoryAppSync(),
|
||||
device = database.device().getDeviceByIdSync(database.config().getOwnDeviceIdSync()!!)!!,
|
||||
rules = database.timeLimitRules().getAllRulesSync(),
|
||||
usedTimes = database.usedTimes().getAllUsedTimeItemsSync()
|
||||
)
|
||||
}
|
||||
|
||||
// limitations:
|
||||
// - child passwords are lost
|
||||
// - all parent users except the migrating one are lost
|
||||
suspend fun apply(
|
||||
authentication: ApplyActionParentPasswordAuthentication,
|
||||
appLogic: AppLogic,
|
||||
newDeviceId: String
|
||||
) {
|
||||
suspend fun apply(action: ParentAction) {
|
||||
try {
|
||||
ApplyActionUtil.applyParentAction(
|
||||
action = action,
|
||||
database = appLogic.database,
|
||||
authentication = authentication,
|
||||
platformIntegration = appLogic.platformIntegration,
|
||||
syncUtil = appLogic.syncUtil
|
||||
)
|
||||
} catch (ex: Exception) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.w(LOG_TAG, "could not apply action $action", ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun apply(action: AppLogicAction) {
|
||||
try {
|
||||
ApplyActionUtil.applyAppLogicAction(
|
||||
action = action,
|
||||
appLogic = appLogic
|
||||
)
|
||||
} catch (ex: Exception) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.w(LOG_TAG, "could not apply action $action", ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// create child users
|
||||
users.filter { it.type == UserType.Child }.forEach { child ->
|
||||
apply(
|
||||
AddUserAction(
|
||||
name = child.name,
|
||||
timeZone = child.timeZone,
|
||||
userId = child.id,
|
||||
userType = UserType.Child,
|
||||
password = null
|
||||
)
|
||||
)
|
||||
|
||||
// disable limits until
|
||||
if (child.disableLimitsUntil != 0L) {
|
||||
apply(
|
||||
SetUserDisableLimitsUntilAction(
|
||||
childId = child.id,
|
||||
timestamp = child.disableLimitsUntil
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// create categories
|
||||
val childCategories = categories.filter { it.childId == child.id }
|
||||
|
||||
childCategories.forEach { category ->
|
||||
apply(
|
||||
CreateCategoryAction(
|
||||
childId = child.id,
|
||||
categoryId = category.id,
|
||||
title = category.title
|
||||
)
|
||||
)
|
||||
|
||||
if (!category.blockedMinutesInWeek.dataNotToModify.isEmpty) {
|
||||
apply(
|
||||
UpdateCategoryBlockedTimesAction(
|
||||
categoryId = category.id,
|
||||
blockedTimes = category.blockedMinutesInWeek
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (category.extraTimeInMillis != 0L) {
|
||||
apply(
|
||||
SetCategoryExtraTimeAction(
|
||||
categoryId = category.id,
|
||||
newExtraTime = category.extraTimeInMillis
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (category.temporarilyBlocked) {
|
||||
apply(
|
||||
UpdateCategoryTemporarilyBlockedAction(
|
||||
categoryId = category.id,
|
||||
blocked = true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// add category apps
|
||||
val thisCategoryApps = categoryApps.filter { it.categoryId == category.id }
|
||||
if (thisCategoryApps.isNotEmpty()) {
|
||||
apply(AddCategoryAppsAction(
|
||||
categoryId = category.id,
|
||||
packageNames = thisCategoryApps.map { it.packageName }
|
||||
))
|
||||
}
|
||||
|
||||
// add used times
|
||||
val thisUsedTimes = usedTimes.filter { it.categoryId == category.id }
|
||||
thisUsedTimes.forEach { usedTime ->
|
||||
apply(AddUsedTimeAction(
|
||||
categoryId = category.id,
|
||||
extraTimeToSubtract = 0,
|
||||
dayOfEpoch = usedTime.dayOfEpoch,
|
||||
timeToAdd = usedTime.usedMillis.toInt()
|
||||
))
|
||||
}
|
||||
|
||||
// add time limit rules
|
||||
val thisRules = rules.filter { it.categoryId == category.id }
|
||||
thisRules.forEach { rule -> apply(CreateTimeLimitRuleAction(rule)) }
|
||||
}
|
||||
|
||||
// parent categories
|
||||
childCategories.forEach { category ->
|
||||
if (category.parentCategoryId != "") {
|
||||
apply(SetParentCategory(
|
||||
categoryId = category.id,
|
||||
parentCategory = category.parentCategoryId
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// category for not assigned apps
|
||||
if (child.categoryForNotAssignedApps != "") {
|
||||
apply(SetCategoryForUnassignedApps(
|
||||
childId = child.id,
|
||||
categoryId = child.categoryForNotAssignedApps
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// update device config
|
||||
if (users.find { it.type == UserType.Child && it.id == device.currentUserId } != null) {
|
||||
apply(SetDeviceUserAction(
|
||||
deviceId = newDeviceId,
|
||||
userId = device.currentUserId
|
||||
))
|
||||
}
|
||||
|
||||
apply(UpdateNetworkTimeVerificationAction(
|
||||
deviceId = newDeviceId,
|
||||
mode = device.networkTime
|
||||
))
|
||||
|
||||
if (users.find { it.type == UserType.Child && it.id == device.defaultUser } != null) {
|
||||
apply(SetDeviceDefaultUserAction(
|
||||
deviceId = newDeviceId,
|
||||
defaultUserId = device.defaultUser
|
||||
))
|
||||
}
|
||||
|
||||
apply(SetDeviceDefaultUserTimeoutAction(
|
||||
deviceId = newDeviceId,
|
||||
timeout = device.defaultUserTimeout
|
||||
))
|
||||
|
||||
if (device.considerRebootManipulation) {
|
||||
apply(SetConsiderRebootManipulationAction(
|
||||
deviceId = newDeviceId,
|
||||
considerRebootManipulation = true
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -40,10 +40,13 @@ import io.timelimit.android.ui.main.ActivityViewModelHolder
|
|||
import io.timelimit.android.ui.main.AuthenticationFab
|
||||
import io.timelimit.android.ui.manage.device.add.AddDeviceFragment
|
||||
import io.timelimit.android.ui.overview.about.AboutFragmentParentHandlers
|
||||
import io.timelimit.android.ui.overview.overview.CanNotAddDevicesInLocalModeDialogFragmentListener
|
||||
import io.timelimit.android.ui.overview.overview.OverviewFragmentParentHandlers
|
||||
import kotlinx.android.synthetic.main.fragment_main.*
|
||||
|
||||
class MainFragment : Fragment(), OverviewFragmentParentHandlers, AboutFragmentParentHandlers {
|
||||
class MainFragment : Fragment(), OverviewFragmentParentHandlers, AboutFragmentParentHandlers,
|
||||
CanNotAddDevicesInLocalModeDialogFragmentListener {
|
||||
|
||||
private val adapter: PagerAdapter by lazy { PagerAdapter(childFragmentManager) }
|
||||
private val logic: AppLogic by lazy { DefaultAppLogic.with(context!!) }
|
||||
private lateinit var navigation: NavController
|
||||
|
@ -205,4 +208,11 @@ class MainFragment : Fragment(), OverviewFragmentParentHandlers, AboutFragmentPa
|
|||
R.id.overviewFragment
|
||||
)
|
||||
}
|
||||
|
||||
override fun migrateToConnectedMode() {
|
||||
navigation.safeNavigate(
|
||||
MainFragmentDirections.actionOverviewFragmentToMigrateToConnectedModeFragment(),
|
||||
R.id.overviewFragment
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import android.os.Bundle
|
|||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import io.timelimit.android.BuildConfig
|
||||
import io.timelimit.android.R
|
||||
import io.timelimit.android.extensions.showSafe
|
||||
|
||||
|
@ -32,7 +33,22 @@ class CanNotAddDevicesInLocalModeDialogFragment: DialogFragment() {
|
|||
return AlertDialog.Builder(context!!, theme)
|
||||
.setTitle(R.string.overview_add_device)
|
||||
.setMessage(R.string.overview_add_error_local_mode)
|
||||
.setPositiveButton(R.string.generic_ok, null)
|
||||
.apply {
|
||||
if (BuildConfig.hasServer) {
|
||||
setNegativeButton(R.string.generic_cancel, null)
|
||||
setPositiveButton(R.string.overview_add_device_migrate_to_connected) { _, _ ->
|
||||
dismiss()
|
||||
|
||||
targetFragment.let { target ->
|
||||
if (target is CanNotAddDevicesInLocalModeDialogFragmentListener) {
|
||||
target.migrateToConnectedMode()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setPositiveButton(R.string.generic_ok, null)
|
||||
}
|
||||
}
|
||||
.create()
|
||||
}
|
||||
|
||||
|
@ -40,3 +56,7 @@ class CanNotAddDevicesInLocalModeDialogFragment: DialogFragment() {
|
|||
showSafe(manager, DIALOG_TAG)
|
||||
}
|
||||
}
|
||||
|
||||
interface CanNotAddDevicesInLocalModeDialogFragmentListener {
|
||||
fun migrateToConnectedMode()
|
||||
}
|
|
@ -39,7 +39,7 @@ import io.timelimit.android.ui.main.getActivityViewModel
|
|||
import kotlinx.android.synthetic.main.fragment_overview.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class OverviewFragment : CoroutineFragment() {
|
||||
class OverviewFragment : CoroutineFragment(), CanNotAddDevicesInLocalModeDialogFragmentListener {
|
||||
private val handlers: OverviewFragmentParentHandlers by lazy { parentFragment as OverviewFragmentParentHandlers }
|
||||
private val logic: AppLogic by lazy { DefaultAppLogic.with(context!!) }
|
||||
private val auth: ActivityViewModel by lazy { getActivityViewModel(activity!!) }
|
||||
|
@ -78,7 +78,9 @@ class OverviewFragment : CoroutineFragment() {
|
|||
override fun onAddDeviceClicked() {
|
||||
launch {
|
||||
if (logic.database.config().getDeviceAuthTokenAsync().waitForNonNullValue().isEmpty()) {
|
||||
CanNotAddDevicesInLocalModeDialogFragment().show(fragmentManager!!)
|
||||
CanNotAddDevicesInLocalModeDialogFragment()
|
||||
.apply { setTargetFragment(this@OverviewFragment, 0) }
|
||||
.show(fragmentManager!!)
|
||||
} else if (auth.requestAuthenticationOrReturnTrue()) {
|
||||
handlers.openAddDeviceScreen()
|
||||
}
|
||||
|
@ -114,9 +116,13 @@ class OverviewFragment : CoroutineFragment() {
|
|||
}
|
||||
).attachToRecyclerView(recycler)
|
||||
}
|
||||
|
||||
override fun migrateToConnectedMode() {
|
||||
handlers.migrateToConnectedMode()
|
||||
}
|
||||
}
|
||||
|
||||
interface OverviewFragmentParentHandlers {
|
||||
interface OverviewFragmentParentHandlers: CanNotAddDevicesInLocalModeDialogFragmentListener {
|
||||
fun openAddUserScreen()
|
||||
fun openAddDeviceScreen()
|
||||
fun openManageDeviceScreen(deviceId: String)
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
type="String" />
|
||||
|
||||
<variable
|
||||
name="usingDefaultServer"
|
||||
name="showSignInWithGoogleButton"
|
||||
type="boolean" />
|
||||
|
||||
<import type="android.text.TextUtils" />
|
||||
|
@ -45,7 +45,7 @@
|
|||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:visibility="@{(usingDefaultServer && BuildConfig.useGoogleApis && TextUtils.isEmpty(mailAddressToWhichCodeWasSent)) ? View.VISIBLE : View.GONE}"
|
||||
android:visibility="@{(showSignInWithGoogleButton && BuildConfig.useGoogleApis && TextUtils.isEmpty(mailAddressToWhichCodeWasSent)) ? View.VISIBLE : View.GONE}"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
|
23
app/src/main/res/layout/migrate_to_connected_mode_done.xml
Normal file
23
app/src/main/res/layout/migrate_to_connected_mode_done.xml
Normal file
|
@ -0,0 +1,23 @@
|
|||
<!--
|
||||
TimeLimit Copyright <C> 2019 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 <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:padding="16dp"
|
||||
android:textAppearance="?android:textAppearanceLarge"
|
||||
android:gravity="center"
|
||||
android:text="@string/migrate_to_connected_mode_done"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="io.timelimit.android.ui.migrate_to_connected.MigrateToConnectedModeFragment" />
|
107
app/src/main/res/layout/migrate_to_connected_mode_fragment.xml
Normal file
107
app/src/main/res/layout/migrate_to_connected_mode_fragment.xml
Normal file
|
@ -0,0 +1,107 @@
|
|||
<!--
|
||||
TimeLimit Copyright <C> 2019 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 <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
tools:context="io.timelimit.android.ui.migrate_to_connected.MigrateToConnectedModeFragment">
|
||||
|
||||
<data>
|
||||
<variable
|
||||
name="isParentSignedIn"
|
||||
type="boolean" />
|
||||
|
||||
<import type="android.view.View" />
|
||||
</data>
|
||||
|
||||
<io.timelimit.android.ui.view.SafeViewFlipper
|
||||
android:id="@+id/flipper"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/scroll"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<LinearLayout
|
||||
android:padding="8dp"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<androidx.cardview.widget.CardView
|
||||
app:cardUseCompatPadding="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:padding="8dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:textAppearance="?android:textAppearanceLarge"
|
||||
android:text="@string/migrate_to_connected_mode_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
android:textAppearance="?android:textAppearanceMedium"
|
||||
android:text="@string/migrate_to_connected_mode_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<Button
|
||||
android:visibility="@{isParentSignedIn ? View.GONE : View.VISIBLE}"
|
||||
android:id="@+id/parent_auth_button"
|
||||
android:background="?selectableItemBackground"
|
||||
android:text="@string/add_user_authentication_required_btn"
|
||||
android:layout_gravity="end"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<EditText
|
||||
android:visibility="@{isParentSignedIn ? View.VISIBLE : View.GONE}"
|
||||
android:id="@+id/parent_name"
|
||||
android:inputType="textPersonName"
|
||||
android:hint="@string/setup_parent_mode_field_name_hint"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<Button
|
||||
android:visibility="@{isParentSignedIn ? View.VISIBLE : View.GONE}"
|
||||
android:id="@+id/go_button"
|
||||
android:background="?selectableItemBackground"
|
||||
android:text="@string/generic_go"
|
||||
android:layout_gravity="end"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/mail_auth_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<include layout="@layout/circular_progress_indicator" />
|
||||
<include layout="@layout/migrate_to_connected_mode_not_possible_due_to_existing_account" />
|
||||
<include layout="@layout/migrate_to_connected_mode_done" />
|
||||
|
||||
</io.timelimit.android.ui.view.SafeViewFlipper>
|
||||
|
||||
</layout>
|
|
@ -0,0 +1,23 @@
|
|||
<!--
|
||||
TimeLimit Copyright <C> 2019 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 <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:padding="16dp"
|
||||
android:textAppearance="?android:textAppearanceLarge"
|
||||
android:gravity="center"
|
||||
android:text="@string/migrate_to_connected_mode_other_account_linked"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="io.timelimit.android.ui.migrate_to_connected.MigrateToConnectedModeFragment" />
|
|
@ -80,6 +80,13 @@
|
|||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
||||
<action
|
||||
android:id="@+id/action_overviewFragment_to_migrateToConnectedModeFragment"
|
||||
app:destination="@id/migrateToConnectedModeFragment"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/manageChildFragment"
|
||||
|
@ -299,4 +306,9 @@
|
|||
android:name="io.timelimit.android.ui.diagnose.DiagnoseSyncFragment"
|
||||
android:label="diagnose_sync_fragment"
|
||||
tools:layout="@layout/diagnose_sync_fragment" />
|
||||
<fragment
|
||||
android:id="@+id/migrateToConnectedModeFragment"
|
||||
android:name="io.timelimit.android.ui.migrate_to_connected.MigrateToConnectedModeFragment"
|
||||
android:label="migrate_to_connected_mode_fragment"
|
||||
tools:layout="@layout/migrate_to_connected_mode_fragment" />
|
||||
</navigation>
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
TimeLimit Copyright <C> 2019 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 <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<resources>
|
||||
<string name="migrate_to_connected_mode_title">Migration zum vernetzten Modus</string>
|
||||
<string name="migrate_to_connected_mode_info">Wenn Sie fortfahren, dann gehen die Passwörter von Kind-Benutzern und alle Elternbenutzer (außer dem, der sich jetzt anmeldet) verloren.</string>
|
||||
|
||||
<string name="migrate_to_connected_mode_other_account_linked">
|
||||
Es gibt bereits ein Konto mit dieser E-Mail-Adresse.
|
||||
Deshalb kann es nicht für eine Migration in den vernetzten Modus verwendet werden.
|
||||
</string>
|
||||
|
||||
<string name="migrate_to_connected_mode_done">
|
||||
Die Migration zum vernetzten Modus ist abgeschlossen. Sie können sich am anderen
|
||||
Gerät mit der selben E-Mail-Adresse anmelden oder die Geräte-Hinzufüge-Funktion
|
||||
an diesem Gerät verwenden.
|
||||
Betätigen Sie die Zurück-Taste, um zur Übersicht zurückzukehren.
|
||||
</string>
|
||||
</resources>
|
|
@ -21,6 +21,7 @@
|
|||
Sie haben TimeLimit im lokalen Modus eingerichtet.
|
||||
In diesem gibt es keine Vernetzung, sodass Sie kein Gerät hinzufügen können.
|
||||
</string>
|
||||
<string name="overview_add_device_migrate_to_connected">Zum vernetzten Modus migrieren</string>
|
||||
|
||||
<string name="overview_intro_title">Willkommen bei TimeLimit</string>
|
||||
<string name="overview_intro_text">
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
TimeLimit Copyright <C> 2019 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 <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<resources>
|
||||
<string name="migrate_to_connected_mode_title">Migrate to the connected mode</string>
|
||||
<string name="migrate_to_connected_mode_info">When using this, password of child users and parent users other than the one who signs in for this process are lost.</string>
|
||||
|
||||
<string name="migrate_to_connected_mode_other_account_linked">
|
||||
There is already an account with this mail address. Due to that,
|
||||
migrating to the connected mode is not possible.
|
||||
</string>
|
||||
|
||||
<string name="migrate_to_connected_mode_done">
|
||||
The migration to the connected mode is done. You can sign in at your device
|
||||
using the same mail address or use the add device procedure from this device.
|
||||
Press back to go to the overview.
|
||||
</string>
|
||||
</resources>
|
|
@ -21,6 +21,7 @@
|
|||
You have set up TimeLimit in the local mode.
|
||||
There is no linking, therefore you can not add devices.
|
||||
</string>
|
||||
<string name="overview_add_device_migrate_to_connected">Migrate to the connected mode</string>
|
||||
|
||||
<string name="overview_intro_title">Welcome to TimeLimit</string>
|
||||
<string name="overview_intro_text">
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue