Skip to content

Commit

Permalink
add multitasking to retroactive mode
Browse files Browse the repository at this point in the history
  • Loading branch information
Razeeman committed Nov 22, 2024
1 parent 1acf082 commit d710325
Show file tree
Hide file tree
Showing 33 changed files with 338 additions and 149 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ object Base {
const val namespace = "com.example.util.simpletimetracker"

// Raise by 2 to account for wear version code.
const val versionCode = 77
const val versionCode = 79
const val versionName = "1.46"
const val minSDK = 21
const val currentSDK = 34
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,17 @@ class RecordRepeatInteractor @Inject constructor(
): ActionResult {
val type = prefsInteractor.getRepeatButtonType()

// TODO repeat several records?
val prevRecord = recordInteractor.getPrev(
timeStarted = System.currentTimeMillis(),
limit = 2,
).let {
when (type) {
is RepeatButtonType.RepeatLast -> it.getOrNull(0)
is RepeatButtonType.RepeatBeforeLast -> it.getOrNull(1)
is RepeatButtonType.RepeatLast -> it
is RepeatButtonType.RepeatBeforeLast -> if (it != null) {
recordInteractor.getPrev(timeStarted = it.timeEnded - 1)
} else {
null
}
}
} ?: run {
messageShower(R.string.running_records_repeat_no_prev_record)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ interface RecordDao {
suspend fun getFromRangeByType(typesIds: List<Long>, start: Long, end: Long): List<RecordWithRecordTagsDBO>

@Transaction
@Query("SELECT * FROM records WHERE time_ended <= :timeStarted ORDER BY time_ended DESC LIMIT :limit")
suspend fun getPrev(timeStarted: Long, limit: Long): List<RecordWithRecordTagsDBO>
@Query("SELECT * FROM records WHERE time_ended <= :timeStarted ORDER BY time_ended DESC LIMIT 1")
suspend fun getPrev(timeStarted: Long): RecordWithRecordTagsDBO?

@Transaction
@Query("SELECT * FROM records WHERE time_started >= :timeEnded ORDER BY time_started ASC LIMIT 1")
Expand All @@ -75,6 +75,14 @@ interface RecordDao {
@Query("SELECT time_ended FROM records WHERE time_ended > :fromTimestamp ORDER BY time_ended ASC LIMIT 1")
suspend fun getNextTimeEnded(fromTimestamp: Long): Long?

@Transaction
@Query("SELECT * FROM records WHERE time_started = :timeStarted")
suspend fun getByTimeStarted(timeStarted: Long): List<RecordWithRecordTagsDBO>

@Transaction
@Query("SELECT * FROM records WHERE time_ended = :timeEnded")
suspend fun getByTimeEnded(timeEnded: Long): List<RecordWithRecordTagsDBO>

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(record: RecordDBO): Long

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,9 @@ class RecordRepoImpl @Inject constructor(
)
}

override suspend fun getPrev(timeStarted: Long, limit: Long): List<Record> = withContext(Dispatchers.IO) {
override suspend fun getPrev(timeStarted: Long): Record? = withContext(Dispatchers.IO) {
logDataAccess("getPrev")
recordDao.getPrev(timeStarted, limit).map(::mapItem)
recordDao.getPrev(timeStarted)?.let(::mapItem)
}

override suspend fun getNext(timeEnded: Long): Record? = withContext(Dispatchers.IO) {
Expand Down Expand Up @@ -137,6 +137,16 @@ class RecordRepoImpl @Inject constructor(
recordDao.getNextTimeEnded(fromTimestamp)
}

override suspend fun getByTimeStarted(timeStarted: Long): List<Record> = withContext(Dispatchers.IO) {
logDataAccess("getByTimeStarted")
recordDao.getByTimeStarted(timeStarted).map(::mapItem)
}

override suspend fun getByTimeEnded(timeEnded: Long): List<Record> = withContext(Dispatchers.IO) {
logDataAccess("getByTimeEnded")
recordDao.getByTimeEnded(timeEnded).map(::mapItem)
}

override suspend fun add(record: Record): Long = mutex.withLockedCache(
logMessage = "add",
accessSource = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.example.util.simpletimetracker.domain.interactor

import com.example.util.simpletimetracker.domain.model.Range
import com.example.util.simpletimetracker.domain.model.Record
import com.example.util.simpletimetracker.domain.model.RecordDataSelectionDialogResult
import com.example.util.simpletimetracker.domain.model.RecordType
Expand Down Expand Up @@ -55,11 +54,31 @@ class AddRunningRecordMediator @Inject constructor(
}
}

suspend fun startTimers(
typeIds: Set<Long>,
) {
val current = System.currentTimeMillis()
val timeStarted = StartTime.Current(current)
val prevRecords = recordInteractor.getAllPrev(current)
typeIds.forEachIndexed { index, id ->
startTimer(
typeId = id,
tagIds = emptyList(),
comment = "",
timeStarted = timeStarted,
prevRecords = PrevRecords.Records(prevRecords),
// Update only on last.
updateNotificationSwitch = index == typeIds.size - 1,
)
}
}

suspend fun startTimer(
typeId: Long,
tagIds: List<Long>,
comment: String,
timeStarted: StartTime = StartTime.TakeCurrent,
prevRecords: PrevRecords = PrevRecords.Load,
updateNotificationSwitch: Boolean = true,
checkDefaultDuration: Boolean = true,
) {
Expand All @@ -70,25 +89,30 @@ class AddRunningRecordMediator @Inject constructor(
is StartTime.Timestamp -> timeStarted.timestampMs
}
val retroactiveTrackingMode = prefsInteractor.getRetroactiveTrackingMode()
val prevRecord = if (retroactiveTrackingMode) {
recordInteractor.getPrev(actualTimeStarted).firstOrNull()
val actualPrevRecords = if (retroactiveTrackingMode) {
when (prevRecords) {
is PrevRecords.Load -> recordInteractor.getAllPrev(actualTimeStarted)
is PrevRecords.Records -> prevRecords.records
}
} else {
null
emptyList()
}
val rulesResult = if (
retroactiveTrackingMode &&
getPrevRecordToMergeWith(typeId, actualPrevRecords) != null
) {
// No need to check rules on merge.
ComplexRuleProcessActionInteractor.Result(
isMultitaskingAllowed = ResultContainer.Undefined,
tagsIds = emptySet(),
)
} else {
processRules(
typeId = typeId,
timeStarted = actualTimeStarted,
prevRecords = actualPrevRecords,
)
}
val rulesResult = processRules(
typeId = typeId,
timeStarted = if (
retroactiveTrackingMode &&
prevRecord != null &&
shouldMergeWithPrevRecord(typeId, prevRecord)
) {
// If will merge - it will be one record,
// so need to check rules from original start.
prevRecord.timeStarted
} else {
actualTimeStarted
},
)
processMultitasking(
typeId = typeId,
isMultitaskingAllowedByRules = rulesResult.isMultitaskingAllowed,
Expand Down Expand Up @@ -116,7 +140,7 @@ class AddRunningRecordMediator @Inject constructor(
updateNotificationSwitch = updateNotificationSwitch,
)
if (retroactiveTrackingMode) {
addRetroactiveModeInternal(startParams, prevRecord)
addRetroactiveModeInternal(startParams, actualPrevRecords)
} else {
addInternal(startParams, checkDefaultDuration)
}
Expand Down Expand Up @@ -160,12 +184,12 @@ class AddRunningRecordMediator @Inject constructor(

private suspend fun addRetroactiveModeInternal(
params: StartParams,
prevRecord: Record?,
prevRecords: List<Record>,
) {
val type = recordTypeInteractor.get(params.typeId) ?: return

if (type.defaultDuration > 0L) {
val newTimeStarted = prevRecord?.timeEnded
val newTimeStarted = prevRecords.firstOrNull()?.timeEnded
?: (params.timeStarted - type.defaultDuration * 1000)
addInstantRecord(
params = params.copy(timeStarted = newTimeStarted),
Expand All @@ -174,7 +198,7 @@ class AddRunningRecordMediator @Inject constructor(
} else {
addRecordRetroactively(
params = params,
prevRecord = prevRecord,
prevRecords = prevRecords,
)
}
}
Expand Down Expand Up @@ -218,10 +242,10 @@ class AddRunningRecordMediator @Inject constructor(

private suspend fun addRecordRetroactively(
params: StartParams,
prevRecord: Record?,
prevRecords: List<Record>,
) {
val shouldMerge = shouldMergeWithPrevRecord(params.typeId, prevRecord)
val record = if (prevRecord != null && shouldMerge) {
val prevRecord = getPrevRecordToMergeWith(params.typeId, prevRecords)
val record = if (prevRecord != null) {
Record(
id = prevRecord.id, // Updates existing record.
typeId = params.typeId,
Expand All @@ -233,7 +257,7 @@ class AddRunningRecordMediator @Inject constructor(
?: prevRecord.tagIds,
)
} else {
val newTimeStarted = prevRecord?.timeEnded
val newTimeStarted = prevRecords.firstOrNull()?.timeEnded
?: (params.timeStarted - TimeUnit.MINUTES.toMillis(5))
Record(
id = 0L, // Creates new record.
Expand All @@ -253,21 +277,17 @@ class AddRunningRecordMediator @Inject constructor(
private suspend fun processRules(
typeId: Long,
timeStarted: Long,
prevRecords: List<Record>,
): ComplexRuleProcessActionInteractor.Result {
// If no rules - no need to check them.
return if (complexRuleProcessActionInteractor.hasRules()) {
// Check running records but also record that are recorded for this time.
val currentRecords = runningRecordInteractor.getAll() +
recordInteractor.getFromRange(Range(timeStarted, timeStarted))
// TODO do not check current records for Continue action?
val currentRecords = runningRecordInteractor.getAll()

// If no current records - check closest previous.
val prevRecord = if (currentRecords.isEmpty()) {
recordInteractor.getPrev(timeStarted = timeStarted, limit = 1)
} else {
emptySet()
}
val records = currentRecords.ifEmpty { prevRecords }

val currentTypeIds = (currentRecords + prevRecord)
val currentTypeIds = records
.map { it.typeIds }
.flatten()
.toSet()
Expand Down Expand Up @@ -319,11 +339,11 @@ class AddRunningRecordMediator @Inject constructor(
return (tagIds + defaultTags + tagIdsFromRules).toSet().toList()
}

private fun shouldMergeWithPrevRecord(
private fun getPrevRecordToMergeWith(
typeId: Long,
prevRecord: Record?,
): Boolean {
return prevRecord != null && prevRecord.typeId == typeId
prevRecords: List<Record>,
): Record? {
return prevRecords.firstOrNull { it.typeId == typeId }
}

private data class StartParams(
Expand All @@ -339,4 +359,9 @@ class AddRunningRecordMediator @Inject constructor(
data class Timestamp(val timestampMs: Long) : StartTime
object TakeCurrent : StartTime
}

sealed interface PrevRecords {
data class Records(val records: List<Record>) : PrevRecords
object Load : PrevRecords
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,26 @@ class RecordInteractor @Inject constructor(
return recordRepo.get(id)
}

suspend fun getPrev(timeStarted: Long, limit: Long = 1): List<Record> {
return recordRepo.getPrev(
timeStarted = timeStarted,
limit = limit,
)
suspend fun getPrev(timeStarted: Long): Record? {
return recordRepo.getPrev(timeStarted)
}

// Can return several records ended at the same time.
suspend fun getAllPrev(timeStarted: Long): List<Record> {
val prev = recordRepo.getPrev(timeStarted) ?: return emptyList()
return recordRepo.getByTimeEnded(prev.timeEnded)
}

suspend fun getNext(timeEnded: Long): Record? {
return recordRepo.getNext(timeEnded)
}

// Can return several records ended at the same time.
suspend fun getAllNext(timeStarted: Long): List<Record> {
val prev = recordRepo.getNext(timeStarted) ?: return emptyList()
return recordRepo.getByTimeStarted(prev.timeStarted)
}

suspend fun getPrevTimeStarted(fromTimestamp: Long): Long? {
return recordRepo.getPrevTimeStarted(fromTimestamp)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,14 @@ interface RecordRepo {

suspend fun getFromRangeByType(typeIds: List<Long>, range: Range): List<Record>

suspend fun getPrev(timeStarted: Long, limit: Long): List<Record>
suspend fun getPrev(timeStarted: Long): Record?

suspend fun getNext(timeEnded: Long): Record?

suspend fun getByTimeStarted(timeStarted: Long): List<Record>

suspend fun getByTimeEnded(timeEnded: Long): List<Record>

suspend fun getPrevTimeStarted(fromTimestamp: Long): Long?

suspend fun getNextTimeStarted(fromTimestamp: Long): Long?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ fun createRecordTypeAdapterDelegate(
itemIsChecked = item.isChecked.orFalse()
itemCompleteIsAnimated = true
itemIsComplete = item.isComplete
val newItemScale = if (item.isSelected) 1.1f else 1.0f
scaleX = newItemScale
scaleY = newItemScale
getCheckmarkOutline().itemIsFiltered = item.itemIsFiltered
onItemClick?.let { setOnClickWith(item, it) }
onItemLongClick?.let { setOnLongClick { it(item, mapOf(this to transitionName)) } }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ data class RecordTypeViewData(
val isChecked: Boolean? = null,
val itemIsFiltered: Boolean = false,
val isComplete: Boolean = false,
val isSelected: Boolean = false,
) : ViewHolderType {

override fun getUniqueId(): Long = id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,8 +318,8 @@ class ChangeRecordActionsAdjustDelegate @Inject constructor(
newTimeEnded: Long,
adjustNextRecordAvailable: Boolean,
): AdjacentRecords {
suspend fun getNext(): Record? {
return recordInteractor.getNext(newTimeEnded)
suspend fun getNext(): List<Record> {
return recordInteractor.getAllNext(newTimeEnded)
}

val recordRange = Range(timeStarted = newTimeStarted, timeEnded = newTimeEnded)
Expand All @@ -328,14 +328,14 @@ class ChangeRecordActionsAdjustDelegate @Inject constructor(

val previousRecords = adjacentRecords
.filter { it.timeStarted < newTimeStarted && it.timeEnded <= newTimeEnded }
.ifEmpty { recordInteractor.getPrev(newTimeStarted) }
.ifEmpty { recordInteractor.getAllPrev(newTimeStarted) }
.filter { it.id != recordId }
val overlappedRecords = adjacentRecords
.filter { it.timeStarted >= newTimeStarted && it.timeEnded <= newTimeEnded }
.filter { it.id != recordId }
val nextRecords = adjacentRecords
.filter { it.timeStarted >= newTimeStarted && it.timeEnded > newTimeEnded }
.ifEmpty { listOfNotNull(if (adjustNextRecordAvailable) getNext() else null) }
.ifEmpty { if (adjustNextRecordAvailable) getNext() else emptyList() }
.takeIf { adjustNextRecordAvailable }
.orEmpty()
.filter { it.id != recordId }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -846,7 +846,7 @@ abstract class ChangeRecordBaseViewModel(
}

private suspend fun initializePrevRecord() {
prevRecord = recordInteractor.getPrev(timeStarted = originalTimeStarted).firstOrNull()
prevRecord = recordInteractor.getPrev(timeStarted = originalTimeStarted)
}

private suspend fun loadTypesViewData(): List<ViewHolderType> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,7 @@ class ChangeRecordViewModel @Inject constructor(
val default = newTimeEnded - ONE_HOUR

return if (daysFromToday == 0) {
recordInteractor.getPrev(newTimeEnded)
.firstOrNull()
?.timeEnded
?: default
recordInteractor.getPrev(newTimeEnded)?.timeEnded ?: default
} else {
default
}
Expand Down
Loading

0 comments on commit d710325

Please sign in to comment.