Skip to content

Commit a727bb9

Browse files
authored
Merge pull request #878 from DimensionDev/feature/inapp_changelog
add in app changelog
2 parents 8258ee5 + ea199f8 commit a727bb9

File tree

6 files changed

+166
-4
lines changed

6 files changed

+166
-4
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package dev.dimension.flare.data.model
2+
3+
import android.content.Context
4+
import androidx.datastore.core.DataStore
5+
import androidx.datastore.core.Serializer
6+
import androidx.datastore.dataStore
7+
import kotlinx.coroutines.Dispatchers
8+
import kotlinx.coroutines.withContext
9+
import kotlinx.serialization.ExperimentalSerializationApi
10+
import kotlinx.serialization.Serializable
11+
import kotlinx.serialization.decodeFromByteArray
12+
import kotlinx.serialization.encodeToByteArray
13+
import kotlinx.serialization.protobuf.ProtoBuf
14+
import java.io.InputStream
15+
import java.io.OutputStream
16+
17+
@Serializable
18+
internal data class AppSettings(
19+
val version: String,
20+
)
21+
22+
@OptIn(ExperimentalSerializationApi::class)
23+
private object PreferencesSerializer : Serializer<AppSettings> {
24+
override suspend fun readFrom(input: InputStream): AppSettings = ProtoBuf.decodeFromByteArray(input.readBytes())
25+
26+
override suspend fun writeTo(
27+
t: AppSettings,
28+
output: OutputStream,
29+
) = withContext(Dispatchers.IO) {
30+
output.write(ProtoBuf.encodeToByteArray(t))
31+
}
32+
33+
override val defaultValue: AppSettings
34+
get() =
35+
AppSettings(
36+
version = "",
37+
)
38+
}
39+
40+
internal val Context.appSettings: DataStore<AppSettings> by dataStore(
41+
fileName = "app_settings.pb",
42+
serializer = PreferencesSerializer,
43+
)

app/src/main/java/dev/dimension/flare/data/repository/SettingsRepository.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package dev.dimension.flare.data.repository
22

33
import android.content.Context
4+
import dev.dimension.flare.data.model.AppSettings
45
import dev.dimension.flare.data.model.AppearanceSettings
56
import dev.dimension.flare.data.model.TabSettings
7+
import dev.dimension.flare.data.model.appSettings
68
import dev.dimension.flare.data.model.appearanceSettings
79
import dev.dimension.flare.data.model.tabSettings
810

