From e3ab7d8e80b18e608ec6edf12f9663cd1fec52dd Mon Sep 17 00:00:00 2001 From: Beingpax Date: Sat, 1 Nov 2025 11:49:29 +0545 Subject: [PATCH] Fix footer button overlap on small screens --- .../Metrics/DashboardPromotionsSection.swift | 20 ++-- .../Metrics/HelpAndResourcesSection.swift | 17 ++- VoiceInk/Views/Metrics/MetricsContent.swift | 108 ++++++++++++------ 3 files changed, 88 insertions(+), 57 deletions(-) diff --git a/VoiceInk/Views/Metrics/DashboardPromotionsSection.swift b/VoiceInk/Views/Metrics/DashboardPromotionsSection.swift index 39ee886..afe0cd4 100644 --- a/VoiceInk/Views/Metrics/DashboardPromotionsSection.swift +++ b/VoiceInk/Views/Metrics/DashboardPromotionsSection.swift @@ -96,7 +96,7 @@ private struct DashboardPromotionCard: View { ) var body: some View { - VStack(alignment: .leading, spacing: 18) { + VStack(alignment: .leading, spacing: 14) { HStack(alignment: .top) { Text(badge.uppercased()) .font(.system(size: 11, weight: .heavy)) @@ -106,9 +106,9 @@ private struct DashboardPromotionCard: View { .background(.white.opacity(0.2)) .clipShape(Capsule()) .foregroundColor(.white) - + Spacer() - + Image(systemName: accentSymbol) .font(.system(size: 20, weight: .bold)) .foregroundColor(.white.opacity(0.85)) @@ -116,17 +116,17 @@ private struct DashboardPromotionCard: View { .background(.white.opacity(0.18)) .clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous)) } - + Text(title) - .font(.system(size: 21, weight: .heavy, design: .rounded)) + .font(.system(size: 20, weight: .heavy, design: .rounded)) .foregroundColor(.white) .fixedSize(horizontal: false, vertical: true) - + Text(message) - .font(.system(size: 13.5, weight: .medium)) + .font(.system(size: 13, weight: .medium)) .foregroundColor(.white.opacity(0.85)) .fixedSize(horizontal: false, vertical: true) - + Button(action: action) { HStack(spacing: 6) { Text(actionTitle) @@ -141,8 +141,8 @@ private struct DashboardPromotionCard: View { } .buttonStyle(.plain) } - .padding(24) - .frame(maxWidth: .infinity, minHeight: 200, alignment: .topLeading) + .padding(18) + .frame(maxWidth: .infinity, alignment: .topLeading) .background( RoundedRectangle(cornerRadius: 28, style: .continuous) .fill(Self.defaultGradient) diff --git a/VoiceInk/Views/Metrics/HelpAndResourcesSection.swift b/VoiceInk/Views/Metrics/HelpAndResourcesSection.swift index 1d4e247..1086649 100644 --- a/VoiceInk/Views/Metrics/HelpAndResourcesSection.swift +++ b/VoiceInk/Views/Metrics/HelpAndResourcesSection.swift @@ -2,33 +2,32 @@ import SwiftUI struct HelpAndResourcesSection: View { var body: some View { - VStack(alignment: .leading, spacing: 15) { + VStack(alignment: .leading, spacing: 14) { Text("Help & Resources") - .font(.system(size: 22, weight: .bold, design: .rounded)) + .font(.system(size: 20, weight: .bold, design: .rounded)) .foregroundColor(.primary.opacity(0.8)) - - VStack(alignment: .leading, spacing: 12) { + + VStack(alignment: .leading, spacing: 10) { resourceLink( icon: "sparkles", title: "Recommended Models", url: "https://tryvoiceink.com/recommended-models" ) - + resourceLink( icon: "video.fill", title: "YouTube Videos & Guides", url: "https://www.youtube.com/@tryvoiceink/videos" ) - + resourceLink( icon: "book.fill", title: "Documentation", - url: "https://tryvoiceink.com/docs" // Placeholder + url: "https://tryvoiceink.com/docs" ) } } - .padding(22) - .padding(.vertical, 2) + .padding(18) .background( RoundedRectangle(cornerRadius: 28, style: .continuous) .fill(Color(nsColor: .windowBackgroundColor)) diff --git a/VoiceInk/Views/Metrics/MetricsContent.swift b/VoiceInk/Views/Metrics/MetricsContent.swift index c3605be..40b1ada 100644 --- a/VoiceInk/Views/Metrics/MetricsContent.swift +++ b/VoiceInk/Views/Metrics/MetricsContent.swift @@ -9,22 +9,28 @@ struct MetricsContent: View { if transcriptions.isEmpty { emptyStateView } else { - ScrollView { - VStack(spacing: 24) { - heroSection - metricsSection - HStack(alignment: .top, spacing: 18) { - HelpAndResourcesSection() - DashboardPromotionsSection(licenseState: licenseState) + GeometryReader { geometry in + ScrollView { + VStack(spacing: 24) { + heroSection + metricsSection + HStack(alignment: .top, spacing: 18) { + HelpAndResourcesSection() + DashboardPromotionsSection(licenseState: licenseState) + } + + Spacer(minLength: 20) + + HStack { + Spacer() + footerActionsView + } } + .frame(minHeight: geometry.size.height - 56) + .padding(.vertical, 28) + .padding(.horizontal, 32) } - .padding(.vertical, 28) - .padding(.horizontal, 32) - } - .background(Color(.windowBackgroundColor)) - .overlay(alignment: .bottomTrailing) { - footerActionsView - .padding() + .background(Color(.windowBackgroundColor)) } } } @@ -133,26 +139,10 @@ struct MetricsContent: View { private var footerActionsView: some View { HStack(spacing: 12) { CopySystemInfoButton() - feedbackButton + FeedbackButton() } } - private var feedbackButton: some View { - Button(action: { - EmailSupport.openSupportEmail() - }) { - HStack(spacing: 8) { - Image(systemName: "exclamationmark.bubble.fill") - Text("Feedback or Issues?") - } - .font(.system(size: 13, weight: .medium)) - .padding(.horizontal, 12) - .padding(.vertical, 8) - .background(Capsule().fill(.thinMaterial)) - } - .buttonStyle(.plain) - } - private var formattedTimeSaved: String { let formatted = Formatters.formattedDuration(timeSaved, style: .full, fallback: "Time savings coming soon") return formatted @@ -250,9 +240,49 @@ private enum Formatters { } } +private struct FeedbackButton: View { + @State private var isClicked: Bool = false + + var body: some View { + Button(action: { + openFeedback() + }) { + HStack(spacing: 8) { + Image(systemName: isClicked ? "checkmark.circle.fill" : "exclamationmark.bubble.fill") + .rotationEffect(.degrees(isClicked ? 360 : 0)) + .animation(.spring(response: 0.3, dampingFraction: 0.7), value: isClicked) + + Text(isClicked ? "Sending" : "Feedback or Issues?") + .animation(.spring(response: 0.3, dampingFraction: 0.7), value: isClicked) + } + .font(.system(size: 13, weight: .medium)) + .padding(.horizontal, 12) + .padding(.vertical, 8) + .background(Capsule().fill(.thinMaterial)) + } + .buttonStyle(.plain) + .scaleEffect(isClicked ? 1.1 : 1.0) + .animation(.spring(response: 0.3, dampingFraction: 0.7), value: isClicked) + } + + private func openFeedback() { + EmailSupport.openSupportEmail() + + withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) { + isClicked = true + } + + DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { + withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) { + isClicked = false + } + } + } +} + private struct CopySystemInfoButton: View { @State private var isCopied: Bool = false - + var body: some View { Button(action: { copySystemInfo() @@ -260,8 +290,10 @@ private struct CopySystemInfoButton: View { HStack(spacing: 8) { Image(systemName: isCopied ? "checkmark" : "doc.on.doc") .rotationEffect(.degrees(isCopied ? 360 : 0)) - + .animation(.spring(response: 0.3, dampingFraction: 0.7), value: isCopied) + Text(isCopied ? "Copied!" : "Copy System Info") + .animation(.spring(response: 0.3, dampingFraction: 0.7), value: isCopied) } .font(.system(size: 13, weight: .medium)) .padding(.horizontal, 12) @@ -272,16 +304,16 @@ private struct CopySystemInfoButton: View { .scaleEffect(isCopied ? 1.1 : 1.0) .animation(.spring(response: 0.3, dampingFraction: 0.7), value: isCopied) } - + private func copySystemInfo() { SystemInfoService.shared.copySystemInfoToClipboard() - - withAnimation { + + withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) { isCopied = true } - + DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { - withAnimation { + withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) { isCopied = false } }