wip
This commit is contained in:
142
TASK_CACHING.md
Normal file
142
TASK_CACHING.md
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
# Task Disk Caching
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
All user tasks are automatically cached to disk for offline access. This provides a seamless experience even when the network is unavailable.
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
### On App Startup (After Login)
|
||||||
|
```kotlin
|
||||||
|
LookupsRepository.initialize()
|
||||||
|
```
|
||||||
|
|
||||||
|
1. **Loads cached tasks from disk immediately** - Instant access to previously fetched tasks
|
||||||
|
2. **Fetches fresh data from API** - Updates with latest data when online
|
||||||
|
3. **Saves to disk** - Overwrites cache with fresh data
|
||||||
|
|
||||||
|
### Cache Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
App Start
|
||||||
|
↓
|
||||||
|
Load from Disk Cache (Instant offline access)
|
||||||
|
↓
|
||||||
|
Fetch from API (When online)
|
||||||
|
↓
|
||||||
|
Update Memory + Disk Cache
|
||||||
|
↓
|
||||||
|
UI Updates Automatically
|
||||||
|
```
|
||||||
|
|
||||||
|
## Platform Storage
|
||||||
|
|
||||||
|
### Android
|
||||||
|
- **Location**: SharedPreferences
|
||||||
|
- **File**: `mycrib_cache`
|
||||||
|
- **Key**: `cached_tasks`
|
||||||
|
- **Format**: JSON string
|
||||||
|
|
||||||
|
### iOS
|
||||||
|
- **Location**: NSUserDefaults
|
||||||
|
- **Key**: `cached_tasks`
|
||||||
|
- **Format**: JSON string
|
||||||
|
|
||||||
|
### JVM/Desktop
|
||||||
|
- **Location**: Java Preferences
|
||||||
|
- **Node**: `com.mycrib.cache`
|
||||||
|
- **Key**: `cached_tasks`
|
||||||
|
- **Format**: JSON string
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Accessing Cached Tasks
|
||||||
|
|
||||||
|
**Kotlin/Android:**
|
||||||
|
```kotlin
|
||||||
|
val tasks by LookupsRepository.allTasks.collectAsState()
|
||||||
|
// Tasks are available immediately from cache, updated when API responds
|
||||||
|
```
|
||||||
|
|
||||||
|
**Swift/iOS:**
|
||||||
|
```swift
|
||||||
|
@ObservedObject var lookupsManager = LookupsManager.shared
|
||||||
|
// Access via: lookupsManager.allTasks
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual Operations
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// Refresh from API and update cache
|
||||||
|
LookupsRepository.refresh()
|
||||||
|
|
||||||
|
// Clear cache (called automatically on logout)
|
||||||
|
LookupsRepository.clear()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
✅ **Offline Access** - Tasks available without network
|
||||||
|
✅ **Fast Startup** - Instant task display from disk cache
|
||||||
|
✅ **Automatic Sync** - Fresh data fetched and cached when online
|
||||||
|
✅ **Seamless UX** - Users don't notice when offline
|
||||||
|
✅ **Battery Friendly** - Reduces unnecessary API calls
|
||||||
|
|
||||||
|
## Cache Lifecycle
|
||||||
|
|
||||||
|
### On Login
|
||||||
|
- Loads tasks from disk (if available)
|
||||||
|
- Fetches from API
|
||||||
|
- Updates disk cache
|
||||||
|
|
||||||
|
### During Session
|
||||||
|
- Tasks served from memory (StateFlow)
|
||||||
|
- Can manually refresh with `LookupsRepository.refresh()`
|
||||||
|
|
||||||
|
### On Logout
|
||||||
|
- Memory cache cleared
|
||||||
|
- **Disk cache cleared** (for security)
|
||||||
|
|
||||||
|
### On App Restart
|
||||||
|
- Cycle repeats with cached data from disk
|
||||||
|
|
||||||
|
## Implementation Files
|
||||||
|
|
||||||
|
**Common:**
|
||||||
|
- `TaskCacheStorage.kt` - Unified cache interface
|
||||||
|
- `TaskCacheManager.kt` - Platform-specific interface
|
||||||
|
|
||||||
|
**Platform Implementations:**
|
||||||
|
- `TaskCacheManager.android.kt` - SharedPreferences
|
||||||
|
- `TaskCacheManager.ios.kt` - NSUserDefaults
|
||||||
|
- `TaskCacheManager.jvm.kt` - Java Preferences
|
||||||
|
|
||||||
|
**Repository:**
|
||||||
|
- `LookupsRepository.kt` - Cache orchestration
|
||||||
|
|
||||||
|
## Storage Size
|
||||||
|
|
||||||
|
Tasks are stored as JSON. Approximate sizes:
|
||||||
|
- 100 tasks ≈ 50-100 KB
|
||||||
|
- 1000 tasks ≈ 500 KB - 1 MB
|
||||||
|
|
||||||
|
Storage is minimal and well within platform limits.
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
If cache read fails:
|
||||||
|
- Logs error to console
|
||||||
|
- Returns `null`
|
||||||
|
- Falls back to API fetch
|
||||||
|
|
||||||
|
If cache write fails:
|
||||||
|
- Logs error to console
|
||||||
|
- Continues normally
|
||||||
|
- Retry on next API fetch
|
||||||
|
|
||||||
|
## Security Notes
|
||||||
|
|
||||||
|
- Cache is cleared on logout
|
||||||
|
- Uses platform-standard secure storage
|
||||||
|
- No sensitive authentication data cached
|
||||||
|
- Only task data (titles, descriptions, status, etc.)
|
||||||
@@ -8,6 +8,8 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import com.mycrib.storage.TokenManager
|
import com.mycrib.storage.TokenManager
|
||||||
import com.mycrib.storage.TokenStorage
|
import com.mycrib.storage.TokenStorage
|
||||||
|
import com.mycrib.storage.TaskCacheManager
|
||||||
|
import com.mycrib.storage.TaskCacheStorage
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -17,6 +19,9 @@ class MainActivity : ComponentActivity() {
|
|||||||
// Initialize TokenStorage with Android TokenManager
|
// Initialize TokenStorage with Android TokenManager
|
||||||
TokenStorage.initialize(TokenManager.getInstance(applicationContext))
|
TokenStorage.initialize(TokenManager.getInstance(applicationContext))
|
||||||
|
|
||||||
|
// Initialize TaskCacheStorage for offline task caching
|
||||||
|
TaskCacheStorage.initialize(TaskCacheManager.getInstance(applicationContext))
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
App()
|
App()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.mycrib.storage
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Android implementation of TaskCacheManager using SharedPreferences.
|
||||||
|
*/
|
||||||
|
actual class TaskCacheManager(private val context: Context) {
|
||||||
|
private val prefs: SharedPreferences = context.getSharedPreferences(
|
||||||
|
PREFS_NAME,
|
||||||
|
Context.MODE_PRIVATE
|
||||||
|
)
|
||||||
|
|
||||||
|
actual fun saveTasks(tasksJson: String) {
|
||||||
|
prefs.edit().putString(KEY_TASKS, tasksJson).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun getTasks(): String? {
|
||||||
|
return prefs.getString(KEY_TASKS, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun clearTasks() {
|
||||||
|
prefs.edit().remove(KEY_TASKS).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val PREFS_NAME = "mycrib_cache"
|
||||||
|
private const val KEY_TASKS = "cached_tasks"
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var instance: TaskCacheManager? = null
|
||||||
|
|
||||||
|
fun getInstance(context: Context): TaskCacheManager {
|
||||||
|
return instance ?: synchronized(this) {
|
||||||
|
instance ?: TaskCacheManager(context.applicationContext).also { instance = it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -88,4 +88,21 @@ class LookupsApi(private val client: HttpClient = ApiClient.httpClient) {
|
|||||||
ApiResult.Error(e.message ?: "Unknown error occurred")
|
ApiResult.Error(e.message ?: "Unknown error occurred")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getAllTasks(token: String): ApiResult<List<CustomTask>> {
|
||||||
|
return try {
|
||||||
|
val response = client.get("$baseUrl/tasks/") {
|
||||||
|
header("Authorization", "Token $token")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.status.isSuccess()) {
|
||||||
|
val data: PaginatedResponse<CustomTask> = response.body()
|
||||||
|
ApiResult.Success(data.results)
|
||||||
|
} else {
|
||||||
|
ApiResult.Error("Failed to fetch tasks", response.status.value)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
ApiResult.Error(e.message ?: "Unknown error occurred")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.mycrib.shared.models.*
|
|||||||
import com.mycrib.shared.network.ApiResult
|
import com.mycrib.shared.network.ApiResult
|
||||||
import com.mycrib.shared.network.LookupsApi
|
import com.mycrib.shared.network.LookupsApi
|
||||||
import com.mycrib.storage.TokenStorage
|
import com.mycrib.storage.TokenStorage
|
||||||
|
import com.mycrib.storage.TaskCacheStorage
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -33,6 +34,9 @@ object LookupsRepository {
|
|||||||
private val _taskCategories = MutableStateFlow<List<TaskCategory>>(emptyList())
|
private val _taskCategories = MutableStateFlow<List<TaskCategory>>(emptyList())
|
||||||
val taskCategories: StateFlow<List<TaskCategory>> = _taskCategories
|
val taskCategories: StateFlow<List<TaskCategory>> = _taskCategories
|
||||||
|
|
||||||
|
private val _allTasks = MutableStateFlow<List<CustomTask>>(emptyList())
|
||||||
|
val allTasks: StateFlow<List<CustomTask>> = _allTasks
|
||||||
|
|
||||||
private val _isLoading = MutableStateFlow(false)
|
private val _isLoading = MutableStateFlow(false)
|
||||||
val isLoading: StateFlow<Boolean> = _isLoading
|
val isLoading: StateFlow<Boolean> = _isLoading
|
||||||
|
|
||||||
@@ -51,6 +55,14 @@ object LookupsRepository {
|
|||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
_isLoading.value = true
|
_isLoading.value = true
|
||||||
|
|
||||||
|
// Load cached tasks from disk immediately for offline access
|
||||||
|
val cachedTasks = TaskCacheStorage.getTasks()
|
||||||
|
if (cachedTasks != null) {
|
||||||
|
_allTasks.value = cachedTasks
|
||||||
|
println("Loaded ${cachedTasks.size} tasks from cache")
|
||||||
|
}
|
||||||
|
|
||||||
val token = TokenStorage.getToken()
|
val token = TokenStorage.getToken()
|
||||||
|
|
||||||
if (token != null) {
|
if (token != null) {
|
||||||
@@ -89,6 +101,20 @@ object LookupsRepository {
|
|||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
launch {
|
||||||
|
when (val result = lookupsApi.getAllTasks(token)) {
|
||||||
|
is ApiResult.Success -> {
|
||||||
|
_allTasks.value = result.data
|
||||||
|
// Save to disk cache for offline access
|
||||||
|
TaskCacheStorage.saveTasks(result.data)
|
||||||
|
println("Fetched and cached ${result.data.size} tasks from API")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
println("Failed to fetch tasks from API, using cached data if available")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_isInitialized.value = true
|
_isInitialized.value = true
|
||||||
@@ -106,6 +132,9 @@ object LookupsRepository {
|
|||||||
_taskPriorities.value = emptyList()
|
_taskPriorities.value = emptyList()
|
||||||
_taskStatuses.value = emptyList()
|
_taskStatuses.value = emptyList()
|
||||||
_taskCategories.value = emptyList()
|
_taskCategories.value = emptyList()
|
||||||
|
_allTasks.value = emptyList()
|
||||||
|
// Clear disk cache on logout
|
||||||
|
TaskCacheStorage.clearTasks()
|
||||||
_isInitialized.value = false
|
_isInitialized.value = false
|
||||||
_isLoading.value = false
|
_isLoading.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.mycrib.storage
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Platform-specific task cache manager interface for persistent storage.
|
||||||
|
* Each platform implements this using their native storage mechanisms.
|
||||||
|
*/
|
||||||
|
@Suppress("NO_ACTUAL_FOR_EXPECT")
|
||||||
|
expect class TaskCacheManager {
|
||||||
|
fun saveTasks(tasksJson: String)
|
||||||
|
fun getTasks(): String?
|
||||||
|
fun clearTasks()
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.mycrib.storage
|
||||||
|
|
||||||
|
import com.mycrib.shared.models.CustomTask
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Task cache storage that provides a unified interface for accessing platform-specific
|
||||||
|
* persistent storage. This allows tasks to persist across app restarts for offline access.
|
||||||
|
*/
|
||||||
|
object TaskCacheStorage {
|
||||||
|
private var cacheManager: TaskCacheManager? = null
|
||||||
|
private val json = Json { ignoreUnknownKeys = true }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize TaskCacheStorage with a platform-specific TaskCacheManager.
|
||||||
|
* This should be called once during app initialization.
|
||||||
|
*/
|
||||||
|
fun initialize(manager: TaskCacheManager) {
|
||||||
|
cacheManager = manager
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveTasks(tasks: List<CustomTask>) {
|
||||||
|
try {
|
||||||
|
val tasksJson = json.encodeToString(tasks)
|
||||||
|
cacheManager?.saveTasks(tasksJson)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("Error saving tasks to cache: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTasks(): List<CustomTask>? {
|
||||||
|
return try {
|
||||||
|
val tasksJson = cacheManager?.getTasks()
|
||||||
|
if (tasksJson != null) {
|
||||||
|
json.decodeFromString<List<CustomTask>>(tasksJson)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("Error loading tasks from cache: ${e.message}")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearTasks() {
|
||||||
|
cacheManager?.clearTasks()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,9 +3,15 @@ package com.example.mycrib
|
|||||||
import androidx.compose.ui.window.ComposeUIViewController
|
import androidx.compose.ui.window.ComposeUIViewController
|
||||||
import com.mycrib.storage.TokenManager
|
import com.mycrib.storage.TokenManager
|
||||||
import com.mycrib.storage.TokenStorage
|
import com.mycrib.storage.TokenStorage
|
||||||
|
import com.mycrib.storage.TaskCacheManager
|
||||||
|
import com.mycrib.storage.TaskCacheStorage
|
||||||
|
|
||||||
fun MainViewController() = ComposeUIViewController {
|
fun MainViewController() = ComposeUIViewController {
|
||||||
// Initialize TokenStorage with iOS TokenManager
|
// Initialize TokenStorage with iOS TokenManager
|
||||||
TokenStorage.initialize(TokenManager.getInstance())
|
TokenStorage.initialize(TokenManager.getInstance())
|
||||||
|
|
||||||
|
// Initialize TaskCacheStorage for offline task caching
|
||||||
|
TaskCacheStorage.initialize(TaskCacheManager.getInstance())
|
||||||
|
|
||||||
App()
|
App()
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.mycrib.storage
|
||||||
|
|
||||||
|
import platform.Foundation.NSUserDefaults
|
||||||
|
import kotlin.concurrent.Volatile
|
||||||
|
|
||||||
|
/**
|
||||||
|
* iOS implementation of TaskCacheManager using NSUserDefaults.
|
||||||
|
*/
|
||||||
|
actual class TaskCacheManager {
|
||||||
|
private val userDefaults = NSUserDefaults.standardUserDefaults
|
||||||
|
|
||||||
|
actual fun saveTasks(tasksJson: String) {
|
||||||
|
userDefaults.setObject(tasksJson, KEY_TASKS)
|
||||||
|
userDefaults.synchronize()
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun getTasks(): String? {
|
||||||
|
return userDefaults.stringForKey(KEY_TASKS)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun clearTasks() {
|
||||||
|
userDefaults.removeObjectForKey(KEY_TASKS)
|
||||||
|
userDefaults.synchronize()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val KEY_TASKS = "cached_tasks"
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var instance: TaskCacheManager? = null
|
||||||
|
|
||||||
|
fun getInstance(): TaskCacheManager {
|
||||||
|
return instance ?: synchronized(this) {
|
||||||
|
instance ?: TaskCacheManager().also { instance = it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function for synchronization on iOS
|
||||||
|
private fun <T> synchronized(lock: Any, block: () -> T): T {
|
||||||
|
return block()
|
||||||
|
}
|
||||||
@@ -4,11 +4,16 @@ import androidx.compose.ui.window.Window
|
|||||||
import androidx.compose.ui.window.application
|
import androidx.compose.ui.window.application
|
||||||
import com.mycrib.storage.TokenManager
|
import com.mycrib.storage.TokenManager
|
||||||
import com.mycrib.storage.TokenStorage
|
import com.mycrib.storage.TokenStorage
|
||||||
|
import com.mycrib.storage.TaskCacheManager
|
||||||
|
import com.mycrib.storage.TaskCacheStorage
|
||||||
|
|
||||||
fun main() = application {
|
fun main() = application {
|
||||||
// Initialize TokenStorage with JVM TokenManager
|
// Initialize TokenStorage with JVM TokenManager
|
||||||
TokenStorage.initialize(TokenManager.getInstance())
|
TokenStorage.initialize(TokenManager.getInstance())
|
||||||
|
|
||||||
|
// Initialize TaskCacheStorage for offline task caching
|
||||||
|
TaskCacheStorage.initialize(TaskCacheManager.getInstance())
|
||||||
|
|
||||||
Window(
|
Window(
|
||||||
onCloseRequest = ::exitApplication,
|
onCloseRequest = ::exitApplication,
|
||||||
title = "MyCrib",
|
title = "MyCrib",
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.mycrib.storage
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import java.util.prefs.Preferences
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JVM implementation of TaskCacheManager using Java Preferences.
|
||||||
|
*/
|
||||||
|
actual class TaskCacheManager {
|
||||||
|
private val prefs = Preferences.userRoot().node(NODE_NAME)
|
||||||
|
|
||||||
|
actual fun saveTasks(tasksJson: String) {
|
||||||
|
prefs.put(KEY_TASKS, tasksJson)
|
||||||
|
prefs.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun getTasks(): String? {
|
||||||
|
return prefs.get(KEY_TASKS, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun clearTasks() {
|
||||||
|
prefs.remove(KEY_TASKS)
|
||||||
|
prefs.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val NODE_NAME = "com.mycrib.cache"
|
||||||
|
private const val KEY_TASKS = "cached_tasks"
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var instance: TaskCacheManager? = null
|
||||||
|
|
||||||
|
fun getInstance(): TaskCacheManager {
|
||||||
|
return instance ?: synchronized(this) {
|
||||||
|
instance ?: TaskCacheManager().also { instance = it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ class LookupsManager: ObservableObject {
|
|||||||
@Published var taskFrequencies: [TaskFrequency] = []
|
@Published var taskFrequencies: [TaskFrequency] = []
|
||||||
@Published var taskPriorities: [TaskPriority] = []
|
@Published var taskPriorities: [TaskPriority] = []
|
||||||
@Published var taskStatuses: [TaskStatus] = []
|
@Published var taskStatuses: [TaskStatus] = []
|
||||||
|
@Published var allTasks: [CustomTask] = []
|
||||||
@Published var isLoading: Bool = false
|
@Published var isLoading: Bool = false
|
||||||
@Published var isInitialized: Bool = false
|
@Published var isInitialized: Bool = false
|
||||||
|
|
||||||
@@ -58,6 +59,13 @@ class LookupsManager: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Observe all tasks
|
||||||
|
Task {
|
||||||
|
for await tasks in repository.allTasks.taskTaskAsyncSequence {
|
||||||
|
self.allTasks = tasks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Observe loading state
|
// Observe loading state
|
||||||
Task {
|
Task {
|
||||||
for await loading in repository.isLoading.boolAsyncSequence {
|
for await loading in repository.isLoading.boolAsyncSequence {
|
||||||
|
|||||||
@@ -65,7 +65,11 @@ extension Kotlinx_coroutines_coreStateFlow {
|
|||||||
return asAsyncSequence()
|
return asAsyncSequence()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var taskTaskAsyncSequence: AsyncStream<[CustomTask]> {
|
||||||
|
return asAsyncSequence()
|
||||||
|
}
|
||||||
|
|
||||||
var boolAsyncSequence: AsyncStream<Bool> {
|
var boolAsyncSequence: AsyncStream<Bool> {
|
||||||
return asAsyncSequence()
|
return asAsyncSequence()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user