Compare commits
No commits in common. "41578f09412b4207863be66fa0735610a85de136" and "6a154f00bc537156e27da4209f02122e22b28eaa" have entirely different histories.
41578f0941
...
6a154f00bc
@ -110,11 +110,7 @@ struct ContentView: View {
|
|||||||
OnboardingView {
|
OnboardingView {
|
||||||
onboardingState.completeWelcome()
|
onboardingState.completeWelcome()
|
||||||
}
|
}
|
||||||
.transition(.asymmetric(
|
.transition(.opacity)
|
||||||
insertion: .opacity,
|
|
||||||
removal: .opacity.combined(with: .move(edge: .bottom)).combined(with: .scale(scale: 0.9))
|
|
||||||
))
|
|
||||||
.zIndex(1) // Ensure it stays on top during transition
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $keepAwakePromptState.isPresented) {
|
.sheet(isPresented: $keepAwakePromptState.isPresented) {
|
||||||
@ -141,7 +137,7 @@ struct ContentView: View {
|
|||||||
guard shouldShowKeepAwakePromptForTab() else { return }
|
guard shouldShowKeepAwakePromptForTab() else { return }
|
||||||
keepAwakePromptState.showIfNeeded(isKeepAwakeEnabled: clockViewModel.style.keepAwake)
|
keepAwakePromptState.showIfNeeded(isKeepAwakeEnabled: clockViewModel.style.keepAwake)
|
||||||
}
|
}
|
||||||
.animation(.spring(duration: 0.45, bounce: 0.2), value: onboardingState.hasCompletedWelcome)
|
.animation(.easeInOut(duration: 0.3), value: onboardingState.hasCompletedWelcome)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func shouldShowKeepAwakePromptForTab() -> Bool {
|
private func shouldShowKeepAwakePromptForTab() -> Bool {
|
||||||
|
|||||||
@ -24,10 +24,6 @@ struct AlarmView: View {
|
|||||||
ZStack {
|
ZStack {
|
||||||
AppSurface.primary.ignoresSafeArea()
|
AppSurface.primary.ignoresSafeArea()
|
||||||
|
|
||||||
GeometryReader { geometry in
|
|
||||||
let isLandscape = geometry.size.width > geometry.size.height
|
|
||||||
let maxWidth = isLandscape ? Design.Size.maxContentWidthLandscape : Design.Size.maxContentWidthPortrait
|
|
||||||
|
|
||||||
Group {
|
Group {
|
||||||
if viewModel.alarms.isEmpty {
|
if viewModel.alarms.isEmpty {
|
||||||
VStack(spacing: Design.Spacing.large) {
|
VStack(spacing: Design.Spacing.large) {
|
||||||
@ -43,13 +39,13 @@ struct AlarmView: View {
|
|||||||
showAddAlarm = true
|
showAddAlarm = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.frame(maxWidth: Design.Size.maxContentWidthPortrait)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .center)
|
||||||
} else {
|
} else {
|
||||||
List {
|
ScrollView {
|
||||||
|
VStack(spacing: Design.Spacing.medium) {
|
||||||
if !isKeepAwakeEnabled {
|
if !isKeepAwakeEnabled {
|
||||||
AlarmLimitationsBanner()
|
AlarmLimitationsBanner()
|
||||||
.listRowInsets(EdgeInsets(top: Design.Spacing.large, leading: Design.Spacing.large, bottom: Design.Spacing.small, trailing: Design.Spacing.large))
|
|
||||||
.listRowBackground(Color.clear)
|
|
||||||
.listRowSeparator(.hidden)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ForEach(viewModel.alarms) { alarm in
|
ForEach(viewModel.alarms) { alarm in
|
||||||
@ -62,27 +58,18 @@ struct AlarmView: View {
|
|||||||
},
|
},
|
||||||
onEdit: {
|
onEdit: {
|
||||||
selectedAlarmForEdit = alarm
|
selectedAlarmForEdit = alarm
|
||||||
},
|
|
||||||
onDelete: {
|
|
||||||
Task {
|
|
||||||
await viewModel.deleteAlarm(id: alarm.id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.listRowInsets(EdgeInsets(top: Design.Spacing.small, leading: Design.Spacing.large, bottom: Design.Spacing.small, trailing: Design.Spacing.large))
|
|
||||||
.listRowBackground(Color.clear)
|
|
||||||
.listRowSeparator(.hidden)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.listStyle(.plain)
|
.padding(.horizontal, Design.Spacing.large)
|
||||||
.scrollContentBackground(.hidden)
|
.padding(.top, Design.Spacing.large)
|
||||||
.background(AppSurface.primary.ignoresSafeArea())
|
|
||||||
}
|
}
|
||||||
}
|
.frame(maxWidth: Design.Size.maxContentWidthPortrait)
|
||||||
.frame(maxWidth: maxWidth)
|
|
||||||
.frame(maxWidth: .infinity, alignment: .center)
|
.frame(maxWidth: .infinity, alignment: .center)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.navigationTitle(isPad ? "" : "Alarms")
|
.navigationTitle(isPad ? "" : "Alarms")
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
|
|||||||
@ -16,7 +16,6 @@ struct AlarmRowView: View {
|
|||||||
let alarm: Alarm
|
let alarm: Alarm
|
||||||
let onToggle: () -> Void
|
let onToggle: () -> Void
|
||||||
let onEdit: () -> Void
|
let onEdit: () -> Void
|
||||||
let onDelete: () -> Void
|
|
||||||
@AppStorage(ClockStyle.appStorageKey) private var clockStyleData: Data = Data()
|
@AppStorage(ClockStyle.appStorageKey) private var clockStyleData: Data = Data()
|
||||||
|
|
||||||
// MARK: - Body
|
// MARK: - Body
|
||||||
@ -66,13 +65,6 @@ struct AlarmRowView: View {
|
|||||||
onEdit()
|
onEdit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
|
||||||
Button(role: .destructive) {
|
|
||||||
onDelete()
|
|
||||||
} label: {
|
|
||||||
Label("Delete", systemImage: "trash")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var isKeepAwakeEnabled: Bool {
|
private var isKeepAwakeEnabled: Bool {
|
||||||
@ -90,8 +82,7 @@ struct AlarmRowView: View {
|
|||||||
AlarmRowView(
|
AlarmRowView(
|
||||||
alarm: Alarm(time: Date()),
|
alarm: Alarm(time: Date()),
|
||||||
onToggle: {},
|
onToggle: {},
|
||||||
onEdit: {},
|
onEdit: {}
|
||||||
onDelete: {}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,33 +37,6 @@ struct NoiseView: View {
|
|||||||
let isLandscape = geometry.size.width > geometry.size.height
|
let isLandscape = geometry.size.width > geometry.size.height
|
||||||
let maxWidth = isLandscape ? Design.Size.maxContentWidthLandscape : Design.Size.maxContentWidthPortrait
|
let maxWidth = isLandscape ? Design.Size.maxContentWidthLandscape : Design.Size.maxContentWidthPortrait
|
||||||
|
|
||||||
VStack(spacing: 0) {
|
|
||||||
// Custom Search Bar - Constrained to maxWidth
|
|
||||||
HStack {
|
|
||||||
HStack(spacing: Design.Spacing.small) {
|
|
||||||
Image(systemName: "magnifyingglass")
|
|
||||||
.foregroundColor(AppTextColors.secondary)
|
|
||||||
|
|
||||||
TextField("Search sounds", text: $searchText)
|
|
||||||
.textFieldStyle(.plain)
|
|
||||||
.foregroundColor(AppTextColors.primary)
|
|
||||||
|
|
||||||
if !searchText.isEmpty {
|
|
||||||
Button(action: { searchText = "" }) {
|
|
||||||
Image(systemName: "xmark.circle.fill")
|
|
||||||
.foregroundColor(AppTextColors.tertiary)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(Design.Spacing.small)
|
|
||||||
.background(AppSurface.overlay)
|
|
||||||
.cornerRadius(Design.CornerRadius.medium)
|
|
||||||
}
|
|
||||||
.padding(.horizontal, Design.Spacing.large)
|
|
||||||
.padding(.top, Design.Spacing.medium)
|
|
||||||
.padding(.bottom, Design.Spacing.small)
|
|
||||||
.frame(maxWidth: maxWidth)
|
|
||||||
|
|
||||||
Group {
|
Group {
|
||||||
if isLandscape {
|
if isLandscape {
|
||||||
// Landscape layout: Player on left, sounds on right
|
// Landscape layout: Player on left, sounds on right
|
||||||
@ -74,13 +47,15 @@ struct NoiseView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(maxWidth: maxWidth)
|
.frame(maxWidth: maxWidth)
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity, alignment: .center)
|
.frame(maxWidth: .infinity, alignment: .center)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle("Noise")
|
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
|
||||||
.animation(.easeInOut(duration: 0.3), value: selectedSound)
|
.animation(.easeInOut(duration: 0.3), value: selectedSound)
|
||||||
|
.searchable(
|
||||||
|
text: $searchText,
|
||||||
|
placement: .navigationBarDrawer(displayMode: .automatic),
|
||||||
|
prompt: "Search sounds"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Computed Properties
|
// MARK: - Computed Properties
|
||||||
@ -107,7 +82,6 @@ struct NoiseView: View {
|
|||||||
if selectedSound != nil {
|
if selectedSound != nil {
|
||||||
soundControlView
|
soundControlView
|
||||||
.centered()
|
.centered()
|
||||||
.padding(.bottom, Design.Spacing.medium)
|
|
||||||
} else {
|
} else {
|
||||||
// Placeholder when no sound selected - Enhanced for CRO
|
// Placeholder when no sound selected - Enhanced for CRO
|
||||||
VStack(spacing: Design.Spacing.medium) {
|
VStack(spacing: Design.Spacing.medium) {
|
||||||
@ -134,31 +108,27 @@ struct NoiseView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.padding(.vertical, Design.Spacing.large)
|
.padding(.vertical, Design.Spacing.xLarge)
|
||||||
.background(AppSurface.overlay, in: RoundedRectangle(cornerRadius: Design.CornerRadius.appLarge))
|
.background(AppSurface.overlay, in: RoundedRectangle(cornerRadius: Design.CornerRadius.appLarge))
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: Design.CornerRadius.appLarge)
|
RoundedRectangle(cornerRadius: Design.CornerRadius.appLarge)
|
||||||
.stroke(AppBorder.subtle, lineWidth: Design.LineWidth.thin)
|
.stroke(AppBorder.subtle, lineWidth: Design.LineWidth.thin)
|
||||||
)
|
)
|
||||||
.padding(.bottom, Design.Spacing.medium)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal, Design.Spacing.large)
|
.contentPadding(horizontal: Design.Spacing.large)
|
||||||
.padding(.top, Design.Spacing.small)
|
.padding(.top, Design.Spacing.large)
|
||||||
.background(AppSurface.primary)
|
.background(AppSurface.primary)
|
||||||
|
|
||||||
// Scrollable sound selection
|
// Scrollable sound selection
|
||||||
List {
|
ScrollView {
|
||||||
SoundCategoryView(
|
SoundCategoryView(
|
||||||
sounds: viewModel.availableSounds,
|
sounds: viewModel.availableSounds,
|
||||||
selectedSound: $selectedSound,
|
selectedSound: $selectedSound,
|
||||||
searchText: $searchText
|
searchText: $searchText
|
||||||
)
|
)
|
||||||
.listRowInsets(EdgeInsets(top: 0, leading: Design.Spacing.large, bottom: Design.Spacing.large, trailing: Design.Spacing.large))
|
.contentPadding(horizontal: Design.Spacing.large, vertical: Design.Spacing.large)
|
||||||
.listRowBackground(Color.clear)
|
|
||||||
.listRowSeparator(.hidden)
|
|
||||||
}
|
}
|
||||||
.listStyle(.plain)
|
|
||||||
.scrollContentBackground(.hidden)
|
.scrollContentBackground(.hidden)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,6 +24,7 @@ struct OnboardingView: View {
|
|||||||
@State private var currentPage = 0
|
@State private var currentPage = 0
|
||||||
@State private var alarmKitPermissionGranted = false
|
@State private var alarmKitPermissionGranted = false
|
||||||
@State private var keepAwakeEnabled = false
|
@State private var keepAwakeEnabled = false
|
||||||
|
@State private var showCelebration = false
|
||||||
|
|
||||||
private let totalPages = 4
|
private let totalPages = 4
|
||||||
|
|
||||||
@ -58,8 +59,11 @@ struct OnboardingView: View {
|
|||||||
.padding(.horizontal, Design.Spacing.xLarge)
|
.padding(.horizontal, Design.Spacing.xLarge)
|
||||||
.padding(.bottom, Design.Spacing.xxLarge)
|
.padding(.bottom, Design.Spacing.xxLarge)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: Design.Size.maxContentWidthPortrait)
|
|
||||||
.frame(maxWidth: .infinity, alignment: .center)
|
// Celebration overlay
|
||||||
|
if showCelebration {
|
||||||
|
celebrationOverlay
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,9 +119,9 @@ struct OnboardingView: View {
|
|||||||
Text(text)
|
Text(text)
|
||||||
.typography(.body)
|
.typography(.body)
|
||||||
.foregroundStyle(AppTextColors.secondary)
|
.foregroundStyle(AppTextColors.secondary)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
}
|
}
|
||||||
.frame(maxWidth: 320, alignment: .leading) // Constrain width and align content to leading
|
|
||||||
.frame(maxWidth: .infinity, alignment: .center) // Center the constrained box in the parent
|
|
||||||
.padding(.horizontal, Design.Spacing.xxLarge)
|
.padding(.horizontal, Design.Spacing.xxLarge)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,9 +225,9 @@ struct OnboardingView: View {
|
|||||||
Text(text)
|
Text(text)
|
||||||
.typography(.body)
|
.typography(.body)
|
||||||
.foregroundStyle(AppTextColors.secondary)
|
.foregroundStyle(AppTextColors.secondary)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
}
|
}
|
||||||
.frame(maxWidth: 320, alignment: .leading) // Constrain width and align content to leading
|
|
||||||
.frame(maxWidth: .infinity, alignment: .center) // Center the constrained box in the parent
|
|
||||||
.padding(.horizontal, Design.Spacing.xxLarge)
|
.padding(.horizontal, Design.Spacing.xxLarge)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,8 +317,6 @@ struct OnboardingView: View {
|
|||||||
.typography(.callout)
|
.typography(.callout)
|
||||||
.foregroundStyle(AppTextColors.secondary)
|
.foregroundStyle(AppTextColors.secondary)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: 320, alignment: .leading) // Constrain width and align content to leading
|
|
||||||
.frame(maxWidth: .infinity, alignment: .center) // Center the constrained box in the parent
|
|
||||||
.padding(.horizontal, Design.Spacing.xxLarge)
|
.padding(.horizontal, Design.Spacing.xxLarge)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -373,6 +375,30 @@ struct OnboardingView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Celebration
|
||||||
|
|
||||||
|
private var celebrationOverlay: some View {
|
||||||
|
ZStack {
|
||||||
|
Color.black.opacity(0.3)
|
||||||
|
.ignoresSafeArea()
|
||||||
|
|
||||||
|
VStack(spacing: Design.Spacing.large) {
|
||||||
|
Image(systemName: "party.popper.fill")
|
||||||
|
.font(.system(size: 60))
|
||||||
|
.foregroundStyle(AppAccent.primary)
|
||||||
|
|
||||||
|
Text("Let's go!")
|
||||||
|
.typography(.heroBold)
|
||||||
|
.foregroundStyle(AppTextColors.primary)
|
||||||
|
}
|
||||||
|
.padding(Design.Spacing.xxxLarge)
|
||||||
|
.background(AppSurface.overlay)
|
||||||
|
.cornerRadius(Design.CornerRadius.xxLarge)
|
||||||
|
.shadow(radius: 20)
|
||||||
|
}
|
||||||
|
.transition(.opacity.combined(with: .scale))
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Actions
|
// MARK: - Actions
|
||||||
|
|
||||||
private func requestAlarmKitPermission() {
|
private func requestAlarmKitPermission() {
|
||||||
@ -421,8 +447,13 @@ struct OnboardingView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func triggerCelebration() {
|
private func triggerCelebration() {
|
||||||
// Snappier transition for a better feel
|
withAnimation(.spring(duration: 0.4)) {
|
||||||
withAnimation(.spring(duration: 0.45, bounce: 0.2)) {
|
showCelebration = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dismiss after short celebration
|
||||||
|
Task {
|
||||||
|
try? await Task.sleep(for: .milliseconds(1200))
|
||||||
onComplete()
|
onComplete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user