From a7bb2cf9064b28da549e5305d7181d29d1f459bf Mon Sep 17 00:00:00 2001 From: Laurent Date: Fri, 17 Mar 2023 17:02:25 +0100 Subject: [PATCH] Improvement on player UI --- LeCountdown.xcodeproj/project.pbxproj | 12 ++++ LeCountdown/AppDelegate.swift | 1 - LeCountdown/Sound/StatePlayer.swift | 82 ++++++++++++++++++++++++ LeCountdown/Subscription/StoreView.swift | 58 +++++++++++++++++ 4 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 LeCountdown/Sound/StatePlayer.swift diff --git a/LeCountdown.xcodeproj/project.pbxproj b/LeCountdown.xcodeproj/project.pbxproj index fa59767..0c92ce2 100644 --- a/LeCountdown.xcodeproj/project.pbxproj +++ b/LeCountdown.xcodeproj/project.pbxproj @@ -112,6 +112,11 @@ C4A16D8F29C4A5BA00143D5E /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = C4A16D8B29C4A5BA00143D5E /* GoogleService-Info.plist */; }; C4A16D9029C4A5BA00143D5E /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = C4A16D8B29C4A5BA00143D5E /* GoogleService-Info.plist */; }; C4A16D9329C4A6DD00143D5E /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = C4A16D9229C4A6DD00143D5E /* FirebaseCrashlytics */; }; + C4A16D9529C4B06400143D5E /* StatePlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A16D9429C4B06400143D5E /* StatePlayer.swift */; }; + C4A16D9629C4B06400143D5E /* StatePlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A16D9429C4B06400143D5E /* StatePlayer.swift */; }; + C4A16D9729C4B06400143D5E /* StatePlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A16D9429C4B06400143D5E /* StatePlayer.swift */; }; + C4A16D9829C4B06400143D5E /* StatePlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A16D9429C4B06400143D5E /* StatePlayer.swift */; }; + C4A16D9929C4B06400143D5E /* StatePlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A16D9429C4B06400143D5E /* StatePlayer.swift */; }; C4BA2AD62993F62700CB4FBA /* SoundSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2AD52993F62700CB4FBA /* SoundSelectionView.swift */; }; C4BA2ADB299549BC00CB4FBA /* TimerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4BA2ADA299549BC00CB4FBA /* TimerModel.swift */; }; C4BA2ADE2995ABA800CB4FBA /* MatriarchFxs_Loop2_Collider.wav in Resources */ = {isa = PBXBuildFile; fileRef = C4BA2ADD2995ABA800CB4FBA /* MatriarchFxs_Loop2_Collider.wav */; }; @@ -367,6 +372,7 @@ C498E5A2298D720600E90DE0 /* TestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestView.swift; sourceTree = ""; }; C498E5A4299152B400E90DE0 /* GreenCheckmarkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GreenCheckmarkView.swift; sourceTree = ""; }; C4A16D8B29C4A5BA00143D5E /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + C4A16D9429C4B06400143D5E /* StatePlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatePlayer.swift; sourceTree = ""; }; C4BA2AD52993F62700CB4FBA /* SoundSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoundSelectionView.swift; sourceTree = ""; }; C4BA2AD72993F7D200CB4FBA /* LeCountdown.0.5.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = LeCountdown.0.5.xcdatamodel; sourceTree = ""; }; C4BA2ADA299549BC00CB4FBA /* TimerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimerModel.swift; sourceTree = ""; }; @@ -716,6 +722,7 @@ C445FA912987CC8A0054D761 /* Sound.swift */, C445FA8E2987B83B0054D761 /* SoundPlayer.swift */, C4E5D67329B88734008E7465 /* DelaySoundPlayer.swift */, + C4A16D9429C4B06400143D5E /* StatePlayer.swift */, ); path = Sound; sourceTree = ""; @@ -1186,6 +1193,7 @@ C4742B5B298414B000D5D950 /* ImageSelectionView.swift in Sources */, C438C7C5298024E900BF3EF9 /* NSManagedContext+Extensions.swift in Sources */, C4F8B15F298961A7005C86A5 /* ReorderableForEach.swift in Sources */, + C4A16D9529C4B06400143D5E /* StatePlayer.swift in Sources */, C4BA2B6129A3C02400CB4FBA /* ActivityView.swift in Sources */, C4BA2B6A29A4BE1800CB4FBA /* Date+Extensions.swift in Sources */, C4BA2B10299BE61E00CB4FBA /* Interval+CoreDataProperties.swift in Sources */, @@ -1202,6 +1210,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + C4A16D9629C4B06400143D5E /* StatePlayer.swift in Sources */, C4060DD7297AE73D003FAB80 /* LeCountdownTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1211,6 +1220,7 @@ buildActionMask = 2147483647; files = ( C4060DE1297AE73D003FAB80 /* LeCountdownUITests.swift in Sources */, + C4A16D9729C4B06400143D5E /* StatePlayer.swift in Sources */, C4060DE3297AE73D003FAB80 /* LeCountdownUITestsLaunchTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1219,6 +1229,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + C4A16D9829C4B06400143D5E /* StatePlayer.swift in Sources */, C4BA2B15299BE6A000CB4FBA /* Interval+CoreDataProperties.swift in Sources */, C498E5A6299152C600E90DE0 /* GreenCheckmarkView.swift in Sources */, C4BA2B3B299F838000CB4FBA /* Model+SharedExtensions.swift in Sources */, @@ -1289,6 +1300,7 @@ C438C81A2982BFF100BF3EF9 /* NSManagedContext+Extensions.swift in Sources */, C4F8B19D298AC288005C86A5 /* AbstractTimer+CoreDataClass.swift in Sources */, C4BA2B3C299F838000CB4FBA /* Model+SharedExtensions.swift in Sources */, + C4A16D9929C4B06400143D5E /* StatePlayer.swift in Sources */, C4BA2AF82996A4F000CB4FBA /* CustomSound+CoreDataProperties.swift in Sources */, C4F8B199298AC288005C86A5 /* Record+CoreDataClass.swift in Sources */, C438C8012981327600BF3EF9 /* Persistence.swift in Sources */, diff --git a/LeCountdown/AppDelegate.swift b/LeCountdown/AppDelegate.swift index b066e62..f0d9551 100644 --- a/LeCountdown/AppDelegate.swift +++ b/LeCountdown/AppDelegate.swift @@ -59,7 +59,6 @@ class AppDelegate : NSObject, UIApplicationDelegate { func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { if userActivity.interaction == nil { - Logger.log("restorationHandler called! interaction is nil") return false } diff --git a/LeCountdown/Sound/StatePlayer.swift b/LeCountdown/Sound/StatePlayer.swift new file mode 100644 index 0000000..6172be3 --- /dev/null +++ b/LeCountdown/Sound/StatePlayer.swift @@ -0,0 +1,82 @@ +// +// StatePlayer.swift +// LeCountdown +// +// Created by Laurent Morvillier on 17/03/2023. +// + +import Foundation +import AVFoundation + +enum DemoError: Error { + case didNotFindDemoFile +} + +class StatePlayer: NSObject, ObservableObject, AVAudioPlayerDelegate { + + enum State { + case noResource + case playing + case paused + case none + + var systemImage: String { + switch self { + case .paused, .none: return "play.circle" + case .playing: return "pause.circle" + case .noResource: return "" + } + } + + } + + @Published var audioPlayer: AVAudioPlayer? = nil + + @Published var state: State = .noResource + + @Published var completion: Double? = nil + + var timer: Timer? = nil + + override init() { + super.init() + self.timer = Timer(timeInterval: 1.0, repeats: true, block: { timer in + self.calculateCompletion() + }) + } + + func load(resource: String, ext: String) { + do { + guard let demoURL = Bundle.main.url(forResource: resource, withExtension: ext) else { + throw DemoError.didNotFindDemoFile + } + self.audioPlayer = try AVAudioPlayer(contentsOf: demoURL) + self.audioPlayer?.delegate = self + self.state = .none + } catch { + Logger.error(error) + } + } + + func calculateCompletion() { + if let player = self.audioPlayer { + self.completion = player.currentTime / player.duration + } + self.completion = nil + } + + func play() { + self.audioPlayer?.play() + self.state = .playing + } + + func pause() { + self.audioPlayer?.pause() + self.state = .paused + } + + func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { + self.state = .none + } + +} diff --git a/LeCountdown/Subscription/StoreView.swift b/LeCountdown/Subscription/StoreView.swift index e27df01..d5b1d3f 100644 --- a/LeCountdown/Subscription/StoreView.swift +++ b/LeCountdown/Subscription/StoreView.swift @@ -113,6 +113,8 @@ struct PlanView: View { .padding(.vertical, 2.0) Spacer() + + PlayerWrapperView() Button { self.actionHandler() @@ -134,6 +136,62 @@ struct PlanView: View { } +struct PlayerWrapperView: View { + + @StateObject var player: StatePlayer = StatePlayer() + + var body: some View { + + let state = self.player.state + Group { + + switch state { + case .none, .paused: + Button { + self._pause() + } label: { + HStack { + Image(systemName: state.systemImage) + Spacer() + ProgressView(value: self.player.completion) + }.font(.title) + }.buttonStyle(.borderedProminent) + .fontWeight(.medium) + + case .playing: + Button { + self._pause() + } label: { + HStack { + Image(systemName: state.systemImage) + Spacer() + ProgressView(value: self.player.completion) + +// Slider(value: self.$player.completion) + }.font(.title) + }.buttonStyle(.borderedProminent) + .fontWeight(.medium) + case .noResource: + EmptyView() + } + + } + .onAppear { + self.player.load(resource: "QP01 0118 Riparian Zone thrush", ext: "wav") + } + + } + + fileprivate func _play() { + self.player.play() + } + + fileprivate func _pause() { + self.player.pause() + } + +} + struct StoreView_Previews: PreviewProvider { static var previews: some View { PlanView(productName: "Pro version",