update profile and UI tweaks

This commit is contained in:
Trey t
2025-11-30 12:37:15 -06:00
parent b0838d85df
commit 94781f4c48
25 changed files with 184 additions and 103 deletions

View File

@@ -100,7 +100,7 @@ class AuthApi(private val client: HttpClient = ApiClient.httpClient) {
suspend fun updateProfile(token: String, request: UpdateProfileRequest): ApiResult<User> { suspend fun updateProfile(token: String, request: UpdateProfileRequest): ApiResult<User> {
return try { return try {
val response = client.put("$baseUrl/auth/update-profile/") { val response = client.put("$baseUrl/auth/profile/") {
header("Authorization", "Token $token") header("Authorization", "Token $token")
contentType(ContentType.Application.Json) contentType(ContentType.Application.Json)
setBody(request) setBody(request)

View File

@@ -162,19 +162,6 @@ struct SmallWidgetView: View {
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) {
// Header
HStack(spacing: 6) {
Image(systemName: "house.fill")
.font(.system(size: 14, weight: .semibold))
.foregroundStyle(.blue)
Text("Casera")
.font(.system(size: 14, weight: .bold))
.foregroundStyle(.primary)
Spacer()
}
// Task Count // Task Count
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
Text("\(entry.taskCount)") Text("\(entry.taskCount)")
@@ -410,16 +397,8 @@ struct LargeWidgetView: View {
VStack(alignment: .leading, spacing: 6) { VStack(alignment: .leading, spacing: 6) {
// Header // Header
HStack(spacing: 6) { HStack(spacing: 6) {
Image(systemName: "house.fill")
.font(.system(size: 16, weight: .semibold))
.foregroundStyle(.blue)
Text("Casera")
.font(.system(size: 16, weight: .bold))
.foregroundStyle(.primary)
Spacer() Spacer()
Text("\(entry.taskCount)") Text("\(entry.taskCount)")
.font(.system(size: 22, weight: .bold)) .font(.system(size: 22, weight: .bold))
.foregroundStyle(.blue) .foregroundStyle(.blue)
@@ -483,7 +462,9 @@ struct LargeTaskRowView: View {
HStack(spacing: 10) { HStack(spacing: 10) {
if let residenceName = task.residenceName { if let residenceName = task.residenceName {
HStack(spacing: 2) { HStack(spacing: 2) {
Image(systemName: "house.fill") Image("icon")
.resizable()
.frame(width: 7, height: 7)
.font(.system(size: 7)) .font(.system(size: 7))
Text(residenceName) Text(residenceName)
.font(.system(size: 9)) .font(.system(size: 9))

View File

@@ -85,6 +85,13 @@
); );
target = 1CBF1BEC2ECD9768001BF56C /* CaseraUITests */; target = 1CBF1BEC2ECD9768001BF56C /* CaseraUITests */;
}; };
1C87A67A2EDCC3100081E450 /* Exceptions for "iosApp" folder in "CaseraExtension" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Assets.xcassets,
);
target = 1C07893C2EBC218B00392B46 /* CaseraExtension */;
};
1CBF1C072ECD97AC001BF56C /* Exceptions for "CaseraTests" folder in "CaseraTests" target */ = { 1CBF1C072ECD97AC001BF56C /* Exceptions for "CaseraTests" folder in "CaseraTests" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet; isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = ( membershipExceptions = (
@@ -135,6 +142,7 @@
isa = PBXFileSystemSynchronizedRootGroup; isa = PBXFileSystemSynchronizedRootGroup;
exceptions = ( exceptions = (
84D9B4B86A80D013B8CBB951 /* Exceptions for "iosApp" folder in "Casera" target */, 84D9B4B86A80D013B8CBB951 /* Exceptions for "iosApp" folder in "Casera" target */,
1C87A67A2EDCC3100081E450 /* Exceptions for "iosApp" folder in "CaseraExtension" target */,
1C77EDA12ECE784100A53003 /* Exceptions for "iosApp" folder in "CaseraUITests" target */, 1C77EDA12ECE784100A53003 /* Exceptions for "iosApp" folder in "CaseraUITests" target */,
); );
path = iosApp; path = iosApp;

View File

@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "house_outline.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "icon.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

View File

@@ -0,0 +1,26 @@
{
"images" : [
{
"filename" : "house_outline 2.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "house_outline 1.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "house_outline.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "template"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -8,15 +8,15 @@ struct ContractorCard: View {
var body: some View { var body: some View {
HStack(spacing: AppSpacing.md) { HStack(spacing: AppSpacing.md) {
// Avatar // Avatar
ZStack { // ZStack {
Circle() // Circle()
.fill(Color.appPrimary.opacity(0.1)) // .fill(Color.appPrimary.opacity(0.1))
.frame(width: 56, height: 56) // .frame(width: 56, height: 56)
//
Image(systemName: "person.fill") // Image(systemName: "person.fill")
.font(.title2) // .font(.title2)
.foregroundColor(Color.appPrimary) // .foregroundColor(Color.appPrimary)
} // }
// Content // Content
VStack(alignment: .leading, spacing: AppSpacing.xxs) { VStack(alignment: .leading, spacing: AppSpacing.xxs) {

View File

@@ -428,10 +428,12 @@ struct ContractorFormSheet: View {
// Set residence if contractor has one // Set residence if contractor has one
if let residenceId = contractor.residenceId { if let residenceId = contractor.residenceId {
selectedResidenceId = residenceId.int32Value selectedResidenceId = residenceId.int32Value
// Try to find residence name from loaded residences if let selectedResidenceId {
if let residences = residenceViewModel.myResidences?.residences, ComposeApp.ResidenceViewModel().getResidence(id: selectedResidenceId, onResult: { result in
let residence = residences.first(where: { $0.id == residenceId.int32Value }) { if let success = result as? ApiResultSuccess<ResidenceResponse> {
selectedResidenceName = residence.name self.selectedResidenceName = success.data?.name
}
})
} }
} }

View File

@@ -27,14 +27,18 @@ struct DocumentCard: View {
var body: some View { var body: some View {
HStack(spacing: AppSpacing.md) { HStack(spacing: AppSpacing.md) {
// Document Icon // Document Icon
ZStack { VStack {
RoundedRectangle(cornerRadius: 8)
.fill(typeColor.opacity(0.1))
.frame(width: 56, height: 56)
Image(systemName: typeIcon) Image(systemName: typeIcon)
.font(.system(size: 24)) .font(.system(size: 24))
.foregroundColor(typeColor) .foregroundColor(typeColor)
.background(content: {
RoundedRectangle(cornerRadius: 8)
.fill(typeColor.opacity(0.1))
.frame(width: 56, height: 56)
})
.padding(AppSpacing.md)
Spacer()
} }
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {

View File

@@ -53,12 +53,7 @@ struct LoginView: View {
VStack(spacing: AppSpacing.lg) { VStack(spacing: AppSpacing.lg) {
// App Icon with gradient // App Icon with gradient
ZStack { ZStack {
Circle() Image("icon")
.fill(LinearGradient(colors: [Color.appPrimary, Color.appPrimary.opacity(0.8)], startPoint: .topLeading, endPoint: .bottomTrailing))
.frame(width: 100, height: 100)
.shadow(color: Color.appPrimary.opacity(0.3), radius: 20, y: 10)
Image(systemName: "house.fill")
.font(.system(size: 50, weight: .semibold)) .font(.system(size: 50, weight: .semibold))
.foregroundStyle(.white) .foregroundStyle(.white)
} }

View File

@@ -13,7 +13,7 @@ struct MainTabView: View {
} }
.id(refreshID) .id(refreshID)
.tabItem { .tabItem {
Label("Residences", systemImage: "house.fill") Label("Residences", image: "tab_view_house")
} }
.tag(0) .tag(0)
.accessibilityIdentifier(AccessibilityIdentifiers.Navigation.residencesTab) .accessibilityIdentifier(AccessibilityIdentifiers.Navigation.residencesTab)

View File

@@ -152,8 +152,16 @@ struct NotificationPreferencesView: View {
.foregroundColor(Color.appTextSecondary) .foregroundColor(Color.appTextSecondary)
} }
} icon: { } icon: {
Image(systemName: "house.fill") Image("house_outline")
.foregroundColor(Color.appPrimary) .resizable()
.frame(width: 22, height: 22)
.foregroundColor(Color.appTextOnPrimary)
.background(content: {
RoundedRectangle(cornerRadius: AppRadius.sm)
.fill(LinearGradient(colors: [Color.appPrimary, Color.appPrimary.opacity(0.8)], startPoint: .topLeading, endPoint: .bottomTrailing))
.frame(width: 22, height: 22)
.shadow(color: Color.appPrimary.opacity(0.3), radius: 6, y: 3)
})
} }
} }
.tint(Color.appPrimary) .tint(Color.appPrimary)

View File

@@ -14,26 +14,26 @@ struct ProfileTabView: View {
var body: some View { var body: some View {
List { List {
Section { // Section {
HStack { // HStack {
Image(systemName: "person.circle.fill") // Image(systemName: "person.circle.fill")
.resizable() // .resizable()
.frame(width: 60, height: 60) // .frame(width: 60, height: 60)
.foregroundColor(Color.appPrimary) // .foregroundColor(Color.appPrimary)
//
VStack(alignment: .leading, spacing: 4) { // VStack(alignment: .leading, spacing: 4) {
Text("User Profile") // Text("User Profile")
.font(.headline) // .font(.headline)
.foregroundColor(Color.appTextPrimary) // .foregroundColor(Color.appTextPrimary)
//
Text("Manage your account") // Text("Manage your account")
.font(.caption) // .font(.caption)
.foregroundColor(Color.appTextSecondary) // .foregroundColor(Color.appTextSecondary)
} // }
} // }
.padding(.vertical, 8) // .padding(.vertical, 8)
.listRowBackground(Color.appBackgroundSecondary) // .listRowBackground(Color.appBackgroundSecondary)
} // }
Section("Account") { Section("Account") {
Button(action: { Button(action: {

View File

@@ -3,7 +3,7 @@ import SwiftUI
struct LoginHeader: View { struct LoginHeader: View {
var body: some View { var body: some View {
VStack(spacing: 8) { VStack(spacing: 8) {
Image(systemName: "house.fill") Image("icon")
.resizable() .resizable()
.aspectRatio(contentMode: .fit) .aspectRatio(contentMode: .fit)
.frame(width: 80, height: 80) .frame(width: 80, height: 80)

View File

@@ -29,7 +29,7 @@ struct OverviewCard: View {
// Stats Grid // Stats Grid
HStack(spacing: AppSpacing.md) { HStack(spacing: AppSpacing.md) {
StatView( StatView(
icon: "house.fill", icon: "house_outline",
value: "\(summary.totalResidences)", value: "\(summary.totalResidences)",
label: "Properties", label: "Properties",
color: Color.appPrimary color: Color.appPrimary

View File

@@ -13,9 +13,22 @@ struct StatView: View {
.fill(color.opacity(0.1)) .fill(color.opacity(0.1))
.frame(width: 48, height: 48) .frame(width: 48, height: 48)
Image(systemName: icon) if icon == "house_outline" {
.font(.system(size: 22, weight: .semibold)) Image("house_outline")
.foregroundColor(color) .resizable()
.frame(width: 22, height: 22)
.foregroundColor(Color.appTextOnPrimary)
.background(content: {
RoundedRectangle(cornerRadius: AppRadius.sm)
.fill(LinearGradient(colors: [Color.appPrimary, Color.appPrimary.opacity(0.8)], startPoint: .topLeading, endPoint: .bottomTrailing))
.frame(width: 22, height: 22)
.shadow(color: Color.appPrimary.opacity(0.3), radius: 6, y: 3)
})
} else {
Image(systemName: icon)
.font(.system(size: 22, weight: .semibold))
.foregroundColor(color)
}
} }
Text(value) Text(value)

View File

@@ -7,9 +7,20 @@ struct PropertyHeaderCard: View {
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 16) { VStack(alignment: .leading, spacing: 16) {
HStack { HStack {
Image(systemName: "house.fill") VStack {
.font(.title2) Image("house_outline")
.foregroundColor(Color.appPrimary) .resizable()
.frame(width: 38, height: 38)
.foregroundColor(Color.appTextOnPrimary)
.background(content: {
RoundedRectangle(cornerRadius: AppRadius.sm)
.fill(LinearGradient(colors: [Color.appPrimary, Color.appPrimary.opacity(0.8)], startPoint: .topLeading, endPoint: .bottomTrailing))
.frame(width: 38, height: 38)
.shadow(color: Color.appPrimary.opacity(0.3), radius: 6, y: 3)
})
Spacer()
}
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
Text(residence.name) Text(residence.name)

View File

@@ -3,29 +3,33 @@ import ComposeApp
struct ResidenceCard: View { struct ResidenceCard: View {
let residence: ResidenceResponse let residence: ResidenceResponse
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: AppSpacing.md) { VStack(alignment: .leading, spacing: AppSpacing.md) {
// Header with property type icon // Header with property type icon
HStack(spacing: AppSpacing.sm) { HStack(spacing: AppSpacing.sm) {
ZStack { VStack {
RoundedRectangle(cornerRadius: AppRadius.sm) Image("house_outline")
.fill(LinearGradient(colors: [Color.appPrimary, Color.appPrimary.opacity(0.8)], startPoint: .topLeading, endPoint: .bottomTrailing)) .resizable()
.frame(width: 44, height: 44) .frame(width: 44, height: 44)
.shadow(color: Color.appPrimary.opacity(0.3), radius: 6, y: 3)
Image(systemName: "house.fill")
.font(.system(size: 20, weight: .semibold))
.foregroundColor(Color.appTextOnPrimary) .foregroundColor(Color.appTextOnPrimary)
.background(content: {
RoundedRectangle(cornerRadius: AppRadius.sm)
.fill(LinearGradient(colors: [Color.appPrimary, Color.appPrimary.opacity(0.8)], startPoint: .topLeading, endPoint: .bottomTrailing))
.frame(width: 44, height: 44)
.shadow(color: Color.appPrimary.opacity(0.3), radius: 6, y: 3)
})
.padding([.trailing], AppSpacing.md)
Spacer()
} }
VStack(alignment: .leading, spacing: AppSpacing.xxs) { VStack(alignment: .leading, spacing: AppSpacing.xxs) {
Text(residence.name) Text(residence.name)
.font(.title3.weight(.semibold)) .font(.title3.weight(.semibold))
.fontWeight(.bold) .fontWeight(.bold)
.foregroundColor(Color.appTextPrimary) .foregroundColor(Color.appTextPrimary)
// .lineLimit(1)
if let propertyTypeName = residence.propertyTypeName { if let propertyTypeName = residence.propertyTypeName {
Text(propertyTypeName) Text(propertyTypeName)
.font(.caption.weight(.medium)) .font(.caption.weight(.medium))
@@ -34,21 +38,26 @@ struct ResidenceCard: View {
.tracking(0.5) .tracking(0.5)
} }
} }
Spacer() Spacer()
if residence.isPrimary { if residence.isPrimary {
ZStack { VStack {
Circle()
.fill(Color.appAccent.opacity(0.2))
.frame(width: 32, height: 32)
Image(systemName: "star.fill") Image(systemName: "star.fill")
.font(.system(size: 14, weight: .bold)) .font(.system(size: 14, weight: .bold))
.foregroundColor(Color.appAccent) .foregroundColor(Color.appAccent)
.background(content: {
Circle()
.fill(Color.appAccent.opacity(0.2))
.frame(width: 32, height: 32)
})
.padding(AppSpacing.md)
Spacer()
} }
} }
} }
// Address // Address
VStack(alignment: .leading, spacing: AppSpacing.xxs) { VStack(alignment: .leading, spacing: AppSpacing.xxs) {
if !residence.streetAddress.isEmpty { if !residence.streetAddress.isEmpty {
@@ -61,7 +70,7 @@ struct ResidenceCard: View {
.foregroundColor(Color.appTextSecondary) .foregroundColor(Color.appTextSecondary)
} }
} }
if !residence.city.isEmpty || !residence.stateProvince.isEmpty { if !residence.city.isEmpty || !residence.stateProvince.isEmpty {
HStack(spacing: AppSpacing.xxs) { HStack(spacing: AppSpacing.xxs) {
Image(systemName: "location.fill") Image(systemName: "location.fill")
@@ -74,13 +83,13 @@ struct ResidenceCard: View {
} }
} }
.padding(.vertical, AppSpacing.xs) .padding(.vertical, AppSpacing.xs)
Divider() Divider()
// Fully dynamic task stats from API - show first 3 categories // Fully dynamic task stats from API - show first 3 categories
HStack(spacing: AppSpacing.sm) { HStack(spacing: AppSpacing.sm) {
let displayCategories = Array(residence.taskSummary.categories.prefix(3)) let displayCategories = Array(residence.taskSummary.categories.prefix(3))
ForEach(displayCategories, id: \.name) { category in ForEach(displayCategories, id: \.name) { category in
TaskStatChip( TaskStatChip(
icon: category.icons.ios, icon: category.icons.ios,

View File

@@ -17,7 +17,7 @@ struct SummaryCard: View {
HStack(spacing: 20) { HStack(spacing: 20) {
SummaryStatView( SummaryStatView(
icon: "house.fill", icon: "house_outline",
value: "\(summary.totalResidences)", value: "\(summary.totalResidences)",
label: "Properties" label: "Properties"
) )

View File

@@ -28,7 +28,7 @@ struct SummaryStatView: View {
#Preview { #Preview {
HStack(spacing: 20) { HStack(spacing: 20) {
SummaryStatView( SummaryStatView(
icon: "house.fill", icon: "house_outline",
value: "3", value: "3",
label: "Properties" label: "Properties"
) )