diff --git a/Sabbath School.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Sabbath School.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/Sabbath School.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Sabbath School.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Sabbath School.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..bb480cc1 --- /dev/null +++ b/Sabbath School.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,60 @@ +{ + "originHash" : "ac3e275717da5797aaa46aa4c566f1fd11beece107238572bcd9a8171edb13a0", + "pins" : [ + { + "identity" : "alamofire", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Alamofire/Alamofire.git", + "state" : { + "revision" : "3dc6a42c7727c49bf26508e29b0a0b35f9c7e1ad", + "version" : "5.8.1" + } + }, + { + "identity" : "cache", + "kind" : "remoteSourceControl", + "location" : "https://github.com/hyperoslo/Cache.git", + "state" : { + "revision" : "ad6abdf2a3a866288a7dad2c4e13379406002a81", + "version" : "6.2.0" + } + }, + { + "identity" : "libwebp-xcode", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SDWebImage/libwebp-Xcode.git", + "state" : { + "revision" : "b2b1d20a90b14d11f6ef4241da6b81c1d3f171e4", + "version" : "1.3.2" + } + }, + { + "identity" : "sdwebimage", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SDWebImage/SDWebImage.git", + "state" : { + "revision" : "b8523c1642f3c142b06dd98443ea7c48343a4dfd", + "version" : "5.19.3" + } + }, + { + "identity" : "sdwebimageswiftui", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SDWebImage/SDWebImageSwiftUI.git", + "state" : { + "revision" : "53573d6dd017e354c0e7d8f1c86b77ef1383c996", + "version" : "2.2.7" + } + }, + { + "identity" : "sdwebimagewebpcoder", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SDWebImage/SDWebImageWebPCoder.git", + "state" : { + "revision" : "f534cfe830a7807ecc3d0332127a502426cfa067", + "version" : "0.14.6" + } + } + ], + "version" : 3 +} diff --git a/Sabbath School.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Sabbath School.xcworkspace/xcshareddata/swiftpm/Package.resolved index cc48e550..bb480cc1 100644 --- a/Sabbath School.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Sabbath School.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,4 +1,5 @@ { + "originHash" : "ac3e275717da5797aaa46aa4c566f1fd11beece107238572bcd9a8171edb13a0", "pins" : [ { "identity" : "alamofire", @@ -14,8 +15,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/hyperoslo/Cache.git", "state" : { - "revision" : "c7f4d633049c3bd649a353bad36f6c17e9df085f", - "version" : "6.0.0" + "revision" : "ad6abdf2a3a866288a7dad2c4e13379406002a81", + "version" : "6.2.0" } }, { @@ -32,8 +33,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/SDWebImage/SDWebImage.git", "state" : { - "revision" : "936f1c7067728d16c362ba4fb93c17df78b5fd79", - "version" : "5.18.2" + "revision" : "b8523c1642f3c142b06dd98443ea7c48343a4dfd", + "version" : "5.19.3" } }, { @@ -41,8 +42,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/SDWebImage/SDWebImageSwiftUI.git", "state" : { - "revision" : "e837c37d45449fbd3b4745c10c5b5274e73edead", - "version" : "2.2.3" + "revision" : "53573d6dd017e354c0e7d8f1c86b77ef1383c996", + "version" : "2.2.7" } }, { @@ -50,10 +51,10 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/SDWebImage/SDWebImageWebPCoder.git", "state" : { - "revision" : "3819cb70faa2454b54d8364779bfacd8c216a6e2", - "version" : "0.13.0" + "revision" : "f534cfe830a7807ecc3d0332127a502426cfa067", + "version" : "0.14.6" } } ], - "version" : 2 + "version" : 3 } diff --git a/Sabbath School/Application/AppDelegate.swift b/Sabbath School/Application/AppDelegate.swift index 0cb50288..83898a79 100644 --- a/Sabbath School/Application/AppDelegate.swift +++ b/Sabbath School/Application/AppDelegate.swift @@ -157,8 +157,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { return true } - func launchQuarterlies() { - let quarterlyController = QuarterlyWireFrame.createQuarterlyModule() + func launchQuarterlies(initiateOpen: Bool = false) { + let quarterlyController = QuarterlyWireFrame.createQuarterlyModule(initiateOpen: initiateOpen) Configuration.window?.rootViewController = quarterlyController Configuration.window?.makeKeyAndVisible() } diff --git a/Sabbath School/Lesson/Interactor/LessonInteractor.swift b/Sabbath School/Lesson/Interactor/LessonInteractor.swift index 5eda4537..9396d77b 100644 --- a/Sabbath School/Lesson/Interactor/LessonInteractor.swift +++ b/Sabbath School/Lesson/Interactor/LessonInteractor.swift @@ -57,7 +57,7 @@ class LessonInteractor: LessonInteractorInputProtocol { self.publishingInfoStorage = APICache.storage?.transformCodable(ofType: PublishingInfoData.self) } - func retrieveQuarterlyInfo(quarterlyIndex: String) { + func retrieveQuarterlyInfo(quarterlyIndex: String, completion: @escaping (QuarterlyInfo?) -> Void) { let parsedIndex = Helper.parseIndex(index: quarterlyIndex) let url = "\(Constants.API.URL)/\(parsedIndex.lang)/quarterlies/\(parsedIndex.quarter)/index.json" @@ -66,12 +66,14 @@ class LessonInteractor: LessonInteractorInputProtocol { if (try? self.storage?.existsObject(forKey: url)) != nil { if let quarterlyInfo = try? self.storage?.entry(forKey: url) { cachedObject = quarterlyInfo.object + completion(quarterlyInfo.object) self.presenter?.didRetrieveQuarterlyInfo(quarterlyInfo: quarterlyInfo.object) } } API.session.request(url).responseDecodable(of: QuarterlyInfo.self, decoder: Helper.SSJSONDecoder()) { response in guard let quarterlyInfo = response.value else { + completion(nil) self.presenter?.onError(response.error) return } @@ -80,6 +82,7 @@ class LessonInteractor: LessonInteractorInputProtocol { return } + completion(quarterlyInfo) self.presenter?.didRetrieveQuarterlyInfo(quarterlyInfo: quarterlyInfo) try? self.storage?.setObject(quarterlyInfo, forKey: url) } diff --git a/Sabbath School/Lesson/Presenter/LessonPresenter.swift b/Sabbath School/Lesson/Presenter/LessonPresenter.swift index ad29ed98..4b9bc29e 100644 --- a/Sabbath School/Lesson/Presenter/LessonPresenter.swift +++ b/Sabbath School/Lesson/Presenter/LessonPresenter.swift @@ -30,7 +30,7 @@ class LessonPresenter: LessonPresenterProtocol { func configure() { interactor?.configure() - interactor?.retrieveQuarterlyInfo(quarterlyIndex: quarterlyIndex!) + interactor?.retrieveQuarterlyInfo(quarterlyIndex: quarterlyIndex!, completion: { _ in }) interactor?.retrievePublishingInfo() } diff --git a/Sabbath School/Lesson/Protocols/LessonProtocols.swift b/Sabbath School/Lesson/Protocols/LessonProtocols.swift index 51415b42..69e2389c 100644 --- a/Sabbath School/Lesson/Protocols/LessonProtocols.swift +++ b/Sabbath School/Lesson/Protocols/LessonProtocols.swift @@ -66,7 +66,7 @@ protocol LessonInteractorInputProtocol: AnyObject { var presenter: LessonInteractorOutputProtocol? { get set } func configure() - func retrieveQuarterlyInfo(quarterlyIndex: String) + func retrieveQuarterlyInfo(quarterlyIndex: String, completion: @escaping (QuarterlyInfo?) -> Void) func retrievePublishingInfo() func retrieveRead(readIndex: String, quarterlyIndex: String?) } diff --git a/Sabbath School/Quarterly/Controller/QuarterlyController.swift b/Sabbath School/Quarterly/Controller/QuarterlyController.swift index b0dad094..eb716e43 100644 --- a/Sabbath School/Quarterly/Controller/QuarterlyController.swift +++ b/Sabbath School/Quarterly/Controller/QuarterlyController.swift @@ -40,7 +40,8 @@ class QuarterlyController: QuarterlyControllerCommon { let lastQuarterlyIndex = Preferences.currentQuarterly() let languageCode = lastQuarterlyIndex.components(separatedBy: "-") if let code = languageCode.first, Preferences.currentLanguage().code == code { - presenter?.presentLessonScreen(quarterlyIndex: lastQuarterlyIndex, initiateOpenToday: false) + presenter?.presentLessonScreen(quarterlyIndex: lastQuarterlyIndex, initiateOpenToday: initiateOpen ?? false + ) } } setupNavigationBar() diff --git a/Sabbath School/Quarterly/WireFrame/QuarterlyWireFrame.swift b/Sabbath School/Quarterly/WireFrame/QuarterlyWireFrame.swift index fcf12b91..6269f3bd 100644 --- a/Sabbath School/Quarterly/WireFrame/QuarterlyWireFrame.swift +++ b/Sabbath School/Quarterly/WireFrame/QuarterlyWireFrame.swift @@ -57,6 +57,7 @@ class SplitVc : UISplitViewController, UISplitViewControllerDelegate { class QuarterlyWireFrame: QuarterlyWireFrameProtocol { class func createQuarterlyModule(initiateOpen: Bool = false) -> ASDKNavigationController { let controller: QuarterlyControllerProtocol = QuarterlyController() + controller.initiateOpen = initiateOpen let presenter: QuarterlyPresenterProtocol & QuarterlyInteractorOutputProtocol = QuarterlyPresenter() let wireFrame: QuarterlyWireFrameProtocol = QuarterlyWireFrame() let interactor: QuarterlyInteractorInputProtocol = QuarterlyInteractor() diff --git a/Sabbath School/Siri/Intents/ListenTodaySabbathSchoolIntent.swift b/Sabbath School/Siri/Intents/ListenTodaySabbathSchoolIntent.swift new file mode 100644 index 00000000..5af4dce5 --- /dev/null +++ b/Sabbath School/Siri/Intents/ListenTodaySabbathSchoolIntent.swift @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2021 Adventech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import AppIntents +import UIKit +import SwiftAudio + +@available(iOS 16.0, *) +struct ListenTodaySabbathSchoolIntent: AudioStartingIntent { + + static var title: LocalizedStringResource = "Starting Sabbath School" + static var openAppWhenRun: Bool = false + + @MainActor + func perform() async throws -> some IntentResult { + let lastQuarterlyIndex = Preferences.currentQuarterly() + retrieveAudio(quarterlyIndex: lastQuarterlyIndex) + + return .result(value: true) + } + + func retrieveAudio(quarterlyIndex: String) { + let audioInteractor = AudioInteractor() + let lessonInteractor = LessonInteractor() + + lessonInteractor.retrieveQuarterlyInfo(quarterlyIndex: quarterlyIndex) { quarterlyInfo in + audioInteractor.retrieveAudio(quarterlyIndex: quarterlyIndex) { audio in + let finalAudio = audio.filter { $0.targetIndex.starts(with: getTodaysLessonIndex(dataSource: quarterlyInfo)) } + + let audioItems: [AudioItem] = finalAudio.map { $0.audioItem() } + + AudioPlayback.configure() + try? AudioPlayback.shared.add(items: audioItems, playWhenReady: false) + try? AudioPlayback.shared.jumpToItem(atIndex: 1, playWhenReady: true) + + AudioPlayback.play() + } + } + } + + + func getTodaysLessonIndex(dataSource: QuarterlyInfo?) -> String { + guard let lessons = dataSource?.lessons else { return "" } + let today = Date() + let weekday = Calendar.current.component(.weekday, from: today) + let hour = Calendar.current.component(.hour, from: today) + var prevLessonIndex: String? = nil + + for lesson in lessons { + let start = Calendar.current.compare(lesson.startDate, to: today, toGranularity: .day) + let end = Calendar.current.compare(lesson.endDate, to: today, toGranularity: .day) + let fallsBetween = ((start == .orderedAscending) || (start == .orderedSame)) && ((end == .orderedDescending) || (end == .orderedSame)) + + if fallsBetween { + if (weekday == 7 && hour < 12 && prevLessonIndex != nil) { + return prevLessonIndex! + } else { + return lesson.index + } + } + prevLessonIndex = lesson.index + } + + if let firstLesson = lessons.first { + return firstLesson.index + } + + return "" + } +} diff --git a/Sabbath School/Siri/Intents/ReadTodaySabbathSchoolIntent.swift b/Sabbath School/Siri/Intents/ReadTodaySabbathSchoolIntent.swift new file mode 100644 index 00000000..d2d1ea95 --- /dev/null +++ b/Sabbath School/Siri/Intents/ReadTodaySabbathSchoolIntent.swift @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 Adventech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import AppIntents +import UIKit + +@available(iOS 16.0, *) +struct ReadTodaySabbathSchoolIntent: AppIntent { + + static var title: LocalizedStringResource = "Starting Sabbath School" + static var openAppWhenRun: Bool = true + + @MainActor + func perform() async throws -> some IntentResult { + let appDelegate = UIApplication.shared.delegate as? AppDelegate + appDelegate?.launchQuarterlies(initiateOpen: true) + + return .result(value: true) + } +} diff --git a/Sabbath School/Siri/SabbathSchoolShortcuts.swift b/Sabbath School/Siri/SabbathSchoolShortcuts.swift new file mode 100644 index 00000000..b43e4ffd --- /dev/null +++ b/Sabbath School/Siri/SabbathSchoolShortcuts.swift @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2021 Adventech + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +import Intents +import AppIntents + +@available(iOS 16.0, *) +struct SabbathSchoolShortcuts: AppShortcutsProvider { + static var appShortcuts: [AppShortcut] { + AppShortcut( + intent: ListenTodaySabbathSchoolIntent(), + phrases: [ + "Listen \(.applicationName)", + "Play \(.applicationName)" + ] + ) + } +} + +//@available(iOS 16.0, *) +//struct SabbathSchoolShortcuts1: AppShortcutsProvider { +// static var appShortcuts: [AppShortcut] { +// AppShortcut( +// intent: ReadTodaySabbathSchoolIntent(), +// phrases: [ +// "Read \(.applicationName)" +// ] +// ) +// } +//}