mirror of
https://codeberg.org/timelimit/timelimit-android.git
synced 2025-10-05 19:42:20 +02:00
Improve time counting with nested categories
This commit is contained in:
parent
192b871ae5
commit
a81bc687fb
9 changed files with 325 additions and 306 deletions
|
@ -69,9 +69,6 @@ abstract class UsedTimeDao {
|
|||
@Query("SELECT * FROM used_time WHERE category_id = :categoryId ORDER BY day_of_epoch DESC")
|
||||
abstract fun getUsedTimesByCategoryId(categoryId: String): DataSource.Factory<Int, UsedTimeItem>
|
||||
|
||||
@Query("SELECT * FROM used_time WHERE category_id = :categoryId AND day_of_epoch = :dayOfEpoch")
|
||||
abstract fun getUsedTimesByCategoryIdAndDayOfEpoch(categoryId: String, dayOfEpoch: Int): LiveData<UsedTimeItem?>
|
||||
|
||||
@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>>
|
||||
|
||||
|
|
|
@ -109,8 +109,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
|
|||
private val shouldDoAutomaticSignOut = cache.shouldDoAutomaticSignOut
|
||||
private val liveDataCaches = cache.liveDataCaches
|
||||
|
||||
private var usedTimeUpdateHelperForegroundApp: UsedTimeItemBatchUpdateHelper? = null
|
||||
private var usedTimeUpdateHelperBackgroundPlayback: UsedTimeItemBatchUpdateHelper? = null
|
||||
private var usedTimeUpdateHelper: UsedTimeUpdateHelper? = null
|
||||
private var previousMainLogicExecutionTime = 0
|
||||
private var previousMainLoopEndTime = 0L
|
||||
private val dayChangeTracker = DayChangeTracker(
|
||||
|
@ -140,8 +139,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
|
|||
val audioPlaybackHandling = BackgroundTaskRestrictionLogicResult()
|
||||
|
||||
private suspend fun commitUsedTimeUpdaters() {
|
||||
usedTimeUpdateHelperForegroundApp?.commit(appLogic)
|
||||
usedTimeUpdateHelperBackgroundPlayback?.commit(appLogic)
|
||||
usedTimeUpdateHelper?.forceCommit(appLogic)
|
||||
}
|
||||
|
||||
private suspend fun backgroundServiceLoop() {
|
||||
|
@ -281,84 +279,55 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
|
|||
result = audioPlaybackHandling
|
||||
)
|
||||
|
||||
// update used time helper if date does not match
|
||||
if (usedTimeUpdateHelper?.date != nowDate) {
|
||||
usedTimeUpdateHelper?.forceCommit(appLogic)
|
||||
usedTimeUpdateHelper = UsedTimeUpdateHelper(nowDate)
|
||||
}
|
||||
|
||||
val usedTimeUpdateHelper = usedTimeUpdateHelper!!
|
||||
|
||||
// check times
|
||||
fun buildUsedTimesSparseArray(items: SparseArray<UsedTimeItem>, categoryId: String): SparseLongArray {
|
||||
val a = usedTimeUpdateHelperForegroundApp
|
||||
val b = usedTimeUpdateHelperBackgroundPlayback
|
||||
|
||||
val result = SparseLongArray()
|
||||
|
||||
for (i in 0..6) {
|
||||
val usedTimesItem = items[i]?.usedMillis
|
||||
val usedTimesItem = items[i]?.usedMillis ?: 0
|
||||
val timeToAddButNotCommited = usedTimeUpdateHelper.timeToAdd[categoryId] ?: 0
|
||||
|
||||
if (a?.date?.dayOfWeek == i && a.childCategoryId == categoryId) {
|
||||
result.put(i, a.getTotalUsedTimeChild())
|
||||
} else if (a?.date?.dayOfWeek == i && a.parentCategoryId == categoryId) {
|
||||
result.put(i, a.getTotalUsedTimeParent())
|
||||
} else if (b?.date?.dayOfWeek == i && b.childCategoryId == categoryId) {
|
||||
result.put(i, b.getTotalUsedTimeChild())
|
||||
} else if (b?.date?.dayOfWeek == i && b.parentCategoryId == categoryId) {
|
||||
result.put(i, b.getTotalUsedTimeParent())
|
||||
} else {
|
||||
result.put(i, usedTimesItem ?: 0)
|
||||
}
|
||||
result.put(i, usedTimesItem + timeToAddButNotCommited)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fun getCachedExtraTimeToSubstract(categoryId: String): Int {
|
||||
val a = usedTimeUpdateHelperForegroundApp
|
||||
val b = usedTimeUpdateHelperBackgroundPlayback
|
||||
|
||||
if (a?.childCategoryId == categoryId || a?.parentCategoryId == categoryId) {
|
||||
return a.getCachedExtraTimeToSubtract()
|
||||
}
|
||||
|
||||
if (b?.childCategoryId == categoryId || b?.parentCategoryId == categoryId) {
|
||||
return b.getCachedExtraTimeToSubtract()
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
suspend fun getRemainingTime(categoryId: String?, parentCategoryId: String?): RemainingTime? {
|
||||
suspend fun getRemainingTime(categoryId: String?): RemainingTime? {
|
||||
categoryId ?: return null
|
||||
|
||||
val category = categories.find { it.id == categoryId } ?: return null
|
||||
val parentCategory = categories.find { it.id == parentCategoryId }
|
||||
|
||||
val rules = timeLimitRules.get(category.id).waitForNonNullValue()
|
||||
val parentRules = parentCategory?.let {
|
||||
timeLimitRules.get(it.id).waitForNonNullValue()
|
||||
} ?: emptyList()
|
||||
|
||||
if (rules.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val usedTimes = usedTimesOfCategoryAndWeekByFirstDayOfWeek.get(Pair(category.id, nowDate.dayOfEpoch - nowDate.dayOfWeek)).waitForNonNullValue()
|
||||
val parentUsedTimes = parentCategory?.let {
|
||||
usedTimesOfCategoryAndWeekByFirstDayOfWeek.get(Pair(it.id, nowDate.dayOfEpoch - nowDate.dayOfWeek)).waitForNonNullValue()
|
||||
} ?: SparseArray()
|
||||
|
||||
val remainingChild = RemainingTime.getRemainingTime(
|
||||
return RemainingTime.getRemainingTime(
|
||||
nowDate.dayOfWeek,
|
||||
buildUsedTimesSparseArray(usedTimes, categoryId),
|
||||
rules,
|
||||
Math.max(0, category.extraTimeInMillis - getCachedExtraTimeToSubstract(category.id))
|
||||
Math.max(0, category.extraTimeInMillis - (usedTimeUpdateHelper.extraTimeToSubtract.get(categoryId) ?: 0))
|
||||
)
|
||||
|
||||
val remainingParent = parentCategory?.let {
|
||||
RemainingTime.getRemainingTime(
|
||||
nowDate.dayOfWeek,
|
||||
buildUsedTimesSparseArray(parentUsedTimes, parentCategory.id),
|
||||
parentRules,
|
||||
Math.max(0, parentCategory.extraTimeInMillis - getCachedExtraTimeToSubstract(parentCategory.id))
|
||||
)
|
||||
}
|
||||
|
||||
return RemainingTime.min(remainingChild, remainingParent)
|
||||
}
|
||||
|
||||
val remainingTimeForegroundApp = getRemainingTime(foregroundAppHandling.categoryId, foregroundAppHandling.parentCategoryId)
|
||||
val remainingTimeBackgroundApp = getRemainingTime(audioPlaybackHandling.categoryId, audioPlaybackHandling.parentCategoryId)
|
||||
val remainingTimeForegroundAppChild = getRemainingTime(foregroundAppHandling.categoryId)
|
||||
val remainingTimeForegroundAppParent = getRemainingTime(foregroundAppHandling.parentCategoryId)
|
||||
val remainingTimeForegroundApp = RemainingTime.min(remainingTimeForegroundAppChild, remainingTimeForegroundAppParent)
|
||||
|
||||
val remainingTimeBackgroundAppChild = getRemainingTime(audioPlaybackHandling.categoryId)
|
||||
val remainingTimeBackgroundAppParent = getRemainingTime(audioPlaybackHandling.parentCategoryId)
|
||||
val remainingTimeBackgroundApp = RemainingTime.min(remainingTimeBackgroundAppChild, remainingTimeBackgroundAppParent)
|
||||
|
||||
// eventually block
|
||||
if (remainingTimeForegroundApp?.hasRemainingTime == false) {
|
||||
|
@ -370,126 +339,75 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
|
|||
}
|
||||
|
||||
// update times
|
||||
suspend fun getUsedTimeItem(categoryId: String?): UsedTimeItem? {
|
||||
categoryId ?: return null
|
||||
|
||||
return cache.usedTimesOfCategoryAndDayOfEpoch.get(categoryId to nowDate.dayOfEpoch).waitForNullableValue()
|
||||
}
|
||||
val timeToSubtract = Math.min(previousMainLogicExecutionTime, MAX_USED_TIME_PER_ROUND)
|
||||
|
||||
val shouldCountForegroundApp = remainingTimeForegroundApp != null && isScreenOn && remainingTimeForegroundApp.hasRemainingTime
|
||||
val shouldCountBackgroundApp = remainingTimeBackgroundApp != null && remainingTimeBackgroundApp.hasRemainingTime
|
||||
val countTwoTypes = shouldCountForegroundApp && shouldCountBackgroundApp
|
||||
val doCategoriesMatch = (
|
||||
foregroundAppHandling.categoryId != null && (
|
||||
foregroundAppHandling.categoryId == audioPlaybackHandling.categoryId ||
|
||||
foregroundAppHandling.categoryId == audioPlaybackHandling.parentCategoryId
|
||||
)
|
||||
) || (
|
||||
foregroundAppHandling.parentCategoryId != null && (
|
||||
foregroundAppHandling.parentCategoryId == audioPlaybackHandling.categoryId ||
|
||||
foregroundAppHandling.parentCategoryId == audioPlaybackHandling.parentCategoryId
|
||||
)
|
||||
)
|
||||
val useSingleCounter = countTwoTypes && doCategoriesMatch
|
||||
|
||||
if (useSingleCounter) {
|
||||
val isBackgroundCategoryHigher = audioPlaybackHandling.categoryId != null &&
|
||||
audioPlaybackHandling.categoryId == foregroundAppHandling.parentCategoryId
|
||||
val categoriesToCount = mutableSetOf<String>()
|
||||
val categoriesToCountExtraTime = mutableSetOf<String>()
|
||||
|
||||
if (usedTimeUpdateHelperForegroundApp !== usedTimeUpdateHelperBackgroundPlayback) {
|
||||
if (isBackgroundCategoryHigher) {
|
||||
usedTimeUpdateHelperForegroundApp?.commit(appLogic)
|
||||
usedTimeUpdateHelperForegroundApp = null
|
||||
} else {
|
||||
usedTimeUpdateHelperBackgroundPlayback?.commit(appLogic)
|
||||
usedTimeUpdateHelperBackgroundPlayback = null
|
||||
if (shouldCountForegroundApp) {
|
||||
remainingTimeForegroundAppChild?.let { remainingTime ->
|
||||
foregroundAppHandling.categoryId?.let { categoryId ->
|
||||
categoriesToCount.add(categoryId)
|
||||
|
||||
if (remainingTime.usingExtraTime) {
|
||||
categoriesToCountExtraTime.add(categoryId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isBackgroundCategoryHigher) {
|
||||
usedTimeUpdateHelperBackgroundPlayback = UsedTimeItemBatchUpdateHelper.eventuallyUpdateInstance(
|
||||
date = nowDate,
|
||||
childCategoryId = audioPlaybackHandling.categoryId!!,
|
||||
parentCategoryId = audioPlaybackHandling.parentCategoryId,
|
||||
oldInstance = usedTimeUpdateHelperBackgroundPlayback,
|
||||
usedTimeItemForDayChild = getUsedTimeItem(audioPlaybackHandling.categoryId),
|
||||
usedTimeItemForDayParent = getUsedTimeItem(audioPlaybackHandling.parentCategoryId),
|
||||
logic = appLogic
|
||||
)
|
||||
remainingTimeForegroundAppParent?.let { remainingTime ->
|
||||
foregroundAppHandling.parentCategoryId?.let {
|
||||
categoriesToCount.add(it)
|
||||
|
||||
usedTimeUpdateHelperForegroundApp = usedTimeUpdateHelperBackgroundPlayback
|
||||
} else {
|
||||
usedTimeUpdateHelperForegroundApp = UsedTimeItemBatchUpdateHelper.eventuallyUpdateInstance(
|
||||
date = nowDate,
|
||||
childCategoryId = foregroundAppHandling.categoryId!!,
|
||||
parentCategoryId = foregroundAppHandling.parentCategoryId,
|
||||
oldInstance = usedTimeUpdateHelperForegroundApp,
|
||||
usedTimeItemForDayChild = getUsedTimeItem(foregroundAppHandling.categoryId),
|
||||
usedTimeItemForDayParent = getUsedTimeItem(foregroundAppHandling.parentCategoryId),
|
||||
logic = appLogic
|
||||
)
|
||||
|
||||
usedTimeUpdateHelperBackgroundPlayback = usedTimeUpdateHelperForegroundApp
|
||||
}
|
||||
} else {
|
||||
if (shouldCountForegroundApp) {
|
||||
usedTimeUpdateHelperForegroundApp = UsedTimeItemBatchUpdateHelper.eventuallyUpdateInstance(
|
||||
date = nowDate,
|
||||
childCategoryId = foregroundAppHandling.categoryId!!,
|
||||
parentCategoryId = foregroundAppHandling.parentCategoryId,
|
||||
oldInstance = usedTimeUpdateHelperForegroundApp,
|
||||
usedTimeItemForDayChild = getUsedTimeItem(foregroundAppHandling.categoryId),
|
||||
usedTimeItemForDayParent = getUsedTimeItem(foregroundAppHandling.parentCategoryId),
|
||||
logic = appLogic
|
||||
)
|
||||
} else {
|
||||
usedTimeUpdateHelperForegroundApp?.commit(appLogic)
|
||||
usedTimeUpdateHelperForegroundApp = null
|
||||
}
|
||||
|
||||
if (shouldCountBackgroundApp) {
|
||||
usedTimeUpdateHelperBackgroundPlayback = UsedTimeItemBatchUpdateHelper.eventuallyUpdateInstance(
|
||||
date = nowDate,
|
||||
childCategoryId = audioPlaybackHandling.categoryId!!,
|
||||
parentCategoryId = audioPlaybackHandling.parentCategoryId,
|
||||
oldInstance = usedTimeUpdateHelperBackgroundPlayback,
|
||||
usedTimeItemForDayChild = getUsedTimeItem(audioPlaybackHandling.categoryId),
|
||||
usedTimeItemForDayParent = getUsedTimeItem(audioPlaybackHandling.parentCategoryId),
|
||||
logic = appLogic
|
||||
)
|
||||
} else {
|
||||
usedTimeUpdateHelperBackgroundPlayback?.commit(appLogic)
|
||||
usedTimeUpdateHelperBackgroundPlayback = null
|
||||
if (remainingTime.usingExtraTime) {
|
||||
categoriesToCountExtraTime.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// count times
|
||||
// never save more than a second of used time
|
||||
// FIXME: currently, this uses extra time if the parent or the child category needs it and it subtracts it from both
|
||||
val timeToSubtract = Math.min(previousMainLogicExecutionTime, MAX_USED_TIME_PER_ROUND)
|
||||
if (shouldCountBackgroundApp) {
|
||||
remainingTimeBackgroundAppChild?.let { remainingTime ->
|
||||
audioPlaybackHandling.categoryId?.let {
|
||||
categoriesToCount.add(it)
|
||||
|
||||
if (usedTimeUpdateHelperForegroundApp === usedTimeUpdateHelperBackgroundPlayback) {
|
||||
usedTimeUpdateHelperForegroundApp?.addUsedTime(
|
||||
time = timeToSubtract,
|
||||
subtractExtraTime = remainingTimeForegroundApp!!.usingExtraTime or remainingTimeBackgroundApp!!.usingExtraTime,
|
||||
appLogic = appLogic
|
||||
)
|
||||
} else {
|
||||
usedTimeUpdateHelperForegroundApp?.addUsedTime(
|
||||
time = timeToSubtract,
|
||||
subtractExtraTime = remainingTimeForegroundApp!!.usingExtraTime,
|
||||
appLogic = appLogic
|
||||
)
|
||||
if (remainingTime.usingExtraTime) {
|
||||
categoriesToCountExtraTime.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
usedTimeUpdateHelperBackgroundPlayback?.addUsedTime(
|
||||
time = timeToSubtract,
|
||||
subtractExtraTime = remainingTimeBackgroundApp!!.usingExtraTime,
|
||||
appLogic = appLogic
|
||||
)
|
||||
remainingTimeBackgroundAppParent?.let { remainingTime ->
|
||||
audioPlaybackHandling.parentCategoryId?.let {
|
||||
categoriesToCount.add(it)
|
||||
|
||||
if (remainingTime.usingExtraTime) {
|
||||
categoriesToCountExtraTime.add(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (categoriesToCount.isNotEmpty()) {
|
||||
categoriesToCount.forEach { categoryId ->
|
||||
usedTimeUpdateHelper.add(
|
||||
categoryId = categoryId,
|
||||
time = timeToSubtract,
|
||||
includingExtraTime = categoriesToCountExtraTime.contains(categoryId)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
usedTimeUpdateHelper.reportCurrentCategories(categoriesToCount)
|
||||
|
||||
if (usedTimeUpdateHelper.shouldDoAutoCommit) {
|
||||
usedTimeUpdateHelper.forceCommit(appLogic)
|
||||
}
|
||||
|
||||
// trigger time warnings
|
||||
// FIXME: this uses the resulting time, not the time per category
|
||||
fun eventuallyTriggerTimeWarning(remaining: RemainingTime, categoryId: String?) {
|
||||
val category = categories.find { it.id == categoryId } ?: return
|
||||
val oldRemainingTime = remaining.includingExtraTime
|
||||
|
@ -509,13 +427,10 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
|
|||
}
|
||||
}
|
||||
|
||||
if (remainingTimeForegroundApp != null) {
|
||||
eventuallyTriggerTimeWarning(remainingTimeForegroundApp, foregroundAppHandling.categoryId)
|
||||
}
|
||||
|
||||
if (remainingTimeBackgroundApp != null) {
|
||||
eventuallyTriggerTimeWarning(remainingTimeBackgroundApp, foregroundAppHandling.categoryId)
|
||||
}
|
||||
remainingTimeForegroundAppChild?.let { eventuallyTriggerTimeWarning(it, foregroundAppHandling.categoryId) }
|
||||
remainingTimeForegroundAppParent?.let { eventuallyTriggerTimeWarning(it, foregroundAppHandling.parentCategoryId) }
|
||||
remainingTimeBackgroundAppChild?.let { eventuallyTriggerTimeWarning(it, audioPlaybackHandling.categoryId) }
|
||||
remainingTimeBackgroundAppParent?.let { eventuallyTriggerTimeWarning(it, audioPlaybackHandling.parentCategoryId) }
|
||||
|
||||
// show notification
|
||||
fun buildStatusMessageWithCurrentAppTitle(
|
||||
|
@ -633,7 +548,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
|
|||
if (foregroundAppHandling.status == BackgroundTaskLogicAppStatus.ShouldBlock) {
|
||||
openLockscreen(foregroundAppPackageName!!, foregroundAppActivityName)
|
||||
|
||||
usedTimeUpdateHelperForegroundApp?.commit(appLogic)
|
||||
commitUsedTimeUpdaters()
|
||||
} else {
|
||||
appLogic.platformIntegration.setShowBlockingOverlay(false)
|
||||
}
|
||||
|
@ -641,7 +556,7 @@ class BackgroundTaskLogic(val appLogic: AppLogic) {
|
|||
if (audioPlaybackHandling.status == BackgroundTaskLogicAppStatus.ShouldBlock && audioPlaybackPackageName != null) {
|
||||
appLogic.platformIntegration.muteAudioIfPossible(audioPlaybackPackageName)
|
||||
|
||||
usedTimeUpdateHelperBackgroundPlayback?.commit(appLogic)
|
||||
commitUsedTimeUpdaters()
|
||||
}
|
||||
} catch (ex: SecurityException) {
|
||||
// this is handled by an other main loop (with a delay)
|
||||
|
|
|
@ -54,11 +54,6 @@ class BackgroundTaskLogicCache (private val appLogic: AppLogic) {
|
|||
return appLogic.database.usedTimes().getUsedTimesOfWeek(key.first, key.second)
|
||||
}
|
||||
}
|
||||
val usedTimesOfCategoryAndDayOfEpoch = object: MultiKeyLiveDataCache<UsedTimeItem?, Pair<String, Int>>() {
|
||||
override fun createValue(key: Pair<String, Int>): LiveData<UsedTimeItem?> {
|
||||
return appLogic.database.usedTimes().getUsedTimesByCategoryIdAndDayOfEpoch(key.first, key.second)
|
||||
}
|
||||
}
|
||||
val shouldDoAutomaticSignOut = SingleItemLiveDataCacheWithRequery { -> appLogic.defaultUserLogic.hasAutomaticSignOut()}
|
||||
|
||||
val liveDataCaches = LiveDataCaches(arrayOf(
|
||||
|
@ -68,7 +63,6 @@ class BackgroundTaskLogicCache (private val appLogic: AppLogic) {
|
|||
appCategories,
|
||||
timeLimitRules,
|
||||
usedTimesOfCategoryAndWeekByFirstDayOfWeek,
|
||||
usedTimesOfCategoryAndDayOfEpoch,
|
||||
shouldDoAutomaticSignOut
|
||||
))
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
/*
|
||||
* 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.logic
|
||||
|
||||
import io.timelimit.android.data.Database
|
||||
import io.timelimit.android.data.model.UsedTimeItem
|
||||
import io.timelimit.android.date.DateInTimezone
|
||||
import io.timelimit.android.livedata.waitForNullableValue
|
||||
import io.timelimit.android.sync.actions.AddUsedTimeAction
|
||||
import io.timelimit.android.sync.actions.apply.ApplyActionUtil
|
||||
|
||||
class UsedTimeItemBatchUpdateHelper(
|
||||
val date: DateInTimezone,
|
||||
val childCategoryId: String,
|
||||
val parentCategoryId: String?,
|
||||
var cachedItemChild: UsedTimeItem?,
|
||||
var cachedItemParent: UsedTimeItem?
|
||||
) {
|
||||
companion object {
|
||||
suspend fun eventuallyUpdateInstance(
|
||||
date: DateInTimezone,
|
||||
childCategoryId: String,
|
||||
parentCategoryId: String?,
|
||||
oldInstance: UsedTimeItemBatchUpdateHelper?,
|
||||
usedTimeItemForDayChild: UsedTimeItem?,
|
||||
usedTimeItemForDayParent: UsedTimeItem?,
|
||||
logic: AppLogic
|
||||
): UsedTimeItemBatchUpdateHelper {
|
||||
if (
|
||||
oldInstance != null &&
|
||||
oldInstance.date == date &&
|
||||
oldInstance.childCategoryId == childCategoryId &&
|
||||
oldInstance.parentCategoryId == parentCategoryId
|
||||
) {
|
||||
if (oldInstance.cachedItemChild != usedTimeItemForDayChild) {
|
||||
oldInstance.cachedItemChild = usedTimeItemForDayChild
|
||||
}
|
||||
|
||||
if (oldInstance.cachedItemParent != usedTimeItemForDayParent) {
|
||||
oldInstance.cachedItemParent = usedTimeItemForDayParent
|
||||
}
|
||||
|
||||
return oldInstance
|
||||
} else {
|
||||
if (oldInstance != null) {
|
||||
oldInstance.commit(logic)
|
||||
}
|
||||
|
||||
return UsedTimeItemBatchUpdateHelper(
|
||||
date = date,
|
||||
childCategoryId = childCategoryId,
|
||||
parentCategoryId = parentCategoryId,
|
||||
cachedItemChild = usedTimeItemForDayChild,
|
||||
cachedItemParent = usedTimeItemForDayParent
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var timeToAdd = 0
|
||||
private var extraTimeToSubtract = 0
|
||||
|
||||
suspend fun addUsedTime(time: Int, subtractExtraTime: Boolean, appLogic: AppLogic) {
|
||||
timeToAdd += time
|
||||
|
||||
if (subtractExtraTime) {
|
||||
extraTimeToSubtract += time
|
||||
}
|
||||
|
||||
if (Math.max(timeToAdd, extraTimeToSubtract) > 1000 * 10 /* 10 seconds */) {
|
||||
commit(appLogic)
|
||||
}
|
||||
}
|
||||
|
||||
fun getTotalUsedTimeChild(): Long = (cachedItemChild?.usedMillis ?: 0) + timeToAdd
|
||||
fun getTotalUsedTimeParent(): Long = (cachedItemParent?.usedMillis ?: 0) + timeToAdd
|
||||
|
||||
fun getCachedExtraTimeToSubtract(): Int {
|
||||
return extraTimeToSubtract
|
||||
}
|
||||
|
||||
suspend fun queryCurrentStatusFromDatabase(database: Database) {
|
||||
cachedItemChild = database.usedTimes().getUsedTimeItem(childCategoryId, date.dayOfEpoch).waitForNullableValue()
|
||||
cachedItemParent = parentCategoryId?.let {
|
||||
database.usedTimes().getUsedTimeItem(parentCategoryId, date.dayOfEpoch).waitForNullableValue()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun commit(logic: AppLogic) {
|
||||
if (timeToAdd == 0) {
|
||||
// do nothing
|
||||
} else {
|
||||
ApplyActionUtil.applyAppLogicAction(
|
||||
action = AddUsedTimeAction(
|
||||
categoryId = childCategoryId,
|
||||
timeToAdd = timeToAdd,
|
||||
dayOfEpoch = date.dayOfEpoch,
|
||||
extraTimeToSubtract = extraTimeToSubtract
|
||||
),
|
||||
appLogic = logic,
|
||||
ignoreIfDeviceIsNotConfigured = true
|
||||
)
|
||||
|
||||
timeToAdd = 0
|
||||
extraTimeToSubtract = 0
|
||||
|
||||
queryCurrentStatusFromDatabase(logic.database)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* TimeLimit Copyright <C> 2019 - 2020 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.logic
|
||||
|
||||
import io.timelimit.android.date.DateInTimezone
|
||||
import io.timelimit.android.sync.actions.AddUsedTimeActionItem
|
||||
import io.timelimit.android.sync.actions.AddUsedTimeActionVersion2
|
||||
import io.timelimit.android.sync.actions.apply.ApplyActionUtil
|
||||
|
||||
class UsedTimeUpdateHelper (val date: DateInTimezone) {
|
||||
val timeToAdd = mutableMapOf<String, Int>()
|
||||
val extraTimeToSubtract = mutableMapOf<String, Int>()
|
||||
var shouldDoAutoCommit = false
|
||||
|
||||
suspend fun add(categoryId: String, time: Int, includingExtraTime: Boolean) {
|
||||
if (time < 0) {
|
||||
throw IllegalArgumentException()
|
||||
}
|
||||
|
||||
if (time == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
timeToAdd[categoryId] = (timeToAdd[categoryId] ?: 0) + time
|
||||
|
||||
if (includingExtraTime) {
|
||||
extraTimeToSubtract[categoryId] = (extraTimeToSubtract[categoryId] ?: 0) + time
|
||||
}
|
||||
|
||||
if (timeToAdd[categoryId]!! >= 1000 * 10) {
|
||||
shouldDoAutoCommit = true
|
||||
}
|
||||
}
|
||||
|
||||
fun reportCurrentCategories(categories: Set<String>) {
|
||||
if (!categories.containsAll(timeToAdd.keys)) {
|
||||
shouldDoAutoCommit = true
|
||||
}
|
||||
|
||||
if (!categories.containsAll(extraTimeToSubtract.keys)) {
|
||||
shouldDoAutoCommit = true
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun forceCommit(appLogic: AppLogic) {
|
||||
if (timeToAdd.isEmpty() && extraTimeToSubtract.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
val categoryIds = timeToAdd.keys + extraTimeToSubtract.keys
|
||||
|
||||
ApplyActionUtil.applyAppLogicAction(
|
||||
action = AddUsedTimeActionVersion2(
|
||||
dayOfEpoch = date.dayOfEpoch,
|
||||
items = categoryIds.map { categoryId ->
|
||||
AddUsedTimeActionItem(
|
||||
categoryId = categoryId,
|
||||
timeToAdd = timeToAdd[categoryId] ?: 0,
|
||||
extraTimeToSubtract = extraTimeToSubtract[categoryId] ?: 0
|
||||
)
|
||||
}
|
||||
),
|
||||
appLogic = appLogic,
|
||||
ignoreIfDeviceIsNotConfigured = true
|
||||
)
|
||||
|
||||
timeToAdd.clear()
|
||||
extraTimeToSubtract.clear()
|
||||
}
|
||||
}
|
|
@ -142,6 +142,82 @@ data class AddUsedTimeAction(val categoryId: String, val dayOfEpoch: Int, val ti
|
|||
}
|
||||
}
|
||||
|
||||
data class AddUsedTimeActionVersion2(val dayOfEpoch: Int, val items: List<AddUsedTimeActionItem>): AppLogicAction() {
|
||||
companion object {
|
||||
const val TYPE_VALUE = "ADD_USED_TIME_V2"
|
||||
private const val DAY_OF_EPOCH = "d"
|
||||
private const val ITEMS = "i"
|
||||
|
||||
fun parse(action: JSONObject): AddUsedTimeActionVersion2 = AddUsedTimeActionVersion2(
|
||||
dayOfEpoch = action.getInt(DAY_OF_EPOCH),
|
||||
items = ParseUtils.readObjectArray(action.getJSONArray(ITEMS)).map { AddUsedTimeActionItem.parse(it) }
|
||||
)
|
||||
}
|
||||
|
||||
init {
|
||||
if (dayOfEpoch < 0) {
|
||||
throw IllegalArgumentException()
|
||||
}
|
||||
|
||||
if (items.isEmpty()) {
|
||||
throw IllegalArgumentException()
|
||||
}
|
||||
|
||||
if (items.distinctBy { it.categoryId }.size != items.size) {
|
||||
throw IllegalArgumentException()
|
||||
}
|
||||
}
|
||||
|
||||
override fun serialize(writer: JsonWriter) {
|
||||
writer.beginObject()
|
||||
|
||||
writer.name(TYPE).value(TYPE_VALUE)
|
||||
writer.name(DAY_OF_EPOCH).value(dayOfEpoch)
|
||||
|
||||
writer.name(ITEMS).beginArray()
|
||||
items.forEach { it.serialize(writer) }
|
||||
writer.endArray()
|
||||
|
||||
writer.endObject()
|
||||
}
|
||||
}
|
||||
|
||||
data class AddUsedTimeActionItem(val categoryId: String, val timeToAdd: Int, val extraTimeToSubtract: Int) {
|
||||
companion object {
|
||||
private const val CATEGORY_ID = "categoryId"
|
||||
private const val TIME_TO_ADD = "tta"
|
||||
private const val EXTRA_TIME_TO_SUBTRACT = "etts"
|
||||
|
||||
fun parse(item: JSONObject): AddUsedTimeActionItem = AddUsedTimeActionItem(
|
||||
categoryId = item.getString(CATEGORY_ID),
|
||||
timeToAdd = item.getInt(TIME_TO_ADD),
|
||||
extraTimeToSubtract = item.getInt(EXTRA_TIME_TO_SUBTRACT)
|
||||
)
|
||||
}
|
||||
|
||||
init {
|
||||
IdGenerator.assertIdValid(categoryId)
|
||||
|
||||
if (timeToAdd < 0) {
|
||||
throw IllegalArgumentException()
|
||||
}
|
||||
|
||||
if (extraTimeToSubtract < 0) {
|
||||
throw IllegalArgumentException()
|
||||
}
|
||||
}
|
||||
|
||||
fun serialize(writer: JsonWriter) {
|
||||
writer.beginObject()
|
||||
|
||||
writer.name(CATEGORY_ID).value(categoryId)
|
||||
writer.name(TIME_TO_ADD).value(timeToAdd)
|
||||
writer.name(EXTRA_TIME_TO_SUBTRACT).value(extraTimeToSubtract)
|
||||
|
||||
writer.endObject()
|
||||
}
|
||||
}
|
||||
|
||||
// data class ClearTemporarilyAllowedAppsAction(val deviceId: String): AppLogicAction(), LocalOnlyAction
|
||||
|
||||
data class InstalledApp(val packageName: String, val title: String, val isLaunchable: Boolean, val recommendation: AppRecommendation) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* TimeLimit Copyright <C> 2019 Jonas Lochmann
|
||||
* TimeLimit Copyright <C> 2019 - 2020 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
|
||||
|
@ -26,6 +26,7 @@ object ActionParser {
|
|||
SignOutAtDeviceAction.TYPE_VALUE -> SignOutAtDeviceAction
|
||||
UpdateAppActivitiesAction.TYPE_VALUE -> UpdateAppActivitiesAction.parse(action)
|
||||
UpdateDeviceStatusAction.TYPE_VALUE -> UpdateDeviceStatusAction.parse(action)
|
||||
AddUsedTimeActionVersion2.TYPE_VALUE -> AddUsedTimeActionVersion2.parse(action)
|
||||
else -> throw IllegalStateException()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* TimeLimit Copyright <C> 2019 Jonas Lochmann
|
||||
* TimeLimit Copyright <C> 2019 - 2020 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
|
||||
|
@ -106,6 +106,51 @@ object ApplyActionUtil {
|
|||
database.setTransactionSuccessful()
|
||||
syncUtil.requestVeryUnimportantSync()
|
||||
|
||||
return@executeAndWait
|
||||
}
|
||||
}
|
||||
} else if (action is AddUsedTimeActionVersion2) {
|
||||
val previousAction = database.pendingSyncAction().getLatestUnscheduledActionSync()
|
||||
|
||||
if (previousAction != null && previousAction.type == PendingSyncActionType.AppLogic) {
|
||||
val parsed = ActionParser.parseAppLogicAction(JSONObject(previousAction.encodedAction))
|
||||
|
||||
if (parsed is AddUsedTimeActionVersion2 && parsed.dayOfEpoch == action.dayOfEpoch) {
|
||||
var updatedAction: AddUsedTimeActionVersion2 = parsed
|
||||
|
||||
action.items.forEach { newItem ->
|
||||
val oldItem = updatedAction.items.find { it.categoryId == newItem.categoryId }
|
||||
|
||||
if (oldItem == null) {
|
||||
updatedAction = updatedAction.copy(
|
||||
items = updatedAction.items + listOf(newItem)
|
||||
)
|
||||
} else {
|
||||
val mergedItem = AddUsedTimeActionItem(
|
||||
timeToAdd = oldItem.timeToAdd + newItem.timeToAdd,
|
||||
extraTimeToSubtract = oldItem.extraTimeToSubtract + newItem.extraTimeToSubtract,
|
||||
categoryId = newItem.categoryId
|
||||
)
|
||||
|
||||
updatedAction = updatedAction.copy(
|
||||
items = updatedAction.items.filter { it.categoryId != mergedItem.categoryId } + listOf(mergedItem)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// update the previous action
|
||||
database.pendingSyncAction().updateEncodedActionSync(
|
||||
sequenceNumber = previousAction.sequenceNumber,
|
||||
action = StringWriter().apply {
|
||||
JsonWriter(this).apply {
|
||||
updatedAction.serialize(this)
|
||||
}
|
||||
}.toString()
|
||||
)
|
||||
|
||||
database.setTransactionSuccessful()
|
||||
syncUtil.requestVeryUnimportantSync()
|
||||
|
||||
return@executeAndWait
|
||||
}
|
||||
}
|
||||
|
@ -126,7 +171,7 @@ object ApplyActionUtil {
|
|||
userId = ""
|
||||
))
|
||||
|
||||
if (action is AddUsedTimeAction) {
|
||||
if (action is AddUsedTimeAction || action is AddUsedTimeActionVersion2) {
|
||||
syncUtil.requestVeryUnimportantSync()
|
||||
} else {
|
||||
if (BuildConfig.DEBUG) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* TimeLimit Copyright <C> 2019 Jonas Lochmann
|
||||
* TimeLimit Copyright <C> 2019 - 2020 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
|
||||
|
@ -76,6 +76,37 @@ object LocalDatabaseAppLogicActionDispatcher {
|
|||
|
||||
null
|
||||
}
|
||||
is AddUsedTimeActionVersion2 -> {
|
||||
action.items.forEach { item ->
|
||||
database.category().getCategoryByIdSync(item.categoryId)!!
|
||||
|
||||
val updatedRows = database.usedTimes().addUsedTime(
|
||||
categoryId = item.categoryId,
|
||||
timeToAdd = item.timeToAdd,
|
||||
dayOfEpoch = action.dayOfEpoch
|
||||
)
|
||||
|
||||
if (updatedRows == 0) {
|
||||
// create new entry
|
||||
|
||||
database.usedTimes().insertUsedTime(UsedTimeItem(
|
||||
categoryId = item.categoryId,
|
||||
dayOfEpoch = action.dayOfEpoch,
|
||||
usedMillis = item.timeToAdd.toLong()
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
if (item.extraTimeToSubtract != 0) {
|
||||
database.category().subtractCategoryExtraTime(
|
||||
categoryId = item.categoryId,
|
||||
removedExtraTime = item.extraTimeToSubtract
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
null
|
||||
}
|
||||
is AddInstalledAppsAction -> {
|
||||
database.app().addAppsSync(
|
||||
action.apps.map {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue