Skip to content

Commit

Permalink
achievements: Implement save achievements marked status
Browse files Browse the repository at this point in the history
  • Loading branch information
Omico committed Jun 9, 2024
1 parent d7761ce commit 8f94200
Show file tree
Hide file tree
Showing 18 changed files with 209 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="wwm.desktop.run" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="env">
<map>
<entry key="WWM_LOCAL_DIRECTORY" value="build/wwm-local" />
</map>
</option>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
Expand All @@ -22,4 +27,4 @@
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>
</component>
3 changes: 3 additions & 0 deletions wwm/core/data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ kotlin {
implementation(project(":wwm-core-foundation"))
implementation(project(":wwm-core-resources"))
}
dependencies {
implementation(kotlinx.serialization.json)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ interface AchievementsRepository {
suspend fun reloadAchievementCategories()
suspend fun reloadAchievementGroups()
suspend fun reloadMultiText(locale: WwLocale)
suspend fun markAchievement(achievementId: Int)
suspend fun unmarkAchievement(achievementId: Int)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ data class WwmAchievements(
val achievementGroups: WwAchievementGroups = emptyList(),
val multiText: WwMultiText = emptyList(),
val locale: WwLocale = WwLocale.ZH_HANS,
val markedAchievementIds: WwmMarkedAchievementIds = emptySet(),
) {
companion object {
val Empty: WwmAchievements = WwmAchievements()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
package dev.omico.wwm.data

import dev.omico.wwm.data.internal.AchievementsRepositoryImpl
import dev.omico.wwm.data.internal.MarkedAchievementsDataStore

interface WwmDataComponent {
val achievementsRepository: AchievementsRepository
fun provideAchievementsRepository(): AchievementsRepository = AchievementsRepositoryImpl()
fun provideAchievementsRepository(): AchievementsRepository =
AchievementsRepositoryImpl(
markedAchievementsDataStore = MarkedAchievementsDataStore(),
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/*
* Copyright 2024 Omico. All Rights Reserved.
*/
package dev.omico.wwm.data

typealias WwmMarkedAchievementIds = Set<Int>
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package dev.omico.wwm.data.internal

import dev.omico.wwm.data.AchievementsRepository
import dev.omico.wwm.data.WwmAchievements
import dev.omico.wwm.data.WwmMarkedAchievementIds
import dev.omico.wwm.resources.WwmResources
import dev.omico.wwm.resources.model.game.WwAchievementCategories
import dev.omico.wwm.resources.model.game.WwAchievementGroups
Expand All @@ -14,31 +15,49 @@ import dev.omico.wwm.resources.model.game.WwMultiText
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

internal class AchievementsRepositoryImpl : AchievementsRepository {
internal class AchievementsRepositoryImpl(
private val markedAchievementsDataStore: MarkedAchievementsDataStore,
) : AchievementsRepository {
private val wwAchievements: MutableStateFlow<WwAchievements> = MutableStateFlow(emptyList())
private val wwAchievementCategories: MutableStateFlow<WwAchievementCategories> = MutableStateFlow(emptyList())
private val wwAchievementGroups: MutableStateFlow<WwAchievementGroups> = MutableStateFlow(emptyList())
private val wwMultiText: MutableStateFlow<WwMultiText> = MutableStateFlow(emptyList())
private val wwLocale: MutableStateFlow<WwLocale> = MutableStateFlow(WwLocale.ZH_HANS)
private val markedAchievementIds: MutableStateFlow<WwmMarkedAchievementIds> = MutableStateFlow(emptySet())

@Suppress("UNCHECKED_CAST")
override val achievements: Flow<WwmAchievements> =
combine(
wwAchievements,
wwAchievementCategories,
wwAchievementGroups,
wwMultiText,
wwLocale,
::WwmAchievements,
)
markedAchievementIds,
) { arguments ->
WwmAchievements(
achievements = arguments[0] as WwAchievements,
achievementCategories = arguments[1] as WwAchievementCategories,
achievementGroups = arguments[2] as WwAchievementGroups,
multiText = arguments[3] as WwMultiText,
locale = arguments[4] as WwLocale,
markedAchievementIds = arguments[5] as WwmMarkedAchievementIds,
)
}

override suspend fun load() {
reloadAchievements()
reloadAchievementCategories()
reloadAchievementGroups()
}

override suspend fun reloadAchievements(): Unit = wwAchievements.emit(WwmResources.loadAchievements())
override suspend fun reloadAchievements() {
wwAchievements.emit(WwmResources.loadAchievements())
markedAchievementIds.emit(markedAchievementsDataStore.load())
}

override suspend fun reloadAchievementCategories(): Unit =
wwAchievementCategories.emit(WwmResources.loadAchievementCategories())
Expand All @@ -50,4 +69,18 @@ internal class AchievementsRepositoryImpl : AchievementsRepository {
wwMultiText.emit(WwmResources.loadMultiText(locale))
wwLocale.emit(locale)
}

override suspend fun markAchievement(achievementId: Int): Unit =
saveMarkedAchievementIds(markedAchievementIds.value + achievementId)

override suspend fun unmarkAchievement(achievementId: Int): Unit =
saveMarkedAchievementIds(markedAchievementIds.value - achievementId)

private val markedAchievementIdsMutex: Mutex = Mutex()

private suspend fun saveMarkedAchievementIds(ids: WwmMarkedAchievementIds): Unit =
markedAchievementIdsMutex.withLock {
markedAchievementsDataStore.save(ids)
markedAchievementIds.emit(ids)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2024 Omico. All Rights Reserved.
*/
package dev.omico.wwm.data.internal

import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

@OptIn(ExperimentalSerializationApi::class)
internal val json: Json =
Json {
prettyPrint = true
prettyPrintIndent = " "
}

internal inline fun <reified T> T.toJson(): String = json.encodeToString(this)

internal inline fun <reified T> String.fromJson(): T = json.decodeFromString(this)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* Copyright 2024 Omico. All Rights Reserved.
*/
package dev.omico.wwm.data.internal

import dev.omico.wwm.data.WwmMarkedAchievementIds

@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") // TODO KT-61573
internal expect class MarkedAchievementsDataStore() {
suspend fun load(): WwmMarkedAchievementIds
suspend fun save(ids: WwmMarkedAchievementIds)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright 2024 Omico. All Rights Reserved.
*/
package dev.omico.wwm.data.internal

import dev.omico.wwm.data.WwmMarkedAchievementIds
import dev.omico.wwm.foundation.wwmMarkedAchievementsFile
import java.nio.file.Path
import kotlin.io.path.exists
import kotlin.io.path.readText
import kotlin.io.path.writeText

@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") // TODO KT-61573
internal actual class MarkedAchievementsDataStore {
actual suspend fun load(): WwmMarkedAchievementIds =
wwmMarkedAchievementsFile.takeIf(Path::exists)?.readText()?.fromJson<WwmMarkedAchievementIds>() ?: emptySet()

actual suspend fun save(ids: WwmMarkedAchievementIds) {
wwmMarkedAchievementsFile.writeText(ids.toJson())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright 2024 Omico. All Rights Reserved.
*/
package dev.omico.wwm.data.internal

import dev.omico.wwm.data.WwmMarkedAchievementIds
import kotlinx.browser.localStorage

@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") // TODO KT-61573
internal actual class MarkedAchievementsDataStore {
actual suspend fun load(): WwmMarkedAchievementIds {
val achievements = localStorage.getItem(ACHIEVEMENTS_KEY) ?: return emptySet()
return achievements.split(",").map(String::toInt).toSet()
}

actual suspend fun save(ids: WwmMarkedAchievementIds): Unit =
localStorage.setItem(ACHIEVEMENTS_KEY, ids.joinToString(","))
}

private const val ACHIEVEMENTS_KEY = "achievements"
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright 2024 Omico. All Rights Reserved.
*/
package dev.omico.wwm.data.internal

import dev.omico.wwm.data.WwmMarkedAchievementIds
import kotlinx.browser.localStorage

@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") // TODO KT-61573
internal actual class MarkedAchievementsDataStore {
actual suspend fun load(): WwmMarkedAchievementIds {
val achievements = localStorage.getItem(ACHIEVEMENTS_KEY) ?: return emptySet()
return achievements.split(",").map(String::toInt).toSet()
}

actual suspend fun save(ids: WwmMarkedAchievementIds): Unit =
localStorage.setItem(ACHIEVEMENTS_KEY, ids.joinToString(","))
}

private const val ACHIEVEMENTS_KEY = "achievements"
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ 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.setValue
import com.slack.circuit.retained.collectAsRetainedState
import dev.omico.wwm.data.WwmAchievements
import dev.omico.wwm.ui.WwmUiComponent
import kotlinx.coroutines.launch

context(WwmUiComponent)
@Composable
internal fun produceAchievementsUiState(): AchievementsUiState {
val scope = rememberCoroutineScope()
val achievements by achievementsRepository.achievements.collectAsRetainedState(initial = WwmAchievements.Empty)
var locale by remember { mutableStateOf(achievements.locale) }
LaunchedEffect(Unit) { achievementsRepository.load() }
Expand All @@ -25,6 +28,13 @@ internal fun produceAchievementsUiState(): AchievementsUiState {
eventSink = { event ->
when (event) {
is AchievementsUiEvent.ChangeLocale -> locale = event.locale
is AchievementsUiEvent.ChangeAchievementMark ->
scope.launch {
when {
event.marked -> achievementsRepository.markAchievement(event.achievementId)
else -> achievementsRepository.unmarkAchievement(event.achievementId)
}
}
}
},
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,9 @@ import dev.omico.wwm.resources.model.game.WwLocale

sealed interface AchievementsUiEvent : CircuitUiEvent {
data class ChangeLocale(val locale: WwLocale) : AchievementsUiEvent

data class ChangeAchievementMark(
val marked: Boolean,
val achievementId: Int,
) : AchievementsUiEvent
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ package dev.omico.wwm.feature.achievements

import com.slack.circuit.runtime.CircuitUiState
import dev.omico.wwm.data.WwmAchievements
import dev.omico.wwm.resources.model.game.WwAchievement

data class AchievementsUiState(
val achievements: WwmAchievements,
val eventSink: (AchievementsUiEvent) -> Unit,
) : CircuitUiState

internal fun AchievementsUiState.isAchievementMarked(achievement: WwAchievement): Boolean =
achievement.id in achievements.markedAchievementIds
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import dev.omico.wwm.feature.achievements.AchievementsUiEvent
import dev.omico.wwm.feature.achievements.AchievementsUiState
import dev.omico.wwm.feature.achievements.isAchievementMarked
import dev.omico.wwm.resources.model.game.WwAchievement
import dev.omico.wwm.resources.model.game.WwAchievementGroup
import dev.omico.wwm.resources.rememberWwText
Expand All @@ -33,12 +35,19 @@ internal fun AchievementsDetailContent(
contentPadding = contentPadding,
content = {
items(
items = achievements,
items = achievements.sortedBy(state::isAchievementMarked),
key = WwAchievement::id,
itemContent = { achievement ->
AchievementsDetailItem(
marked = false,
onMarkedChange = {},
marked = state.isAchievementMarked(achievement),
onMarkedChange = { marked ->
state.eventSink(
AchievementsUiEvent.ChangeAchievementMark(
marked = marked,
achievementId = achievement.id,
),
)
},
name = rememberWwText(
multiText = state.achievements.multiText,
name = achievement.name,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright 2024 Omico. All Rights Reserved.
*/
package dev.omico.wwm.foundation

object DesktopApplicationEnvironments {
private val environments: Map<String, String> = System.getenv()

val WWM_LOCAL_DIRECTORY: String? = environments["WWM_LOCAL_DIRECTORY"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright 2024 Omico. All Rights Reserved.
*/
package dev.omico.wwm.foundation

import java.nio.file.Path
import kotlin.io.path.Path
import kotlin.io.path.createParentDirectories

private val userHomeDirectory: Path = Path(System.getProperty("user.home"))

private val wwmLocalDirectory: Path =
DesktopApplicationEnvironments.WWM_LOCAL_DIRECTORY?.let(::Path)
?: userHomeDirectory.resolve(".wwm")

val wwmMarkedAchievementsFile: Path = wwmLocalDirectory.resolve("MarkedAchievements.json").createParentDirectories()

0 comments on commit 8f94200

Please sign in to comment.