-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathFeedStore.kt
143 lines (128 loc) · 5.11 KB
/
FeedStore.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package com.github.jetbrains.rssreader.app
import com.github.jetbrains.rssreader.core.RssReader
import com.github.jetbrains.rssreader.core.entity.Feed
import io.github.aakira.napier.Napier
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
data class FeedState(
val progress: Boolean,
val feeds: List<Feed>,
val selectedFeed: Feed? = null //null means selected all
) : State
fun FeedState.mainFeedPosts() = (selectedFeed?.posts ?: feeds.flatMap { it.posts }).sortedByDescending { it.date }
sealed class FeedAction : Action {
data class Refresh(val forceLoad: Boolean) : FeedAction()
data class Add(val url: String) : FeedAction()
data class Delete(val url: String) : FeedAction()
data class SelectFeed(val feed: Feed?) : FeedAction()
data class Data(val feeds: List<Feed>) : FeedAction()
data class Error(val error: Exception) : FeedAction()
}
sealed class FeedSideEffect : Effect {
data class Error(val error: Exception) : FeedSideEffect()
}
class FeedStore(
private val rssReader: RssReader
) : Store<FeedState, FeedAction, FeedSideEffect>,
CoroutineScope by CoroutineScope(Dispatchers.Main) {
private val state = MutableStateFlow(FeedState(false, emptyList()))
private val sideEffect = MutableSharedFlow<FeedSideEffect>()
override fun observeState(): StateFlow<FeedState> = state
override fun observeSideEffect(): Flow<FeedSideEffect> = sideEffect
override fun dispatch(action: FeedAction) {
Napier.d(tag = "FeedStore", message = "Action: $action")
val oldState = state.value
val newState = when (action) {
is FeedAction.Refresh -> {
if (oldState.progress) {
launch { sideEffect.emit(FeedSideEffect.Error(Exception("In progress"))) }
oldState
} else {
launch { loadAllFeeds(action.forceLoad) }
oldState.copy(progress = true)
}
}
is FeedAction.Add -> {
if (oldState.progress) {
launch { sideEffect.emit(FeedSideEffect.Error(Exception("In progress"))) }
oldState
} else {
launch { addFeed(action.url) }
FeedState(true, oldState.feeds)
}
}
is FeedAction.Delete -> {
if (oldState.progress) {
launch { sideEffect.emit(FeedSideEffect.Error(Exception("In progress"))) }
oldState
} else {
launch { deleteFeed(action.url) }
FeedState(true, oldState.feeds)
}
}
is FeedAction.SelectFeed -> {
if (action.feed == null || oldState.feeds.contains(action.feed)) {
oldState.copy(selectedFeed = action.feed)
} else {
launch { sideEffect.emit(FeedSideEffect.Error(Exception("Unknown feed"))) }
oldState
}
}
is FeedAction.Data -> {
if (oldState.progress) {
val selected = oldState.selectedFeed?.let {
if (action.feeds.contains(it)) it else null
}
FeedState(false, action.feeds, selected)
} else {
launch { sideEffect.emit(FeedSideEffect.Error(Exception("Unexpected action"))) }
oldState
}
}
is FeedAction.Error -> {
if (oldState.progress) {
launch { sideEffect.emit(FeedSideEffect.Error(action.error)) }
FeedState(false, oldState.feeds)
} else {
launch { sideEffect.emit(FeedSideEffect.Error(Exception("Unexpected action"))) }
oldState
}
}
}
if (newState != oldState) {
Napier.d(tag = "FeedStore", message = "NewState: $newState")
state.value = newState
}
}
private suspend fun loadAllFeeds(forceLoad: Boolean) {
try {
val allFeeds = rssReader.getAllFeeds(forceLoad)
dispatch(FeedAction.Data(allFeeds))
} catch (e: Exception) {
dispatch(FeedAction.Error(e))
}
}
private suspend fun addFeed(url: String) {
try {
rssReader.addFeed(url)
val allFeeds = rssReader.getAllFeeds(false)
dispatch(FeedAction.Data(allFeeds))
} catch (e: Exception) {
dispatch(FeedAction.Error(e))
}
}
private suspend fun deleteFeed(url: String) {
try {
rssReader.deleteFeed(url)
val allFeeds = rssReader.getAllFeeds(false)
dispatch(FeedAction.Data(allFeeds))
} catch (e: Exception) {
dispatch(FeedAction.Error(e))
}
}
}