This commit is contained in:
Trey t
2025-11-05 18:16:46 -06:00
parent b2b8cc62de
commit 6bad8d6b5a
13 changed files with 401 additions and 1 deletions

View File

@@ -8,6 +8,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.mycrib.storage.TokenManager
import com.mycrib.storage.TokenStorage
import com.mycrib.storage.TaskCacheManager
import com.mycrib.storage.TaskCacheStorage
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
@@ -17,6 +19,9 @@ class MainActivity : ComponentActivity() {
// Initialize TokenStorage with Android TokenManager
TokenStorage.initialize(TokenManager.getInstance(applicationContext))
// Initialize TaskCacheStorage for offline task caching
TaskCacheStorage.initialize(TaskCacheManager.getInstance(applicationContext))
setContent {
App()
}

View File

@@ -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 }
}
}
}
}

View File

@@ -88,4 +88,21 @@ class LookupsApi(private val client: HttpClient = ApiClient.httpClient) {
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")
}
}
}

View File

@@ -4,6 +4,7 @@ import com.mycrib.shared.models.*
import com.mycrib.shared.network.ApiResult
import com.mycrib.shared.network.LookupsApi
import com.mycrib.storage.TokenStorage
import com.mycrib.storage.TaskCacheStorage
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
@@ -33,6 +34,9 @@ object LookupsRepository {
private val _taskCategories = MutableStateFlow<List<TaskCategory>>(emptyList())
val taskCategories: StateFlow<List<TaskCategory>> = _taskCategories
private val _allTasks = MutableStateFlow<List<CustomTask>>(emptyList())
val allTasks: StateFlow<List<CustomTask>> = _allTasks
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading
@@ -51,6 +55,14 @@ object LookupsRepository {
scope.launch {
_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()
if (token != null) {
@@ -89,6 +101,20 @@ object LookupsRepository {
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
@@ -106,6 +132,9 @@ object LookupsRepository {
_taskPriorities.value = emptyList()
_taskStatuses.value = emptyList()
_taskCategories.value = emptyList()
_allTasks.value = emptyList()
// Clear disk cache on logout
TaskCacheStorage.clearTasks()
_isInitialized.value = false
_isLoading.value = false
}

View File

@@ -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()
}

View File

@@ -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()
}
}

View File

@@ -3,9 +3,15 @@ package com.example.mycrib
import androidx.compose.ui.window.ComposeUIViewController
import com.mycrib.storage.TokenManager
import com.mycrib.storage.TokenStorage
import com.mycrib.storage.TaskCacheManager
import com.mycrib.storage.TaskCacheStorage
fun MainViewController() = ComposeUIViewController {
// Initialize TokenStorage with iOS TokenManager
TokenStorage.initialize(TokenManager.getInstance())
// Initialize TaskCacheStorage for offline task caching
TaskCacheStorage.initialize(TaskCacheManager.getInstance())
App()
}

View File

@@ -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()
}

View File

@@ -4,11 +4,16 @@ import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import com.mycrib.storage.TokenManager
import com.mycrib.storage.TokenStorage
import com.mycrib.storage.TaskCacheManager
import com.mycrib.storage.TaskCacheStorage
fun main() = application {
// Initialize TokenStorage with JVM TokenManager
TokenStorage.initialize(TokenManager.getInstance())
// Initialize TaskCacheStorage for offline task caching
TaskCacheStorage.initialize(TaskCacheManager.getInstance())
Window(
onCloseRequest = ::exitApplication,
title = "MyCrib",

View File

@@ -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 }
}
}
}
}