9-
class SettingsRepository(
11+
internal class SettingsRepository(
1012
private val context: Context,
1113
) {
1214
private val appearanceSettingsStore by lazy {
@@ -15,6 +17,12 @@ class SettingsRepository(
1517
val appearanceSettings by lazy {
1618
appearanceSettingsStore.data
1719
}
20+
private val appSettingsStore by lazy {
21+
context.appSettings
22+
}
23+
val appSettings by lazy {
24+
appSettingsStore.data
25+
}
1826

1927
suspend fun updateAppearanceSettings(block: AppearanceSettings.() -> AppearanceSettings) {
2028
appearanceSettingsStore.updateData(block)
@@ -31,4 +39,8 @@ class SettingsRepository(
3139
suspend fun updateTabSettings(block: TabSettings.() -> TabSettings) {
3240
tabSettingsStore.updateData(block)
3341
}
42+
43+
suspend fun updateAppSettings(block: AppSettings.() -> AppSettings) {
44+
appSettingsStore.updateData(block)
45+
}
3446
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package dev.dimension.flare.ui.screen.home
2+
3+
import android.content.Context
4+
import androidx.compose.runtime.Composable
5+
import androidx.compose.runtime.getValue
6+
import androidx.compose.runtime.remember
7+
import androidx.compose.runtime.rememberCoroutineScope
8+
import androidx.compose.ui.text.AnnotatedString
9+
import androidx.compose.ui.text.fromHtml
10+
import dev.dimension.flare.BuildConfig
11+
import dev.dimension.flare.data.repository.SettingsRepository
12+
import dev.dimension.flare.ui.model.UiState
13+
import dev.dimension.flare.ui.model.collectAsUiState
14+
import dev.dimension.flare.ui.model.map
15+
import kotlinx.coroutines.launch
16+
import org.koin.compose.koinInject
17+
18+
@Composable
19+
internal fun changeLogPresenter(
20+
context: Context = koinInject(),
21+
repository: SettingsRepository = koinInject(),
22+
): ChangeLogState {
23+
val scope = rememberCoroutineScope()
24+
val appSettings by repository.appSettings.collectAsUiState()
25+
val shouldShowChangeLog =
26+
remember(appSettings) {
27+
appSettings.map {
28+
it.version != BuildConfig.VERSION_NAME
29+
}
30+
}
31+
val changeLog =
32+
remember(BuildConfig.VERSION_NAME) {
33+
runCatching {
34+
val id = context.resources.getIdentifier("changelog_${BuildConfig.VERSION_NAME}", "string", context.packageName)
35+
context.getString(id)
36+
}.getOrNull()?.let {
37+
AnnotatedString.fromHtml(it)
38+
}
39+
}
40+
return object : ChangeLogState {
41+
override val shouldShowChangeLog: UiState<Boolean> = shouldShowChangeLog
42+
override val changeLog: AnnotatedString? = changeLog
43+
44+
override fun dismissChangeLog() {
45+
scope.launch {
46+
repository.updateAppSettings {
47+
copy(version = BuildConfig.VERSION_NAME)
48+
}
49+
}
50+
}
51+
}
52+
}
53+
54+
interface ChangeLogState {
55+
val shouldShowChangeLog: UiState<Boolean>
56+
val changeLog: AnnotatedString?
57+
58+
fun dismissChangeLog()
59+
}

app/src/main/java/dev/dimension/flare/ui/screen/home/HomeTimelineScreen.kt

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import androidx.compose.animation.scaleIn
55
import androidx.compose.animation.scaleOut
66
import androidx.compose.animation.slideInVertically
77
import androidx.compose.animation.slideOutVertically
8+
import androidx.compose.foundation.layout.Arrangement
9+
import androidx.compose.foundation.layout.Column
810
import androidx.compose.foundation.layout.PaddingValues
911
import androidx.compose.foundation.layout.Spacer
1012
import androidx.compose.foundation.layout.fillMaxSize
@@ -16,11 +18,13 @@ import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState
1618
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
1719
import androidx.compose.foundation.pager.HorizontalPager
1820
import androidx.compose.foundation.pager.rememberPagerState
21+
import androidx.compose.material3.Button
1922
import androidx.compose.material3.DrawerState
2023
import androidx.compose.material3.ExperimentalMaterial3Api
2124
import androidx.compose.material3.FilledTonalButton
2225
import androidx.compose.material3.FloatingActionButton
2326
import androidx.compose.material3.IconButton
27+
import androidx.compose.material3.MaterialTheme
2428
import androidx.compose.material3.SecondaryScrollableTabRow
2529
import androidx.compose.material3.Tab
2630
import androidx.compose.material3.Text
@@ -67,6 +71,7 @@ import dev.dimension.flare.ui.component.FlareTopAppBar
6771
import dev.dimension.flare.ui.component.LocalBottomBarHeight
6872
import dev.dimension.flare.ui.component.RefreshContainer
6973
import dev.dimension.flare.ui.component.ThemeWrapper
74+
import dev.dimension.flare.ui.component.status.AdaptiveCard
7075
import dev.dimension.flare.ui.component.status.LazyStatusVerticalStaggeredGrid
7176
import dev.dimension.flare.ui.component.status.status
7277
import dev.dimension.flare.ui.model.UiState
@@ -81,6 +86,7 @@ import dev.dimension.flare.ui.presenter.home.UserPresenter
8186
import dev.dimension.flare.ui.presenter.home.UserState
8287
import dev.dimension.flare.ui.presenter.invoke
8388
import dev.dimension.flare.ui.screen.settings.TabTitle
89+
import dev.dimension.flare.ui.theme.screenHorizontalPadding
8490
import kotlinx.collections.immutable.toImmutableList
8591
import kotlinx.coroutines.flow.distinctUntilChanged
8692
import kotlinx.coroutines.flow.drop
@@ -274,6 +280,44 @@ internal fun TimelineItemContent(
274280
contentPadding = contentPadding,
275281
modifier = Modifier.fillMaxSize(),
276282
) {
283+
state.shouldShowChangeLog.onSuccess {
284+
state.changeLog?.let { changelog ->
285+
if (it) {
286+
item {
287+
AdaptiveCard {
288+
Column(
289+
modifier =
290+
Modifier
291+
.padding(
292+
horizontal = screenHorizontalPadding,
293+
).padding(top = 16.dp, bottom = 8.dp),
294+
verticalArrangement = Arrangement.spacedBy(8.dp),
295+
) {
296+
Text(
297+
stringResource(R.string.changelog_title),
298+
style = MaterialTheme.typography.titleMedium,
299+
)
300+
Text(
301+
stringResource(R.string.changelog_message),
302+
style = MaterialTheme.typography.bodySmall,
303+
)
304+
Text(changelog)
305+
Button(
306+
onClick = {
307+
state.dismissChangeLog()
308+
},
309+
) {
310+
Text(
311+
stringResource(android.R.string.ok),
312+
)
313+
}
314+
}
315+
}
316+
}
317+
}
318+
}
319+
}
320+
277321
status(state.listState)
278322
}
279323
state.listState.onSuccess {
@@ -358,6 +402,7 @@ private fun timelinePresenter(
358402

359403
@Composable
360404
internal fun timelineItemPresenter(timelineTabItem: TimelineTabItem): TimelineItemState {
405+
val changeLogState = changeLogPresenter()
361406
val timelinePresenter =
362407
remember(timelineTabItem) {
363408
timelineTabItem.createPresenter()
@@ -396,7 +441,7 @@ internal fun timelineItemPresenter(timelineTabItem: TimelineTabItem): TimelineIt
396441
showNewToots = false
397442
}
398443
}
399-
return object : TimelineItemState {
444+
return object : TimelineItemState, ChangeLogState by changeLogState {
400445
override val listState = state.listState
401446
override val showNewToots = showNewToots
402447
override val isRefreshing = state.listState.isRefreshing
@@ -417,7 +462,7 @@ internal fun timelineItemPresenter(timelineTabItem: TimelineTabItem): TimelineIt
417462
}
418463

419464
@Immutable
420-
internal interface TimelineItemState {
465+
internal interface TimelineItemState : ChangeLogState {
421466
val listState: PagingState<UiTimeline>
422467
val showNewToots: Boolean
423468
val isRefreshing: Boolean

app/src/main/res/values/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,4 +292,7 @@
292292

293293
<string name="emoji_picker_recent">Recent</string>
294294
<string name="emoji_picker_search">Search</string>
295+
296+
<string name="changelog_title">Welcome back!</string>
297+
<string name="changelog_message">We have made some changes since you last logged in. Here are the details:</string>
295298
</resources>

shared/ui/component/src/commonMain/kotlin/dev/dimension/flare/ui/component/status/LazyStatusItems.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,8 @@ public fun LazyStaggeredGridScope.status(
201201

202202
@Composable
203203
public fun AdaptiveCard(
204-
content: @Composable () -> Unit,
205204
modifier: Modifier = Modifier,
205+
content: @Composable () -> Unit,
206206
) {
207207
val bigScreen = isBigScreen()
208208
if (bigScreen) {

0 commit comments

Comments
 (0)