From d487bfe0e694373ecb195257f9794f2aca6b03c2 Mon Sep 17 00:00:00 2001 From: preethamrn Date: Fri, 21 Jul 2023 14:22:15 -0700 Subject: [PATCH 1/3] Clamp records to time range when filtering by time --- .../core/interactor/RecordFilterInteractor.kt | 111 +++++++++--------- .../domain/mapper/RangeMapper.kt | 7 ++ 2 files changed, 62 insertions(+), 56 deletions(-) diff --git a/core/src/main/java/com/example/util/simpletimetracker/core/interactor/RecordFilterInteractor.kt b/core/src/main/java/com/example/util/simpletimetracker/core/interactor/RecordFilterInteractor.kt index 5ef6a1fcf..fb8196c87 100644 --- a/core/src/main/java/com/example/util/simpletimetracker/core/interactor/RecordFilterInteractor.kt +++ b/core/src/main/java/com/example/util/simpletimetracker/core/interactor/RecordFilterInteractor.kt @@ -164,49 +164,49 @@ class RecordFilterInteractor @Inject constructor( // TODO multitask filters. - fun RecordBase.selectedByActivity(): Boolean { - return typeIds.isEmpty() || this.typeIds.firstOrNull().orZero() in typeIds + fun RecordBase.selectedByActivity(): RecordBase? { + return if (typeIds.isEmpty() || this.typeIds.firstOrNull().orZero() in typeIds) this else null } - fun RecordBase.selectedByComment(): Boolean { - if (selectedCommentItems.isEmpty()) return true + fun RecordBase.selectedByComment(): RecordBase? { + if (selectedCommentItems.isEmpty()) return this val comment = this.comment.lowercase() - return (selectedNoComment && comment.isEmpty()) || + return if ((selectedNoComment && comment.isEmpty()) || (selectedAnyComment && comment.isNotEmpty()) || - (comment.isNotEmpty() && comments.any { comment.contains(it) }) + (comment.isNotEmpty() && comments.any { comment.contains(it) })) this else null } - fun RecordBase.selectedByDate(): Boolean { - if (ranges.isEmpty()) return true - return ranges.any { range -> timeStarted < range.timeEnded && timeEnded > range.timeStarted } + fun RecordBase.selectedByDate(): RecordBase? { + if (ranges.isEmpty()) return this + return if (ranges.any { range -> timeStarted < range.timeEnded && timeEnded > range.timeStarted }) this else null } - fun RecordBase.selectedByTag(): Boolean { - if (selectedTagItems.isEmpty()) return true + fun RecordBase.selectedByTag(): RecordBase? { + if (selectedTagItems.isEmpty()) return this return if (tagIds.isNotEmpty()) { - tagIds.any { tagId -> tagId in selectedTaggedIds } + if (tagIds.any { tagId -> tagId in selectedTaggedIds }) this else null } else { - selectedUntagged + if (selectedUntagged) this else null } } - fun RecordBase.filteredByTag(): Boolean { - if (filteredTagItems.isEmpty()) return false + fun RecordBase.filteredByTag(): RecordBase? { + if (filteredTagItems.isEmpty()) return this return if (tagIds.isNotEmpty()) { - tagIds.any { tagId -> tagId in filteredTaggedIds } + if (tagIds.any { tagId -> tagId in filteredTaggedIds }) null else this } else { - filteredUntagged + if (filteredUntagged) null else this } } - fun RecordBase.isManuallyFiltered(): Boolean { - if (manuallyFilteredIds.isEmpty()) return false - if (this !is Record) return false - return id in manuallyFilteredIds + fun RecordBase.isManuallyFiltered(): RecordBase? { + if (manuallyFilteredIds.isEmpty()) return this + if (this !is Record) return this + return if (id in manuallyFilteredIds) null else this } - fun RecordBase.selectedByDayOfWeek(): Boolean { - if (daysOfWeek.isEmpty()) return true + fun RecordBase.selectedByDayOfWeek(): RecordBase? { + if (daysOfWeek.isEmpty()) return this val daysOfRecord: MutableSet = mutableSetOf() val startDay = timeMapper.getDayOfWeek( @@ -243,51 +243,50 @@ class RecordFilterInteractor @Inject constructor( } } - return daysOfRecord.any { it in daysOfWeek } + return if (daysOfRecord.any { it in daysOfWeek }) this else null } - fun RecordBase.selectedByTimeOfDay(): Boolean { - if (timeOfDay == null) return true + fun RecordBase.selectedByTimeOfDay(): List? { + if (timeOfDay == null) return listOf(this) // Check empty range. - if (timeOfDay.duration == 0L) return false + if (timeOfDay.duration == 0L) return null + + // probably try something like this from selectedByDayOfWeek // Check long records. - if (duration > dayInMillis) return true + val filteredRecords = ArrayList() - val recordStart = timeStarted - timeMapper.getStartOfDayTimeStamp(timeStarted, calendar) - val recordEnd = timeEnded - timeMapper.getStartOfDayTimeStamp(timeEnded, calendar) - val recordRanges = if (recordStart <= recordEnd) { - listOf(Range(recordStart, recordEnd)) - } else { - listOf(Range(0, recordEnd), Range(recordStart, dayInMillis)) - } - val timeOfDayRanges = if (timeOfDay.timeStarted <= timeOfDay.timeEnded) { - listOf(Range(timeOfDay.timeStarted, timeOfDay.timeEnded)) - } else { - listOf(Range(0, timeOfDay.timeEnded), Range(timeOfDay.timeStarted, dayInMillis)) + // Continuously add one day to time start until reach time ended. + // For records spanning multiple days (eg. 48+ hours), we need to split it into multiple records + // in order to support clamping because there will be two regions that intersect with the time range. + calendar.timeInMillis = timeOfDay.timeStarted + timeMapper.getStartOfDayTimeStamp(timeStarted, calendar) + while (calendar.timeInMillis < timeEnded) { + val filterRange = Range(calendar.timeInMillis, calendar.timeInMillis + timeOfDay.timeEnded) + if (rangeMapper.isRecordInRange(this, filterRange)) + filteredRecords.add(rangeMapper.clampRecordToRange(this, filterRange)) + calendar.add(Calendar.DATE, 1) } - return recordRanges.any { recordRange -> - timeOfDayRanges.any { it.isOverlappingWith(recordRange) } - } + return if (filteredRecords.size > 0) filteredRecords else null } - fun RecordBase.selectedByDuration(): Boolean { - if (durations.isEmpty()) return true - return durations.any { duration >= it.timeStarted && duration <= it.timeEnded } + fun RecordBase.selectedByDuration(): RecordBase? { + if (durations.isEmpty()) return this + return if (durations.any { duration >= it.timeStarted && duration <= it.timeEnded }) this else null } - return@withContext records.filter { record -> - record.selectedByActivity() && - record.selectedByComment() && - record.selectedByDate() && - record.selectedByTag() && - !record.filteredByTag() && - !record.isManuallyFiltered() && - record.selectedByDayOfWeek() && - record.selectedByTimeOfDay() && - record.selectedByDuration() - } + return@withContext records.mapNotNull { record -> + record + .selectedByActivity() + ?.selectedByComment() + ?.selectedByDate() + ?.selectedByTag() + ?.filteredByTag() + ?.isManuallyFiltered() + ?.selectedByDayOfWeek() + ?.selectedByDuration() + ?.selectedByTimeOfDay() + }.flatten() } private suspend fun getAllRecords( diff --git a/domain/src/main/java/com/example/util/simpletimetracker/domain/mapper/RangeMapper.kt b/domain/src/main/java/com/example/util/simpletimetracker/domain/mapper/RangeMapper.kt index 041f3cb5c..0896f4476 100644 --- a/domain/src/main/java/com/example/util/simpletimetracker/domain/mapper/RangeMapper.kt +++ b/domain/src/main/java/com/example/util/simpletimetracker/domain/mapper/RangeMapper.kt @@ -25,6 +25,13 @@ class RangeMapper @Inject constructor() { return records.filter { it.isInRange(range) } } + fun isRecordInRange( + record: RecordBase, + range: Range, + ): Boolean { + return record.isInRange(range) + } + fun clampToRange( record: RecordBase, range: Range, From c7a039d92c6003c930ca6543eadc82e734fa3cbb Mon Sep 17 00:00:00 2001 From: preethamrn Date: Tue, 1 Aug 2023 00:50:14 -0700 Subject: [PATCH 2/3] Fix bug where incorrect time range length was being used --- .../core/interactor/RecordFilterInteractor.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/com/example/util/simpletimetracker/core/interactor/RecordFilterInteractor.kt b/core/src/main/java/com/example/util/simpletimetracker/core/interactor/RecordFilterInteractor.kt index fb8196c87..bef3be20b 100644 --- a/core/src/main/java/com/example/util/simpletimetracker/core/interactor/RecordFilterInteractor.kt +++ b/core/src/main/java/com/example/util/simpletimetracker/core/interactor/RecordFilterInteractor.kt @@ -260,8 +260,9 @@ class RecordFilterInteractor @Inject constructor( // For records spanning multiple days (eg. 48+ hours), we need to split it into multiple records // in order to support clamping because there will be two regions that intersect with the time range. calendar.timeInMillis = timeOfDay.timeStarted + timeMapper.getStartOfDayTimeStamp(timeStarted, calendar) + val timeRangeLength = timeOfDay.timeEnded - timeOfDay.timeStarted while (calendar.timeInMillis < timeEnded) { - val filterRange = Range(calendar.timeInMillis, calendar.timeInMillis + timeOfDay.timeEnded) + val filterRange = Range(calendar.timeInMillis, calendar.timeInMillis + timeRangeLength) if (rangeMapper.isRecordInRange(this, filterRange)) filteredRecords.add(rangeMapper.clampRecordToRange(this, filterRange)) calendar.add(Calendar.DATE, 1) From c416d8cc327616ae64e36a621cfe9d917c629cbe Mon Sep 17 00:00:00 2001 From: preethamrn Date: Tue, 1 Aug 2023 01:34:09 -0700 Subject: [PATCH 3/3] Fix issue with time range across date boundaries --- .../core/interactor/RecordFilterInteractor.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/example/util/simpletimetracker/core/interactor/RecordFilterInteractor.kt b/core/src/main/java/com/example/util/simpletimetracker/core/interactor/RecordFilterInteractor.kt index bef3be20b..44efce1a8 100644 --- a/core/src/main/java/com/example/util/simpletimetracker/core/interactor/RecordFilterInteractor.kt +++ b/core/src/main/java/com/example/util/simpletimetracker/core/interactor/RecordFilterInteractor.kt @@ -259,8 +259,16 @@ class RecordFilterInteractor @Inject constructor( // Continuously add one day to time start until reach time ended. // For records spanning multiple days (eg. 48+ hours), we need to split it into multiple records // in order to support clamping because there will be two regions that intersect with the time range. - calendar.timeInMillis = timeOfDay.timeStarted + timeMapper.getStartOfDayTimeStamp(timeStarted, calendar) - val timeRangeLength = timeOfDay.timeEnded - timeOfDay.timeStarted + var timeRangeLength = timeOfDay.duration + if (timeOfDay.duration >= 0) { + calendar.timeInMillis = timeOfDay.timeStarted + timeMapper.getStartOfDayTimeStamp(timeStarted, calendar) + } else { + // If duration is negative then it means we are crossing a day boundary (eg. 2300hrs -> 200hrs) + // So we add 1 day to the timeRangeLength and start the calendar at the previous day to ensure we don't skip the event. + calendar.timeInMillis = timeOfDay.timeStarted + timeMapper.getStartOfDayTimeStamp(timeStarted, calendar) + calendar.add(Calendar.DATE, -1) + timeRangeLength = timeOfDay.duration + 86400000L + } while (calendar.timeInMillis < timeEnded) { val filterRange = Range(calendar.timeInMillis, calendar.timeInMillis + timeRangeLength) if (rangeMapper.isRecordInRange(this, filterRange))