Skip to content

Commit

Permalink
Merge pull request #1063 from TortugaPower/develop
Browse files Browse the repository at this point in the history
Release 5.1.1
  • Loading branch information
GianniCarlo authored Jan 3, 2024
2 parents 48f7ad9 + bd2a275 commit 2293c0a
Show file tree
Hide file tree
Showing 67 changed files with 1,271 additions and 314 deletions.
5 changes: 2 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@ jobs:
- name: Swiftlint
run: swiftlint
- name: Set Xcode version
run: sudo xcode-select -s "/Applications/Xcode_15.0.1.app/Contents/Developer"
run: sudo xcode-select -s "/Applications/Xcode_15.1.app/Contents/Developer"
- name: Resolve dependencies
run: xcodebuild -resolvePackageDependencies
- name: Build and Run tests
run: xcodebuild -scheme BookPlayer test -testPlan Unit\ Tests -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.0.1'

run: xcodebuild -scheme BookPlayer test -testPlan Unit\ Tests -destination 'platform=iOS Simulator,name=iPhone 15,OS=17.2'
66 changes: 46 additions & 20 deletions BookPlayer.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/devicekit/DeviceKit.git",
"state" : {
"revision" : "d37e70cb2646666dcf276d7d3d4a9760a41ff8a6",
"version" : "4.9.0"
"revision" : "66837ecf1516e41fd4251bbb684dc4b1997f08ab",
"version" : "5.1.0"
}
},
{
Expand Down
30 changes: 28 additions & 2 deletions BookPlayer/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
return lastSceneToResignActive
}
}
/// Reference for observer
/// Reference for observers
private var crashReportsAccessObserver: NSKeyValueObservation?
private var sharedWidgetActionURLObserver: NSKeyValueObservation?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
Self.shared = self
Expand All @@ -69,6 +70,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
self.setupRevenueCat()
// Setup Sentry
self.setupSentry()
// Setup observer for interactive widgets
self.setupSharedWidgetActionObserver()

return true
}
Expand Down Expand Up @@ -178,7 +181,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
_ relativePath: String,
autoplay: Bool,
showPlayer: (() -> Void)?,
alertPresenter: AlertPresenter
alertPresenter: AlertPresenter,
recordAsLastBook: Bool = true
) {
Task { @MainActor in
let fileURL = DataManager.getProcessedFolderURL().appendingPathComponent(relativePath)
Expand Down Expand Up @@ -222,6 +226,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate {

playerManager?.load(item, autoplay: autoplay)

if recordAsLastBook {
await libraryService?.setLibraryLastBook(with: item.relativePath)
}

showPlayer?()
}
}
Expand Down Expand Up @@ -380,6 +388,24 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
)
}

func setupSharedWidgetActionObserver() {
let sharedDefaults = UserDefaults.sharedDefaults

if let actionURL = sharedDefaults.sharedWidgetActionURL {
ActionParserService.process(actionURL)
sharedDefaults.removeObject(forKey: Constants.UserDefaults.sharedWidgetActionURL)
}

sharedWidgetActionURLObserver = sharedDefaults.observe(\.sharedWidgetActionURL) { defaults, _ in
DispatchQueue.main.async {
guard let actionURL = defaults.sharedWidgetActionURL else { return }

ActionParserService.process(actionURL)
sharedDefaults.removeObject(forKey: Constants.UserDefaults.sharedWidgetActionURL)
}
}
}

