diff --git a/pedi-foods/Darwin/PediFoods.xcodeproj/project.pbxproj b/pedi-foods/Darwin/PediFoods.xcodeproj/project.pbxproj index 57006fe..416a597 100644 --- a/pedi-foods/Darwin/PediFoods.xcodeproj/project.pbxproj +++ b/pedi-foods/Darwin/PediFoods.xcodeproj/project.pbxproj @@ -38,7 +38,6 @@ 496EB72F2A6AE4DE00C1253A /* Skip.env */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Skip.env; path = ../Skip.env; sourceTree = ""; }; 496EB72F2A6AE4DE00C1253B /* PediFoods.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = PediFoods.xcconfig; sourceTree = ""; }; 496EB72F2A6AE4DE00C1253C /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; - 499AB9082B0581F4005E8330 /* plugins */ = {isa = PBXFileReference; lastKnownFileType = folder; name = plugins; path = ../../Intermediates.noindex/BuildToolPluginIntermediates; sourceTree = BUILT_PRODUCTS_DIR; }; 49F90C2B2A52156200F06D93 /* Main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Main.swift; path = Sources/Main.swift; sourceTree = SOURCE_ROOT; }; 49F90C2F2A52156300F06D93 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 49F90C312A52156300F06D93 /* Entitlements.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Entitlements.plist; sourceTree = ""; }; @@ -66,14 +65,6 @@ name = Products; sourceTree = ""; }; - 49AB54462B066A7E007B79B2 /* SkipStone */ = { - isa = PBXGroup; - children = ( - 499AB9082B0581F4005E8330 /* plugins */, - ); - name = SkipStone; - sourceTree = ""; - }; 49F90C1F2A52156200F06D93 = { isa = PBXGroup; children = ( @@ -83,7 +74,6 @@ 496BDBED2B8A7E9C00C09264 /* Localizable.xcstrings */, 493609562A6B7EAE00C401E2 /* PediFoods */, 49F90C2A2A52156200F06D93 /* App */, - 49AB54462B066A7E007B79B2 /* SkipStone */, 496BDBEC2B89A47800C09264 /* Products */, ); sourceTree = ""; @@ -110,7 +100,6 @@ 499CD43A2AC5B799001AE8D8 /* Sources */, 499CD43C2AC5B799001AE8D8 /* Frameworks */, 499CD43E2AC5B799001AE8D8 /* Resources */, - 499CD4452AC5B869001AE8D8 /* Run skip gradle */, 499CD44A2AC5B9C6001AE8D8 /* Embed Frameworks */, ); buildRules = ( @@ -173,26 +162,6 @@ }; /* End PBXResourcesBuildPhase section */ -/* Begin PBXShellScriptBuildPhase section */ - 499CD4452AC5B869001AE8D8 /* Run skip gradle */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Run skip gradle"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = "/bin/sh -e"; - shellScript = "if [ \"${SKIP_ZERO}\" != \"\" ]; then\n echo \"note: skipping skip due to SKIP_ZERO\"\n exit 0\nelif [ \"${ENABLE_PREVIEWS}\" = \"YES\" ]; then\n echo \"note: skipping skip due to ENABLE_PREVIEWS\"\n exit 0\nelif [ \"${ACTION}\" = \"install\" ]; then\n echo \"note: skipping skip due to archive install\"\n exit 0\nelif [ \"${PLATFORM_NAME}\" = \"iphonesimulator\" ]; then\n echo \"note: skipping skip gradle for iOS Simulator\"\n exit 0\nelif [ \"${SKIP_ACTION}\" = \"none\" ]; then\n echo \"note: skipping skip due to SKIP_ACTION none\"\n exit 0\nelse\n SKIP_ACTION=\"${SKIP_ACTION:-launch}\"\nfi\nPATH=${BUILD_ROOT}/Release:${BUILD_ROOT}/Debug:${BUILD_ROOT}/../../SourcePackages/artifacts/skip/skip/skip.artifactbundle/macos:${PATH}:${HOMEBREW_PREFIX:-/opt/homebrew}/bin\necho \"note: running gradle build with: $(which skip) gradle -p ${PWD}/../Android ${SKIP_ACTION:-launch}${CONFIGURATION:-Debug}\"\nskip gradle -p ../Android ${SKIP_ACTION:-launch}${CONFIGURATION:-Debug}\n"; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ diff --git a/pedi-foods/Package.swift b/pedi-foods/Package.swift index 888bcd1..e63b9a8 100644 --- a/pedi-foods/Package.swift +++ b/pedi-foods/Package.swift @@ -1,5 +1,4 @@ // swift-tools-version: 6.1 -// This is a Skip (https://skip.tools) package. import PackageDescription let package = Package( @@ -10,15 +9,12 @@ let package = Package( .library(name: "PediFoods", type: .dynamic, targets: ["PediFoods"]), ], dependencies: [ - .package(url: "https://source.skip.tools/skip.git", from: "1.8.4"), - .package(path: "Vendor/skip-fuse-ui"), .package(url: "https://git.loverde.com.br/Loverde-Company-LTDA/LCEssentials.git", from: "1.0.5") ], targets: [ .target(name: "PediFoods", dependencies: [ - .product(name: "SkipFuseUI", package: "skip-fuse-ui"), - .product(name: "LCEssentials", package: "lcessentials", condition: .when(platforms: [.iOS])) - ], resources: [.process("Resources")], plugins: [.plugin(name: "skipstone", package: "skip")]), + .product(name: "LCEssentials", package: "lcessentials") + ], resources: [.process("Resources")]), .testTarget(name: "PediFoodsTests", dependencies: ["PediFoods"]), ] ) diff --git a/pedi-foods/Sources/PediFoods/Components/Buttons.swift b/pedi-foods/Sources/PediFoods/Components/Buttons.swift index 6c28091..7c02c3d 100644 --- a/pedi-foods/Sources/PediFoods/Components/Buttons.swift +++ b/pedi-foods/Sources/PediFoods/Components/Buttons.swift @@ -39,6 +39,7 @@ struct PrimaryButtonLabel: View { .frame(height: 56) .background(AppColors.primary) .clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous)) + .labelStyle(.titleOnly) } } diff --git a/pedi-foods/Sources/PediFoods/Components/SnackbarOverlay.swift b/pedi-foods/Sources/PediFoods/Components/SnackbarOverlay.swift index 379b084..fda4649 100644 --- a/pedi-foods/Sources/PediFoods/Components/SnackbarOverlay.swift +++ b/pedi-foods/Sources/PediFoods/Components/SnackbarOverlay.swift @@ -1,54 +1,9 @@ import SwiftUI struct SnackbarOverlay: View { -#if os(Android) - let center: SnackbarCenter - @State var current: SnackbarMessage? - @State var observer: Any? -#else @ObservedObject var center: SnackbarCenter -#endif var body: some View { -#if os(Android) - VStack { - if let message = current { - HStack(spacing: 10) { - if let icon = message.iconSystemName, !icon.isEmpty { - Image(systemName: icon) - .font(.system(size: 16, weight: .semibold)) - } - Text(message.title) - .font(AppTypography.heading3) - .multilineTextAlignment(.leading) - .lineLimit(3) - Spacer(minLength: 0) - } - .foregroundStyle(Color.white) - .padding(.top, 14) - .padding(.horizontal, 16) - .padding(.bottom, 16) - .frame(maxWidth: .infinity, alignment: .leading) - .background(message.style.backgroundColor) - .appContentShape(Rectangle()) - .onTapGesture { - center.handleTap() - } - .transition(.move(edge: .top).combined(with: .opacity)) - .zIndex(999) - } - Spacer() - } - .animation(.spring(response: 0.3, dampingFraction: 0.9), value: current?.id) - .allowsHitTesting(current != nil) - .onAppear { - current = center.current - attachObserverIfNeeded() - } - .onDisappear { - detachObserver() - } -#else VStack { if let message = center.current { HStack(spacing: 10) { @@ -79,27 +34,5 @@ struct SnackbarOverlay: View { } .animation(.spring(response: 0.3, dampingFraction: 0.9), value: center.current?.id) .allowsHitTesting(center.current != nil) -#endif } - -#if os(Android) - private func attachObserverIfNeeded() { - guard observer == nil else { return } - observer = NotificationCenter.default.addObserver( - forName: .snackbarDidChange, - object: nil, - queue: nil - ) { _ in - Task { @MainActor in - current = center.current - } - } - } - - private func detachObserver() { - guard let observer else { return } - NotificationCenter.default.removeObserver(observer) - self.observer = nil - } -#endif } diff --git a/pedi-foods/Sources/PediFoods/Components/StoreCard.swift b/pedi-foods/Sources/PediFoods/Components/StoreCard.swift index bf26a0e..df66098 100644 --- a/pedi-foods/Sources/PediFoods/Components/StoreCard.swift +++ b/pedi-foods/Sources/PediFoods/Components/StoreCard.swift @@ -95,21 +95,12 @@ struct FeaturedStoreCard: View { private var storeIconPlaceholder: some View { ZStack { -#if os(Android) - SwiftUI.Image("placeholder-product", bundle: .module) - .resizable() - .scaledToFill() - .opacity(0.7) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .clipped() -#else Image("placeholder-product") .resizable() .scaledToFill() .opacity(0.7) .frame(maxWidth: .infinity, maxHeight: .infinity) .clipped() -#endif Circle() .fill(AppColors.surface.opacity(0.92)) @@ -129,6 +120,7 @@ struct FeaturedStoreCardModel: Identifiable { let rating: Double let reviews: String let distance: String + let deliveryFee: Double? let category: String let promoText: String? let isFavorite: Bool diff --git a/pedi-foods/Sources/PediFoods/ContentView.swift b/pedi-foods/Sources/PediFoods/ContentView.swift index 5beb618..f33137f 100644 --- a/pedi-foods/Sources/PediFoods/ContentView.swift +++ b/pedi-foods/Sources/PediFoods/ContentView.swift @@ -12,16 +12,10 @@ struct ContentView: View { @State var shouldPulseLaunchSplash = true @State var shouldPrepareAuthEntryAnimation = DefaultTokenStore().jwt == nil @State var authEntryAnimationToken = 0 - #if os(iOS) @State private var sessionExpiredObserver: NSObjectProtocol? - #endif @State var cartResetObserver: Any? @State var appResumeObserver: Any? -#if os(Android) - @State var snackbarCenter = SnackbarCenter.shared -#else @StateObject var snackbarCenter = SnackbarCenter.shared -#endif var body: some View { ZStack(alignment: .top) { @@ -95,16 +89,12 @@ struct ContentView: View { .onAppear { attachCartResetObserverIfNeeded() attachAppResumeObserverIfNeeded() - #if os(iOS) attachSessionExpiredObserverIfNeeded() - #endif } .onDisappear { detachCartResetObserver() detachAppResumeObserver() - #if os(iOS) detachSessionExpiredObserver() - #endif } } @@ -402,7 +392,6 @@ struct ContentView: View { } } - #if os(iOS) private func attachSessionExpiredObserverIfNeeded() { guard sessionExpiredObserver == nil else { return } sessionExpiredObserver = NotificationCenter.default.addObserver( @@ -421,7 +410,6 @@ struct ContentView: View { NotificationCenter.default.removeObserver(observer) sessionExpiredObserver = nil } - #endif } struct AddressPickerModalView: View { diff --git a/pedi-foods/Sources/PediFoods/PediFoodsApp.swift b/pedi-foods/Sources/PediFoods/PediFoodsApp.swift index 309fc70..9b2eeea 100644 --- a/pedi-foods/Sources/PediFoods/PediFoodsApp.swift +++ b/pedi-foods/Sources/PediFoods/PediFoodsApp.swift @@ -1,60 +1,30 @@ import Foundation -import SkipFuse +import OSLog import SwiftUI -/// A logger for the PediFoods module. let logger: Logger = Logger(subsystem: "com.br.pedifoods.app", category: "PediFoods") -/// The shared top-level view for the app, loaded from the platform-specific App delegates below. -/// -/// The default implementation merely loads the `ContentView` for the app and logs a message. -/* SKIP @bridge */public struct PediFoodsRootView : View { - /* SKIP @bridge */public init() { - } +public struct PediFoodsRootView: View { + public init() {} public var body: some View { ContentView() - .task { - logger.info("Skip app logs are viewable in the Xcode console for iOS; Android logs can be viewed in Studio or using adb logcat") - } } } -/// Global application delegate functions. -/// -/// These functions can update a shared observable object to communicate app state changes to interested views. -/* SKIP @bridge */public final class PediFoodsAppDelegate : Sendable { - /* SKIP @bridge */public static let shared = PediFoodsAppDelegate() +public final class PediFoodsAppDelegate: Sendable { + public static let shared = PediFoodsAppDelegate() - private init() { - } + private init() {} - /* SKIP @bridge */public func onInit() { - logger.debug("onInit") - } - - /* SKIP @bridge */public func onLaunch() { - logger.debug("onLaunch") - } - - /* SKIP @bridge */public func onResume() { + public func onInit() { logger.debug("onInit") } + public func onLaunch() { logger.debug("onLaunch") } + public func onResume() { logger.debug("onResume") NotificationCenter.default.post(name: .appDidResume, object: nil) } - - /* SKIP @bridge */public func onPause() { - logger.debug("onPause") - } - - /* SKIP @bridge */public func onStop() { - logger.debug("onStop") - } - - /* SKIP @bridge */public func onDestroy() { - logger.debug("onDestroy") - } - - /* SKIP @bridge */public func onLowMemory() { - logger.debug("onLowMemory") - } + public func onPause() { logger.debug("onPause") } + public func onStop() { logger.debug("onStop") } + public func onDestroy() { logger.debug("onDestroy") } + public func onLowMemory() { logger.debug("onLowMemory") } } diff --git a/pedi-foods/Sources/PediFoods/Resources/Localizable.xcstrings b/pedi-foods/Sources/PediFoods/Resources/Localizable.xcstrings index b6d87f9..22f37e4 100644 --- a/pedi-foods/Sources/PediFoods/Resources/Localizable.xcstrings +++ b/pedi-foods/Sources/PediFoods/Resources/Localizable.xcstrings @@ -297,6 +297,9 @@ "Cardápio indisponível no momento." : { "comment" : "A message displayed when a store's menu is unavailable.", "isCommentAutoGenerated" : true + }, + "Carregando cardápio..." : { + }, "Carregando pedido..." : { diff --git a/pedi-foods/Sources/PediFoods/Services/ApiOrderModels.swift b/pedi-foods/Sources/PediFoods/Services/ApiOrderModels.swift index 2e255d3..275c804 100644 --- a/pedi-foods/Sources/PediFoods/Services/ApiOrderModels.swift +++ b/pedi-foods/Sources/PediFoods/Services/ApiOrderModels.swift @@ -152,4 +152,38 @@ struct ValidateDeliveryAddressResult: Decodable { let deliveryFee: Double? let deliveryTime: String? let sameCity: Bool? + + enum CodingKeys: String, CodingKey { + case deliveryAllowed + case delivery_allowed + case reasonCode + case reason_code + case reasonMessage + case reason_message + case deliveryMode + case delivery_mode + case distance + case deliveryFee + case delivery_fee + case fee + case taxa + case deliveryTime + case delivery_time + case sameCity + case same_city + } + + init(from decoder: Decoder) throws { + let c = try decoder.container(keyedBy: CodingKeys.self) + deliveryAllowed = (try? c.decode(Bool.self, forKey: .deliveryAllowed)) + ?? (try? c.decode(Bool.self, forKey: .delivery_allowed)) + reasonCode = ApiService.decodeFlexibleString(from: c, keys: [.reasonCode, .reason_code]) + reasonMessage = ApiService.decodeFlexibleString(from: c, keys: [.reasonMessage, .reason_message]) + deliveryMode = ApiService.decodeFlexibleString(from: c, keys: [.deliveryMode, .delivery_mode]) + distance = ApiService.decodeFlexibleDouble(from: c, keys: [.distance]) + deliveryFee = ApiService.decodeFlexibleDouble(from: c, keys: [.deliveryFee, .delivery_fee, .fee, .taxa]) + deliveryTime = ApiService.decodeFlexibleString(from: c, keys: [.deliveryTime, .delivery_time]) + sameCity = (try? c.decode(Bool.self, forKey: .sameCity)) + ?? (try? c.decode(Bool.self, forKey: .same_city)) + } } diff --git a/pedi-foods/Sources/PediFoods/Services/ApiReviewModels.swift b/pedi-foods/Sources/PediFoods/Services/ApiReviewModels.swift index 1a356f0..d4184b4 100644 --- a/pedi-foods/Sources/PediFoods/Services/ApiReviewModels.swift +++ b/pedi-foods/Sources/PediFoods/Services/ApiReviewModels.swift @@ -8,8 +8,6 @@ enum ReviewPlatform: String, Encodable { static var current: ReviewPlatform { #if os(iOS) return .ios - #elseif os(Android) - return .android #else return .web #endif diff --git a/pedi-foods/Sources/PediFoods/Services/FeatureControlService.swift b/pedi-foods/Sources/PediFoods/Services/FeatureControlService.swift index 49de109..0c77e5b 100644 --- a/pedi-foods/Sources/PediFoods/Services/FeatureControlService.swift +++ b/pedi-foods/Sources/PediFoods/Services/FeatureControlService.swift @@ -267,11 +267,7 @@ final class FeatureControlService { } private func platformName() -> String { - #if os(Android) - return "android" - #else return "ios" - #endif } private func appVersion() -> String { diff --git a/pedi-foods/Sources/PediFoods/Skip/skip.yml b/pedi-foods/Sources/PediFoods/Skip/skip.yml deleted file mode 100644 index fdfcd15..0000000 --- a/pedi-foods/Sources/PediFoods/Skip/skip.yml +++ /dev/null @@ -1,12 +0,0 @@ -# Configuration file for https://skip.tools project -# -# Kotlin dependencies and Gradle build options for this module can be configured here -#build: -# contents: -# - block: 'dependencies' -# contents: -# - 'implementation("androidx.compose.runtime:runtime")' - -# this is a natively-compiled Skip Fuse module -skip: - mode: 'native' diff --git a/pedi-foods/Sources/PediFoods/State/SnackbarCenter.swift b/pedi-foods/Sources/PediFoods/State/SnackbarCenter.swift index d9f959d..ef52d17 100644 --- a/pedi-foods/Sources/PediFoods/State/SnackbarCenter.swift +++ b/pedi-foods/Sources/PediFoods/State/SnackbarCenter.swift @@ -5,56 +5,6 @@ extension Notification.Name { static let snackbarDidChange = Notification.Name("snackbarDidChange") } -#if os(Android) -@MainActor -final class SnackbarCenter { - static let shared = SnackbarCenter() - var current: SnackbarMessage? - private var dismissTask: Task? - - func show( - title: String, - style: SnackbarStyle = .info, - icon: String? = nil, - duration: TimeInterval = 3.5, - action: (() -> Void)? = nil - ) { - dismissTask?.cancel() - current = SnackbarMessage( - title: title, - style: style, - iconSystemName: icon, - duration: duration, - action: action - ) - notifyChange() - - dismissTask = Task { [weak self] in - let nanos = UInt64(max(0.2, duration) * 1_000_000_000) - try? await Task.sleep(nanoseconds: nanos) - guard !Task.isCancelled else { return } - self?.dismiss(animated: true) - } - } - - func handleTap() { - let action = current?.action - dismiss(animated: true) - action?() - } - - func dismiss(animated: Bool) { - dismissTask?.cancel() - dismissTask = nil - current = nil - notifyChange() - } - - private func notifyChange() { - NotificationCenter.default.post(name: .snackbarDidChange, object: nil) - } -} -#else @MainActor final class SnackbarCenter: ObservableObject { static let shared = SnackbarCenter() @@ -105,7 +55,6 @@ final class SnackbarCenter: ObservableObject { } } } -#endif enum SnackbarStyle: Sendable { case info diff --git a/pedi-foods/Sources/PediFoods/Views/Auth/LegalViews.swift b/pedi-foods/Sources/PediFoods/Views/Auth/LegalViews.swift index 71e47a3..ed4d739 100644 --- a/pedi-foods/Sources/PediFoods/Views/Auth/LegalViews.swift +++ b/pedi-foods/Sources/PediFoods/Views/Auth/LegalViews.swift @@ -17,9 +17,7 @@ struct TermsOfUseView: View { } .background((colorScheme == .dark ? Color.black : AppColors.backgroundLight).ignoresSafeArea()) .navigationTitle("Termos de Uso") -#if os(iOS) .appInlineNavigationTitle() -#endif } } @@ -40,8 +38,6 @@ struct PrivacyPolicyView: View { } .background((colorScheme == .dark ? Color.black : AppColors.backgroundLight).ignoresSafeArea()) .navigationTitle("Privacidade") -#if os(iOS) .appInlineNavigationTitle() -#endif } } diff --git a/pedi-foods/Sources/PediFoods/Views/Auth/LoginEmailView.swift b/pedi-foods/Sources/PediFoods/Views/Auth/LoginEmailView.swift index 787699b..a85de32 100644 --- a/pedi-foods/Sources/PediFoods/Views/Auth/LoginEmailView.swift +++ b/pedi-foods/Sources/PediFoods/Views/Auth/LoginEmailView.swift @@ -14,13 +14,8 @@ struct LoginEmailView: View { @Environment(\.colorScheme) var colorScheme @ViewBuilder private var logoImage: some View { -#if os(Android) - SwiftUI.Image("pedifoods", bundle: .module) - .resizable() -#else SwiftUI.Image("pedifoods") .resizable() -#endif } var body: some View { @@ -40,9 +35,9 @@ struct LoginEmailView: View { VStack(spacing: 16) { VStack(alignment: .leading, spacing: 16) { - LoginField(icon: "envelope", placeholder: "seu@email.com", text: $email) + LoginField(icon: "envelope", placeholder: "seu@email.com", keyboardType: .emailAddress, text: $email) - LoginField(icon: "phone", placeholder: "(00) 00000-0000", text: $phone) + LoginField(icon: "phone", placeholder: "(00) 00000-0000", keyboardType: .numberPad, text: $phone) .onChange(of: phone) { _, newValue in let masked = formatPhoneBR(newValue) if masked != newValue { @@ -78,6 +73,7 @@ struct LoginEmailView: View { dismiss() } } + .buttonStyle(.plain) .font(AppTypography.body) .padding(.top, 8) @@ -125,6 +121,7 @@ struct LoginEmailView: View { struct LoginField: View { let icon: String let placeholder: String + let keyboardType: UIKeyboardType @Binding var text: String var body: some View { @@ -135,6 +132,7 @@ struct LoginField: View { TextField(text: $text, prompt: Text(placeholder).foregroundColor(Color.gray)) { } .appNoAutoCap() .foregroundColor(.black) + .keyboardType(keyboardType) } .padding(.horizontal, 16) .frame(height: 52) diff --git a/pedi-foods/Sources/PediFoods/Views/Auth/LoginView.swift b/pedi-foods/Sources/PediFoods/Views/Auth/LoginView.swift index ae21205..6d79a9e 100644 --- a/pedi-foods/Sources/PediFoods/Views/Auth/LoginView.swift +++ b/pedi-foods/Sources/PediFoods/Views/Auth/LoginView.swift @@ -15,23 +15,13 @@ struct LoginView: View { @State var lastAnimatedToken = 0 @ViewBuilder private var logoImage: some View { -#if os(Android) - SwiftUI.Image("pedifoods", bundle: .module) - .resizable() -#else SwiftUI.Image("pedifoods") .resizable() -#endif } @ViewBuilder private var pinHeroImage: some View { -#if os(Android) - SwiftUI.Image(colorScheme == .dark ? "pin_image_app_dark" : "pin_image_app", bundle: .module) - .resizable() -#else SwiftUI.Image("pin_image_app") .resizable() -#endif } var body: some View { @@ -43,13 +33,6 @@ struct LoginView: View { (colorScheme == .dark ? Color.black : AppColors.backgroundLight) .ignoresSafeArea() -#if os(Android) - pinHeroImage - .scaledToFill() - .frame(height: heroHeight + 80) - .offset(y: heroVisible ? -60 : -(heroHeight + 220)) - .opacity(colorScheme == .dark ? 0.08 : 0.15) -#else pinHeroImage .scaledToFill() .frame(height: heroHeight + 80) @@ -61,7 +44,6 @@ struct LoginView: View { endPoint: .bottom ) ) -#endif VStack(spacing: 18) { Spacer().frame(height: logoTopInset) @@ -90,6 +72,7 @@ struct LoginView: View { .tint(AppColors.tertiary) .offset(y: buttonVisible ? 0 : 140) .opacity(buttonVisible ? 1.0 : 0.0) + .buttonStyle(.plain) HStack(spacing: 6) { Text("Não tem conta ainda?") @@ -97,6 +80,7 @@ struct LoginView: View { Button("Criar conta") { navigate(.registration) } + .buttonStyle(.plain) .foregroundStyle(AppColors.primary) } .font(AppTypography.body) @@ -108,36 +92,15 @@ struct LoginView: View { } .ignoresSafeArea() .onAppear { -#if os(Android) - // No Android, rodamos a animação localmente de forma direta - applyHiddenStateWithoutAnimation() - Task { - try? await Task.sleep(nanoseconds: 100_000_000) - withAnimation(.easeOut(duration: 0.5)) { - heroVisible = true - } - try? await Task.sleep(nanoseconds: 150_000_000) - withAnimation(.easeOut(duration: 0.4)) { - textVisible = true - } - try? await Task.sleep(nanoseconds: 150_000_000) - withAnimation(.easeOut(duration: 0.4)) { - buttonVisible = true - } - } -#else if shouldPrepareEntryAnimation { applyHiddenStateWithoutAnimation() } else { showFinalStateWithoutAnimation() } -#endif } -#if os(iOS) .task(id: authEntryAnimationToken) { await runEntryAnimationIfNeeded(for: authEntryAnimationToken) } -#endif } } diff --git a/pedi-foods/Sources/PediFoods/Views/Auth/OtpView.swift b/pedi-foods/Sources/PediFoods/Views/Auth/OtpView.swift index 08ccd07..5808611 100644 --- a/pedi-foods/Sources/PediFoods/Views/Auth/OtpView.swift +++ b/pedi-foods/Sources/PediFoods/Views/Auth/OtpView.swift @@ -25,19 +25,11 @@ struct OtpView: View { var body: some View { ScrollView { VStack(spacing: 0) { -#if os(Android) - SwiftUI.Image("pedifoods", bundle: .module) - .resizable() - .scaledToFit() - .frame(width: 74, height: 74) - .padding(.top, 140) -#else SwiftUI.Image("pedifoods") .resizable() .scaledToFit() .frame(width: 74, height: 74) .padding(.top, 140) -#endif Text("Verificação") .font(AppTypography.heading1) @@ -72,6 +64,10 @@ struct OtpView: View { if trimmed != newValue { otp = trimmed } + if trimmed.count == 8 && !isLoading { + isOtpFocused = false + validateOtp() + } } } .frame(height: 204) @@ -92,6 +88,7 @@ struct OtpView: View { Button(resendButtonTitle) { resendOtp() } + .buttonStyle(.plain) .font(AppTypography.heading3) .foregroundStyle(AppColors.primary) .disabled(isResending || !canResend) @@ -101,6 +98,7 @@ struct OtpView: View { Button("Colar código") { pasteOtpFromClipboard() } + .buttonStyle(.plain) .font(AppTypography.heading3) .foregroundStyle(AppColors.primary) .padding(.top, 22) @@ -124,6 +122,7 @@ struct OtpView: View { .padding(.bottom, 18) } } + .scrollDismissesKeyboard(.interactively) .background(AppColors.backgroundLight) .ignoresSafeArea() .onAppear { diff --git a/pedi-foods/Sources/PediFoods/Views/Auth/RegistrationView.swift b/pedi-foods/Sources/PediFoods/Views/Auth/RegistrationView.swift index 405cca1..a03f8bb 100644 --- a/pedi-foods/Sources/PediFoods/Views/Auth/RegistrationView.swift +++ b/pedi-foods/Sources/PediFoods/Views/Auth/RegistrationView.swift @@ -18,13 +18,8 @@ struct RegistrationView: View { private var isFormValid: Bool { !name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !email.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !phone.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && acceptedTerms } @ViewBuilder private var logoImage: some View { -#if os(Android) - SwiftUI.Image("pedifoods", bundle: .module) - .resizable() -#else SwiftUI.Image("pedifoods") .resizable() -#endif } var body: some View { @@ -49,9 +44,9 @@ struct RegistrationView: View { VStack(spacing: 16) { VStack(alignment: .leading, spacing: 16) { - LoginField(icon: "person", placeholder: "Ex: Maria Silva", text: $name) - LoginField(icon: "envelope", placeholder: "seu@email.com", text: $email) - LoginField(icon: "phone", placeholder: "(00) 00000-0000", text: $phone) + LoginField(icon: "person", placeholder: "Ex: Maria Silva", keyboardType: .default, text: $name) + LoginField(icon: "envelope", placeholder: "seu@email.com", keyboardType: .emailAddress, text: $email) + LoginField(icon: "phone", placeholder: "(00) 00000-0000", keyboardType: .namePhonePad, text: $phone) .onChange(of: phone) { _, newValue in let masked = formatPhoneBR(newValue) if masked != newValue { diff --git a/pedi-foods/Sources/PediFoods/Views/Launch/LaunchSplashView.swift b/pedi-foods/Sources/PediFoods/Views/Launch/LaunchSplashView.swift index 7e10f16..e05c592 100644 --- a/pedi-foods/Sources/PediFoods/Views/Launch/LaunchSplashView.swift +++ b/pedi-foods/Sources/PediFoods/Views/Launch/LaunchSplashView.swift @@ -36,12 +36,7 @@ struct LaunchSplashView: View { } @ViewBuilder var splashLogo: some View { -#if os(Android) - SwiftUI.Image("pedifoods", bundle: .module) - .resizable() -#else SwiftUI.Image("pedifoods") .resizable() -#endif } } diff --git a/pedi-foods/Sources/PediFoods/Views/Main/AddAddressFormView.swift b/pedi-foods/Sources/PediFoods/Views/Main/AddAddressFormView.swift index 94c955b..c391380 100644 --- a/pedi-foods/Sources/PediFoods/Views/Main/AddAddressFormView.swift +++ b/pedi-foods/Sources/PediFoods/Views/Main/AddAddressFormView.swift @@ -32,13 +32,8 @@ struct AddAddressFormView: View { } @ViewBuilder private var logoImage: some View { -#if os(Android) - SwiftUI.Image("pedifoods", bundle: .module) - .resizable() -#else SwiftUI.Image("pedifoods") .resizable() -#endif } var body: some View { @@ -70,8 +65,8 @@ struct AddAddressFormView: View { .padding(.bottom, 20) VStack(alignment: .leading, spacing: 16) { - LoginField(icon: "tag", placeholder: "Ex: Casa, Trabalho", text: $label) - LoginField(icon: "mail", placeholder: "CEP", text: $zipCode) + LoginField(icon: "tag", placeholder: "Ex: Casa, Trabalho", keyboardType: .default, text: $label) + LoginField(icon: "mail", placeholder: "CEP", keyboardType: .numberPad, text: $zipCode) .onChange(of: zipCode) { _, newValue in let masked = formatZipCodeBR(newValue) if masked != newValue { @@ -99,12 +94,12 @@ struct AddAddressFormView: View { .foregroundStyle(AppColors.textMuted) .padding(.horizontal, 6) } - LoginField(icon: "mappin.and.ellipse", placeholder: "Rua / Avenida", text: $address) - LoginField(icon: "number", placeholder: "Número", text: $number) - LoginField(icon: "plus.app", placeholder: "Complemento (opcional)", text: $complement) - LoginField(icon: "square.grid.2x2", placeholder: "Bairro", text: $neighborhood) - LoginField(icon: "building.2", placeholder: "Cidade", text: $city) - LoginField(icon: "map", placeholder: "Estado (UF)", text: $state) + LoginField(icon: "mappin.and.ellipse", placeholder: "Rua / Avenida", keyboardType: .default, text: $address) + LoginField(icon: "number", placeholder: "Número", keyboardType: .default, text: $number) + LoginField(icon: "plus.app", placeholder: "Complemento (opcional)", keyboardType: .default, text: $complement) + LoginField(icon: "square.grid.2x2", placeholder: "Bairro", keyboardType: .default, text: $neighborhood) + LoginField(icon: "building.2", placeholder: "Cidade", keyboardType: .default, text: $city) + LoginField(icon: "map", placeholder: "Estado (UF)", keyboardType: .default, text: $state) .onChange(of: state) { _, newValue in let normalized = String(newValue.uppercased().prefix(2)) if normalized != newValue { diff --git a/pedi-foods/Sources/PediFoods/Views/Main/HomeView+Filtering.swift b/pedi-foods/Sources/PediFoods/Views/Main/HomeView+Filtering.swift index 7cbf61c..25682b5 100644 --- a/pedi-foods/Sources/PediFoods/Views/Main/HomeView+Filtering.swift +++ b/pedi-foods/Sources/PediFoods/Views/Main/HomeView+Filtering.swift @@ -61,6 +61,7 @@ extension HomeView { rating: store.rating ?? 0, reviews: String(store.positiveReviews ?? store.reviewsCount ?? 0), distance: formatDistance(store.distance), + deliveryFee: store.deliveryFee, category: store.category ?? "Loja", promoText: nil, isFavorite: appState.favorites.storeIds.contains(store.id), diff --git a/pedi-foods/Sources/PediFoods/Views/Main/HomeView.swift b/pedi-foods/Sources/PediFoods/Views/Main/HomeView.swift index afb0cfc..d4e8b34 100644 --- a/pedi-foods/Sources/PediFoods/Views/Main/HomeView.swift +++ b/pedi-foods/Sources/PediFoods/Views/Main/HomeView.swift @@ -101,6 +101,7 @@ struct HomeView: View { storeCategory: store.category, storeRating: store.rating, storeDistance: store.distance, + storeDeliveryFee: store.deliveryFee, appState: $appState ) } label: { @@ -158,6 +159,7 @@ struct HomeView: View { ) } } + .buttonStyle(.plain) .font(AppTypography.heading3) .foregroundStyle(AppColors.primary) } @@ -179,6 +181,7 @@ struct HomeView: View { storeCategory: store.category, storeRating: store.rating, storeDistance: store.distance, + storeDeliveryFee: store.deliveryFee, appState: $appState ) } label: { diff --git a/pedi-foods/Sources/PediFoods/Views/Main/HomeViewComponents.swift b/pedi-foods/Sources/PediFoods/Views/Main/HomeViewComponents.swift index 2346194..2a519f3 100644 --- a/pedi-foods/Sources/PediFoods/Views/Main/HomeViewComponents.swift +++ b/pedi-foods/Sources/PediFoods/Views/Main/HomeViewComponents.swift @@ -49,6 +49,7 @@ struct SearchBar: View { Image(systemName: "slider.horizontal.3") .foregroundStyle(AppColors.textMuted) } + .buttonStyle(.plain) } .padding(.horizontal, 16) .frame(height: 52) diff --git a/pedi-foods/Sources/PediFoods/Views/Main/OrderTrackingView.swift b/pedi-foods/Sources/PediFoods/Views/Main/OrderTrackingView.swift index 9af70e4..70006c6 100644 --- a/pedi-foods/Sources/PediFoods/Views/Main/OrderTrackingView.swift +++ b/pedi-foods/Sources/PediFoods/Views/Main/OrderTrackingView.swift @@ -222,29 +222,15 @@ struct OrderTrackingView: View { Group { if hasTrackingImage { -#if os(Android) - SwiftUI.Image(trackingImageName, bundle: .module) - .renderingMode(.original) - .resizable() - .scaledToFit() -#else Image(trackingImageName) .renderingMode(.original) .resizable() .scaledToFit() -#endif } else if hasPlaceholderProductImage { -#if os(Android) - SwiftUI.Image("placeholder-product", bundle: .module) - .renderingMode(.original) - .resizable() - .scaledToFit() -#else Image("placeholder-product") .renderingMode(.original) .resizable() .scaledToFit() -#endif } else { ZStack { Color.black.opacity(0.08) diff --git a/pedi-foods/Sources/PediFoods/Views/Main/OrdersView.swift b/pedi-foods/Sources/PediFoods/Views/Main/OrdersView.swift index 0b0b755..5be7f66 100644 --- a/pedi-foods/Sources/PediFoods/Views/Main/OrdersView.swift +++ b/pedi-foods/Sources/PediFoods/Views/Main/OrdersView.swift @@ -688,58 +688,57 @@ struct OrderEntryDestinationView: View { func resolveRoute() async { defer { isResolvingRoute = false } - if routeIntent == .tracking { - return - } - let order = await fetchOrderForRouting() guard let order else { return } + if routeIntent == .details { orderDetails = order return } - if shouldOpenOrderDetails(for: order) { + + // For both .tracking and .auto: show payment screen if payment is still pending. + // Timeline only shows once payment is confirmed or method is off-app. + if shouldOpenPaymentScreen(for: order) { + let normalizedMethod = normalizePaymentMethod(order) + if normalizedMethod.contains("PIX") { + let pixFromPayment = order.payment?.pix + let pixFromPayload = order.paymentPayload + let copyPaste = (pixFromPayment?.copyPaste?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false) + ? pixFromPayment?.copyPaste + : pixFromPayload?.copyPaste + let qrCodeImage = (pixFromPayment?.qrCodeImage?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false) + ? pixFromPayment?.qrCodeImage + : pixFromPayload?.qrCodeImage + let expirationDate = (pixFromPayment?.expirationDate?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false) + ? pixFromPayment?.expirationDate + : pixFromPayload?.expirationDate + pixContext = PixPaymentContext( + id: order.id, + orderId: order.id, + shortId: order.shortId ?? initialShortId, + copyPaste: copyPaste?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false + ? (copyPaste ?? "") + : "Código PIX indisponível no momento. Aguarde e tente novamente.", + qrCodeImageBase64: qrCodeImage, + expirationDate: expirationDate + ) + return + } + if normalizedMethod == "CREDIT_CARD" || normalizedMethod == "DEBIT_CARD" { + cardContext = CardPaymentContext( + orderId: order.id, + shortId: order.shortId ?? initialShortId, + total: order.total ?? fallbackTotal ?? 0 + ) + return + } + } + + // For .auto only: route terminal/canceled orders to details instead of timeline. + if routeIntent == .auto && shouldOpenOrderDetails(for: order) { orderDetails = order - return - } - guard shouldOpenPaymentScreen(for: order) else { return } - - let normalizedMethod = normalizePaymentMethod(order) - if normalizedMethod.contains("PIX") { - let pixFromPayment = order.payment?.pix - let pixFromPayload = order.paymentPayload - let copyPaste = (pixFromPayment?.copyPaste?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false) - ? pixFromPayment?.copyPaste - : pixFromPayload?.copyPaste - - let qrCodeImage = (pixFromPayment?.qrCodeImage?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false) - ? pixFromPayment?.qrCodeImage - : pixFromPayload?.qrCodeImage - let expirationDate = (pixFromPayment?.expirationDate?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false) - ? pixFromPayment?.expirationDate - : pixFromPayload?.expirationDate - - pixContext = PixPaymentContext( - id: order.id, - orderId: order.id, - shortId: order.shortId ?? initialShortId, - copyPaste: copyPaste?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false - ? (copyPaste ?? "") - : "Código PIX indisponível no momento. Aguarde e tente novamente.", - qrCodeImageBase64: qrCodeImage, - expirationDate: expirationDate - ) - return - } - - if normalizedMethod == "CREDIT_CARD" || normalizedMethod == "DEBIT_CARD" { - cardContext = CardPaymentContext( - orderId: order.id, - shortId: order.shortId ?? initialShortId, - total: order.total ?? fallbackTotal ?? 0 - ) - return } + // .tracking (and .auto fallthrough) → nil states → body renders OrderTrackingView } @MainActor diff --git a/pedi-foods/Sources/PediFoods/Views/Main/PizzaFlavorAddonsSheet.swift b/pedi-foods/Sources/PediFoods/Views/Main/PizzaFlavorAddonsSheet.swift index 3170436..bc27f6b 100644 --- a/pedi-foods/Sources/PediFoods/Views/Main/PizzaFlavorAddonsSheet.swift +++ b/pedi-foods/Sources/PediFoods/Views/Main/PizzaFlavorAddonsSheet.swift @@ -81,6 +81,7 @@ struct PizzaFlavorAddonsSheet: View { .appInlineNavigationTitle() .appTopBarTrailingToolbar { Button("Concluir") { dismiss() } + .buttonStyle(.plain) .foregroundStyle(AppColors.primary) } } diff --git a/pedi-foods/Sources/PediFoods/Views/Main/UserProfileView+Android.swift b/pedi-foods/Sources/PediFoods/Views/Main/UserProfileView+Android.swift deleted file mode 100644 index 23a9fa5..0000000 --- a/pedi-foods/Sources/PediFoods/Views/Main/UserProfileView+Android.swift +++ /dev/null @@ -1,224 +0,0 @@ -#if os(Android) -import SwiftUI - -struct UserProfileView: View { - @Binding var appState: AppState - - @State var name: String = "" - @State var email: String = "" - @State var phone: String = "" - @State var profilePicture: String = "" - @State var isSaving = false - - var body: some View { - ScrollView(showsIndicators: false) { - VStack(spacing: 22) { - avatarSection - formSection - saveButton - } - .padding(.horizontal, 20) - .padding(.top, 18) - .padding(.bottom, UIDevice.bottomNotch + 24) - } - .background(AppColors.backgroundLight) - .navigationTitle("Meu Perfil") - .appInlineNavigationTitle() - .onAppear { - hydrateFromAppState() - } - } - - private var avatarSection: some View { - VStack(spacing: 12) { - Circle() - .fill(AppColors.brandSoft) - .frame(width: 110, height: 110) - .overlay { - if let imageSource = resolvedProfilePicture { - AsyncStoreImage(imageURL: imageSource) - .frame(width: 104, height: 104) - .clipShape(Circle()) - } else { - Text(initials) - .font(.system(size: 32, weight: .bold)) - .foregroundStyle(AppColors.primary) - } - } - - Button("Remover") { - profilePicture = "" - } - .font(AppTypography.caption) - .foregroundStyle(AppColors.textMuted) - .buttonStyle(.plain) - .disabled(profilePicture.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) - } - .frame(maxWidth: .infinity) - } - - private var formSection: some View { - VStack(alignment: .leading, spacing: 14) { - textFieldSection(title: "Nome", placeholder: "Seu nome completo", text: $name) - - textFieldSection(title: "E-mail", placeholder: "seu@email.com", text: $email) - .appNoAutoCap() - - textFieldSection(title: "Telefone", placeholder: "(00) 00000-0000", text: $phone) - .onChange(of: phone) { _, newValue in - let masked = formatPhoneBR(displayPhoneDigits(newValue)) - if masked != newValue { - phone = masked - } - } - .appNoAutoCap() - - textFieldSection( - title: "Foto (URL/Base64)", - placeholder: "https://... ou data:image/jpeg;base64,...", - text: $profilePicture - ) - .appNoAutoCap() - } - .padding(16) - .background(AppColors.surface) - .clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous)) - } - - private var saveButton: some View { - Button(isSaving ? "Salvando..." : "Salvar Alterações") { - Task { await saveProfile() } - } - .font(AppTypography.button) - .foregroundStyle(AppColors.textInverse) - .frame(maxWidth: .infinity, minHeight: 56) - .background(AppColors.primary) - .clipShape(RoundedRectangle(cornerRadius: AppLayout.radiusLG, style: .continuous)) - .buttonStyle(.plain) - .disabled(isSaving || canSave == false) - .opacity((isSaving || canSave == false) ? 0.6 : 1.0) - } - - private var initials: String { - let parts = name - .trimmingCharacters(in: .whitespacesAndNewlines) - .split(separator: " ") - .prefix(2) - let letters = parts.compactMap { $0.first }.map(String.init).joined() - return letters.isEmpty ? "PF" : letters.uppercased() - } - - private var resolvedProfilePicture: String? { - let trimmed = profilePicture.trimmingCharacters(in: .whitespacesAndNewlines) - guard trimmed.isEmpty == false else { return nil } - return ImageSourceResolver.resolve(trimmed) - } - - private var canSave: Bool { - let cleanName = name.trimmingCharacters(in: .whitespacesAndNewlines) - let cleanEmail = email.trimmingCharacters(in: .whitespacesAndNewlines) - let normalizedPhone = normalizePhoneNumberForAPI(phone) - return cleanName.isEmpty == false - && cleanEmail.isEmpty == false - && cleanEmail.contains("@") - && normalizedPhone.isEmpty == false - } - - private func hydrateFromAppState() { - name = appState.profile.name - email = appState.profile.email - phone = formatPhoneForDisplay(appState.profile.phone) - profilePicture = appState.profile.profilePicture - } - - @MainActor - private func saveProfile() async { - guard canSave else { return } - - let cleanName = name.trimmingCharacters(in: .whitespacesAndNewlines) - let cleanEmail = email.trimmingCharacters(in: .whitespacesAndNewlines) - let normalizedPhone = normalizePhoneNumberForAPI(phone) - let cleanPhoto = profilePicture.trimmingCharacters(in: .whitespacesAndNewlines) - - isSaving = true - defer { isSaving = false } - - do { - let response = try await ApiService().updateCustomerProfile( - name: cleanName, - email: cleanEmail, - phoneNumber: normalizedPhone, - profilePicture: cleanPhoto.isEmpty ? nil : cleanPhoto - ) - - if response.error { - SnackbarCenter.shared.show( - title: response.message ?? "Não foi possível atualizar seu perfil.", - style: .error, - icon: "xmark.octagon.fill", - duration: 3.0 - ) - return - } - - let customer = response.result - appState.profile.id = customer?.id ?? appState.profile.id - appState.profile.name = customer?.name ?? cleanName - appState.profile.email = customer?.email ?? cleanEmail - appState.profile.phone = customer?.phoneNumber ?? normalizedPhone - appState.profile.profilePicture = customer?.profilePicture ?? cleanPhoto - SessionStateStore.setActiveUserKey( - SessionStateStore.makeUserKey(profileId: appState.profile.id, email: appState.profile.email) - ) - - SnackbarCenter.shared.show( - title: "Perfil atualizado com sucesso.", - style: .success, - icon: "checkmark.circle.fill", - duration: 2.0 - ) - } catch { - SnackbarCenter.shared.show( - title: "Não foi possível atualizar seu perfil.", - style: .error, - icon: "xmark.octagon.fill", - duration: 3.0 - ) - } - } - - private func formatPhoneForDisplay(_ raw: String) -> String { - let digits = displayPhoneDigits(raw) - if digits.isEmpty { return "" } - return formatPhoneBR(digits) - } - - private func displayPhoneDigits(_ raw: String) -> String { - var digits = raw.filter(\.isNumber) - if digits.hasPrefix("55"), digits.count > 11 { - digits = String(digits.dropFirst(2)) - } - return String(digits.prefix(11)) - } - - private func textFieldSection(title: String, placeholder: String, text: Binding) -> some View { - VStack(alignment: .leading, spacing: 8) { - Text(title) - .font(AppTypography.caption) - .foregroundStyle(AppColors.textMuted) - - TextField(placeholder, text: text) - .font(AppTypography.body) - .foregroundStyle(AppColors.textPrimary) - .padding(.horizontal, 12) - .frame(height: 50) - .background(AppColors.backgroundLight) - .clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous)) - .overlay( - RoundedRectangle(cornerRadius: 14, style: .continuous) - .stroke(AppColors.secondary.opacity(0.2), lineWidth: 1) - ) - } - } -} -#endif diff --git a/pedi-foods/Sources/PediFoods/Views/Main/UserProfileView.swift b/pedi-foods/Sources/PediFoods/Views/Main/UserProfileView.swift index 2e9a54c..b04cb9e 100644 --- a/pedi-foods/Sources/PediFoods/Views/Main/UserProfileView.swift +++ b/pedi-foods/Sources/PediFoods/Views/Main/UserProfileView.swift @@ -1,9 +1,6 @@ -#if !os(Android) import SwiftUI -#if canImport(PhotosUI) && os(iOS) +#if os(iOS) import PhotosUI -#endif -#if canImport(UIKit) import UIKit #endif @@ -17,8 +14,8 @@ struct UserProfileView: View { @State var profilePicture: String = "" @State var isSaving = false -#if canImport(PhotosUI) && os(iOS) - @State var selectedPhotoItem: PhotosPickerItem? +#if os(iOS) + @State private var selectedPhotoItem: PhotosPickerItem? #endif var body: some View { @@ -38,7 +35,7 @@ struct UserProfileView: View { .onAppear { hydrateFromAppState() } -#if canImport(PhotosUI) && os(iOS) +#if os(iOS) .onChange(of: selectedPhotoItem) { _, newItem in Task { await applySelectedPhoto(newItem) } } @@ -63,7 +60,7 @@ struct UserProfileView: View { } HStack(spacing: 10) { -#if canImport(PhotosUI) && os(iOS) +#if os(iOS) PhotosPicker(selection: $selectedPhotoItem, matching: .images, photoLibrary: .shared()) { Text("Trocar Foto") .font(AppTypography.caption) @@ -244,19 +241,15 @@ struct UserProfileView: View { } } -#if canImport(PhotosUI) && os(iOS) +#if os(iOS) @MainActor private func applySelectedPhoto(_ item: PhotosPickerItem?) async { guard let item else { return } do { guard let data = try await item.loadTransferable(type: Data.self) else { return } - #if canImport(UIKit) guard let image = UIImage(data: data), let jpegData = image.jpegData(compressionQuality: 0.82) else { return } profilePicture = "data:image/jpeg;base64,\(jpegData.base64EncodedString())" - #else - profilePicture = "data:image/jpeg;base64,\(data.base64EncodedString())" - #endif } catch { SnackbarCenter.shared.show( title: "Não foi possível carregar a foto selecionada.", @@ -268,4 +261,3 @@ struct UserProfileView: View { } #endif } -#endif