From dc80455f3e3c0ef008d186621fd275da4818d1cb Mon Sep 17 00:00:00 2001 From: Kenneth Date: Sun, 28 Jul 2024 01:04:21 +0100 Subject: [PATCH] initial commit --- .gitignore | 54 ++++++++++++++++ InfinifiIOS.xcodeproj/project.pbxproj | 14 +++++ .../BaseColor.colorset/Contents.json | 38 +++++++++++ .../Surface1Color.colorset/Contents.json | 38 +++++++++++ .../TextColor.colorset/Contents.json | 38 +++++++++++ InfinifiIOS/ContentView.swift | 55 +++++++++++----- InfinifiIOS/InfinifiIOSApp.swift | 7 --- InfinifiIOS/NeuButton.swift | 63 +++++++++++++++++++ InfinifiIOS/PlaybackManager.swift | 39 ++++++++++++ 9 files changed, 325 insertions(+), 21 deletions(-) create mode 100644 .gitignore create mode 100644 InfinifiIOS/Assets.xcassets/BaseColor.colorset/Contents.json create mode 100644 InfinifiIOS/Assets.xcassets/Surface1Color.colorset/Contents.json create mode 100644 InfinifiIOS/Assets.xcassets/TextColor.colorset/Contents.json create mode 100644 InfinifiIOS/NeuButton.swift create mode 100644 InfinifiIOS/PlaybackManager.swift diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3e69a10 --- /dev/null +++ b/.gitignore @@ -0,0 +1,54 @@ +# Created by https://www.toptal.com/developers/gitignore/api/xcode,macos +# Edit at https://www.toptal.com/developers/gitignore?templates=xcode,macos + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Xcode ### +## User settings +xcuserdata/ + +## Xcode 8 and earlier +*.xcscmblueprint +*.xccheckout + +### Xcode Patch ### +*.xcodeproj/* +!*.xcodeproj/project.pbxproj +!*.xcodeproj/xcshareddata/ +!*.xcodeproj/project.xcworkspace/ +!*.xcworkspace/contents.xcworkspacedata +/*.gcno +**/xcshareddata/WorkspaceSettings.xcsettings + +# End of https://www.toptal.com/developers/gitignore/api/xcode,macos diff --git a/InfinifiIOS.xcodeproj/project.pbxproj b/InfinifiIOS.xcodeproj/project.pbxproj index c25eafe..f5c1930 100644 --- a/InfinifiIOS.xcodeproj/project.pbxproj +++ b/InfinifiIOS.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + F12B73452C55505E0064A230 /* NeuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F12B73442C55505E0064A230 /* NeuButton.swift */; }; + F12B73472C55AC6B0064A230 /* PlaybackManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F12B73462C55AC6B0064A230 /* PlaybackManager.swift */; }; + F12B73492C55C0C40064A230 /* .gitignore in Resources */ = {isa = PBXBuildFile; fileRef = F12B73482C55C0C40064A230 /* .gitignore */; }; F13839702C51BABD00B4814F /* InfinifiIOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F138396F2C51BABD00B4814F /* InfinifiIOSApp.swift */; }; F13839722C51BABD00B4814F /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F13839712C51BABD00B4814F /* ContentView.swift */; }; F13839742C51BABE00B4814F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F13839732C51BABE00B4814F /* Assets.xcassets */; }; @@ -14,6 +17,9 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + F12B73442C55505E0064A230 /* NeuButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NeuButton.swift; sourceTree = ""; }; + F12B73462C55AC6B0064A230 /* PlaybackManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackManager.swift; sourceTree = ""; }; + F12B73482C55C0C40064A230 /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = SOURCE_ROOT; }; F138396C2C51BABD00B4814F /* InfinifiIOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = InfinifiIOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; F138396F2C51BABD00B4814F /* InfinifiIOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfinifiIOSApp.swift; sourceTree = ""; }; F13839712C51BABD00B4814F /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; @@ -55,6 +61,9 @@ F13839712C51BABD00B4814F /* ContentView.swift */, F13839732C51BABE00B4814F /* Assets.xcassets */, F13839752C51BABE00B4814F /* Preview Content */, + F12B73442C55505E0064A230 /* NeuButton.swift */, + F12B73462C55AC6B0064A230 /* PlaybackManager.swift */, + F12B73482C55C0C40064A230 /* .gitignore */, ); path = InfinifiIOS; sourceTree = ""; @@ -126,6 +135,7 @@ buildActionMask = 2147483647; files = ( F13839772C51BABE00B4814F /* Preview Assets.xcassets in Resources */, + F12B73492C55C0C40064A230 /* .gitignore in Resources */, F13839742C51BABE00B4814F /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -137,7 +147,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + F12B73452C55505E0064A230 /* NeuButton.swift in Sources */, F13839722C51BABD00B4814F /* ContentView.swift in Sources */, + F12B73472C55AC6B0064A230 /* PlaybackManager.swift in Sources */, F13839702C51BABD00B4814F /* InfinifiIOSApp.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -275,6 +287,7 @@ DEVELOPMENT_TEAM = 5FG7YZ49ZA; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = infinifi; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -304,6 +317,7 @@ DEVELOPMENT_TEAM = 5FG7YZ49ZA; ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CFBundleDisplayName = infinifi; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; diff --git a/InfinifiIOS/Assets.xcassets/BaseColor.colorset/Contents.json b/InfinifiIOS/Assets.xcassets/BaseColor.colorset/Contents.json new file mode 100644 index 0000000..a1b5872 --- /dev/null +++ b/InfinifiIOS/Assets.xcassets/BaseColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF5", + "green" : "0xF1", + "red" : "0xEF" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x2E", + "green" : "0x1E", + "red" : "0x1E" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/InfinifiIOS/Assets.xcassets/Surface1Color.colorset/Contents.json b/InfinifiIOS/Assets.xcassets/Surface1Color.colorset/Contents.json new file mode 100644 index 0000000..be1f89a --- /dev/null +++ b/InfinifiIOS/Assets.xcassets/Surface1Color.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xCC", + "green" : "0xC0", + "red" : "0xBC" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x5A", + "green" : "0x47", + "red" : "0x45" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/InfinifiIOS/Assets.xcassets/TextColor.colorset/Contents.json b/InfinifiIOS/Assets.xcassets/TextColor.colorset/Contents.json new file mode 100644 index 0000000..717863d --- /dev/null +++ b/InfinifiIOS/Assets.xcassets/TextColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x5A", + "green" : "0x47", + "red" : "0x45" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF4", + "green" : "0xD6", + "red" : "0xCD" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/InfinifiIOS/ContentView.swift b/InfinifiIOS/ContentView.swift index 4da357d..df85abd 100644 --- a/InfinifiIOS/ContentView.swift +++ b/InfinifiIOS/ContentView.swift @@ -1,21 +1,48 @@ -// -// ContentView.swift -// InfinifiIOS -// -// Created by Kenneth on 24/07/2024. -// - +import AVKit import SwiftUI struct ContentView: View { - var body: some View { - VStack { - Image(systemName: "globe") - .imageScale(.large) - .foregroundStyle(.tint) - Text("Hello, world!") + @ObservedObject private var playbackManager = PlaybackManager() + + private func toggleAudioPlayback() { + switch playbackManager.playbackState { + case .playing: + playbackManager.stop() + case .paused: + playbackManager.nextTrack() } - .padding() + } + + var body: some View { + let buttonImageName = switch playbackManager.playbackState { + case .paused: "play" + case .playing: "pause" + } + + VStack(alignment: .center) { + Text("infinifi") + .font(.title3) + .bold() + .monospaced() + .padding() + + Spacer() + + NeuButton(action: { + toggleAudioPlayback() + }) { + Image(systemName: buttonImageName) + .font(.system(size: 24)) + .tint(.text) + } + + Spacer() + } + .frame( + maxWidth: .infinity, + maxHeight: .infinity + ) + .background(.base) } } diff --git a/InfinifiIOS/InfinifiIOSApp.swift b/InfinifiIOS/InfinifiIOSApp.swift index 10236a8..67742d0 100644 --- a/InfinifiIOS/InfinifiIOSApp.swift +++ b/InfinifiIOS/InfinifiIOSApp.swift @@ -1,10 +1,3 @@ -// -// InfinifiIOSApp.swift -// InfinifiIOS -// -// Created by Kenneth on 24/07/2024. -// - import SwiftUI @main diff --git a/InfinifiIOS/NeuButton.swift b/InfinifiIOS/NeuButton.swift new file mode 100644 index 0000000..3902e1b --- /dev/null +++ b/InfinifiIOS/NeuButton.swift @@ -0,0 +1,63 @@ +import Foundation +import SwiftUI + +struct NeuButtonStyle: PrimitiveButtonStyle { + private let onPressStateChange: (_ isPressed: Bool) -> Void + private let impact = UIImpactFeedbackGenerator(style: .medium) + + init(onPressStateChange: @escaping (_: Bool) -> Void) { + self.onPressStateChange = onPressStateChange + } + + func makeBody(configuration: Configuration) -> some View { + configuration.label + .onTapGesture { + configuration.trigger() + } + .onLongPressGesture(minimumDuration: 0) {} onPressingChanged: { isPressing in + onPressStateChange(isPressing) + impact.impactOccurred() + } + } +} + +struct NeuButton: View where Content: View { + let content: () -> Content + let action: () -> Void + + @State private var isPressed = false + + init(action: @escaping () -> Void, @ViewBuilder content: @escaping () -> Content) { + self.content = content + self.action = action + } + + var body: some View { + Button(action: action) { + Rectangle() + .fill(isPressed ? .base : .text) + .offset( + x: isPressed ? 0 : 4, + y: isPressed ? 0 : 4 + ) + .overlay { + ZStack { + Rectangle() + .fill(isPressed ? .text : .base) + + content() + .foregroundStyle(isPressed ? .base : .text) + } + } + } + .border(.text, width: 2) + .frame(width: 64, height: 64) + .offset( + x: isPressed ? 6 : 0, + y: isPressed ? 6 : 0 + ) + .buttonStyle(NeuButtonStyle { isPressed in + self.isPressed = isPressed + }) + } +} diff --git a/InfinifiIOS/PlaybackManager.swift b/InfinifiIOS/PlaybackManager.swift new file mode 100644 index 0000000..aa7a7d8 --- /dev/null +++ b/InfinifiIOS/PlaybackManager.swift @@ -0,0 +1,39 @@ +import AVKit +import Foundation + +enum PlaybackState { + case playing + case paused +} + +class PlaybackManager: ObservableObject { + @Published var playbackState: PlaybackState = .paused + @Published var hasError = false + + private var audioPlayer = AVPlayer() + + func nextTrack() { + let now = Date().timeIntervalSince1970 + // add timestamp to the url to prevent caching + let playerItem = AVPlayerItem(url: URL(string: "https://infinifi.cafe/current.mp3?t=\(now)")!) + NotificationCenter.default.addObserver(self, selector: #selector(playbackFinished), name: AVPlayerItem.didPlayToEndTimeNotification, object: playerItem) + + audioPlayer.replaceCurrentItem(with: playerItem) + audioPlayer.play() + + playbackState = .playing + } + + func stop() { + audioPlayer.pause() + playbackState = .paused + } + + @objc + private func playbackFinished() { + NotificationCenter.default.removeObserver(self, name: AVPlayerItem.didPlayToEndTimeNotification, object: audioPlayer.currentItem) + if playbackState == .playing { + nextTrack() + } + } +}