Add contractor sharing feature and move settings to navigation bar
Contractor Sharing: - Add .casera file format for sharing contractors between users - Create SharedContractor model with JSON serialization - Implement ContractorSharingManager for iOS (Swift) and Android (Kotlin) - Register .casera file type in iOS Info.plist and Android manifest - Add share button to ContractorDetailView (iOS) and ContractorDetailScreen (Android) - Add import confirmation, success, and error dialogs - Create expect/actual platform implementations for sharing and import handling Navigation Changes: - Remove Profile tab from bottom tab bar (iOS and Android) - Add settings gear icon to left side of "My Properties" title - Settings gear opens Profile/Settings screen as sheet (iOS) or navigates (Android) - Add property button to top right action bar Bug Fixes: - Fix ResidenceUsersResponse to match API's flat array response format - Fix GenerateShareCodeResponse handling to access nested shareCode property - Update ManageUsersDialog to accept residenceOwnerId parameter 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -205,6 +205,11 @@ object DataManager {
|
||||
private val _lastSyncTime = MutableStateFlow(0L)
|
||||
val lastSyncTime: StateFlow<Long> = _lastSyncTime.asStateFlow()
|
||||
|
||||
// ==================== SEEDED DATA ETAG ====================
|
||||
|
||||
private val _seededDataETag = MutableStateFlow<String?>(null)
|
||||
val seededDataETag: StateFlow<String?> = _seededDataETag.asStateFlow()
|
||||
|
||||
// ==================== INITIALIZATION ====================
|
||||
|
||||
/**
|
||||
@@ -584,6 +589,34 @@ object DataManager {
|
||||
_lookupsInitialized.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all lookups from unified seeded data response.
|
||||
* Also stores the ETag for future conditional requests.
|
||||
*/
|
||||
fun setAllLookupsFromSeededData(seededData: SeededDataResponse, etag: String?) {
|
||||
setResidenceTypes(seededData.residenceTypes)
|
||||
setTaskFrequencies(seededData.taskFrequencies)
|
||||
setTaskPriorities(seededData.taskPriorities)
|
||||
setTaskStatuses(seededData.taskStatuses)
|
||||
setTaskCategories(seededData.taskCategories)
|
||||
setContractorSpecialties(seededData.contractorSpecialties)
|
||||
setTaskTemplatesGrouped(seededData.taskTemplates)
|
||||
setSeededDataETag(etag)
|
||||
_lookupsInitialized.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ETag for seeded data. Used for conditional requests.
|
||||
*/
|
||||
fun setSeededDataETag(etag: String?) {
|
||||
_seededDataETag.value = etag
|
||||
if (etag != null) {
|
||||
persistenceManager?.save(KEY_SEEDED_DATA_ETAG, etag)
|
||||
} else {
|
||||
persistenceManager?.remove(KEY_SEEDED_DATA_ETAG)
|
||||
}
|
||||
}
|
||||
|
||||
fun markLookupsInitialized() {
|
||||
_lookupsInitialized.value = true
|
||||
}
|
||||
@@ -632,6 +665,7 @@ object DataManager {
|
||||
_taskTemplates.value = emptyList()
|
||||
_taskTemplatesGrouped.value = null
|
||||
_lookupsInitialized.value = false
|
||||
_seededDataETag.value = null
|
||||
|
||||
// Clear cache timestamps
|
||||
residencesCacheTime = 0L
|
||||
@@ -723,6 +757,11 @@ object DataManager {
|
||||
manager.load(KEY_HAS_COMPLETED_ONBOARDING)?.let { data ->
|
||||
_hasCompletedOnboarding.value = data.toBooleanStrictOrNull() ?: false
|
||||
}
|
||||
|
||||
// Load seeded data ETag for conditional requests
|
||||
manager.load(KEY_SEEDED_DATA_ETAG)?.let { data ->
|
||||
_seededDataETag.value = data
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println("DataManager: Error loading from disk: ${e.message}")
|
||||
}
|
||||
@@ -733,4 +772,5 @@ object DataManager {
|
||||
|
||||
private const val KEY_CURRENT_USER = "dm_current_user"
|
||||
private const val KEY_HAS_COMPLETED_ONBOARDING = "dm_has_completed_onboarding"
|
||||
private const val KEY_SEEDED_DATA_ETAG = "dm_seeded_data_etag"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user