Skip to content

Commit

Permalink
refactor external events logic
Browse files Browse the repository at this point in the history
  • Loading branch information
Razeeman committed Dec 7, 2024
1 parent 43af918 commit 61ce510
Show file tree
Hide file tree
Showing 32 changed files with 434 additions and 331 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import com.example.util.simpletimetracker.feature_notification.activitySwitch.in
import com.example.util.simpletimetracker.feature_notification.goalTime.interactor.NotificationGoalCountInteractorImpl
import com.example.util.simpletimetracker.feature_notification.goalTime.interactor.NotificationGoalRangeEndInteractorImpl
import com.example.util.simpletimetracker.feature_notification.pomodoro.interactor.PomodoroCycleNotificationInteractorImpl
import com.example.util.simpletimetracker.feature_notification.recordType.interactor.ActivityStartedStoppedBroadcastInteractorImpl
import com.example.util.simpletimetracker.feature_notification.external.ActivityStartedStoppedBroadcastInteractorImpl
import com.example.util.simpletimetracker.feature_notification.recordType.interactor.NotificationTypeInteractorImpl
import dagger.Binds
import dagger.Module
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.example.util.simpletimetracker.feature_notification.recordType.interactor
package com.example.util.simpletimetracker.feature_notification.external

import android.content.Context
import android.content.Intent
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
package com.example.util.simpletimetracker.feature_notification.external

import com.example.util.simpletimetracker.domain.extension.orEmpty
import com.example.util.simpletimetracker.domain.interactor.AddRecordMediator
import com.example.util.simpletimetracker.domain.interactor.AddRunningRecordMediator
import com.example.util.simpletimetracker.domain.interactor.GetSelectableTagsInteractor
import com.example.util.simpletimetracker.domain.interactor.RecordInteractor
import com.example.util.simpletimetracker.domain.interactor.RecordTypeInteractor
import com.example.util.simpletimetracker.domain.interactor.RecordsUpdateInteractor
import com.example.util.simpletimetracker.domain.interactor.RemoveRunningRecordMediator
import com.example.util.simpletimetracker.domain.interactor.RunningRecordInteractor
import com.example.util.simpletimetracker.domain.model.ExternalActionCommentMode
import com.example.util.simpletimetracker.domain.model.ExternalActionFindRecordMode
import com.example.util.simpletimetracker.domain.model.Record
import java.text.SimpleDateFormat
import java.util.Locale
import javax.inject.Inject

