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:
@@ -5,10 +5,11 @@ struct ManageUsersView: View {
|
||||
let residenceId: Int32
|
||||
let residenceName: String
|
||||
let isPrimaryOwner: Bool
|
||||
let residenceOwnerId: Int32
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
@State private var users: [ResidenceUserResponse] = []
|
||||
@State private var ownerId: Int32?
|
||||
private var ownerId: Int32 { residenceOwnerId }
|
||||
@State private var shareCode: ShareCodeResponse?
|
||||
@State private var isLoading = true
|
||||
@State private var errorMessage: String?
|
||||
@@ -97,10 +98,9 @@ struct ManageUsersView: View {
|
||||
let result = try await APILayer.shared.getResidenceUsers(residenceId: Int32(Int(residenceId)))
|
||||
|
||||
await MainActor.run {
|
||||
if let successResult = result as? ApiResultSuccess<ResidenceUsersResponse>,
|
||||
let responseData = successResult.data as? ResidenceUsersResponse {
|
||||
self.users = Array(responseData.users)
|
||||
self.ownerId = Int32(responseData.owner.id)
|
||||
if let successResult = result as? ApiResultSuccess<NSArray>,
|
||||
let responseData = successResult.data as? [ResidenceUserResponse] {
|
||||
self.users = responseData
|
||||
self.isLoading = false
|
||||
} else if let errorResult = result as? ApiResultError {
|
||||
self.errorMessage = ErrorMessageParser.parse(errorResult.message)
|
||||
@@ -148,8 +148,9 @@ struct ManageUsersView: View {
|
||||
let result = try await APILayer.shared.generateShareCode(residenceId: Int32(Int(residenceId)))
|
||||
|
||||
await MainActor.run {
|
||||
if let successResult = result as? ApiResultSuccess<ShareCodeResponse> {
|
||||
self.shareCode = successResult.data
|
||||
if let successResult = result as? ApiResultSuccess<GenerateShareCodeResponse>,
|
||||
let response = successResult.data {
|
||||
self.shareCode = response.shareCode
|
||||
self.isGeneratingCode = false
|
||||
} else if let errorResult = result as? ApiResultError {
|
||||
self.errorMessage = ErrorMessageParser.parse(errorResult.message)
|
||||
@@ -195,5 +196,5 @@ struct ManageUsersView: View {
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ManageUsersView(residenceId: 1, residenceName: "My Home", isPrimaryOwner: true)
|
||||
ManageUsersView(residenceId: 1, residenceName: "My Home", isPrimaryOwner: true, residenceOwnerId: 1)
|
||||
}
|
||||
|
||||
@@ -120,7 +120,8 @@ struct ResidenceDetailView: View {
|
||||
ManageUsersView(
|
||||
residenceId: residence.id,
|
||||
residenceName: residence.name,
|
||||
isPrimaryOwner: isCurrentUserOwner(of: residence)
|
||||
isPrimaryOwner: isCurrentUserOwner(of: residence),
|
||||
residenceOwnerId: residence.ownerId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ struct ResidencesListView: View {
|
||||
@State private var showingAddResidence = false
|
||||
@State private var showingJoinResidence = false
|
||||
@State private var showingUpgradePrompt = false
|
||||
@State private var showingSettings = false
|
||||
@StateObject private var authManager = AuthenticationManager.shared
|
||||
@StateObject private var subscriptionCache = SubscriptionCacheWrapper.shared
|
||||
|
||||
@@ -46,6 +47,17 @@ struct ResidencesListView: View {
|
||||
.navigationTitle(L10n.Residences.title)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button(action: {
|
||||
showingSettings = true
|
||||
}) {
|
||||
Image(systemName: "gearshape.fill")
|
||||
.font(.system(size: 18, weight: .semibold))
|
||||
.foregroundColor(Color.appPrimary)
|
||||
}
|
||||
.accessibilityIdentifier(AccessibilityIdentifiers.Navigation.settingsButton)
|
||||
}
|
||||
|
||||
ToolbarItemGroup(placement: .navigationBarTrailing) {
|
||||
Button(action: {
|
||||
// Check if we should show upgrade prompt before joining
|
||||
@@ -93,6 +105,11 @@ struct ResidencesListView: View {
|
||||
.sheet(isPresented: $showingUpgradePrompt) {
|
||||
UpgradePromptView(triggerKey: "add_second_property", isPresented: $showingUpgradePrompt)
|
||||
}
|
||||
.sheet(isPresented: $showingSettings) {
|
||||
NavigationView {
|
||||
ProfileTabView()
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if authManager.isAuthenticated {
|
||||
viewModel.loadMyResidences()
|
||||
|
||||
Reference in New Issue
Block a user