diff --git a/MacMagazine/Features/OnboardingLibrary/Sources/OnboardingLibrary/Model/OnBoardingFeature.swift b/MacMagazine/Features/OnboardingLibrary/Sources/OnboardingLibrary/Model/OnBoardingFeature.swift index 4f1239cf..1bd11fad 100644 --- a/MacMagazine/Features/OnboardingLibrary/Sources/OnboardingLibrary/Model/OnBoardingFeature.swift +++ b/MacMagazine/Features/OnboardingLibrary/Sources/OnboardingLibrary/Model/OnBoardingFeature.swift @@ -13,57 +13,57 @@ public extension OnBoardingFeature { OnBoardingFeature( symbol: "sparkles", title: "Nova interface", - subTitle: "Liquid Glass. Rápido e fácil de usar." + subTitle: "Liquid Glass. Rápida, moderna e fácil de usar." ), OnBoardingFeature( symbol: "ipad.and.iphone", - title: "iPhone e iPad", - subTitle: "Experiências diferentes, pensadas para cada dispositivo." + title: "Dispositivos suportados", + subTitle: "iPhone, iPad, Mac e Apple Watch" ), OnBoardingFeature( symbol: "apple.podcasts.pages", - title: "Experiência em podcasts", - subTitle: "Controle completo do player. Capítulos." + title: "MacMagazine no Ar", + subTitle: "Player completo para o nosso podcast, com suporte a capítulos." ), OnBoardingFeature( symbol: "photo.on.rectangle.angled", title: "Instagram", - subTitle: "Nova categoria dedicada ao Instagram." - ), - OnBoardingFeature( - symbol: "folder.fill", - title: "Categorias de posts", - subTitle: "Explore conteúdos organizados por categorias." + subTitle: "Nova aba dedicada à rede social." ), +// OnBoardingFeature( +// symbol: "folder.fill", +// title: "Categorias de posts", +// subTitle: "Explore conteúdos organizados por categorias." +// ), OnBoardingFeature( symbol: "slider.horizontal.3", title: "App personalizável", - subTitle: "Deixe o app com a sua cara, com ajustes personalizados." + subTitle: "Deixe o app com a sua cara!" ), OnBoardingFeature( symbol: "icloud.fill", title: "Sincronização com iCloud", - subTitle: "Suas preferências sincronizadas em todos os dispositivos." + subTitle: "Preferências sincronizadas em todos os seus dispositivos." ), OnBoardingFeature( symbol: "applewatch", title: "Novo app para Apple Watch", - subTitle: "Fique informado direto no pulso." + subTitle: "Fique informado diretamente no pulso." ), OnBoardingFeature( symbol: "square.grid.2x2.fill", - title: "Nova interface de widgets", - subTitle: "Widgets lindos com design Liquid Glass." - ), - OnBoardingFeature( - symbol: "gift.fill", - title: "Mais uma novidade", - subTitle: "Descubra recursos escondidos por todo o app." - ), - OnBoardingFeature( - symbol: "desktopcomputer", - title: "App para macOS", - subTitle: "Aplicativo completo para Mac, pensado para o desktop." + title: "Widgets lindos…", + subTitle: "Três tamanhos de widgets para você escolher." ) +// OnBoardingFeature( +// symbol: "gift.fill", +// title: "Mais uma novidade", +// subTitle: "Descubra recursos escondidos por todo o app." +// ), +// OnBoardingFeature( +// symbol: "desktopcomputer", +// title: "App para macOS", +// subTitle: "Aplicativo completo para Mac, pensado para o desktop." +// ) ] } diff --git a/MacMagazine/Features/OnboardingLibrary/Sources/OnboardingLibrary/Views/Components/OnboardingCTAButton.swift b/MacMagazine/Features/OnboardingLibrary/Sources/OnboardingLibrary/Views/Components/OnboardingCTAButton.swift index 53f7d485..460f63b7 100644 --- a/MacMagazine/Features/OnboardingLibrary/Sources/OnboardingLibrary/Views/Components/OnboardingCTAButton.swift +++ b/MacMagazine/Features/OnboardingLibrary/Sources/OnboardingLibrary/Views/Components/OnboardingCTAButton.swift @@ -34,7 +34,7 @@ public struct OnboardingCTAButton: View { HStack(spacing: 8) { Text(title) .textCase(.uppercase) - .fontWeight(.semibold) + .fontWeight(.bold) .font(isLandscape ? .subheadline : .body) if showChevron { diff --git a/MacMagazine/Features/OnboardingLibrary/Sources/OnboardingLibrary/Views/Components/OnboardingSkipButton.swift b/MacMagazine/Features/OnboardingLibrary/Sources/OnboardingLibrary/Views/Components/OnboardingSkipButton.swift index 298e2a3c..28d66ab3 100644 --- a/MacMagazine/Features/OnboardingLibrary/Sources/OnboardingLibrary/Views/Components/OnboardingSkipButton.swift +++ b/MacMagazine/Features/OnboardingLibrary/Sources/OnboardingLibrary/Views/Components/OnboardingSkipButton.swift @@ -29,7 +29,7 @@ public struct OnboardingSkipButton: View { .font(.body) .foregroundColor(.primary) .padding() - .glassEffect(.clear.interactive()) + .glassEffect() } .accessibilityLabel(label) .accessibilityHint(hint) diff --git a/MacMagazine/Features/OnboardingLibrary/Sources/OnboardingLibrary/Views/OnboardingContainerView.swift b/MacMagazine/Features/OnboardingLibrary/Sources/OnboardingLibrary/Views/OnboardingContainerView.swift index 5adf44a8..15c3330d 100644 --- a/MacMagazine/Features/OnboardingLibrary/Sources/OnboardingLibrary/Views/OnboardingContainerView.swift +++ b/MacMagazine/Features/OnboardingLibrary/Sources/OnboardingLibrary/Views/OnboardingContainerView.swift @@ -31,7 +31,6 @@ public struct OnboardingContainerView: View { PermissionsView(coordinator: coordinator, logoNamespace: logoAnimation) } } - .frame(maxWidth: .infinity, maxHeight: .infinity) .transition(.opacity) } .frame(maxWidth: .infinity, maxHeight: .infinity) @@ -44,7 +43,7 @@ public struct OnboardingContainerView: View { // MARK: - Preview #if DEBUG -#Preview("Sheet - iPhone") { +#Preview("Sheet - Portrait") { OnboardingSheetPreviewHost() } diff --git a/MacMagazine/Features/OnboardingLibrary/Sources/OnboardingLibrary/Views/Screen1_WelcomeView.swift b/MacMagazine/Features/OnboardingLibrary/Sources/OnboardingLibrary/Views/Screen1_WelcomeView.swift index 4b2c1a88..c51dafcb 100644 --- a/MacMagazine/Features/OnboardingLibrary/Sources/OnboardingLibrary/Views/Screen1_WelcomeView.swift +++ b/MacMagazine/Features/OnboardingLibrary/Sources/OnboardingLibrary/Views/Screen1_WelcomeView.swift @@ -48,17 +48,22 @@ struct WelcomeView: View { // MARK: - Portrait Layout private var portraitLayout: some View { - VStack(spacing: 24) { - Spacer() - + VStack(spacing: 0) { + // Logo fixed at top logoView(width: 152, height: 152) - .padding(.bottom, 48) + .padding(.top, 70) + .onboardingFade(animateIn, delay: 0.00, duration: reduceMotion ? 0 : 0.70) + .scaleEffect(animateIn ? 1 : (reduceMotion ? 1 : 0.90)) + .animation(reduceMotion ? nil : .easeOut(duration: 0.70), value: animateIn) + + Spacer() messageView .onboardingFade(animateIn, delay: 0.25, duration: 0.60) Spacer() - + } + .safeAreaInset(edge: .bottom) { ctaButton .padding(.horizontal, 20) .padding(.vertical, 16) @@ -100,27 +105,23 @@ struct WelcomeView: View { .padding(.vertical, 16) } - // MARK: - Componentes Compartilhados + // MARK: - Shared Components private func logoView(width: CGFloat, height: CGFloat) -> some View { OnboardingLogoView(width: width, height: height) - .matchedGeometryEffect(id: "onboarding_logo", in: logoNamespace) - .onboardingFade(animateIn, delay: 0.00, duration: reduceMotion ? 0 : 0.70) - .scaleEffect(animateIn ? 1 : (reduceMotion ? 1 : 0.90)) - .animation(reduceMotion ? nil : .easeOut(duration: 0.70), value: animateIn) .accessibilityHidden(true) } private var messageView: some View { VStack(spacing: 10) { - Text("Bem-vindo ao novo MacMagazine") + Text("Bem-vindo ao novo app do MacMagazine") .font(isLandscape ? .title2.weight(.bold) : .largeTitle.weight(.bold)) .multilineTextAlignment(.center) .lineLimit(nil) .fixedSize(horizontal: false, vertical: true) .accessibilityAddTraits(.isHeader) - Text("Notícias, reviews e podcasts em um visual totalmente renovado.") + Text("Notícias, reviews, vídeos e podcasts em um visual totalmente renovado.") .font(isLandscape ? .body : .title3) .foregroundStyle(.secondary) .multilineTextAlignment(.center) @@ -132,18 +133,9 @@ struct WelcomeView: View { } private var ctaButton: some View { - PrimaryButton( - "Continuar", - size: 340, - style: ButtonStyleConfiguration( - color: .white, - stroke: theme.button.primary.color ?? .blue, - fill: theme.button.primary.color ?? .blue - ) - ) { + OnboardingCTAButton("Continuar") { trackAndNavigate() } - .frame(maxWidth: .infinity) .accessibilityLabel("Continuar") .accessibilityHint("Avança para ver as novidades do app") } @@ -155,7 +147,7 @@ struct WelcomeView: View { buttonId: AnalyticsConstants.ButtonID.onboardingWelcomeContinue.id, screen: AnalyticsConstants.Screen.onboardingWelcome.name )) - withAnimation(reduceMotion ? nil : .spring(response: 0.6, dampingFraction: 0.8)) { + withAnimation(reduceMotion ? nil : .spring(response: 0.7, dampingFraction: 0.85)) { coordinator.navigate(to: .features) } } @@ -166,29 +158,38 @@ struct WelcomeView: View { #if DEBUG #Preview("Welcome — Portrait") { @Previewable @Namespace var namespace - - NavigationStack { - WelcomeView( - coordinator: OnboardingCoordinator( - permissionManager: PermissionManager(analytics: AnalyticsManager()), - analytics: AnalyticsManager() - ), - logoNamespace: namespace - ) - } + WelcomeSheetPreviewHost() } #Preview("Welcome — Landscape", traits: .landscapeLeft) { @Previewable @Namespace var namespace + WelcomeSheetPreviewHost() +} - NavigationStack { - WelcomeView( - coordinator: OnboardingCoordinator( - permissionManager: PermissionManager(analytics: AnalyticsManager()), - analytics: AnalyticsManager() - ), - logoNamespace: namespace - ) +private struct WelcomeSheetPreviewHost: View { + @State private var isPresented = true + @State private var coordinator = OnboardingCoordinator( + permissionManager: PermissionManager(analytics: AnalyticsManager()), + analytics: AnalyticsManager() + ) + + var body: some View { + ZStack { + Color.gray.opacity(0.12) + .ignoresSafeArea() + + Text("MainView (simulação)") + .font(.headline) + } + .sheet(isPresented: $isPresented) { + OnboardingContainerView(coordinator: coordinator) + .environment(\.theme, ThemeColor()) + .presentationDetents([.large]) + .interactiveDismissDisabled(true) + } + .onAppear { + isPresented = true + } } } #endif diff --git a/MacMagazine/Features/OnboardingLibrary/Sources/OnboardingLibrary/Views/Screen2_FeaturesView.swift b/MacMagazine/Features/OnboardingLibrary/Sources/OnboardingLibrary/Views/Screen2_FeaturesView.swift index 602f2190..9b9582ac 100644 --- a/MacMagazine/Features/OnboardingLibrary/Sources/OnboardingLibrary/Views/Screen2_FeaturesView.swift +++ b/MacMagazine/Features/OnboardingLibrary/Sources/OnboardingLibrary/Views/Screen2_FeaturesView.swift @@ -17,17 +17,18 @@ struct FeaturesView: View { @State private var animateIn = false @State private var currentPage: Int = 0 - // MARK: - Layout Adaptativos + // MARK: - Layout Properties private var isLandscape: Bool { verticalSizeClass == .compact } + private var isIPad: Bool { + horizontalSizeClass == .regular + } + private var numberOfColumns: Int { - if horizontalSizeClass == .regular { - return 2 - } - return isLandscape ? 2 : 1 + isIPad || isLandscape ? 2 : 1 } private var cardsPerPage: Int { @@ -35,7 +36,7 @@ struct FeaturesView: View { } private var gridColumns: [GridItem] { - Array(repeating: GridItem(.flexible(), spacing: 16), count: numberOfColumns) + Array(repeating: GridItem(.flexible(), spacing: isLandscape ? 12 : 16), count: numberOfColumns) } private var pages: [[OnBoardingFeature]] { @@ -58,101 +59,113 @@ struct FeaturesView: View { } .containerRelativeFrame([.horizontal, .vertical]) .overlay(alignment: .topTrailing) { - if !isLastPage { - OnboardingSkipButton( - label: "Pular novidades", - hint: "Vai direto para a tela de permissões" - ) { - coordinator.skipToPermissions() - } - .padding(.top, 16) - .padding(.trailing, 20) - } - } - .onAppear { - withAnimation { - animateIn = true + // Skip button - acts like a toolbar + OnboardingSkipButton( + label: "Pular novidades", + hint: "Vai direto para a tela de permissões" + ) { + coordinator.skipToPermissions() } + .padding(.top, 16) + .padding(.trailing, 20) + .opacity(isLastPage ? 0 : 1) + .animation(.easeInOut(duration: 0.25), value: isLastPage) + .allowsHitTesting(!isLastPage) } - .background(OnboardingBackground()) + // .background(OnboardingBackground()) + .onAppear { animateIn = true } + .onDisappear { animateIn = false } .trackScreen(AnalyticsConstants.Screen.onboardingFeatures.name, analytics: coordinator.analytics) } // MARK: - Portrait Layout private var portraitLayout: some View { - VStack(spacing: 16) { - OnboardingLogoView(width: 80, height: 80) - .matchedGeometryEffect(id: "onboarding_logo", in: logoNamespace) - .padding(.top, 80) - .accessibilityHidden(true) + VStack(spacing: 0) { + // Logo fixed at top - same position as WelcomeView + logoView + .padding(.top, 70) - OnboardingTitleView("Novidades no App MacMagazine", animateIn: animateIn) + Text("Novidades no App MacMagazine") + .font(.title2) + .fontWeight(.bold) + .multilineTextAlignment(.leading) + .accessibilityAddTraits(.isHeader) + .padding(.top, 24) - Spacer(minLength: 16) + Spacer() paginatedCardsView + .onboardingFade(animateIn, delay: 0.15, duration: 0.4) - pageIndicator - .padding(.top, 8) - - Spacer(minLength: 16) - + Spacer() + } + .safeAreaInset(edge: .bottom) { footerSection - .padding(.bottom, 20) + .padding(.horizontal, 20) + .padding(.vertical, 16) } - .padding(.horizontal, 20) } // MARK: - Landscape Layout private var landscapeLayout: some View { - HStack(spacing: 24) { - VStack(spacing: 12) { - Spacer() - - OnboardingLogoView(width: 80, height: 80) - .matchedGeometryEffect(id: "onboarding_logo", in: logoNamespace) - .accessibilityHidden(true) - + HStack(spacing: 20) { + // Left side - Logo and title + VStack(spacing: 8) { + logoView Text("Novidades no App") - .font(.headline) + .font(.subheadline) .fontWeight(.bold) .multilineTextAlignment(.center) - .opacity(animateIn ? 1 : 0) .accessibilityAddTraits(.isHeader) - - Spacer() - - compactFooterSection } - .frame(maxWidth: 180) - .padding(.leading, -40) + .frame(width: 120) - VStack(spacing: 12) { + // Right side - Cards and footer + VStack(spacing: 0) { Spacer() paginatedCardsView - - pageIndicator + .onboardingFade(animateIn, delay: 0.15, duration: 0.4) Spacer() + // Footer at bottom: dots centered, or dots left + button right HStack { - Spacer() - continueButton - .opacity(isLastPage ? 1 : 0) - .offset(y: isLastPage ? 0 : 20) - .animation(.easeInOut(duration: 0.4), value: isLastPage) + if isLastPage { + pageIndicator + + Spacer() + + OnboardingCTAButton("Continuar") { + handleContinue() + } + .accessibilityLabel("Continuar para permissões") + .accessibilityHint("Vai para a tela de permissões") + } else { + Spacer() + pageIndicator + Spacer() + } } + .padding(.bottom, 8) + .animation(.easeInOut(duration: 0.3), value: isLastPage) } - .padding(.top, 40) + .frame(maxWidth: .infinity) } .padding(.horizontal, 20) - .padding(.vertical, 16) + .padding(.vertical, 8) } - // MARK: - Paginated Cards with Native TabView + // MARK: - Logo View + + private var logoView: some View { + OnboardingLogoView(width: 80, height: 80) + .accessibilityHidden(true) + } + + // MARK: - Paginated Cards private var paginatedCardsView: some View { TabView(selection: $currentPage) { @@ -163,7 +176,6 @@ struct FeaturesView: View { } .tabViewStyle(.page(indexDisplayMode: .never)) .frame(height: tabViewHeight) - .onboardingAnimateIn(animateIn, delay: 0.2, reduceMotion: reduceMotion) .accessibilityLabel("Lista de novidades, página \(currentPage + 1) de \(pages.count)") } @@ -171,46 +183,19 @@ struct FeaturesView: View { if isLandscape { return 200 } - if horizontalSizeClass == .regular { - return 280 + if isIPad { + return 260 } - return 320 - } - - // MARK: - Custom Page Indicator - - private var pageIndicator: some View { - HStack(spacing: 8) { - ForEach(0.. some View { - LazyVGrid(columns: gridColumns, spacing: 16) { + LazyVGrid(columns: gridColumns, spacing: isLandscape ? 8 : 16) { ForEach(cards) { card in cardView(card: card) } } - .padding(.horizontal, 8) + .padding(.horizontal, isLandscape ? 8 : 16) } private func cardView(card: OnBoardingFeature) -> some View { @@ -240,54 +225,55 @@ struct FeaturesView: View { .accessibilityElement(children: .combine) } - // MARK: - Footer Section + // MARK: - Page Indicator - private var footerSection: some View { - VStack(spacing: 12) { - VStack(alignment: .leading, spacing: 6) { - Image(systemName: "person.3.fill") - .foregroundStyle(.primary) - .accessibilityHidden(true) - - Text("Aqui podemos colocar qualquer texto como a Apple faz.") - .font(.caption2) - .foregroundStyle(.gray) - .lineLimit(3) - .fixedSize(horizontal: false, vertical: true) + private var pageIndicator: some View { + HStack(spacing: 8) { + ForEach(0..