class ExternalBroadcastInteractor @Inject constructor(
private val recordTypeInteractor: RecordTypeInteractor,
private val addRunningRecordMediator: AddRunningRecordMediator,
private val addRecordMediator: AddRecordMediator,
private val removeRunningRecordMediator: RemoveRunningRecordMediator,
private val runningRecordInteractor: RunningRecordInteractor,
private val recordInteractor: RecordInteractor,
private val getSelectableTagsInteractor: GetSelectableTagsInteractor,
private val recordsUpdateInteractor: RecordsUpdateInteractor,
) {

suspend fun onActionActivityStart(
name: String,
comment: String?,
tagNames: List<String>,
timeStarted: String?,
) {
val typeId = getTypeIdByName(name) ?: return
val runningRecord = runningRecordInteractor.get(typeId)
if (runningRecord != null) return // Already running.
val tagIds = findTagIdByName(tagNames, typeId)
val newTimeStarted = timeStarted?.let(::parseTimestamp)

addRunningRecordMediator.startTimer(
typeId = typeId,
comment = comment.orEmpty(),
tagIds = tagIds,
timeStarted = if (newTimeStarted != null) {
AddRunningRecordMediator.StartTime.Timestamp(newTimeStarted)
} else {
AddRunningRecordMediator.StartTime.TakeCurrent
},
)
}

suspend fun onActionActivityStopByName(
name: String,
timeEnded: String?,
) {
val typeId = getTypeIdByName(name) ?: return
val newTimeEnded = timeEnded?.let(::parseTimestamp)
val runningRecord = runningRecordInteractor.get(typeId)
?: return // Not running.

removeRunningRecordMediator.removeWithRecordAdd(
runningRecord = runningRecord,
timeEnded = newTimeEnded,
)
}

suspend fun onActionActivityStopAll() {
runningRecordInteractor.getAll()
.forEach { removeRunningRecordMediator.removeWithRecordAdd(it) }
}

suspend fun onActionActivityStopShortest() {
runningRecordInteractor.getAll()
.maxByOrNull { it.timeStarted }
?.let { removeRunningRecordMediator.removeWithRecordAdd(it) }
}

suspend fun onActionActivityStopLongest() {
runningRecordInteractor.getAll()
.minByOrNull { it.timeStarted }
?.let { removeRunningRecordMediator.removeWithRecordAdd(it) }
}

suspend fun onActionActivityRestart(
comment: String?,
tagNames: List<String>,
) {
val previousRecord = recordInteractor.getPrev(
timeStarted = System.currentTimeMillis(),
) ?: return
val typeId = previousRecord.typeId
val tagIds = findTagIdByName(tagNames, typeId)

addRunningRecordMediator.startTimer(
typeId = typeId,
comment = comment
?: previousRecord.comment,
tagIds = tagIds
.takeUnless { tagNames.isEmpty() }
?: previousRecord.tagIds,
)
}

suspend fun onRecordAdd(
name: String,
timeStarted: String,
timeEnded: String,
comment: String?,
tagNames: List<String>,
) {
val typeId = getTypeIdByName(name) ?: return
val newTimeStarted = parseTimestamp(timeStarted) ?: return
val newTimeEnded = parseTimestamp(timeEnded) ?: return
val tagIds = findTagIdByName(tagNames, typeId)

Record(
id = 0, // Zero creates new record.
typeId = typeId,
timeStarted = newTimeStarted,
timeEnded = newTimeEnded,
comment = comment.orEmpty(),
tagIds = tagIds,
).let {
addRecordMediator.add(it)
recordsUpdateInteractor.send()
}
}

suspend fun onRecordChange(
findModeData: String?,
name: String?,
comment: String?,
commentModeData: String?,
) {
val typeId = name?.let { getTypeIdByName(it) }
val findMode = ExternalActionFindRecordMode.entries.firstOrNull {
it.dataValue == findModeData
} ?: ExternalActionFindRecordMode.CURRENT_OR_LAST
val commentMode = ExternalActionCommentMode.entries.firstOrNull {
it.dataValue == commentModeData
} ?: ExternalActionCommentMode.SET

fun processComment(
oldComment: String,
newComment: String,
): String {
return when (commentMode) {
ExternalActionCommentMode.SET -> newComment
ExternalActionCommentMode.APPEND -> oldComment + newComment
ExternalActionCommentMode.PREFIX -> newComment + oldComment
}
}

suspend fun changeCurrent(): Boolean {
var wasChanged = false
runningRecordInteractor.getAll().let { allRecords ->
if (typeId != null) allRecords.filter { it.id == typeId } else allRecords
}.forEach { record ->
record.copy(
comment = processComment(
oldComment = record.comment,
newComment = comment.orEmpty(),
),
).let { runningRecordInteractor.add(it) }
wasChanged = true
}
return wasChanged
}

suspend fun changeLast() {
recordInteractor.getAllPrev(System.currentTimeMillis()).let { allRecords ->
if (typeId != null) allRecords.filter { it.id == typeId } else allRecords
}.forEach { record ->
record.copy(
comment = processComment(
oldComment = record.comment,
newComment = comment.orEmpty(),
),
).let { recordInteractor.add(it) }
}
recordsUpdateInteractor.send()
}

when (findMode) {
ExternalActionFindRecordMode.CURRENT_OR_LAST -> {
val currentWasChanged = changeCurrent()
if (!currentWasChanged) changeLast()
}
ExternalActionFindRecordMode.CURRENT -> changeCurrent()
ExternalActionFindRecordMode.LAST -> changeLast()
}
}

private suspend fun getTypeIdByName(name: String): Long? {
return recordTypeInteractor.getAll().firstOrNull { it.name == name }?.id
}

private suspend fun findTagIdByName(
names: List<String>,
typeId: Long,
): List<Long> {
if (names.isEmpty()) return emptyList()
return getSelectableTagsInteractor.execute(typeId)
.filter { it.name in names && !it.archived }
.map { it.id }
.orEmpty()
}

/**
* Supported formats:
* [dateTimeFormat],
* UTC timestamp in milliseconds.
*/
private fun parseTimestamp(timeString: String): Long? {
return parseDateTime(timeString)
?: timeString.toLongOrNull()
}

private fun parseDateTime(timeString: String): Long? {
return synchronized(dateTimeFormat) {
runCatching {
dateTimeFormat.parse(timeString)
}.getOrNull()?.time
}
}

companion object {
private val dateTimeFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package com.example.util.simpletimetracker.feature_notification.external

import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import javax.inject.Inject
import javax.inject.Singleton

@OptIn(DelicateCoroutinesApi::class)
@Singleton
class NotificationExternalBroadcastController @Inject constructor(
private val externalBroadcastInteractor: ExternalBroadcastInteractor,
) {

private val mutex = Mutex()

fun onActionExternalActivityStart(
name: String?,
comment: String?,
tagNames: List<String>,
timeStarted: String?,
) = GlobalScope.launch {
name ?: return@launch
mutex.withLock {
externalBroadcastInteractor.onActionActivityStart(
name = name,
comment = comment,
tagNames = tagNames,
timeStarted = timeStarted,
)
}
}

fun onActionExternalActivityStop(
name: String?,
timeEnded: String?,
) = GlobalScope.launch {
name ?: return@launch
mutex.withLock {
externalBroadcastInteractor.onActionActivityStopByName(
name = name,
timeEnded = timeEnded,
)
}
}

fun onActionExternalActivityStopAll() = GlobalScope.launch {
mutex.withLock {
externalBroadcastInteractor.onActionActivityStopAll()
}
}

fun onActionExternalActivityStopShortest() = GlobalScope.launch {
mutex.withLock {
externalBroadcastInteractor.onActionActivityStopShortest()
}
}

fun onActionExternalActivityStopLongest() = GlobalScope.launch {
mutex.withLock {
externalBroadcastInteractor.onActionActivityStopLongest()
}
}

fun onActionExternalActivityRestart(
comment: String?,
tagNames: List<String>,
) = GlobalScope.launch {
mutex.withLock {
externalBroadcastInteractor.onActionActivityRestart(
comment = comment, tagNames = tagNames,
)
}
}

fun onActionExternalRecordAdd(
name: String?,
timeStarted: String?,
timeEnded: String?,
comment: String?,
tagNames: List<String>,
) = GlobalScope.launch {
name ?: return@launch
timeStarted ?: return@launch
timeEnded ?: return@launch
mutex.withLock {
externalBroadcastInteractor.onRecordAdd(
name = name,
timeStarted = timeStarted,
timeEnded = timeEnded,
comment = comment,
tagNames = tagNames,
)
}
}

fun onActionExternalRecordChange(
findMode: String?,
name: String?,
comment: String?,
commentMode: String?,
) = GlobalScope.launch {
mutex.withLock {
externalBroadcastInteractor.onRecordChange(
findModeData = findMode,
name = name,
comment = comment,
commentModeData = commentMode,
)
}
}
}
Loading

0 comments on commit 61ce510

Please sign in to comment.