Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix blink favorite icon #945

Closed
wants to merge 14 commits into from
Closed
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.ProvidedValue
import androidx.compose.runtime.State
import androidx.compose.runtime.currentComposer
import androidx.compose.runtime.produceState
import androidx.compose.runtime.staticCompositionLocalOf
import io.github.takahirom.rin.produceRetainedState
import kotlinx.coroutines.CancellationException
Expand Down Expand Up @@ -57,6 +58,28 @@ fun SafeLaunchedEffect(key: Any?, block: suspend CoroutineScope.() -> Unit) {
}
}

@Composable
fun <T : R, R> StateFlow<T>.safeCollectAsState(
context: CoroutineContext = EmptyCoroutineContext,
): State<R> {
val composeEffectErrorHandler = LocalComposeEffectErrorHandler.current
return produceState(value, this, context) {
try {
if (context == EmptyCoroutineContext) {
collect { value = it }
} else {
withContext(context) {
collect { value = it }
}
}
} catch (e: CancellationException) {
throw e
} catch (e: Throwable) {
composeEffectErrorHandler.emit(e)
}
}
}

@Composable
fun <T : R, R> Flow<T>.safeCollectAsRetainedState(
initial: R,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import javax.inject.Singleton

@InstallIn(SingletonComponent::class)
Expand All @@ -18,6 +20,9 @@ public class UserDataStoreModule {
@UserDataStoreQualifier
dataStore: DataStore<Preferences>,
): UserDataStore {
return UserDataStore(dataStore)
return UserDataStore(
dataStore = dataStore,
coroutineScope = CoroutineScope(Job()),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ import androidx.compose.runtime.setValue
import co.touchlab.kermit.Logger
import io.github.droidkaigi.confsched.compose.SafeLaunchedEffect
import io.github.droidkaigi.confsched.compose.safeCollectAsRetainedState
import io.github.droidkaigi.confsched.compose.safeCollectAsState
import io.github.droidkaigi.confsched.data.sessions.response.SessionsAllResponse
import io.github.droidkaigi.confsched.data.user.UserDataStore
import io.github.droidkaigi.confsched.model.DroidKaigi2024Day
import io.github.droidkaigi.confsched.model.SessionsRepository
import io.github.droidkaigi.confsched.model.Timetable
import io.github.droidkaigi.confsched.model.TimetableItem
import io.github.droidkaigi.confsched.model.TimetableItemId
import kotlinx.collections.immutable.persistentSetOf
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
Expand All @@ -44,7 +44,7 @@ public class DefaultSessionsRepository(
refreshSessionData()
emitAll(sessionCacheDataStore.getTimetableStream())
},
userDataStore.getFavoriteSessionStream(),
userDataStore.getFavoriteSessionStream,
) { timetable, favorites ->
timetable.copy(bookmarks = favorites)
}
Expand Down Expand Up @@ -110,8 +110,8 @@ public class DefaultSessionsRepository(
}
}.safeCollectAsRetainedState(Timetable())
val favoriteSessions by remember {
userDataStore.getFavoriteSessionStream()
}.safeCollectAsRetainedState(persistentSetOf())
userDataStore.getFavoriteSessionStream
}.safeCollectAsState()

Logger.d { "DefaultSessionsRepository timetable() count=${timetable.timetableItems.size}" }
return timetable.copy(bookmarks = favoriteSessions)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,28 @@ import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.core.stringPreferencesKey
import io.github.droidkaigi.confsched.model.TimetableItemId
import kotlinx.collections.immutable.PersistentSet
import kotlinx.collections.immutable.persistentSetOf
import kotlinx.collections.immutable.toPersistentSet
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn

public class UserDataStore(private val dataStore: DataStore<Preferences>) {
public class UserDataStore(
private val dataStore: DataStore<Preferences>,
coroutineScope: CoroutineScope,
) {

private val mutableIdToken = MutableStateFlow<String?>(null)
public val idToken: StateFlow<String?> = mutableIdToken

public fun getFavoriteSessionStream(): Flow<PersistentSet<TimetableItemId>> {
return dataStore.data
public val getFavoriteSessionStream: StateFlow<PersistentSet<TimetableItemId>> =
dataStore.data
.catch { exception ->
if (exception is IOException) {
emit(emptyPreferences())
Expand All @@ -34,11 +41,14 @@ public class UserDataStore(private val dataStore: DataStore<Preferences>) {
(preferences[KEY_FAVORITE_SESSION_IDS]?.split(",") ?: listOf())
.map { TimetableItemId(it) }
.toPersistentSet()
}
}
}.stateIn(
scope = coroutineScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = persistentSetOf(),
)

public suspend fun toggleFavorite(id: TimetableItemId) {
val updatedFavorites = getFavoriteSessionStream().first().toMutableSet()
val updatedFavorites = getFavoriteSessionStream.first().toMutableSet()

if (updatedFavorites.contains(id)) {
updatedFavorites.remove(id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import io.ktor.client.plugins.logging.Logging
import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import org.koin.core.module.Module
import org.koin.core.module.dsl.singleOf
Expand Down Expand Up @@ -109,7 +110,10 @@ public val dataModule: Module = module {
requireNotNull(documentDirectory).path + "/confsched2024.preferences_pb"
},
)
UserDataStore(dataStore)
UserDataStore(
dataStore = dataStore,
coroutineScope = CoroutineScope(Job()),
)
}
single {
val dataStore = createDataStore(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.github.droidkaigi.confsched.testing.data

import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import dagger.Module
import dagger.Provides
import dagger.hilt.components.SingletonComponent
import dagger.hilt.testing.TestInstallIn
import io.github.droidkaigi.confsched.data.user.UserDataStore
import io.github.droidkaigi.confsched.data.user.UserDataStoreModule
import io.github.droidkaigi.confsched.data.user.UserDataStoreQualifier
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.test.TestDispatcher
import javax.inject.Singleton

@Module
@TestInstallIn(components = [SingletonComponent::class], replaces = [UserDataStoreModule::class])
class TestUserDataStoreModule {

@Provides
@Singleton
public fun provideUserDataStore(
@UserDataStoreQualifier
dataStore: DataStore<Preferences>,
testDispatcher: TestDispatcher,
): UserDataStore {
return UserDataStore(
dataStore = dataStore,
coroutineScope = CoroutineScope(testDispatcher + Job()),
)
}
}
Loading