diff --git a/Pocket Poster.xcodeproj/project.pbxproj b/Pocket Poster.xcodeproj/project.pbxproj index 6ebde64..40da974 100644 --- a/Pocket Poster.xcodeproj/project.pbxproj +++ b/Pocket Poster.xcodeproj/project.pbxproj @@ -14,22 +14,9 @@ 6F09B79D2DEB694B00CDE89C /* Pocket Poster.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Pocket Poster.app"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ -/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ - 6F09B7F72DEBBFEB00CDE89C /* Exceptions for "Pocket Poster" folder in "Pocket Poster" target */ = { - isa = PBXFileSystemSynchronizedBuildFileExceptionSet; - membershipExceptions = ( - "Pocket-Poster-Info.plist", - ); - target = 6F09B79C2DEB694B00CDE89C /* Pocket Poster */; - }; -/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ - /* Begin PBXFileSystemSynchronizedRootGroup section */ 6F09B79F2DEB694B00CDE89C /* Pocket Poster */ = { isa = PBXFileSystemSynchronizedRootGroup; - exceptions = ( - 6F09B7F72DEBBFEB00CDE89C /* Exceptions for "Pocket Poster" folder in "Pocket Poster" target */, - ); path = "Pocket Poster"; sourceTree = ""; }; diff --git a/Pocket Poster/Assets.xcassets/AppIcon.appiconset/Contents.json b/Pocket Poster/Assets.xcassets/AppIcon.appiconset/Contents.json index 2305880..9453d3c 100644 --- a/Pocket Poster/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Pocket Poster/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,6 +1,7 @@ { "images" : [ { + "filename" : "pocketposter-2.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" @@ -12,6 +13,7 @@ "value" : "dark" } ], + "filename" : "pocketposter-transparent.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" diff --git a/Pocket Poster/Assets.xcassets/AppIcon.appiconset/pocketposter-2.png b/Pocket Poster/Assets.xcassets/AppIcon.appiconset/pocketposter-2.png new file mode 100644 index 0000000..72b5782 Binary files /dev/null and b/Pocket Poster/Assets.xcassets/AppIcon.appiconset/pocketposter-2.png differ diff --git a/Pocket Poster/Assets.xcassets/AppIcon.appiconset/pocketposter-transparent.png b/Pocket Poster/Assets.xcassets/AppIcon.appiconset/pocketposter-transparent.png new file mode 100644 index 0000000..1f0522e Binary files /dev/null and b/Pocket Poster/Assets.xcassets/AppIcon.appiconset/pocketposter-transparent.png differ diff --git a/Pocket Poster/Assets.xcassets/OnBoarding/Colors/Contents.json b/Pocket Poster/Assets.xcassets/OnBoarding/Colors/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Pocket Poster/Assets.xcassets/OnBoarding/Colors/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pocket Poster/Assets.xcassets/OnBoarding/Colors/WelcomeDark.colorset/Contents.json b/Pocket Poster/Assets.xcassets/OnBoarding/Colors/WelcomeDark.colorset/Contents.json new file mode 100644 index 0000000..b3d49d1 --- /dev/null +++ b/Pocket Poster/Assets.xcassets/OnBoarding/Colors/WelcomeDark.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "0.500", + "red" : "0.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.500", + "green" : "0.000", + "red" : "0.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pocket Poster/Assets.xcassets/OnBoarding/Colors/WelcomeLight.colorset/Contents.json b/Pocket Poster/Assets.xcassets/OnBoarding/Colors/WelcomeLight.colorset/Contents.json new file mode 100644 index 0000000..d8f867a --- /dev/null +++ b/Pocket Poster/Assets.xcassets/OnBoarding/Colors/WelcomeLight.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "1.000", + "red" : "0.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "0.000", + "red" : "0.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pocket Poster/Assets.xcassets/OnBoarding/Contents.json b/Pocket Poster/Assets.xcassets/OnBoarding/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Pocket Poster/Assets.xcassets/OnBoarding/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pocket Poster/Assets.xcassets/OnBoarding/Cowabunga.imageset/Contents.json b/Pocket Poster/Assets.xcassets/OnBoarding/Cowabunga.imageset/Contents.json new file mode 100644 index 0000000..6aaf45f --- /dev/null +++ b/Pocket Poster/Assets.xcassets/OnBoarding/Cowabunga.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Screenshot 2025-05-31 at 20.52.14.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pocket Poster/Assets.xcassets/OnBoarding/Cowabunga.imageset/Screenshot 2025-05-31 at 20.52.14.png b/Pocket Poster/Assets.xcassets/OnBoarding/Cowabunga.imageset/Screenshot 2025-05-31 at 20.52.14.png new file mode 100644 index 0000000..77a92ad Binary files /dev/null and b/Pocket Poster/Assets.xcassets/OnBoarding/Cowabunga.imageset/Screenshot 2025-05-31 at 20.52.14.png differ diff --git a/Pocket Poster/Assets.xcassets/OnBoarding/Logo.imageset/Contents.json b/Pocket Poster/Assets.xcassets/OnBoarding/Logo.imageset/Contents.json new file mode 100644 index 0000000..c9dfabf --- /dev/null +++ b/Pocket Poster/Assets.xcassets/OnBoarding/Logo.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "pocketposter-transparent.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pocket Poster/Assets.xcassets/OnBoarding/Logo.imageset/pocketposter-transparent.png b/Pocket Poster/Assets.xcassets/OnBoarding/Logo.imageset/pocketposter-transparent.png new file mode 100644 index 0000000..1f0522e Binary files /dev/null and b/Pocket Poster/Assets.xcassets/OnBoarding/Logo.imageset/pocketposter-transparent.png differ diff --git a/Pocket Poster/Assets.xcassets/OnBoarding/Nugget.imageset/Contents.json b/Pocket Poster/Assets.xcassets/OnBoarding/Nugget.imageset/Contents.json new file mode 100644 index 0000000..0e128d8 --- /dev/null +++ b/Pocket Poster/Assets.xcassets/OnBoarding/Nugget.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Screenshot 2025-05-31 at 20.51.24.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pocket Poster/Assets.xcassets/OnBoarding/Nugget.imageset/Screenshot 2025-05-31 at 20.51.24.png b/Pocket Poster/Assets.xcassets/OnBoarding/Nugget.imageset/Screenshot 2025-05-31 at 20.51.24.png new file mode 100644 index 0000000..23c4af9 Binary files /dev/null and b/Pocket Poster/Assets.xcassets/OnBoarding/Nugget.imageset/Screenshot 2025-05-31 at 20.51.24.png differ diff --git a/Pocket Poster/Assets.xcassets/OnBoarding/Shortcuts.imageset/2019-11-26-shortcuts.jpg b/Pocket Poster/Assets.xcassets/OnBoarding/Shortcuts.imageset/2019-11-26-shortcuts.jpg new file mode 100644 index 0000000..ba6ff5a Binary files /dev/null and b/Pocket Poster/Assets.xcassets/OnBoarding/Shortcuts.imageset/2019-11-26-shortcuts.jpg differ diff --git a/Pocket Poster/Assets.xcassets/OnBoarding/Shortcuts.imageset/Contents.json b/Pocket Poster/Assets.xcassets/OnBoarding/Shortcuts.imageset/Contents.json new file mode 100644 index 0000000..15abb0e --- /dev/null +++ b/Pocket Poster/Assets.xcassets/OnBoarding/Shortcuts.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "2019-11-26-shortcuts.jpg", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Pocket Poster/Pocket_PosterApp.swift b/Pocket Poster/Pocket_PosterApp.swift index 9342ebc..3b484bc 100644 --- a/Pocket Poster/Pocket_PosterApp.swift +++ b/Pocket Poster/Pocket_PosterApp.swift @@ -9,9 +9,19 @@ import SwiftUI @main struct Pocket_PosterApp: App { + @AppStorage("finishedTutorial") var finishedTutorial: Bool = false + var body: some Scene { WindowGroup { - ContentView() + Group { + if finishedTutorial { + ContentView() + } else { + OnBoardingView() + } + } + .transition(.opacity) + .animation(.easeOut(duration: 0.5), value: finishedTutorial) } } } diff --git a/Pocket Poster/Views/ContentView.swift b/Pocket Poster/Views/ContentView.swift index baf15c8..e3a7482 100644 --- a/Pocket Poster/Views/ContentView.swift +++ b/Pocket Poster/Views/ContentView.swift @@ -92,7 +92,7 @@ struct ContentView: View { } .alert("Success!", isPresented: $showSuccessAlert) { Button("OK") { - PosterBoardManager.runShortcut(named: "PosterBoard") // shortcut url: https://www.icloud.com/shortcuts/9035ef1d51c247dba8c9861dc10b4aa7 + PosterBoardManager.runShortcut(named: "PosterBoard") } } message: { Text(lastError ?? "???") diff --git a/Pocket Poster/Views/OnBoarding/OnBoardingCardButtons.swift b/Pocket Poster/Views/OnBoarding/OnBoardingCardButtons.swift new file mode 100644 index 0000000..4c87138 --- /dev/null +++ b/Pocket Poster/Views/OnBoarding/OnBoardingCardButtons.swift @@ -0,0 +1,145 @@ +// +// OnBoardingCardButtons.swift +// Pocket Poster +// +// Created by lemin on 5/31/25. +// + +import SwiftUI + +struct OnBoardingCardButtons: View { + @AppStorage("finishedTutorial") var finishedTutorial: Bool? + + @Binding var idx: Int + @Binding var isAnimating: Bool + + var showDismiss: Bool + var showFinish: Bool + + var body: some View { + HStack { + // MARK: Dismiss Button + if showDismiss { + Button(action: { + playFinishAnimation() + }) { + HStack(spacing: 8) { + Image(systemName: "x.circle") + .imageScale(.large) + + Text("Skip") + } + .padding(.horizontal, 16) + .padding(.vertical, 10) + .background( + Capsule().strokeBorder(Color.white, lineWidth: 1.25) + ) + } + .accentColor(Color.white) + .accessibilityIdentifier("OnBoardingSkip") + } + + // MARK: Next Button + Button(action: { + if showFinish { + playFinishAnimation() + } else { + // move to next + isAnimating = false + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: { + idx += 1 + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: { + isAnimating = true + }) + }) + } + }) { + HStack(spacing: 8) { + Text(showFinish ? "Finish" : "Next") + + Image(systemName: showFinish ? "checkmark.circle" : "arrow.right.circle") + .imageScale(.large) + } + .padding(.horizontal, 16) + .padding(.vertical, 10) + .background( + Capsule().strokeBorder(Color.white, lineWidth: 1.25) + ) + } + .accentColor(Color.white) + .accessibilityIdentifier("OnBoardingNext") + } + } + + func playFinishAnimation() { + isAnimating = false + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: { + finishedTutorial = true + }) + } +} + +struct OnBoardingCardView: View { + var info: OnBoardingPage + var pageCount: Int + @Binding var idx: Int + + @State private var isAnimating: Bool = false + @State private var imgCornerRadius: CGFloat = 10.0 + + var body: some View { + VStack(spacing: 20) { + // Image + Image(info.image) + .resizable() + .scaledToFit() + .clipShape(RoundedRectangle(cornerRadius: imgCornerRadius)) + .shadow(color: Color(red: 0, green: 0, blue: 0, opacity: 0.15), radius: 8, x: 6, y: 8) + .scaleEffect(isAnimating ? 1.0 : 0.6) + + // Title + Text(info.title) + .foregroundColor(Color.white) + .multilineTextAlignment(.center) + .font(.largeTitle) + .fontWeight(.heavy) + .shadow(color: Color(red: 0, green: 0, blue: 0, opacity: 0.15), radius: 2, x: 2, y: 2) + + // Description + Text(info.description) + .foregroundColor(Color.white) + .multilineTextAlignment(.center) + .padding(.horizontal, 16) + .frame(maxWidth: 480) + + // Link Button + if let url = info.link, let linkName = info.linkName { + Link(destination: url) { + HStack(spacing: 8) { + Image(systemName: "link.circle") + .imageScale(.large) + + Text(linkName) + } + .padding(.horizontal, 16) + .padding(.vertical, 10) + .background( + Capsule().strokeBorder(Color.white, lineWidth: 1.25) + ) + } + .accentColor(Color.white) + } + + OnBoardingCardButtons(idx: $idx, isAnimating: $isAnimating, showDismiss: idx == 0, showFinish: idx == pageCount-1) + } + .opacity(isAnimating ? 1.0 : 0.0) + .animation(.easeOut(duration: 0.5), value: isAnimating) + .onAppear { + isAnimating = true + } + .padding(.horizontal, 20) + .padding(.vertical, 25) + .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .center) + .background(LinearGradient(gradient: Gradient(colors: info.gradientColors), startPoint: .top, endPoint: .bottom).ignoresSafeArea()) + } +} diff --git a/Pocket Poster/Views/OnBoarding/OnBoardingPage.swift b/Pocket Poster/Views/OnBoarding/OnBoardingPage.swift new file mode 100644 index 0000000..31777aa --- /dev/null +++ b/Pocket Poster/Views/OnBoarding/OnBoardingPage.swift @@ -0,0 +1,57 @@ +// +// OnBoardingPage.swift +// Pocket Poster +// +// Created by lemin on 5/31/25. +// + +import SwiftUICore + +struct OnBoardingPage: Identifiable { + var id = UUID() + var title: String + var description: String + var image: String + var link: URL? + var linkName: String? + var gradientColors: [Color] + + init(title: String, description: String, image: String, link: URL? = nil, linkName: String? = nil, gradientColors: [Color] = [Color("WelcomeLight"), Color("WelcomeDark")]) { + self.id = UUID() + self.title = title + self.description = description + self.image = image + self.link = link + self.linkName = linkName + self.gradientColors = gradientColors + } +} + +let onBoardingCards: [OnBoardingPage] = [ + .init( + title: "Welcome to Pocket Poster!", + description: "Here is a tutorial to help you get started with the app.", + image: "Logo" + ), + .init( + title: "Install the Shortcut", + description: "To apply, you will need a shortcut that opens PosterBoard.", + image: "Shortcuts", + link: URL(string: "https://www.icloud.com/shortcuts/9035ef1d51c247dba8c9861dc10b4aa7"), + linkName: "Get Shortcut" + ), + .init( + title: "Install Nugget", + description: "To get the app bundle id, Nugget is required.\n\nOn your computer, download Nugget from the GitHub.", + image: "Nugget", + link: URL(string: "https://github.com/leminlimez/Nugget/releases/latest"), + linkName: "Open GitHub" + ), + .init( + title: "Enjoy!", + description: "You can find wallpapers on the official Cowabun.ga website.", + image: "Cowabunga", + link: URL(string: "https://cowabun.ga/wallpapers"), + linkName: "Find Wallpapers" + ) +] diff --git a/Pocket Poster/Views/OnBoarding/OnBoardingView.swift b/Pocket Poster/Views/OnBoarding/OnBoardingView.swift new file mode 100644 index 0000000..f03306c --- /dev/null +++ b/Pocket Poster/Views/OnBoarding/OnBoardingView.swift @@ -0,0 +1,18 @@ +// +// OnBoardingView.swift +// Pocket Poster +// +// Created by lemin on 5/31/25. +// + +import SwiftUI + +struct OnBoardingView: View { + @State var currentIndex: Int = 0 + + var body: some View { + VStack { + OnBoardingCardView(info: onBoardingCards[currentIndex], pageCount: onBoardingCards.count, idx: $currentIndex) + } + } +}