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:
Trey t
2025-12-05 22:30:19 -06:00
parent 2965ec4031
commit 859a6679ed
43 changed files with 1848 additions and 148 deletions

View File

@@ -31,11 +31,14 @@ import com.example.casera.storage.ThemeStorageManager
import com.example.casera.ui.theme.ThemeManager
import com.example.casera.fcm.FCMManager
import com.example.casera.platform.BillingManager
import com.example.casera.network.APILayer
import com.example.casera.sharing.ContractorSharingManager
import kotlinx.coroutines.launch
class MainActivity : ComponentActivity(), SingletonImageLoader.Factory {
private var deepLinkResetToken by mutableStateOf<String?>(null)
private var navigateToTaskId by mutableStateOf<Int?>(null)
private var pendingContractorImportUri by mutableStateOf<Uri?>(null)
private lateinit var billingManager: BillingManager
override fun onCreate(savedInstanceState: Bundle?) {
@@ -55,9 +58,10 @@ class MainActivity : ComponentActivity(), SingletonImageLoader.Factory {
// Initialize BillingManager for subscription management
billingManager = BillingManager.getInstance(applicationContext)
// Handle deep link and notification navigation from intent
// Handle deep link, notification navigation, and file import from intent
handleDeepLink(intent)
handleNotificationNavigation(intent)
handleFileImport(intent)
// Request notification permission and setup FCM
setupFCM()
@@ -74,6 +78,10 @@ class MainActivity : ComponentActivity(), SingletonImageLoader.Factory {
navigateToTaskId = navigateToTaskId,
onClearNavigateToTask = {
navigateToTaskId = null
},
pendingContractorImportUri = pendingContractorImportUri,
onClearContractorImport = {
pendingContractorImportUri = null
}
)
}
@@ -183,10 +191,21 @@ class MainActivity : ComponentActivity(), SingletonImageLoader.Factory {
}
}
override fun onResume() {
super.onResume()
// Check if lookups have changed on server (efficient ETag-based check)
// This ensures app has fresh data when coming back from background
lifecycleScope.launch {
Log.d("MainActivity", "🔄 App resumed, checking for lookup updates...")
APILayer.refreshLookupsIfChanged()
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
handleDeepLink(intent)
handleNotificationNavigation(intent)
handleFileImport(intent)
}
private fun handleNotificationNavigation(intent: Intent?) {
@@ -209,6 +228,16 @@ class MainActivity : ComponentActivity(), SingletonImageLoader.Factory {
}
}
private fun handleFileImport(intent: Intent?) {
if (intent?.action == Intent.ACTION_VIEW) {
val uri = intent.data
if (uri != null && ContractorSharingManager.isCaseraFile(applicationContext, uri)) {
Log.d("MainActivity", "Contractor file received: $uri")
pendingContractorImportUri = uri
}
}
}
override fun newImageLoader(context: PlatformContext): ImageLoader {
return ImageLoader.Builder(context)
.components {