/// Setup or stop Sentry based on flag
/// - Parameter isDisabled: Determines user preference for crash reports
private func handleSentryPreference(isDisabled: Bool) {
Expand Down
2 changes: 1 addition & 1 deletion BookPlayer/AppIntents/CancelSleepTimerIntent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import AppIntents

@available(iOS 16.4, macOS 14.0, watchOS 10.0, *)
struct CancelSleepTimerIntent: AppIntent {
static var title: LocalizedStringResource = .init("intent_sleeptimer_cancel", table: "Localizable.strings")
static var title: LocalizedStringResource = "intent_sleeptimer_cancel"

func perform() async throws -> some IntentResult {
SleepTimer.shared.setTimer(.off)
Expand Down
2 changes: 1 addition & 1 deletion BookPlayer/AppIntents/CustomSleepTimerIntent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import AppIntents

@available(iOS 16.0, macOS 14.0, watchOS 10.0, tvOS 16.0, *)
struct CustomSleepTimerIntent: AppIntent {
static var title: LocalizedStringResource = .init("intent_sleeptimer_set_duration", table: "Localizable.strings")
static var title: LocalizedStringResource = "intent_sleeptimer_set_duration"

@Parameter(
title: LocalizedStringResource("duration_title"),
Expand Down
2 changes: 1 addition & 1 deletion BookPlayer/AppIntents/EndChapterSleepTimerIntent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import AppIntents

@available(iOS 16.4, macOS 14.0, watchOS 10.0, *)
struct EndChapterSleepTimerIntent: AppIntent {
static var title: LocalizedStringResource = .init("intent_sleeptimer_eoc_title", table: "Localizable.strings")
static var title: LocalizedStringResource = "intent_sleeptimer_eoc_title"

func perform() async throws -> some IntentResult {
SleepTimer.shared.setTimer(.endOfChapter)
Expand Down
2 changes: 1 addition & 1 deletion BookPlayer/AppIntents/LastBookStartPlaybackIntent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import BookPlayerKit

@available(iOS 16.4, macOS 14.0, watchOS 10.0, *)
struct LastBookStartPlaybackIntent: AudioStartingIntent, ForegroundContinuableIntent {
static var title: LocalizedStringResource = .init("intent_lastbook_play_title", table: "Localizable.strings")
static var title: LocalizedStringResource = "intent_lastbook_play_title"

func perform() async throws -> some IntentResult {
let stack = try await DatabaseInitializer().loadCoreDataStack()
Expand Down
2 changes: 1 addition & 1 deletion BookPlayer/AppIntents/PausePlaybackIntent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import BookPlayerKit

@available(iOS 16.4, macOS 14.0, watchOS 10.0, *)
struct PausePlaybackIntent: AudioStartingIntent, ForegroundContinuableIntent {
static var title: LocalizedStringResource = .init("intent_playback_pause_title", table: "Localizable.strings")
static var title: LocalizedStringResource = "intent_playback_pause_title"

func perform() async throws -> some IntentResult {
let stack = try await DatabaseInitializer().loadCoreDataStack()
Expand Down
2 changes: 2 additions & 0 deletions BookPlayer/Base.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -300,3 +300,5 @@ We're working hard on providing a seamless experience, if possible, please conta
"intent_lastbook_play_title" = "Resume last played book";
"intent_lastbook_empty_error" = "There's no last played book";
"intent_playback_pause_title" = "Pause playback";
"storage_artwork_cache_title" = "Artwork cache size";
"settings_share_debug_information" = "Share debug information";
2 changes: 1 addition & 1 deletion BookPlayer/Base.lproj/Main.storyboard
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@
<constraints>
<constraint firstItem="GAA-F6-6lf" firstAttribute="leading" secondItem="oVP-SJ-Vkq" secondAttribute="leading" constant="12" id="G7I-0H-RUz"/>
<constraint firstItem="GAA-F6-6lf" firstAttribute="top" secondItem="q8y-d7-HKX" secondAttribute="bottom" constant="16" id="Kdg-10-Xf3"/>
<constraint firstItem="q8y-d7-HKX" firstAttribute="centerY" secondItem="ine-tP-Is6" secondAttribute="centerY" constant="-50" id="LV4-lL-Boh"/>
<constraint firstItem="q8y-d7-HKX" firstAttribute="centerY" secondItem="ine-tP-Is6" secondAttribute="centerY" constant="-80" id="LV4-lL-Boh"/>
<constraint firstAttribute="trailing" secondItem="GAA-F6-6lf" secondAttribute="trailing" constant="16" id="Tqt-AP-cw8"/>
<constraint firstItem="ine-tP-Is6" firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="GAA-F6-6lf" secondAttribute="bottom" id="Yl9-3o-iAe"/>
<constraint firstItem="q8y-d7-HKX" firstAttribute="top" relation="greaterThanOrEqual" secondItem="ine-tP-Is6" secondAttribute="top" id="jfg-AW-LQJ"/>
Expand Down
2 changes: 2 additions & 0 deletions BookPlayer/Coordinators/FolderListCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ class FolderListCoordinator: ItemListCoordinator {
}

override func syncList() {
guard syncService.canSyncListContents(at: folderRelativePath) else { return }

Task { @MainActor in
do {
_ = try await syncService.syncListContents(at: folderRelativePath)
Expand Down
17 changes: 15 additions & 2 deletions BookPlayer/Coordinators/LibraryListCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class LibraryListCoordinator: ItemListCoordinator, UINavigationControllerDelegat
var importOperationSubscription: AnyCancellable?
/// Reference to know if the import screen is already being shown (or in the process of showing)
weak var importCoordinator: ImportCoordinator?
/// Reference to ongoing library fetch task
var contentsFetchTask: Task<(), Error>?

private var disposeBag = Set<AnyCancellable>()

Expand Down Expand Up @@ -165,7 +167,8 @@ class LibraryListCoordinator: ItemListCoordinator, UINavigationControllerDelegat
self?.showPlayer()
}
},
alertPresenter: self
alertPresenter: self,
recordAsLastBook: false
)
}

Expand Down Expand Up @@ -212,7 +215,11 @@ class LibraryListCoordinator: ItemListCoordinator, UINavigationControllerDelegat
}

override func syncList() {
Task { @MainActor in
guard syncService.canSyncListContents(at: nil) else { return }

/// Create new task to sync the library and the last played
contentsFetchTask?.cancel()
contentsFetchTask = Task { @MainActor in
do {
if UserDefaults.standard.bool(forKey: Constants.UserDefaults.hasScheduledLibraryContents) == true {
try await syncService.syncListContents(at: nil)
Expand Down Expand Up @@ -267,3 +274,9 @@ class LibraryListCoordinator: ItemListCoordinator, UINavigationControllerDelegat
)
}
}

extension LibraryListCoordinator: PlaybackSyncProgressDelegate {
func waitForSyncInProgress() async {
_ = await contentsFetchTask?.result
}
}
4 changes: 3 additions & 1 deletion BookPlayer/Coordinators/MainCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ class MainCoordinator: NSObject {
playbackService: self.playbackService,
syncService: syncService
)
playerManager.syncProgressDelegate = libraryCoordinator
self.libraryCoordinator = libraryCoordinator
libraryCoordinator.tabBarController = tabBarController
libraryCoordinator.start()
Expand All @@ -109,7 +110,8 @@ class MainCoordinator: NSObject {
func startSettingsCoordinator(with tabBarController: UITabBarController) {
let settingsCoordinator = SettingsCoordinator(
flow: .pushFlow(navigationController: AppNavigationController.instantiate(from: .Settings)),
libraryService: self.libraryService,
libraryService: self.libraryService,
syncService: self.syncService,
accountService: self.accountService
)
settingsCoordinator.tabBarController = tabBarController
Expand Down
29 changes: 26 additions & 3 deletions BookPlayer/Coordinators/SettingsCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,27 @@ class SettingsCoordinator: Coordinator, AlertPresenter {

let flow: BPCoordinatorPresentationFlow
let libraryService: LibraryServiceProtocol
let syncService: SyncServiceProtocol
let accountService: AccountServiceProtocol

init(
flow: BPCoordinatorPresentationFlow,
libraryService: LibraryServiceProtocol,
syncService: SyncServiceProtocol,
accountService: AccountServiceProtocol
) {
self.flow = flow
self.libraryService = libraryService
self.syncService = syncService
self.accountService = accountService
}

func start() {
let viewModel = SettingsViewModel(accountService: accountService)
let viewModel = SettingsViewModel(
accountService: accountService,
libraryService: libraryService,
syncService: syncService
)

viewModel.onTransition = { route in
switch route {
Expand All @@ -48,6 +55,8 @@ class SettingsCoordinator: Coordinator, AlertPresenter {
self.showTipJar()
case .credits:
self.showCredits()
case .shareDebugInformation(let info):
self.shareDebugInformation(info: info)
}
}

Expand Down Expand Up @@ -102,13 +111,12 @@ class SettingsCoordinator: Coordinator, AlertPresenter {
}
}

let vc = UIHostingController(rootView: StorageView(viewModel: viewModel))
let vc = UIHostingController(rootView: StorageCloudDeletedView(viewModel: viewModel))
let nav = AppNavigationController(rootViewController: vc)
flow.navigationController.present(nav, animated: true)
}

func showPro() {
// let presentingVC = flow.navigationController.getTopViewController()
let child: Coordinator

if self.accountService.getAccountId() != nil {
Expand Down Expand Up @@ -208,4 +216,19 @@ class SettingsCoordinator: Coordinator, AlertPresenter {

flow.navigationController.present(nav, animated: true)
}

func shareDebugInformation(info: String) {
let provider = DebugInformationActivityItemProvider(info: info)

let shareController = UIActivityViewController(activityItems: [provider], applicationActivities: nil)

if let popoverPresentationController = shareController.popoverPresentationController,
let view = flow.navigationController.topViewController?.view {
popoverPresentationController.permittedArrowDirections = []
popoverPresentationController.sourceView = view
popoverPresentationController.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0)
}

flow.navigationController.present(shareController, animated: true, completion: nil)
}
}
57 changes: 57 additions & 0 deletions BookPlayer/Generated/AutoMockable.generated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,22 @@ class LibraryServiceProtocolMock: LibraryServiceProtocol {
return fetchContentsAtLimitOffsetReturnValue
}
}
//MARK: - fetchIdentifiers

var fetchIdentifiersCallsCount = 0
var fetchIdentifiersCalled: Bool {
return fetchIdentifiersCallsCount > 0
}
var fetchIdentifiersReturnValue: [String]!
var fetchIdentifiersClosure: (() -> [String])?
func fetchIdentifiers() -> [String] {
fetchIdentifiersCallsCount += 1
if let fetchIdentifiersClosure = fetchIdentifiersClosure {
return fetchIdentifiersClosure()
} else {
return fetchIdentifiersReturnValue
}
}
//MARK: - getMaxItemsCount

var getMaxItemsCountAtCallsCount = 0
Expand Down Expand Up @@ -1069,6 +1085,7 @@ class PlayerManagerProtocolMock: PlayerManagerProtocol {
set(value) { underlyingIsPlaying = value }
}
var underlyingIsPlaying: Bool!
var syncProgressDelegate: PlaybackSyncProgressDelegate?
//MARK: - load

var loadAutoplayCallsCount = 0
Expand Down Expand Up @@ -1402,6 +1419,26 @@ class SyncServiceProtocolMock: SyncServiceProtocol {
set(value) { underlyingDownloadProgressPublisher = value }
}
var underlyingDownloadProgressPublisher: PassthroughSubject<(String, String, String?, Double), Never>!
//MARK: - canSyncListContents

var canSyncListContentsAtCallsCount = 0
var canSyncListContentsAtCalled: Bool {
return canSyncListContentsAtCallsCount > 0
}
var canSyncListContentsAtReceivedRelativePath: String?
var canSyncListContentsAtReceivedInvocations: [String?] = []
var canSyncListContentsAtReturnValue: Bool!
var canSyncListContentsAtClosure: ((String?) -> Bool)?
func canSyncListContents(at relativePath: String?) -> Bool {
canSyncListContentsAtCallsCount += 1
canSyncListContentsAtReceivedRelativePath = relativePath
canSyncListContentsAtReceivedInvocations.append(relativePath)
if let canSyncListContentsAtClosure = canSyncListContentsAtClosure {
return canSyncListContentsAtClosure(relativePath)
} else {
return canSyncListContentsAtReturnValue
}
}
//MARK: - syncListContents

var syncListContentsAtThrowableError: Error?
Expand Down Expand Up @@ -1460,6 +1497,26 @@ class SyncServiceProtocolMock: SyncServiceProtocol {
return syncBookmarksListRelativePathReturnValue
}
}
//MARK: - fetchSyncedIdentifiers

var fetchSyncedIdentifiersThrowableError: Error?
var fetchSyncedIdentifiersCallsCount = 0
var fetchSyncedIdentifiersCalled: Bool {
return fetchSyncedIdentifiersCallsCount > 0
}
var fetchSyncedIdentifiersReturnValue: [String]!
var fetchSyncedIdentifiersClosure: (() async throws -> [String])?
func fetchSyncedIdentifiers() async throws -> [String] {
if let error = fetchSyncedIdentifiersThrowableError {
throw error
}
fetchSyncedIdentifiersCallsCount += 1
if let fetchSyncedIdentifiersClosure = fetchSyncedIdentifiersClosure {
return try await fetchSyncedIdentifiersClosure()
} else {
return fetchSyncedIdentifiersReturnValue
}
}
//MARK: - getRemoteFileURLs

var getRemoteFileURLsOfTypeThrowableError: Error?
Expand Down
15 changes: 1 addition & 14 deletions BookPlayer/Library/ItemList Screen/ItemListViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -451,20 +451,7 @@ extension ItemListViewController: UITableViewDataSource {
remoteURL: item.remoteURL
),
placeholder: defaultArtwork,
options: [.targetCache(ArtworkService.cache)]) { [weak self] result in
/// Cache default artwork if the provider errors out with .missingImage to avoid multiple
/// retries on files we know don't have an artwork for
guard
case .failure(let error) = result,
case .imageSettingError(let reason) = error,
case .dataProviderError(let provider, let error) = reason,
let providerError = error as? AVAudioAssetImageDataProvider.ProviderError,
providerError == .missingImage,
let artworkData = self?.viewModel.defaultArtwork
else { return }

ArtworkService.storeInCache(artworkData, for: provider.cacheKey)
}
options: [.targetCache(ArtworkService.cache)])
}
let label = VoiceOverService.getAccessibilityLabel(for: item)
cell.setAccessibilityLabel(label)
Expand Down
Loading

0 comments on commit 2293c0a

Please sign in to comment.