Skip to content

Commit

Permalink
Adding a changelog popup. Fixes #375 (#394)
Browse files Browse the repository at this point in the history
  • Loading branch information
dessalines authored Sep 9, 2023
1 parent d7eaf4d commit f0a9b5f
Show file tree
Hide file tree
Showing 9 changed files with 225 additions and 15 deletions.
4 changes: 4 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ android {
}

dependencies {

implementation "com.github.jeziellago:compose-markdown:0.3.5"
implementation "com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.11"

implementation "androidx.navigation:navigation-compose:2.7.2"
implementation 'com.github.alorma:compose-settings-ui-m3:0.27.0'

Expand Down
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".ThumbkeyApplication"
android:allowBackup="true"
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/com/dessalines/thumbkey/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import com.dessalines.thumbkey.db.AppDB
import com.dessalines.thumbkey.db.AppSettingsRepository
import com.dessalines.thumbkey.db.AppSettingsViewModel
import com.dessalines.thumbkey.db.AppSettingsViewModelFactory
import com.dessalines.thumbkey.ui.components.common.ShowChangelog
import com.dessalines.thumbkey.ui.components.settings.SettingsActivity
import com.dessalines.thumbkey.ui.components.settings.about.AboutActivity
import com.dessalines.thumbkey.ui.components.settings.lookandfeel.LookAndFeelActivity
Expand Down Expand Up @@ -67,6 +68,8 @@ class MainActivity : AppCompatActivity() {
) {
val navController = rememberNavController()

ShowChangelog(appSettingsViewModel = appSettingsViewModel)

NavHost(
navController = navController,
startDestination = startDestination,
Expand Down
77 changes: 67 additions & 10 deletions app/src/main/java/com/dessalines/thumbkey/db/AppDb.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.dessalines.thumbkey.db
import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase.CONFLICT_IGNORE
import android.util.Log
import androidx.annotation.WorkerThread
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
Expand All @@ -19,8 +20,16 @@ import androidx.room.RoomDatabase
import androidx.room.Update
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit

const val DEFAULT_KEY_SIZE = 64
const val DEFAULT_ANIMATION_SPEED = 250
Expand All @@ -39,8 +48,6 @@ const val DEFAULT_HIDE_SYMBOLS = 0
const val DEFAULT_KEY_BORDERS = 1
const val DEFAULT_SPACEBAR_MULTITAPS = 1

const val UPDATE_APP_CHANGELOG_UNVIEWED = "UPDATE AppSettings SET viewed_changelog = 0"

@Entity
data class AppSettings(
@PrimaryKey(autoGenerate = true) val id: Int,
Expand Down Expand Up @@ -94,6 +101,7 @@ data class AppSettings(
defaultValue = DEFAULT_THEME_COLOR.toString(),
)
val themeColor: Int,
// TODO get rid of this column next time you regenerate the app
@ColumnInfo(
name = "viewed_changelog",
defaultValue = "0",
Expand Down Expand Up @@ -134,6 +142,11 @@ data class AppSettings(
defaultValue = DEFAULT_HIDE_SYMBOLS.toString(),
)
val hideSymbols: Int,
@ColumnInfo(
name = "last_version_code_viewed",
defaultValue = "0",
)
val lastVersionCodeViewed: Int,
)

@Dao
Expand All @@ -144,14 +157,17 @@ interface AppSettingsDao {
@Update
suspend fun updateAppSettings(appSettings: AppSettings)

@Query("UPDATE AppSettings set viewed_changelog = 1")
suspend fun markChangelogViewed()
@Query("UPDATE AppSettings SET last_version_code_viewed = :versionCode")
suspend fun updateLastVersionCode(versionCode: Int)
}

// Declares the DAO as a private property in the constructor. Pass in the DAO
// instead of the whole database, because you only need access to the DAO
class AppSettingsRepository(private val appSettingsDao: AppSettingsDao) {

private val _changelog = MutableStateFlow("")
val changelog = _changelog.asStateFlow()

// Room executes all queries on a separate thread.
// Observed Flow will notify the observer when the data has changed.
val appSettings = appSettingsDao.getSettings()
Expand All @@ -162,8 +178,36 @@ class AppSettingsRepository(private val appSettingsDao: AppSettingsDao) {
}

@WorkerThread
suspend fun markChangelogViewed() {
appSettingsDao.markChangelogViewed()
suspend fun updateLastVersionCodeViewed(versionCode: Int) {
appSettingsDao.updateLastVersionCode(versionCode)
}

@WorkerThread
suspend fun updateChangelog() {
withContext(Dispatchers.IO) {
try {
val httpClient: OkHttpClient = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.addNetworkInterceptor { chain ->
chain.request().newBuilder()
.header("User-Agent", "Thumb-Key")
.build()
.let(chain::proceed)
}
.build()
Log.d("thumb-key", "Fetching RELEASES.md ...")
// Fetch the markdown text
val releasesUrl =
"https://raw.githubusercontent.com/dessalines/thumb-key/main/RELEASES.md".toHttpUrl()
val req = Request.Builder().url(releasesUrl).build()
val res = httpClient.newCall(req).execute()
_changelog.value = res.body.string()
} catch (e: Exception) {
Log.e("thumb-key", "Failed to load changelog: $e")
}
}
}
}

Expand Down Expand Up @@ -223,8 +267,16 @@ val MIGRATION_7_8 = object : Migration(7, 8) {
}
}

val MIGRATION_8_9 = object : Migration(8, 9) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(
"alter table AppSettings add column last_version_code_viewed INTEGER NOT NULL default 0",
)
}
}

@Database(
version = 8,
version = 9,
entities = [AppSettings::class],
exportSchema = true,
)
Expand Down Expand Up @@ -255,6 +307,7 @@ abstract class AppDB : RoomDatabase() {
MIGRATION_5_6,
MIGRATION_6_7,
MIGRATION_7_8,
MIGRATION_8_9,
)
// Necessary because it can't insert data on creation
.addCallback(object : Callback() {
Expand All @@ -280,15 +333,19 @@ abstract class AppDB : RoomDatabase() {
}

class AppSettingsViewModel(private val repository: AppSettingsRepository) : ViewModel() {

val appSettings = repository.appSettings
val changelog = repository.changelog

fun update(appSettings: AppSettings) = viewModelScope.launch {
repository.update(appSettings)
}

fun markChangelogViewed() = viewModelScope.launch {
repository.markChangelogViewed()
fun updateLastVersionCodeViewed(versionCode: Int) = viewModelScope.launch {
repository.updateLastVersionCodeViewed(versionCode)
}

fun updateChangelog() = viewModelScope.launch {
repository.updateChangelog()
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package com.dessalines.thumbkey.ui.components.common

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
import com.dessalines.thumbkey.R
import com.dessalines.thumbkey.db.AppSettingsViewModel
import com.dessalines.thumbkey.utils.getVersionCode
import dev.jeziellago.compose.markdowntext.MarkdownText

val DONATION_MARKDOWN = """
### Support Thumb-Key
[Thumb-Key](https://github.com/dessalines/thumb-key) is free,
open-source software, meaning no spying, keylogging, or advertising,
ever.
No one likes recurring donations, but they've proven to be the only
way open source software like Thumb-Key can stay alive. If you find
yourself using Thumb-Key every day, please consider donating:
- [Support on Liberapay](https://liberapay.com/dessalines).
- [Support on Patreon](https://www.patreon.com/dessalines).
""".trimIndent()

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun ShowChangelog(appSettingsViewModel: AppSettingsViewModel) {
val ctx = LocalContext.current
val lastVersionCodeViewed = appSettingsViewModel.appSettings.observeAsState().value?.lastVersionCodeViewed

// Make sure its initialized
lastVersionCodeViewed?.also { lastViewed ->
val currentVersionCode = ctx.getVersionCode()
val viewed = lastViewed == currentVersionCode

var whatsChangedDialogOpen by remember { mutableStateOf(!viewed) }

if (whatsChangedDialogOpen) {
val scrollState = rememberScrollState()
val markdown by appSettingsViewModel.changelog.collectAsState()
LaunchedEffect(appSettingsViewModel) {
appSettingsViewModel.updateChangelog()
}

AlertDialog(
text = {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(scrollState),
) {
val markdownText = DONATION_MARKDOWN + markdown
MarkdownText(markdownText)
}
},
confirmButton = {
Button(
onClick = {
whatsChangedDialogOpen = false
appSettingsViewModel.updateLastVersionCodeViewed(currentVersionCode)
},
modifier = Modifier.fillMaxWidth(),
) {
Text(stringResource(R.string.done))
}
},
onDismissRequest = {
whatsChangedDialogOpen = false
appSettingsViewModel.updateLastVersionCodeViewed(currentVersionCode)
},
modifier = Modifier.semantics { testTagsAsResourceId = true },
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,7 @@ private fun updateAppSettings(
theme = themeState.value,
themeColor = themeColorState.value,
viewedChangelog = appSettingsViewModel.appSettings.value?.viewedChangelog ?: 0,
lastVersionCodeViewed = appSettingsViewModel.appSettings.value?.lastVersionCodeViewed ?: 0,
),
)
}
Expand Down
Loading

0 comments on commit f0a9b5f

Please sign in to comment.