From 4626b8e32a8371c4558e5d65e3ee1a6a18e4f314 Mon Sep 17 00:00:00 2001 From: Michael Flisar Date: Tue, 12 Nov 2024 10:38:45 +0100 Subject: [PATCH] ... --- build.gradle.kts | 5 + demo/android/build.gradle.kts | 2 +- .../composechangelog/demo/MainActivity.kt | 15 +- demo/android/src/main/res/raw/changelog.xml | 10 +- demo/desktop/build.gradle.kts | 8 +- demo/desktop/changelog.preferences_pb | 2 +- demo/desktop/changelog.xml | 2 +- .../composechangelog/demo/Main.kt | 37 ++- gradle/libs.versions.toml | 2 +- .../composechangelog/Changelog.kt | 264 ------------------ .../composechangelog/ChangelogDefaults.kt | 131 +++++++++ .../composechangelog/ShowChangelogDialog.kt | 139 +++++++++ .../composechangelog/ChangelogExtensions.kt | 55 +++- .../ChangelogStateSaverPreferences.kt | 2 +- 14 files changed, 379 insertions(+), 295 deletions(-) delete mode 100644 library/core/src/commonMain/kotlin/com/michaelflisar/composechangelog/Changelog.kt create mode 100644 library/core/src/commonMain/kotlin/com/michaelflisar/composechangelog/ChangelogDefaults.kt create mode 100644 library/core/src/commonMain/kotlin/com/michaelflisar/composechangelog/ShowChangelogDialog.kt diff --git a/build.gradle.kts b/build.gradle.kts index 0365b6a..27b6670 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,4 +22,9 @@ tasks.register("publishToMavenLocal") { tasks.register("publishToMaven") { dependsOn(gradle.includedBuild("gradle-plugin").task(":gradle-plugin:publishToMaven")) //dependsOn(gradle.includedBuild("gradle-plugin").task(":gradle-plugin-shared:publishToMavenLocal")) +} + +tasks.register("publish") { + dependsOn(gradle.includedBuild("gradle-plugin").task(":gradle-plugin:publish")) + //dependsOn(gradle.includedBuild("gradle-plugin").task(":gradle-plugin-shared:publishToMavenLocal")) } \ No newline at end of file diff --git a/demo/android/build.gradle.kts b/demo/android/build.gradle.kts index c4f1e69..bae6202 100644 --- a/demo/android/build.gradle.kts +++ b/demo/android/build.gradle.kts @@ -9,7 +9,7 @@ plugins { id("compose-changelog") } -val version = "1.0.6" +val version = "1.0.4" val code = Changelog.buildVersionCode(version, DefaultVersionFormatter(DefaultVersionFormatter.Format.MajorMinorPatchCandidate)) android { diff --git a/demo/android/src/main/java/com/michaelflisar/composechangelog/demo/MainActivity.kt b/demo/android/src/main/java/com/michaelflisar/composechangelog/demo/MainActivity.kt index 77fe86e..294a923 100644 --- a/demo/android/src/main/java/com/michaelflisar/composechangelog/demo/MainActivity.kt +++ b/demo/android/src/main/java/com/michaelflisar/composechangelog/demo/MainActivity.kt @@ -30,9 +30,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import com.michaelflisar.composechangelog.Changelog import com.michaelflisar.composechangelog.ChangelogDefaults import com.michaelflisar.composechangelog.ChangelogUtil +import com.michaelflisar.composechangelog.ShowChangelogDialog +import com.michaelflisar.composechangelog.ShowChangelogDialogIfNecessary import com.michaelflisar.composechangelog.demo.classes.DemoPrefs import com.michaelflisar.composechangelog.getAppVersionCode import com.michaelflisar.composechangelog.getAppVersionName @@ -60,11 +61,14 @@ class MainActivity : DemoActivity() { // needed - you can also provide your own implementation instead of this simple one // (which simply saves the last shown version inside a preference file) - val changelogStateSaver = ChangelogStateSaverPreferences.create(LocalContext.current) + val changelogStateSaver = remember { + ChangelogStateSaverPreferences.create(context) + } // ALTERNATIVE: if you use my kotpreference library like this demo you can do following: - val changelogStateSaverKotPrefs = + val changelogStateSaverKotPrefs = remember { ChangelogStateSaverKotPreferences(DemoPrefs.lastShownVersionForChangelog) + } // optional - here you can apply some customisations like changelog resource id, localized texts, styles, filter, sorter, ... val setup = ChangelogDefaults.setup( @@ -73,8 +77,7 @@ class MainActivity : DemoActivity() { // Changelog - this will show the changelog once only if the changelog was not shown for the current app version yet val versionName = ChangelogUtil.getAppVersionName(context) - Changelog.CheckedShowChangelog(changelogStateSaver, versionName, setup) - + ShowChangelogDialogIfNecessary(changelogStateSaver, versionName, setup) val showChangelog = remember { mutableStateOf(false) } val infos = remember { mutableStateListOf() } @@ -140,7 +143,7 @@ class MainActivity : DemoActivity() { } else ChangelogDefaults.renderer(), versionFormatter = Constants.CHANGELOG_FORMATTER ) - Changelog.ShowChangelogDialog(setup) { + ShowChangelogDialog(setup) { showChangelog.value = false } } diff --git a/demo/android/src/main/res/raw/changelog.xml b/demo/android/src/main/res/raw/changelog.xml index 9ae7267..4620359 100644 --- a/demo/android/src/main/res/raw/changelog.xml +++ b/demo/android/src/main/res/raw/changelog.xml @@ -4,7 +4,7 @@ This version has a summary item only - no show more button will be shown even if show more buttons are enabled - + Some info 1 - apostrophe test: it's weird, but apostrophes do not work in precompiled xml files placed in xml resources! Some improvement 1 Some bugfix 1 @@ -16,20 +16,20 @@ Some bugfix 3 My custom tag text... - + This version has a summary item only - no show more button will be shown even if show more buttons are enabled - + Some info Some improvement Some bugfix - + Some dogs info - filter only set in release tag Some dogs improvement - filter only set in release tag Some dogs bugfix - filter only set in release tag - + single summary of version 1.05 Some cats info - filter only set in release tag Some cats improvement - filter only set in release tag diff --git a/demo/desktop/build.gradle.kts b/demo/desktop/build.gradle.kts index 451ea02..a8b6b00 100644 --- a/demo/desktop/build.gradle.kts +++ b/demo/desktop/build.gradle.kts @@ -1,4 +1,3 @@ -import org.jetbrains.compose.desktop.application.dsl.TargetFormat import com.michaelflisar.composechangelog.gradle.plugin.Changelog import com.michaelflisar.composechangelog.DefaultVersionFormatter @@ -42,9 +41,12 @@ compose.desktop { mainClass = "com.michaelflisar.composechangelog.demo.MainKt" nativeDistributions { - targetFormats(TargetFormat.Exe) - packageName = "Changelog JVM Demo" + //targetFormats(TargetFormat.Exe) + + packageName = "com.michaelflisar.composechangelog" packageVersion = version + + modules("java.instrument", "jdk.unsupported") } } } \ No newline at end of file diff --git a/demo/desktop/changelog.preferences_pb b/demo/desktop/changelog.preferences_pb index db15d9d..6b43396 100644 --- a/demo/desktop/changelog.preferences_pb +++ b/demo/desktop/changelog.preferences_pb @@ -1,3 +1,3 @@  -changelogVersion ˜‰= \ No newline at end of file +changelogVersion ´ˆ= \ No newline at end of file diff --git a/demo/desktop/changelog.xml b/demo/desktop/changelog.xml index 7308a9a..9bd2068 100644 --- a/demo/desktop/changelog.xml +++ b/demo/desktop/changelog.xml @@ -4,7 +4,7 @@ This version has a summary item only - no show more button will be shown even if show more buttons are enabled - + Some info 1 - apostrophe test: it's weird, but apostrophes do not work in precompiled xml files placed in xml resources! Some improvement 1 Some bugfix 1 diff --git a/demo/desktop/src/jvmMain/kotlin/com/michaelflisar/composechangelog/demo/Main.kt b/demo/desktop/src/jvmMain/kotlin/com/michaelflisar/composechangelog/demo/Main.kt index 6d087b7..68ae5eb 100644 --- a/demo/desktop/src/jvmMain/kotlin/com/michaelflisar/composechangelog/demo/Main.kt +++ b/demo/desktop/src/jvmMain/kotlin/com/michaelflisar/composechangelog/demo/Main.kt @@ -6,8 +6,11 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material.Button import androidx.compose.material.MaterialTheme import androidx.compose.material.Text +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight @@ -16,8 +19,11 @@ import androidx.compose.ui.window.Window import androidx.compose.ui.window.WindowPosition import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState -import com.michaelflisar.composechangelog.Changelog import com.michaelflisar.composechangelog.ChangelogDefaults +import com.michaelflisar.composechangelog.ChangelogUtil +import com.michaelflisar.composechangelog.ShowChangelogDialog +import com.michaelflisar.composechangelog.ShowChangelogDialogIfNecessary +import com.michaelflisar.composechangelog.getAppVersionName import com.michaelflisar.composechangelog.setup import com.michaelflisar.composechangelog.statesaver.preferences.ChangelogStateSaverPreferences import com.michaelflisar.composechangelog.statesaver.preferences.create @@ -26,15 +32,28 @@ fun main() { application { - // TODO: get it from the exe in a real jvm application - val versionName = "1.0.6" + // does only work if you build an exe file!!! not when you run the debug config... + // test it with createDistributable for the demo => then you can remove the "if (versionName == "")"! + var versionName = ChangelogUtil.getAppVersionName() + println("versionName = $versionName") + if (versionName == "") { + versionName = "1.0.5" + } val setup = ChangelogDefaults.setup( versionFormatter = Constants.CHANGELOG_FORMATTER ) + // saver for the automatic changelog showing + val changelogStateSaver = ChangelogStateSaverPreferences.create() + + var lastChangelog by remember { mutableStateOf(-1L) } + LaunchedEffect(Unit) { + lastChangelog = changelogStateSaver.lastShownVersion() + } + Window( - title = "Changelog Demo", + title = "Changelog Demo ($versionName)", onCloseRequest = ::exitApplication, state = rememberWindowState( position = WindowPosition(Alignment.Center), @@ -58,6 +77,10 @@ fun main() { "Name: $versionName", style = MaterialTheme.typography.body1 ) + Text( + "Last Changelog: $lastChangelog", + style = MaterialTheme.typography.body1 + ) } Button(onClick = { showChangelog.value = true @@ -68,14 +91,14 @@ fun main() { // manual changelog dialog if (showChangelog.value) { - Changelog.ShowChangelogDialog(setup) { + ShowChangelogDialog(setup) { showChangelog.value = false } } // automatic changelog dialog - val changelogStateSaver = ChangelogStateSaverPreferences.create() - Changelog.CheckedShowChangelog(changelogStateSaver, versionName, setup) + + ShowChangelogDialogIfNecessary(changelogStateSaver, versionName, setup) } } } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d2d62a8..45b7e47 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -18,7 +18,7 @@ datastoreprefs = "1.1.1" # mflisar kotpreferences = "0.7.0" -toolbox = "2.2.0" +toolbox = "2.2.2" [plugins] diff --git a/library/core/src/commonMain/kotlin/com/michaelflisar/composechangelog/Changelog.kt b/library/core/src/commonMain/kotlin/com/michaelflisar/composechangelog/Changelog.kt deleted file mode 100644 index 9ef23a2..0000000 --- a/library/core/src/commonMain/kotlin/com/michaelflisar/composechangelog/Changelog.kt +++ /dev/null @@ -1,264 +0,0 @@ -package com.michaelflisar.composechangelog - -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.luminance -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp -import com.michaelflisar.composechangelog.classes.ChangelogData -import com.michaelflisar.composechangelog.classes.DataItem -import com.michaelflisar.composechangelog.classes.DataItemRelease -import com.michaelflisar.composechangelog.classes.ShowChangelog -import com.michaelflisar.composechangelog.composables.Changelog -import com.michaelflisar.composechangelog.composables.ChangelogItem -import com.michaelflisar.composechangelog.composables.ChangelogItemMore -import com.michaelflisar.composechangelog.composables.ChangelogItemRelease -import com.michaelflisar.composechangelog.composables.ChangelogTag -import com.michaelflisar.composechangelog.interfaces.IChangelogFilter -import com.michaelflisar.composechangelog.interfaces.IChangelogStateSaver -import composechangelog.composechangelog.core.generated.resources.Res -import composechangelog.composechangelog.core.generated.resources.changelog_dialog_title -import composechangelog.composechangelog.core.generated.resources.changelog_button_show_more -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.jetbrains.compose.resources.stringResource - -object Changelog { - - fun filterAndSort( - changelog: ChangelogData, - setup: ChangelogSetup - ): ChangelogData { - return ChangelogData( - releases = changelog.releases - .filter { setup.filter?.keep(it) ?: true } - .map { release -> - val filtered = setup.filter?.let { filter -> - release.items.filter { - filter.keep( - release, - it - ) - } - } ?: release.items - val sorted = setup.sorter?.let { it(filtered) } ?: filtered - release.copy(items = sorted) - } - .filter { it.items.isNotEmpty() } - ) - } - - @Composable - fun CheckedShowChangelog( - storage: IChangelogStateSaver, - versionName: String, - setup: ChangelogSetup - ) { - val scope = rememberCoroutineScope() - var showChangelog by rememberSaveable(setup) { mutableStateOf(null) } - LaunchedEffect(setup) { - println("showChangelog1 = $showChangelog") - if (showChangelog == null) { - println("showChangelog2 = $showChangelog") - showChangelog = ChangelogUtil.shouldShowChangelogOnStart(storage, versionName, setup.versionFormatter) - println("showChangelog3 = $showChangelog") - if (showChangelog?.isInitialVersion == true) { - println("Updating last shown: ${showChangelog!!.currentVersion}") - scope.launch(Dispatchers.IO) { - storage.saveLastShownVersion(showChangelog!!.currentVersion) - } - } - } - } - - showChangelog - ?.takeIf { it.shouldShow } - ?.let { data -> - val filter = object : IChangelogFilter { - override fun keep(release: DataItemRelease): Boolean { - return release.versionCode >= data.lastShownVersion && (setup.filter?.keep( - release - ) ?: true) - } - - override fun keep(release: DataItemRelease, item: DataItem): Boolean { - return setup.filter?.keep(release) ?: true - } - } - val setup = setup.copy( - filter = filter - ) - ShowChangelogDialog(setup) { - scope.launch(Dispatchers.IO) { - storage.saveLastShownVersion(data.currentVersion) - showChangelog = ShowChangelog(data.currentVersion, data.currentVersion) - } - } - } - } - - @Composable - fun ShowChangelogDialog( - setup: ChangelogSetup, - onDismiss: () -> Unit = {} - ) { - val context = LocalContext() - val changelog = remember { mutableStateOf(null) } - LaunchedEffect(Unit) { - withContext(Dispatchers.IO) { - val data = ChangelogUtil.read(context, setup.changelogId, setup.versionFormatter) - changelog.value = filterAndSort(data, setup) - } - } - - val data = changelog.value - if (data != null && !data.isEmpty()) { - val openDialog = remember { mutableStateOf(true) } - if (openDialog.value) { - AlertDialog( - onDismissRequest = { - onDismiss() - openDialog.value = false - }, - title = { - Text(text = setup.texts.dialogTitle) - }, - text = { - Changelog(data, setup) - }, - confirmButton = { - TextButton( - onClick = { - onDismiss() - openDialog.value = false - }) { - Text(setup.texts.dialogButtonDismiss) - } - } - ) - } - } - } -} - -object ChangelogDefaults { - - @Composable - fun tagNameFormatter() = @Composable { tag: String -> - tag.uppercase() - } - - @Composable - fun tagColorProvider() = @Composable { tag: String -> - when (tag.lowercase()) { - "bug", - "bugfix" -> MaterialTheme.colorScheme.error - "new" -> if (MaterialTheme.colorScheme.background.luminance() > 0.5) - Color(0, 100, 0) /* dark green */ - else - Color(144, 238, 144) /* light green */ - else -> Color.Unspecified - } - } - - - @Composable - fun sorter() = { items: List -> - items - .sortedBy { it.tag } - .sortedBy { - when (it.tag) { - "new" -> 0 - "bug", - "bugfix" -> 2 - else -> 1 - } - } - } - - fun filter(filter: String, keepEmptyFilterRows: Boolean = true): IChangelogFilter { - return object : IChangelogFilter { - override fun keep(release: DataItemRelease): Boolean { - if (release.filter == null) - return true - return release.filter.contains( - filter, - true - ) //&& release.items.count { keep(release, it) } > 0 - } - - override fun keep(release: DataItemRelease, item: DataItem): Boolean { - return release.filter?.contains(filter, true) == true || - item.filter?.contains( - filter, - true - ) ?: keepEmptyFilterRows - } - } - } - - @Composable - fun texts( - dialogTitle: String = stringResource(Res.string.changelog_dialog_title), - dialogButtonDismiss: String = stringOk(), - buttonShowMore: String = stringResource(Res.string.changelog_button_show_more) - ) = ChangelogSetup.Texts( - dialogTitle = dialogTitle, - dialogButtonDismiss = dialogButtonDismiss, - buttonShowMore = buttonShowMore - ) - - @Composable - fun defaultItemRelease(modifier: Modifier, item: DataItemRelease) { - ChangelogItemRelease(modifier, item) - } - - @Composable - fun defaultItem( - item: DataItem, - width: Dp? = 64.dp, - tagAlignment: Alignment.Horizontal = Alignment.Start, - tagColorProvider: @Composable (String) -> Color = tagColorProvider(), - tagNameFormatter: @Composable (String) -> String = tagNameFormatter() - ) { - ChangelogItem(item = item, tagAlignment = tagAlignment) { - ChangelogTag(item, width, tagColorProvider, tagNameFormatter) - } - } - - @Composable - fun defaultItemMore(modifier: Modifier, setup: ChangelogSetup, onClick: () -> Unit) { - ChangelogItemMore(modifier, setup, onClick) - } - - @Composable - fun renderer( - itemRelease: @Composable (modifier: Modifier, item: DataItemRelease, setup: ChangelogSetup) -> Unit = { modifier, item, setup -> - defaultItemRelease(modifier, item) - }, - item: @Composable (modifier: Modifier, item: DataItem, setup: ChangelogSetup) -> Unit = { modifier, item, setup -> - defaultItem(item) - }, - itemShowMore: @Composable (modifier: Modifier, setup: ChangelogSetup, onClick: () -> Unit) -> Unit = { modifier, setup, onClick -> - defaultItemMore(modifier, setup, onClick) - } - ) = ChangelogSetup.Renderer( - itemRelease, - item, - itemShowMore - ) -} \ No newline at end of file diff --git a/library/core/src/commonMain/kotlin/com/michaelflisar/composechangelog/ChangelogDefaults.kt b/library/core/src/commonMain/kotlin/com/michaelflisar/composechangelog/ChangelogDefaults.kt new file mode 100644 index 0000000..a9770d1 --- /dev/null +++ b/library/core/src/commonMain/kotlin/com/michaelflisar/composechangelog/ChangelogDefaults.kt @@ -0,0 +1,131 @@ +package com.michaelflisar.composechangelog + +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.luminance +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.michaelflisar.composechangelog.classes.DataItem +import com.michaelflisar.composechangelog.classes.DataItemRelease +import com.michaelflisar.composechangelog.composables.ChangelogItem +import com.michaelflisar.composechangelog.composables.ChangelogItemMore +import com.michaelflisar.composechangelog.composables.ChangelogItemRelease +import com.michaelflisar.composechangelog.composables.ChangelogTag +import com.michaelflisar.composechangelog.interfaces.IChangelogFilter +import composechangelog.composechangelog.core.generated.resources.Res +import composechangelog.composechangelog.core.generated.resources.changelog_button_show_more +import composechangelog.composechangelog.core.generated.resources.changelog_dialog_title +import org.jetbrains.compose.resources.stringResource + +object ChangelogDefaults { + + @Composable + fun tagNameFormatter() = @Composable { tag: String -> + tag.uppercase() + } + + @Composable + fun tagColorProvider() = @Composable { tag: String -> + when (tag.lowercase()) { + "bug", + "bugfix" -> MaterialTheme.colorScheme.error + + "new" -> if (MaterialTheme.colorScheme.background.luminance() > 0.5) + Color(0, 100, 0) /* dark green */ + else + Color(144, 238, 144) /* light green */ + else -> Color.Unspecified + } + } + + + @Composable + fun sorter() = { items: List -> + items + .sortedBy { it.tag } + .sortedBy { + when (it.tag) { + "new" -> 0 + "bug", + "bugfix" -> 2 + + else -> 1 + } + } + } + + fun filter(filter: String, keepEmptyFilterRows: Boolean = true): IChangelogFilter { + return object : IChangelogFilter { + override fun keep(release: DataItemRelease): Boolean { + if (release.filter == null) + return true + return release.filter.contains( + filter, + true + ) //&& release.items.count { keep(release, it) } > 0 + } + + override fun keep(release: DataItemRelease, item: DataItem): Boolean { + return release.filter?.contains(filter, true) == true || + item.filter?.contains( + filter, + true + ) ?: keepEmptyFilterRows + } + } + } + + @Composable + fun texts( + dialogTitle: String = stringResource(Res.string.changelog_dialog_title), + dialogButtonDismiss: String = stringOk(), + buttonShowMore: String = stringResource(Res.string.changelog_button_show_more) + ) = ChangelogSetup.Texts( + dialogTitle = dialogTitle, + dialogButtonDismiss = dialogButtonDismiss, + buttonShowMore = buttonShowMore + ) + + @Composable + fun defaultItemRelease(modifier: Modifier, item: DataItemRelease) { + ChangelogItemRelease(modifier, item) + } + + @Composable + fun defaultItem( + item: DataItem, + width: Dp? = 64.dp, + tagAlignment: Alignment.Horizontal = Alignment.Start, + tagColorProvider: @Composable (String) -> Color = tagColorProvider(), + tagNameFormatter: @Composable (String) -> String = tagNameFormatter() + ) { + ChangelogItem(item = item, tagAlignment = tagAlignment) { + ChangelogTag(item, width, tagColorProvider, tagNameFormatter) + } + } + + @Composable + fun defaultItemMore(modifier: Modifier, setup: ChangelogSetup, onClick: () -> Unit) { + ChangelogItemMore(modifier, setup, onClick) + } + + @Composable + fun renderer( + itemRelease: @Composable (modifier: Modifier, item: DataItemRelease, setup: ChangelogSetup) -> Unit = { modifier, item, setup -> + defaultItemRelease(modifier, item) + }, + item: @Composable (modifier: Modifier, item: DataItem, setup: ChangelogSetup) -> Unit = { modifier, item, setup -> + defaultItem(item) + }, + itemShowMore: @Composable (modifier: Modifier, setup: ChangelogSetup, onClick: () -> Unit) -> Unit = { modifier, setup, onClick -> + defaultItemMore(modifier, setup, onClick) + } + ) = ChangelogSetup.Renderer( + itemRelease, + item, + itemShowMore + ) +} \ No newline at end of file diff --git a/library/core/src/commonMain/kotlin/com/michaelflisar/composechangelog/ShowChangelogDialog.kt b/library/core/src/commonMain/kotlin/com/michaelflisar/composechangelog/ShowChangelogDialog.kt new file mode 100644 index 0000000..e454300 --- /dev/null +++ b/library/core/src/commonMain/kotlin/com/michaelflisar/composechangelog/ShowChangelogDialog.kt @@ -0,0 +1,139 @@ +package com.michaelflisar.composechangelog + +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import com.michaelflisar.composechangelog.classes.ChangelogData +import com.michaelflisar.composechangelog.classes.DataItem +import com.michaelflisar.composechangelog.classes.DataItemRelease +import com.michaelflisar.composechangelog.classes.ShowChangelog +import com.michaelflisar.composechangelog.composables.Changelog +import com.michaelflisar.composechangelog.interfaces.IChangelogFilter +import com.michaelflisar.composechangelog.interfaces.IChangelogStateSaver +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +@Composable +fun ShowChangelogDialog( + setup: ChangelogSetup, + onDismiss: () -> Unit = {} +) { + val context = LocalContext() + val changelog = remember { mutableStateOf(null) } + LaunchedEffect(Unit) { + withContext(Dispatchers.IO) { + val data = ChangelogUtil.read(context, setup.changelogId, setup.versionFormatter) + changelog.value = filterAndSort(data, setup) + } + } + + val data = changelog.value + if (data != null && !data.isEmpty()) { + val openDialog = remember { mutableStateOf(true) } + if (openDialog.value) { + AlertDialog( + onDismissRequest = { + onDismiss() + openDialog.value = false + }, + title = { + Text(text = setup.texts.dialogTitle) + }, + text = { + Changelog(data, setup) + }, + confirmButton = { + TextButton( + onClick = { + onDismiss() + openDialog.value = false + }) { + Text(setup.texts.dialogButtonDismiss) + } + } + ) + } + } +} + +@Composable +fun ShowChangelogDialogIfNecessary( + storage: IChangelogStateSaver, + versionName: String, + setup: ChangelogSetup +) { + val scope = rememberCoroutineScope() + var showChangelog by rememberSaveable(setup) { mutableStateOf(null) } + LaunchedEffect(setup) { + if (showChangelog == null) { + showChangelog = ChangelogUtil.shouldShowChangelogOnStart( + storage, + versionName, + setup.versionFormatter + ) + if (showChangelog?.isInitialVersion == true) { + //println("Updating last shown: ${showChangelog!!.currentVersion}") + scope.launch(Dispatchers.IO) { + storage.saveLastShownVersion(showChangelog!!.currentVersion) + } + } + } + } + + showChangelog + ?.takeIf { it.shouldShow } + ?.let { data -> + val filter = object : IChangelogFilter { + override fun keep(release: DataItemRelease): Boolean { + return release.versionCode > data.lastShownVersion && (setup.filter?.keep( + release + ) ?: true) + } + + override fun keep(release: DataItemRelease, item: DataItem): Boolean { + return setup.filter?.keep(release) ?: true + } + } + val setup = setup.copy( + filter = filter + ) + ShowChangelogDialog(setup) { + scope.launch(Dispatchers.IO) { + storage.saveLastShownVersion(data.currentVersion) + showChangelog = ShowChangelog(data.currentVersion, data.currentVersion) + } + } + } +} + +private fun filterAndSort( + changelog: ChangelogData, + setup: ChangelogSetup +): ChangelogData { + return ChangelogData( + releases = changelog.releases + .filter { setup.filter?.keep(it) ?: true } + .map { release -> + val filtered = setup.filter?.let { filter -> + release.items.filter { + filter.keep( + release, + it + ) + } + } ?: release.items + val sorted = setup.sorter?.let { it(filtered) } ?: filtered + release.copy(items = sorted) + } + .filter { it.items.isNotEmpty() } + ) +} \ No newline at end of file diff --git a/library/core/src/jvmMain/kotlin/com/michaelflisar/composechangelog/ChangelogExtensions.kt b/library/core/src/jvmMain/kotlin/com/michaelflisar/composechangelog/ChangelogExtensions.kt index 756f4ce..3ff5807 100644 --- a/library/core/src/jvmMain/kotlin/com/michaelflisar/composechangelog/ChangelogExtensions.kt +++ b/library/core/src/jvmMain/kotlin/com/michaelflisar/composechangelog/ChangelogExtensions.kt @@ -1,21 +1,21 @@ package com.michaelflisar.composechangelog import androidx.compose.runtime.Composable -import com.michaelflisar.composechangelog.ChangelogDefaults.renderer -import com.michaelflisar.composechangelog.ChangelogDefaults.texts import com.michaelflisar.composechangelog.classes.DataItem import com.michaelflisar.composechangelog.interfaces.IChangelogFilter +import java.io.BufferedReader import java.io.File +import java.io.InputStreamReader @Composable fun ChangelogDefaults.setup( file: File = File("changelog.xml"), - texts: ChangelogSetup.Texts = texts(), + texts: ChangelogSetup.Texts = ChangelogDefaults.texts(), useShowMoreButtons: Boolean = true, versionFormatter: ChangelogVersionFormatter = DefaultVersionFormatter(), sorter: ((items: List) -> List)? = ChangelogDefaults.sorter(), filter: IChangelogFilter? = null, - renderer: ChangelogSetup.Renderer = renderer() + renderer: ChangelogSetup.Renderer = ChangelogDefaults.renderer() ) = ChangelogSetup( changelogId = file, texts = texts, @@ -24,4 +24,49 @@ fun ChangelogDefaults.setup( filter = filter, sorter = sorter, renderer = renderer -) \ No newline at end of file +) + +/** + * returns the app version name (the packageVersion from the exe file metadata) + * + * @param exe the exe to read the file version from + * + * @return the app version name + */ +fun ChangelogUtil.getAppVersionName( + exe: File = File(System.getProperty("user.dir")).let { + File(it, it.name + ".exe") + } +): String { + val versionInfo = runPS("(Get-Item '${exe.absolutePath}').VersionInfo.FileVersion") + return versionInfo.takeIf { it.isNotEmpty() } ?: "" +} + +fun runPS(command: String): String { + try { + @Suppress("DEPRECATION") + val powerShellProcess = Runtime.getRuntime().exec("powershell.exe $command") + powerShellProcess.outputStream.close() + + val lines = ArrayList() + + var line: String? + //println("Standard Output:") + val stdout = BufferedReader(InputStreamReader(powerShellProcess.inputStream)) + while ((stdout.readLine().also { line = it }) != null) { + line?.let { lines += it } + } + stdout.close() + //println("Standard Error:") + val stderr = BufferedReader(InputStreamReader(powerShellProcess.errorStream)) + while ((stderr.readLine().also { line = it }) != null) { + println(line) + } + stderr.close() + //println("Done") + + return lines.firstOrNull() ?: "" + } catch(e: Exception) { + return "" + } +} \ No newline at end of file diff --git a/library/modules/statesaver-preferences/src/androidMain/kotlin/com/michaelflisar/composechangelog/statesaver/preferences/ChangelogStateSaverPreferences.kt b/library/modules/statesaver-preferences/src/androidMain/kotlin/com/michaelflisar/composechangelog/statesaver/preferences/ChangelogStateSaverPreferences.kt index ca4eb22..7c7c05e 100644 --- a/library/modules/statesaver-preferences/src/androidMain/kotlin/com/michaelflisar/composechangelog/statesaver/preferences/ChangelogStateSaverPreferences.kt +++ b/library/modules/statesaver-preferences/src/androidMain/kotlin/com/michaelflisar/composechangelog/statesaver/preferences/ChangelogStateSaverPreferences.kt @@ -6,7 +6,7 @@ import okio.Path.Companion.toPath fun ChangelogStateSaverPreferences.Companion.create( context: Context, - name: String = "settings", + name: String = "changelog", preferenceKey: String = PREF_KEY ): ChangelogStateSaverPreferences { return ChangelogStateSaverPreferences(