From becdccbe7fdeea4dabfc927abf39415659a44d0b Mon Sep 17 00:00:00 2001 From: Junwei Zhao Date: Sat, 1 Mar 2025 21:49:02 +1100 Subject: [PATCH] User now can select clock position --- image-flow.xcodeproj/project.pbxproj | 8 +- image-flow/ClockPreviewView.swift | 3 +- image-flow/ClockStyle.swift | 30 ++++++++ image-flow/ContentView.swift | 105 +++++++++++++++++++++++++-- image-flow/SlideshowView.swift | 70 ++++++++++++++++-- 5 files changed, 197 insertions(+), 19 deletions(-) create mode 100644 image-flow/ClockStyle.swift diff --git a/image-flow.xcodeproj/project.pbxproj b/image-flow.xcodeproj/project.pbxproj index 9a5dd88..9781e9a 100644 --- a/image-flow.xcodeproj/project.pbxproj +++ b/image-flow.xcodeproj/project.pbxproj @@ -396,7 +396,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"image-flow/Preview Content\""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = AQ879ZQMD2; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = ""; @@ -407,7 +407,7 @@ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 18.3; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -433,7 +433,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"image-flow/Preview Content\""; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = AQ879ZQMD2; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = ""; @@ -444,7 +444,7 @@ INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - IPHONEOS_DEPLOYMENT_TARGET = 18.3; + IPHONEOS_DEPLOYMENT_TARGET = 18.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/image-flow/ClockPreviewView.swift b/image-flow/ClockPreviewView.swift index 17e951c..4ef8b69 100644 --- a/image-flow/ClockPreviewView.swift +++ b/image-flow/ClockPreviewView.swift @@ -6,9 +6,10 @@ // import SwiftUI +// ClockStyle is defined in ClockStyle.swift struct ClockPreviewView: View { - let clockStyle: ClockStyle + let clockStyle: ClockStyle // Using ClockStyle from ClockStyle.swift @State private var currentTime = Date() let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() diff --git a/image-flow/ClockStyle.swift b/image-flow/ClockStyle.swift new file mode 100644 index 0000000..c663adb --- /dev/null +++ b/image-flow/ClockStyle.swift @@ -0,0 +1,30 @@ +// +// ClockStyle.swift +// image-flow +// +// Created by Junv on 1/3/2025. +// + +import Foundation + +enum ClockStyle: String, CaseIterable { + case digital + case analog + case minimal + + var displayName: String { + switch self { + case .digital: return "Digital" + case .analog: return "Analog" + case .minimal: return "Minimal" + } + } + + var icon: String { + switch self { + case .digital: return "clock" + case .analog: return "clock.circle" + case .minimal: return "clock.badge" + } + } +} diff --git a/image-flow/ContentView.swift b/image-flow/ContentView.swift index 5d949d6..10254a4 100644 --- a/image-flow/ContentView.swift +++ b/image-flow/ContentView.swift @@ -7,13 +7,15 @@ import SwiftUI import PhotosUI +// ClockStyle is defined in the same module, no need for import struct ContentView: View { @StateObject private var photoManager = PhotoPickerManager() - @State private var clockStyle: ClockStyle = .digital @State private var isFullscreen = false @State private var currentImageIndex = 0 @State private var transitionAnimation: TransitionAnimation = .fade + @State private var clockStyle: ClockStyle = .digital + @State private var clockPosition: ClockPosition = .center @State private var showingPermissionAlert = false let timer = Timer.publish(every: 10, on: .main, in: .common).autoconnect() @@ -23,6 +25,7 @@ struct ContentView: View { SlideshowView( images: photoManager.selectedImages, clockStyle: clockStyle, + clockPosition: clockPosition, currentIndex: $currentImageIndex, animation: transitionAnimation ) @@ -95,7 +98,15 @@ struct ContentView: View { } .pickerStyle(.segmented) + // Preview of selected clock style ClockPreviewView(clockStyle: clockStyle) + .frame(height: 100) + .padding(.vertical, 8) + } + + Section("Clock Position") { + ClockPositionGrid(selectedPosition: $clockPosition) + .frame(height: 280) .padding(.vertical, 8) } @@ -187,16 +198,42 @@ struct ContentView: View { } } -enum ClockStyle: String, CaseIterable { - case digital - case analog - case minimal +enum ClockPosition: String, CaseIterable { + case topCenter + case topLeft + case topRight + case centerLeft + case center + case centerRight + case bottomCenter + case bottomLeft + case bottomRight var displayName: String { switch self { - case .digital: return "Digital" - case .analog: return "Analog" - case .minimal: return "Minimal" + case .topCenter: return "Top" + case .topLeft: return "Top Left" + case .topRight: return "Top Right" + case .centerLeft: return "Left" + case .center: return "Center" + case .centerRight: return "Right" + case .bottomCenter: return "Bottom" + case .bottomLeft: return "Bottom Left" + case .bottomRight: return "Bottom Right" + } + } + + var icon: String { + switch self { + case .topCenter: return "arrow.up.to.line" + case .topLeft: return "arrow.up.left.to.line" + case .topRight: return "arrow.up.right.to.line" + case .centerLeft: return "arrow.left.to.line" + case .center: return "plus" + case .centerRight: return "arrow.right.to.line" + case .bottomCenter: return "arrow.down.to.line" + case .bottomLeft: return "arrow.down.left.to.line" + case .bottomRight: return "arrow.down.right.to.line" } } } @@ -215,6 +252,58 @@ enum TransitionAnimation: String, CaseIterable { } } +struct ClockPositionGrid: View { + @Binding var selectedPosition: ClockPosition + + var body: some View { + let positions: [[ClockPosition]] = [ + [.topLeft, .topCenter, .topRight], + [.centerLeft, .center, .centerRight], + [.bottomLeft, .bottomCenter, .bottomRight] + ] + + VStack(spacing: 12) { + ForEach(positions, id: \.self) { row in + HStack(spacing: 12) { + ForEach(row, id: \.self) { position in + Button(action: { + selectedPosition = position + }) { + ZStack { + RoundedRectangle(cornerRadius: 12) + .fill(selectedPosition == position ? Color.blue : Color.gray.opacity(0.2)) + .frame(width: 65, height: 65) + + VStack(spacing: 4) { + Image(systemName: position.icon) + .font(.system(size: 24)) + .foregroundColor(selectedPosition == position ? Color.white : Color.primary) + + Text(position.displayName) + .font(.caption2) + .foregroundColor(selectedPosition == position ? Color.white : Color.primary) + .lineLimit(1) + .minimumScaleFactor(0.8) + } + .padding(.horizontal, 4) + } + } + .buttonStyle(PlainButtonStyle()) + .overlay( + RoundedRectangle(cornerRadius: 12) + .stroke(selectedPosition == position ? Color.blue : Color.clear, lineWidth: 2) + ) + .shadow(color: selectedPosition == position ? Color.blue.opacity(0.5) : Color.clear, radius: 4) + } + } + } + } + .padding() + .background(Color.gray.opacity(0.1)) + .cornerRadius(16) + } +} + #Preview { ContentView() } diff --git a/image-flow/SlideshowView.swift b/image-flow/SlideshowView.swift index 9ba24e6..6cb3f30 100644 --- a/image-flow/SlideshowView.swift +++ b/image-flow/SlideshowView.swift @@ -10,6 +10,7 @@ import SwiftUI struct SlideshowView: View { let images: [UIImage] let clockStyle: ClockStyle + let clockPosition: ClockPosition @Binding var currentIndex: Int let animation: TransitionAnimation @@ -36,8 +37,8 @@ struct SlideshowView: View { // Overlay gradient for better clock visibility LinearGradient( gradient: Gradient(colors: [Color.black.opacity(0.4), Color.clear]), - startPoint: .top, - endPoint: .center + startPoint: getGradientStartPoint(), + endPoint: getGradientEndPoint() ) } } @@ -45,11 +46,14 @@ struct SlideshowView: View { .animation(.easeInOut(duration: 1.0), value: currentIndex) } - // Clock overlay - VStack { + // Clock overlay with positioning + GeometryReader { geometry in clockView - .padding(.top, 50) - + .position(getClockPosition(in: geometry.size)) + } + + // Image counter at the bottom + VStack { Spacer() // Image counter @@ -82,6 +86,59 @@ struct SlideshowView: View { } } + private func getClockPosition(in size: CGSize) -> CGPoint { + switch clockPosition { + case .topCenter: + return CGPoint(x: size.width / 2, y: 100) + case .topLeft: + return CGPoint(x: size.width * 0.25, y: 100) + case .topRight: + return CGPoint(x: size.width * 0.75, y: 100) + case .centerLeft: + return CGPoint(x: 100, y: size.height / 2) + case .center: + return CGPoint(x: size.width / 2, y: size.height / 2) + case .centerRight: + return CGPoint(x: size.width - 100, y: size.height / 2) + case .bottomCenter: + return CGPoint(x: size.width / 2, y: size.height - 100) + case .bottomLeft: + return CGPoint(x: size.width * 0.25, y: size.height - 100) + case .bottomRight: + return CGPoint(x: size.width * 0.75, y: size.height - 100) + } + } + + private func getGradientStartPoint() -> UnitPoint { + switch clockPosition { + case .topCenter, .topLeft, .topRight: + return .top + case .centerLeft: + return .leading + case .center: + return .center + case .centerRight: + return .trailing + case .bottomCenter, .bottomLeft, .bottomRight: + return .bottom + } + } + + private func getGradientEndPoint() -> UnitPoint { + switch clockPosition { + case .topCenter, .topLeft, .topRight: + return .center + case .centerLeft: + return .trailing + case .center: + return .trailing + case .centerRight: + return .leading + case .bottomCenter, .bottomLeft, .bottomRight: + return .center + } + } + private func transitionEffect() -> AnyTransition { switch animation { case .fade: @@ -229,6 +286,7 @@ struct MinimalClockView: View { SlideshowView( images: [UIImage(systemName: "photo")!], clockStyle: .digital, + clockPosition: .topCenter, currentIndex: .constant(0), animation: .fade )