mirror of
https://codeberg.org/timelimit/timelimit-android.git
synced 2025-10-03 09:49:25 +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")
|
@Query("SELECT * FROM category_app LIMIT :pageSize OFFSET :offset")
|
||||||
abstract fun getCategoryAppPageSync(offset: Int, pageSize: Int): List<CategoryApp>
|
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")
|
@Query("UPDATE category SET parent_category_id = :parentCategoryId WHERE id = :categoryId")
|
||||||
abstract fun updateParentCategory(categoryId: String, parentCategoryId: String)
|
abstract fun updateParentCategory(categoryId: String, parentCategoryId: String)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM category")
|
||||||
|
abstract fun getAllCategoriesSync(): List<Category>
|
||||||
}
|
}
|
||||||
|
|
||||||
data class CategoryWithVersionNumbers(
|
data class CategoryWithVersionNumbers(
|
||||||
|
|
|
@ -56,4 +56,7 @@ abstract class TimeLimitRuleDao {
|
||||||
|
|
||||||
@Query("SELECT * FROM time_limit_rule LIMIT :pageSize OFFSET :offset")
|
@Query("SELECT * FROM time_limit_rule LIMIT :pageSize OFFSET :offset")
|
||||||
abstract fun getRulePageSync(offset: Int, pageSize: Int): List<TimeLimitRule>
|
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")
|
@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>>
|
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() {
|
class AuthenticateByMailFragment : Fragment() {
|
||||||
companion object {
|
companion object {
|
||||||
private const val REQUEST_SIGN_IN_WITH_GOOGLE = 1
|
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 listener: AuthenticateByMailFragmentListener by lazy { parentFragment as AuthenticateByMailFragmentListener }
|
||||||
private val googleAuthUtil: GoogleSignInUtil by lazy { (activity as MainActivity).googleSignInUtil }
|
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 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? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
val binding = FragmentAuthenticateByMailBinding.inflate(layoutInflater, container, false)
|
val binding = FragmentAuthenticateByMailBinding.inflate(layoutInflater, container, false)
|
||||||
|
|
||||||
model.usingDefaultServer.observe(this, Observer {
|
model.usingDefaultServer.observe(this, Observer {
|
||||||
binding.usingDefaultServer = it
|
binding.showSignInWithGoogleButton = it && (!hideSignInWithGoogleButton)
|
||||||
})
|
})
|
||||||
|
|
||||||
binding.signInWithGoogleButton.setOnClickListener {
|
binding.signInWithGoogleButton.setOnClickListener {
|
||||||
|
|
|
@ -166,6 +166,8 @@ class ActivityViewModel(application: Application): AndroidViewModel(application)
|
||||||
authenticatedUserMetadata.value = user
|
authenticatedUserMetadata.value = user
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getAuthenticatedUser() = authenticatedUserMetadata.value
|
||||||
|
|
||||||
fun logOut() {
|
fun logOut() {
|
||||||
authenticatedUserMetadata.value = null
|
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.main.AuthenticationFab
|
||||||
import io.timelimit.android.ui.manage.device.add.AddDeviceFragment
|
import io.timelimit.android.ui.manage.device.add.AddDeviceFragment
|
||||||
import io.timelimit.android.ui.overview.about.AboutFragmentParentHandlers
|
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 io.timelimit.android.ui.overview.overview.OverviewFragmentParentHandlers
|
||||||
import kotlinx.android.synthetic.main.fragment_main.*
|
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 adapter: PagerAdapter by lazy { PagerAdapter(childFragmentManager) }
|
||||||
private val logic: AppLogic by lazy { DefaultAppLogic.with(context!!) }
|
private val logic: AppLogic by lazy { DefaultAppLogic.with(context!!) }
|
||||||
private lateinit var navigation: NavController
|
private lateinit var navigation: NavController
|
||||||
|
@ -205,4 +208,11 @@ class MainFragment : Fragment(), OverviewFragmentParentHandlers, AboutFragmentPa
|
||||||
R.id.overviewFragment
|
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.appcompat.app.AlertDialog
|
||||||
import androidx.fragment.app.DialogFragment
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
|
import io.timelimit.android.BuildConfig
|
||||||
import io.timelimit.android.R
|
import io.timelimit.android.R
|
||||||
import io.timelimit.android.extensions.showSafe
|
import io.timelimit.android.extensions.showSafe
|
||||||
|
|
||||||
|
@ -32,7 +33,22 @@ class CanNotAddDevicesInLocalModeDialogFragment: DialogFragment() {
|
||||||
return AlertDialog.Builder(context!!, theme)
|
return AlertDialog.Builder(context!!, theme)
|
||||||
.setTitle(R.string.overview_add_device)
|
.setTitle(R.string.overview_add_device)
|
||||||
.setMessage(R.string.overview_add_error_local_mode)
|
.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()
|
.create()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,3 +56,7 @@ class CanNotAddDevicesInLocalModeDialogFragment: DialogFragment() {
|
||||||
showSafe(manager, DIALOG_TAG)
|
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.android.synthetic.main.fragment_overview.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class OverviewFragment : CoroutineFragment() {
|
class OverviewFragment : CoroutineFragment(), CanNotAddDevicesInLocalModeDialogFragmentListener {
|
||||||
private val handlers: OverviewFragmentParentHandlers by lazy { parentFragment as OverviewFragmentParentHandlers }
|
private val handlers: OverviewFragmentParentHandlers by lazy { parentFragment as OverviewFragmentParentHandlers }
|
||||||
private val logic: AppLogic by lazy { DefaultAppLogic.with(context!!) }
|
private val logic: AppLogic by lazy { DefaultAppLogic.with(context!!) }
|
||||||
private val auth: ActivityViewModel by lazy { getActivityViewModel(activity!!) }
|
private val auth: ActivityViewModel by lazy { getActivityViewModel(activity!!) }
|
||||||
|
@ -78,7 +78,9 @@ class OverviewFragment : CoroutineFragment() {
|
||||||
override fun onAddDeviceClicked() {
|
override fun onAddDeviceClicked() {
|
||||||
launch {
|
launch {
|
||||||
if (logic.database.config().getDeviceAuthTokenAsync().waitForNonNullValue().isEmpty()) {
|
if (logic.database.config().getDeviceAuthTokenAsync().waitForNonNullValue().isEmpty()) {
|
||||||
CanNotAddDevicesInLocalModeDialogFragment().show(fragmentManager!!)
|
CanNotAddDevicesInLocalModeDialogFragment()
|
||||||
|
.apply { setTargetFragment(this@OverviewFragment, 0) }
|
||||||
|
.show(fragmentManager!!)
|
||||||
} else if (auth.requestAuthenticationOrReturnTrue()) {
|
} else if (auth.requestAuthenticationOrReturnTrue()) {
|
||||||
handlers.openAddDeviceScreen()
|
handlers.openAddDeviceScreen()
|
||||||
}
|
}
|
||||||
|
@ -114,9 +116,13 @@ class OverviewFragment : CoroutineFragment() {
|
||||||
}
|
}
|
||||||
).attachToRecyclerView(recycler)
|
).attachToRecyclerView(recycler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun migrateToConnectedMode() {
|
||||||
|
handlers.migrateToConnectedMode()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OverviewFragmentParentHandlers {
|
interface OverviewFragmentParentHandlers: CanNotAddDevicesInLocalModeDialogFragmentListener {
|
||||||
fun openAddUserScreen()
|
fun openAddUserScreen()
|
||||||
fun openAddDeviceScreen()
|
fun openAddDeviceScreen()
|
||||||
fun openManageDeviceScreen(deviceId: String)
|
fun openManageDeviceScreen(deviceId: String)
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
type="String" />
|
type="String" />
|
||||||
|
|
||||||
<variable
|
<variable
|
||||||
name="usingDefaultServer"
|
name="showSignInWithGoogleButton"
|
||||||
type="boolean" />
|
type="boolean" />
|
||||||
|
|
||||||
<import type="android.text.TextUtils" />
|
<import type="android.text.TextUtils" />
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<LinearLayout
|
<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:orientation="vertical"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
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:exitAnim="@anim/nav_default_exit_anim"
|
||||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||||
app:popExitAnim="@anim/nav_default_pop_exit_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>
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/manageChildFragment"
|
android:id="@+id/manageChildFragment"
|
||||||
|
@ -299,4 +306,9 @@
|
||||||
android:name="io.timelimit.android.ui.diagnose.DiagnoseSyncFragment"
|
android:name="io.timelimit.android.ui.diagnose.DiagnoseSyncFragment"
|
||||||
android:label="diagnose_sync_fragment"
|
android:label="diagnose_sync_fragment"
|
||||||
tools:layout="@layout/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>
|
</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.
|
Sie haben TimeLimit im lokalen Modus eingerichtet.
|
||||||
In diesem gibt es keine Vernetzung, sodass Sie kein Gerät hinzufügen können.
|
In diesem gibt es keine Vernetzung, sodass Sie kein Gerät hinzufügen können.
|
||||||
</string>
|
</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_title">Willkommen bei TimeLimit</string>
|
||||||
<string name="overview_intro_text">
|
<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.
|
You have set up TimeLimit in the local mode.
|
||||||
There is no linking, therefore you can not add devices.
|
There is no linking, therefore you can not add devices.
|
||||||
</string>
|
</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_title">Welcome to TimeLimit</string>
|
||||||
<string name="overview_intro_text">
|
<string name="overview_intro_text">
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue