@@ -25,6 +25,8 @@ import kotlinx.coroutines.flow.filterNotNull
2525import kotlinx.coroutines.flow.flow
2626import kotlinx.coroutines.flow.onEach
2727import kotlinx.coroutines.launch
28+ import kotlinx.coroutines.sync.Mutex
29+ import kotlinx.coroutines.sync.withLock
2830import javax.inject.Inject
2931import javax.inject.Singleton
3032
@@ -36,9 +38,10 @@ class RoutineRepositoryImpl @Inject constructor(
3638) : RoutineRepository {
3739
3840 private val repositoryScope = CoroutineScope (SupervisorJob () + dispatcher)
41+ private val mutex = Mutex ()
3942 private val pendingChangesByDate = mutableMapOf<String , MutableMap <String , RoutineCompletionInfo >>()
4043 private val originalStatesByDate = mutableMapOf<String , MutableMap <String , RoutineCompletionInfo >>()
41- private val syncTrigger = MutableSharedFlow <String >(
44+ private val syncTrigger = MutableSharedFlow <Unit >(
4245 extraBufferCapacity = 1 ,
4346 onBufferOverflow = BufferOverflow .DROP_OLDEST ,
4447 )
@@ -48,7 +51,7 @@ class RoutineRepositoryImpl @Inject constructor(
4851 repositoryScope.launch {
4952 syncTrigger
5053 .debounce(500L )
51- .collect { dateKey -> flushPendingChanges(dateKey ) }
54+ .collect { flushAllPendingChanges( ) }
5255 }
5356 }
5457
@@ -71,28 +74,41 @@ class RoutineRepositoryImpl @Inject constructor(
7174 }
7275
7376 override suspend fun applyRoutineToggle (dateKey : String , routineId : String , completionInfo : RoutineCompletionInfo ) {
74- if (originalStatesByDate[dateKey]?.containsKey(routineId) != true ) {
75- routineLocalDataSource.getCompletionInfo(dateKey, routineId)?.let {
76- originalStatesByDate.getOrPut(dateKey) { mutableMapOf () }[routineId] = it
77+ mutex.withLock {
78+ if (originalStatesByDate[dateKey]?.containsKey(routineId) != true ) {
79+ routineLocalDataSource.getCompletionInfo(dateKey, routineId)?.let {
80+ originalStatesByDate.getOrPut(dateKey) { mutableMapOf () }[routineId] = it
81+ }
7782 }
83+ pendingChangesByDate.getOrPut(dateKey) { mutableMapOf () }[routineId] = completionInfo
7884 }
7985 routineLocalDataSource.applyOptimisticToggle(dateKey, routineId, completionInfo)
80- pendingChangesByDate.getOrPut(dateKey) { mutableMapOf () }[routineId] = completionInfo
81- syncTrigger.emit(dateKey)
86+ syncTrigger.emit(Unit )
8287 }
8388
84- private suspend fun flushPendingChanges (dateKey : String ) {
85- val snapshot = pendingChangesByDate.remove(dateKey)
86- val originals = originalStatesByDate.remove(dateKey)
87- val actualChanges = snapshot?.filter { (routineId, pending) -> originals?.get(routineId) != pending }
88- if (actualChanges.isNullOrEmpty()) return
89-
90- val syncRequest = RoutineCompletionInfos (routineCompletionInfos = actualChanges.values.toList())
91- routineRemoteDataSource.syncRoutineCompletion(syncRequest.toDto())
92- .onFailure {
93- val range = routineLocalDataSource.lastFetchRange ? : return
94- fetchAndSave(range.first, range.second)
89+ private suspend fun flushAllPendingChanges () {
90+ val snapshot: Map <String , Map <String , RoutineCompletionInfo >>
91+ val originals: Map <String , Map <String , RoutineCompletionInfo >>
92+ mutex.withLock {
93+ snapshot = pendingChangesByDate.mapValues { it.value.toMap() }
94+ originals = originalStatesByDate.mapValues { it.value.toMap() }
95+ pendingChangesByDate.clear()
96+ originalStatesByDate.clear()
97+ }
98+
99+ for ((dateKey, pendingForDate) in snapshot) {
100+ val actualChanges = pendingForDate.filter { (routineId, pending) ->
101+ originals[dateKey]?.get(routineId) != pending
95102 }
103+ if (actualChanges.isEmpty()) continue
104+
105+ val syncRequest = RoutineCompletionInfos (routineCompletionInfos = actualChanges.values.toList())
106+ routineRemoteDataSource.syncRoutineCompletion(syncRequest.toDto())
107+ .onFailure {
108+ val range = routineLocalDataSource.lastFetchRange ? : return @onFailure
109+ fetchAndSave(range.first, range.second)
110+ }
111+ }
96112 }
97113
98114 private suspend fun refreshCache () {
0 commit comments