diff --git a/BookPlayer.xcodeproj/project.pbxproj b/BookPlayer.xcodeproj/project.pbxproj index 52d28653..d4a00c72 100644 --- a/BookPlayer.xcodeproj/project.pbxproj +++ b/BookPlayer.xcodeproj/project.pbxproj @@ -4154,7 +4154,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 5.4.1; + MARKETING_VERSION = 5.4.2; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).BookPlayerIntents"; @@ -4188,7 +4188,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 5.4.1; + MARKETING_VERSION = 5.4.2; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).BookPlayerIntents"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -4220,7 +4220,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 5.4.1; + MARKETING_VERSION = 5.4.2; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).BookPlayerIntents"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -4256,7 +4256,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 5.4.1; + MARKETING_VERSION = 5.4.2; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).watchkitapp"; @@ -4297,7 +4297,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 5.4.1; + MARKETING_VERSION = 5.4.2; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).watchkitapp"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -4335,7 +4335,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 5.4.1; + MARKETING_VERSION = 5.4.2; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).watchkitapp"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -4504,7 +4504,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 5.4.1; + MARKETING_VERSION = 5.4.2; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).BookPlayerWidgetUI"; @@ -4542,7 +4542,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 5.4.1; + MARKETING_VERSION = 5.4.2; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).BookPlayerWidgetUI"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -4578,7 +4578,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 5.4.1; + MARKETING_VERSION = 5.4.2; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).BookPlayerWidgetUI"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -4731,7 +4731,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 5.4.1; + MARKETING_VERSION = 5.4.2; PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER)"; PRODUCT_NAME = BookPlayer; PROVISIONING_PROFILE_SPECIFIER = "$(BP_PROVISIONING_MAIN)"; @@ -4769,7 +4769,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 5.4.1; + MARKETING_VERSION = 5.4.2; PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER)"; PRODUCT_NAME = BookPlayer; PROVISIONING_PROFILE_SPECIFIER = "$(BP_PROVISIONING_MAIN)"; @@ -4991,7 +4991,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 5.4.1; + MARKETING_VERSION = 5.4.2; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).watchkitapp.widgets"; @@ -5029,7 +5029,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 5.4.1; + MARKETING_VERSION = 5.4.2; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).watchkitapp.widgets"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -5065,7 +5065,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 5.4.1; + MARKETING_VERSION = 5.4.2; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).watchkitapp.widgets"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -5104,7 +5104,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 5.4.1; + MARKETING_VERSION = 5.4.2; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).BookPlayerShareExtension"; @@ -5144,7 +5144,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 5.4.1; + MARKETING_VERSION = 5.4.2; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).BookPlayerShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -5182,7 +5182,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 5.4.1; + MARKETING_VERSION = 5.4.2; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).BookPlayerShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -5274,7 +5274,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 5.4.1; + MARKETING_VERSION = 5.4.2; PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER)"; PRODUCT_NAME = BookPlayer; PROVISIONING_PROFILE_SPECIFIER = "$(BP_PROVISIONING_MAIN)"; diff --git a/BookPlayer/Base.lproj/Localizable.strings b/BookPlayer/Base.lproj/Localizable.strings index 618b56b7..ac5dc5fc 100644 --- a/BookPlayer/Base.lproj/Localizable.strings +++ b/BookPlayer/Base.lproj/Localizable.strings @@ -319,3 +319,5 @@ We're working hard on providing a seamless experience, if possible, please conta "Rewind ${interval}" = "Rewind ${interval}"; "settings_lock_orientation_title" = "Orientation Locked"; "more_title" = "More"; +"repeat_turn_on_title" = "Turn on Repeat for this book"; +"repeat_turn_off_title" = "Turn off Repeat for this book"; diff --git a/BookPlayer/Coordinators/Coordinator.swift b/BookPlayer/Coordinators/Coordinator.swift index c690c280..4c754047 100644 --- a/BookPlayer/Coordinators/Coordinator.swift +++ b/BookPlayer/Coordinators/Coordinator.swift @@ -20,6 +20,10 @@ extension AlertPresenter where Self: Coordinator { flow.navigationController.showAlert(title, message: message, completion: completion) } + func showAlert(_ content: BPAlertContent) { + flow.navigationController.showAlert(content) + } + func showLoader() { LoadingUtils.loadAndBlock(in: flow.navigationController) } diff --git a/BookPlayer/Coordinators/DataInitializerCoordinator.swift b/BookPlayer/Coordinators/DataInitializerCoordinator.swift index 8f1e9628..51c46c79 100644 --- a/BookPlayer/Coordinators/DataInitializerCoordinator.swift +++ b/BookPlayer/Coordinators/DataInitializerCoordinator.swift @@ -74,17 +74,31 @@ class DataInitializerCoordinator: BPLogger { \(error.localizedDescription) Error Domain - \(error.domain) + \(error.domain) (\(error.code) Additional Info \(error.userInfo) """ - alertPresenter.showAlert( - "error_title".localized, - message: errorDescription - ) { - fatalError("Unresolved error \(error.localizedDescription)") - } + alertPresenter.showAlert(BPAlertContent( + title: "error_title".localized, + message: errorDescription, + style: .alert, + actionItems: [ + BPActionItem( + title: "ok_button".localized, + handler: { + fatalError("Unresolved error \(error.domain) (\(error.code)): \(error.localizedDescription)") + } + ), + .init( + title: "Reset and recover database", + style: .destructive, + handler: { + self.recoverLibraryFromFailedMigration() + } + ) + ] + )) } } } diff --git a/BookPlayer/Coordinators/MainCoordinator.swift b/BookPlayer/Coordinators/MainCoordinator.swift index 185c63a3..73cc9aa0 100644 --- a/BookPlayer/Coordinators/MainCoordinator.swift +++ b/BookPlayer/Coordinators/MainCoordinator.swift @@ -238,6 +238,10 @@ extension MainCoordinator: AlertPresenter { navigationController.showAlert(title, message: message, completion: completion) } + func showAlert(_ content: BPAlertContent) { + navigationController.showAlert(content) + } + func showLoader() { LoadingUtils.loadAndBlock(in: navigationController) } diff --git a/BookPlayer/Library/ItemDetails Screen/ItemDetailsFormViewModel.swift b/BookPlayer/Library/ItemDetails Screen/ItemDetailsFormViewModel.swift index 7d31ea9f..ffec8b03 100644 --- a/BookPlayer/Library/ItemDetails Screen/ItemDetailsFormViewModel.swift +++ b/BookPlayer/Library/ItemDetails Screen/ItemDetailsFormViewModel.swift @@ -20,6 +20,8 @@ class ItemDetailsFormViewModel: ObservableObject { @Published var author: String /// Artwork image @Published var selectedImage: UIImage? + /// Progress of the current item + let progress: Double /// Original item title var titlePlaceholder: String /// Original item author @@ -37,6 +39,7 @@ class ItemDetailsFormViewModel: ObservableObject { self.titlePlaceholder = item.title self.author = item.details self.authorPlaceholder = item.details + self.progress = item.progress self.originalFileName = item.originalFileName self.showAuthor = item.type == .book self.originalImageDataProvider = ArtworkService.getArtworkProvider(for: item.relativePath) diff --git a/BookPlayer/Library/ItemDetails Screen/Views/ItemDetailsForm.swift b/BookPlayer/Library/ItemDetails Screen/Views/ItemDetailsForm.swift index 5fa19c19..c84756cd 100644 --- a/BookPlayer/Library/ItemDetails Screen/Views/ItemDetailsForm.swift +++ b/BookPlayer/Library/ItemDetails Screen/Views/ItemDetailsForm.swift @@ -66,9 +66,14 @@ struct ItemDetailsForm: View { Section { EmptyView() } footer: { - Text(viewModel.originalFileName) - .font(Font(Fonts.body)) - .foregroundColor(themeViewModel.secondaryColor) + VStack(alignment: .leading) { + Text(viewModel.originalFileName) + .font(Font(Fonts.body)) + .foregroundColor(themeViewModel.secondaryColor) + Text("\(Int(viewModel.progress * 100))%") + .font(Font(Fonts.body)) + .foregroundColor(themeViewModel.secondaryColor) + } } } .onChange(of: viewModel.selectedImage, perform: { _ in diff --git a/BookPlayer/Library/ItemList Screen/ItemListViewModel.swift b/BookPlayer/Library/ItemList Screen/ItemListViewModel.swift index ad416604..8491e383 100644 --- a/BookPlayer/Library/ItemList Screen/ItemListViewModel.swift +++ b/BookPlayer/Library/ItemList Screen/ItemListViewModel.swift @@ -1372,7 +1372,11 @@ extension ItemListViewModel: AlertPresenter { ) )) } - + + func showAlert(_ content: BPAlertContent) { + sendEvent(.showAlert(content: content)) + } + func showLoader() { sendEvent(.showLoader(flag: true)) } diff --git a/BookPlayer/Player/Player Screen/PlayerViewController.swift b/BookPlayer/Player/Player Screen/PlayerViewController.swift index bea6723c..af71d582 100755 --- a/BookPlayer/Player/Player Screen/PlayerViewController.swift +++ b/BookPlayer/Player/Player Screen/PlayerViewController.swift @@ -604,6 +604,17 @@ extension PlayerViewController { ) ) + actionSheet.addAction( + UIAlertAction( + title: self.viewModel.isRepeatEnabled() + ? "repeat_turn_off_title".localized : "repeat_turn_on_title".localized, + style: .default, + handler: { [weak self] _ in + self?.viewModel.handleEnableRepeat() + } + ) + ) + actionSheet.addAction(UIAlertAction(title: "cancel_button".localized, style: .cancel, handler: nil)) if let popoverPresentationController = actionSheet.popoverPresentationController { diff --git a/BookPlayer/Player/Player Screen/PlayerViewModel.swift b/BookPlayer/Player/Player Screen/PlayerViewModel.swift index b39b6db5..856b13ff 100755 --- a/BookPlayer/Player/Player Screen/PlayerViewModel.swift +++ b/BookPlayer/Player/Player Screen/PlayerViewModel.swift @@ -128,7 +128,8 @@ class PlayerViewModel: ViewModelProtocol { UIImpactFeedbackGenerator(style: .medium).impactOccurred() if let currentChapter = self.playerManager.currentItem?.currentChapter, - let previousChapter = self.playerManager.currentItem?.previousChapter(before: currentChapter) { + let previousChapter = self.playerManager.currentItem?.previousChapter(before: currentChapter) + { self.playerManager.jumpToChapter(previousChapter) sendEvent(.updateProgress(getCurrentProgressState())) } else { @@ -140,7 +141,8 @@ class PlayerViewModel: ViewModelProtocol { UIImpactFeedbackGenerator(style: .medium).impactOccurred() if let currentChapter = self.playerManager.currentItem?.currentChapter, - let nextChapter = self.playerManager.currentItem?.nextChapter(after: currentChapter) { + let nextChapter = self.playerManager.currentItem?.nextChapter(after: currentChapter) + { self.playerManager.jumpToChapter(nextChapter) sendEvent(.updateProgress(getCurrentProgressState())) } else { @@ -152,26 +154,33 @@ class PlayerViewModel: ViewModelProtocol { return self.playerManager.currentItem?.isFinished ?? false } + func isRepeatEnabled() -> Bool { + guard let currentItem = self.playerManager.currentItem else { return false } + return UserDefaults.standard.bool( + forKey: currentItem.filename + Constants.UserDefaults.repeatEnabledSuffix + ) + } + func getBookCurrentTime() -> TimeInterval { return self.playerManager.currentItem?.currentTimeInContext(self.prefersChapterContext) ?? 0 } func getCurrentTimeVoiceOverPrefix() -> String { return self.prefersChapterContext - ? "voiceover_chapter_time_title".localized - : "book_time_current_title".localized + ? "voiceover_chapter_time_title".localized + : "book_time_current_title".localized } func getMaxTimeVoiceOverPrefix() -> String { if self.prefersChapterContext { return self.prefersRemainingTime - ? "chapter_time_remaining_title".localized - : "chapter_duration_title".localized + ? "chapter_time_remaining_title".localized + : "chapter_duration_title".localized } return self.prefersRemainingTime - ? "book_time_remaining_title".localized - : "book_duration_title".localized + ? "book_time_remaining_title".localized + : "book_duration_title".localized } func handlePlayPauseAction() { @@ -202,6 +211,14 @@ class PlayerViewModel: ViewModelProtocol { self.playerManager.markAsCompleted(!self.isBookFinished()) } + func handleEnableRepeat() { + guard let filename = self.playerManager.currentItem?.filename else { return } + UserDefaults.standard.set( + !isRepeatEnabled(), + forKey: filename + Constants.UserDefaults.repeatEnabledSuffix + ) + } + func processToggleMaxTime() -> ProgressObject { self.prefersRemainingTime = !self.prefersRemainingTime sharedDefaults.set(self.prefersRemainingTime, forKey: Constants.UserDefaults.remainingTimeEnabled) @@ -225,9 +242,14 @@ class PlayerViewModel: ViewModelProtocol { let currentItem = item ?? self.playerManager.currentItem if self.prefersChapterContext, - let currentItem = currentItem, - let currentChapter = currentItem.currentChapter { - progress = String.localizedStringWithFormat("player_chapter_description".localized, currentChapter.index, currentItem.chapters.count) + let currentItem = currentItem, + let currentChapter = currentItem.currentChapter + { + progress = String.localizedStringWithFormat( + "player_chapter_description".localized, + currentChapter.index, + currentItem.chapters.count + ) sliderValue = Float((currentItem.currentTime - currentChapter.start) / currentChapter.duration) } else { progress = "\(Int(round((currentItem?.progressPercentage ?? 0) * 100)))%" @@ -237,12 +259,14 @@ class PlayerViewModel: ViewModelProtocol { // Update local chapter self.chapterBeforeSliderValueChange = currentItem?.currentChapter - let prevChapterImageName = self.hasChapter(before: currentItem?.currentChapter) - ? "chevron.left" - : "chevron.left.2" - let nextChapterImageName = self.hasChapter(after: currentItem?.currentChapter) - ? "chevron.right" - : "chevron.right.2" + let prevChapterImageName = + self.hasChapter(before: currentItem?.currentChapter) + ? "chevron.left" + : "chevron.left.2" + let nextChapterImageName = + self.hasChapter(after: currentItem?.currentChapter) + ? "chevron.right" + : "chevron.right.2" return ProgressObject( currentTime: currentTime, @@ -252,8 +276,8 @@ class PlayerViewModel: ViewModelProtocol { prevChapterImageName: prevChapterImageName, nextChapterImageName: nextChapterImageName, chapterTitle: currentItem?.currentChapter?.title - ?? currentItem?.title - ?? "" + ?? currentItem?.title + ?? "" ) } @@ -273,7 +297,8 @@ class PlayerViewModel: ViewModelProtocol { var nextChapterImageName = "chevron.right.2" var newCurrentTime: TimeInterval if self.prefersChapterContext, - let currentChapter = self.chapterBeforeSliderValueChange { + let currentChapter = self.chapterBeforeSliderValueChange + { newCurrentTime = TimeInterval(value) * currentChapter.duration chapterTitle = currentChapter.title @@ -317,8 +342,8 @@ class PlayerViewModel: ViewModelProtocol { prevChapterImageName: prevChapterImageName, nextChapterImageName: nextChapterImageName, chapterTitle: chapterTitle ?? self.chapterBeforeSliderValueChange?.title - ?? self.playerManager.currentItem?.title - ?? "" + ?? self.playerManager.currentItem?.title + ?? "" ) } @@ -334,7 +359,8 @@ class PlayerViewModel: ViewModelProtocol { var newTimeToDisplay = TimeInterval(value) * (self.playerManager.currentItem?.duration ?? 0) if self.prefersChapterContext, - let currentChapter = self.chapterBeforeSliderValueChange { + let currentChapter = self.chapterBeforeSliderValueChange + { newTimeToDisplay = currentChapter.start + TimeInterval(value) * currentChapter.duration } @@ -348,9 +374,9 @@ class PlayerViewModel: ViewModelProtocol { // request for review if app is active guard UIApplication.shared.applicationState == .active else { return } -#if RELEASE - AppDelegate.shared?.requestReview() -#endif + #if RELEASE + AppDelegate.shared?.requestReview() + #endif UserDefaults.standard.set(false, forKey: "ask_review") } @@ -417,14 +443,16 @@ class PlayerViewModel: ViewModelProtocol { actions.append(BPActionItem.cancelAction) - sendEvent(.sleepTimerAlert( - content: BPAlertContent( - title: nil, - message: getSleepTimerAlertMessage(), - style: .actionSheet, - actionItems: actions + sendEvent( + .sleepTimerAlert( + content: BPAlertContent( + title: nil, + message: getSleepTimerAlertMessage(), + style: .actionSheet, + actionItems: actions + ) ) - )) + ) } func getSleepTimerAlertMessage() -> String { @@ -514,23 +542,38 @@ extension PlayerViewModel { func showBookmarkSuccessAlert(vc: UIViewController, bookmark: SimpleBookmark, existed: Bool) { let formattedTime = TimeParser.formatTime(bookmark.time) - let titleKey = existed - ? "bookmark_exists_title" - : "bookmark_created_title" + let titleKey = + existed + ? "bookmark_exists_title" + : "bookmark_created_title" - let alert = UIAlertController(title: String.localizedStringWithFormat(titleKey.localized, formattedTime), - message: nil, - preferredStyle: .alert) + let alert = UIAlertController( + title: String.localizedStringWithFormat(titleKey.localized, formattedTime), + message: nil, + preferredStyle: .alert + ) if !existed { - alert.addAction(UIAlertAction(title: "bookmark_note_action_title".localized, style: .default, handler: { [weak self] _ in - self?.showBookmarkNoteAlert(vc: vc, bookmark: bookmark) - })) + alert.addAction( + UIAlertAction( + title: "bookmark_note_action_title".localized, + style: .default, + handler: { [weak self] _ in + self?.showBookmarkNoteAlert(vc: vc, bookmark: bookmark) + } + ) + ) } - alert.addAction(UIAlertAction(title: "bookmarks_see_title".localized, style: .default, handler: { [weak self] _ in - self?.showBookmarks() - })) + alert.addAction( + UIAlertAction( + title: "bookmarks_see_title".localized, + style: .default, + handler: { [weak self] _ in + self?.showBookmarks() + } + ) + ) alert.addAction(UIAlertAction(title: "ok_button".localized, style: .cancel, handler: nil)) @@ -538,27 +581,35 @@ extension PlayerViewModel { } func showBookmarkNoteAlert(vc: UIViewController, bookmark: SimpleBookmark) { - let alert = UIAlertController(title: "bookmark_note_action_title".localized, - message: nil, - preferredStyle: .alert) + let alert = UIAlertController( + title: "bookmark_note_action_title".localized, + message: nil, + preferredStyle: .alert + ) alert.addTextField(configurationHandler: { textfield in textfield.text = "" }) alert.addAction(UIAlertAction(title: "cancel_button".localized, style: .cancel, handler: nil)) - alert.addAction(UIAlertAction(title: "ok_button".localized, style: .default, handler: { [weak self] _ in - guard let note = alert.textFields?.first?.text else { - return - } - - self?.libraryService.addNote(note, bookmark: bookmark) - self?.syncService.scheduleSetBookmark( - relativePath: bookmark.relativePath, - time: bookmark.time, - note: note + alert.addAction( + UIAlertAction( + title: "ok_button".localized, + style: .default, + handler: { [weak self] _ in + guard let note = alert.textFields?.first?.text else { + return + } + + self?.libraryService.addNote(note, bookmark: bookmark) + self?.syncService.scheduleSetBookmark( + relativePath: bookmark.relativePath, + time: bookmark.time, + note: note + ) + } ) - })) + ) vc.present(alert, animated: true, completion: nil) } diff --git a/BookPlayer/Player/PlayerManager.swift b/BookPlayer/Player/PlayerManager.swift index 54ee0c58..818c33db 100755 --- a/BookPlayer/Player/PlayerManager.swift +++ b/BookPlayer/Player/PlayerManager.swift @@ -1100,13 +1100,22 @@ extension PlayerManager { let endOfChapterActive = SleepTimer.shared.state == .endOfChapter if currentItem.chapters.last == currentItem.currentChapter { - self.libraryService.setLibraryLastBook(with: nil) + if UserDefaults.standard.bool( + forKey: currentItem.filename + Constants.UserDefaults.repeatEnabledSuffix + ) { + updatePlaybackTime(item: currentItem, time: 0) + let firstChapter = currentItem.chapters.first! + currentItem.currentChapter = firstChapter + loadChapterMetadata(firstChapter, autoplay: !endOfChapterActive) + } else { + self.libraryService.setLibraryLastBook(with: nil) - self.markAsCompleted(true) + self.markAsCompleted(true) - self.playNextItem(autoPlayed: true, shouldAutoplay: !endOfChapterActive) + self.playNextItem(autoPlayed: true, shouldAutoplay: !endOfChapterActive) - NotificationCenter.default.post(name: .bookEnd, object: nil) + NotificationCenter.default.post(name: .bookEnd, object: nil) + } } else if currentItem.isBoundBook { updatePlaybackTime(item: currentItem, time: currentItem.currentTime) /// Load next chapter diff --git a/BookPlayer/Services/CarPlayManager.swift b/BookPlayer/Services/CarPlayManager.swift index bfe4d5ae..47a9ca19 100644 --- a/BookPlayer/Services/CarPlayManager.swift +++ b/BookPlayer/Services/CarPlayManager.swift @@ -565,6 +565,33 @@ extension CarPlayManager: AlertPresenter { self.interfaceController?.presentTemplate(alertTemplate, animated: true, completion: nil) } + + public func showAlert(_ content: BPAlertContent) { + let actions = content.actionItems.map({ item in + return CPAlertAction( + title: item.title, + style: .default, + handler: { _ in + self.interfaceController?.dismissTemplate(animated: true, completion: nil) + item.handler() + } + ) + }) + + var completeMessage = "" + + if let title = content.title { + completeMessage += title + } + + if let message = content.message { + completeMessage += ": \(message)" + } + + let alertTemplate = CPAlertTemplate(titleVariants: [completeMessage], actions: actions) + + self.interfaceController?.presentTemplate(alertTemplate, animated: true, completion: nil) + } } extension CarPlayManager: CPTabBarTemplateDelegate { diff --git a/BookPlayer/Utils/AlertPresenter.swift b/BookPlayer/Utils/AlertPresenter.swift index d4a1e087..f8feb24d 100644 --- a/BookPlayer/Utils/AlertPresenter.swift +++ b/BookPlayer/Utils/AlertPresenter.swift @@ -10,6 +10,7 @@ import Foundation protocol AlertPresenter: AnyObject { func showAlert(_ title: String?, message: String?, completion: (() -> Void)?) + func showAlert(_ content: BPAlertContent) func showLoader() func stopLoader() } @@ -20,6 +21,8 @@ protocol AlertPresenter: AnyObject { class VoidAlertPresenter: AlertPresenter { func showAlert(_ title: String?, message: String?, completion: (() -> Void)?) {} + func showAlert(_ content: BPAlertContent) {} + func showLoader() {} func stopLoader() {} diff --git a/BookPlayer/ar.lproj/Localizable.strings b/BookPlayer/ar.lproj/Localizable.strings index fa5bcf56..da40e515 100644 --- a/BookPlayer/ar.lproj/Localizable.strings +++ b/BookPlayer/ar.lproj/Localizable.strings @@ -319,3 +319,5 @@ "Rewind ${interval}" = "ترجيع ${interval}"; "settings_lock_orientation_title" = "الاتجاه مغلق"; "more_title" = "أكثر"; +"repeat_turn_on_title" = "قم بتشغيل التكرار لهذا الكتاب"; +"repeat_turn_off_title" = "إيقاف تكرار هذا الكتاب"; diff --git a/BookPlayer/cs.lproj/Localizable.strings b/BookPlayer/cs.lproj/Localizable.strings index 9453e20d..42381944 100644 --- a/BookPlayer/cs.lproj/Localizable.strings +++ b/BookPlayer/cs.lproj/Localizable.strings @@ -319,3 +319,5 @@ "Rewind ${interval}" = "Přetočit ${interval}"; "settings_lock_orientation_title" = "Orientace uzamčena"; "more_title" = "Více"; +"repeat_turn_on_title" = "Zapněte opakování pro tuto knihu"; +"repeat_turn_off_title" = "Vypnout opakování pro tuto knihu"; diff --git a/BookPlayer/da.lproj/Localizable.strings b/BookPlayer/da.lproj/Localizable.strings index cc1a46d7..44c0023c 100644 --- a/BookPlayer/da.lproj/Localizable.strings +++ b/BookPlayer/da.lproj/Localizable.strings @@ -319,3 +319,5 @@ "Rewind ${interval}" = "Spol ${interval} tilbage"; "settings_lock_orientation_title" = "Orientering låst"; "more_title" = "Mere"; +"repeat_turn_on_title" = "Slå Gentag til for denne bog"; +"repeat_turn_off_title" = "Slå Gentag fra for denne bog"; diff --git a/BookPlayer/de.lproj/Localizable.strings b/BookPlayer/de.lproj/Localizable.strings index a703e2f2..22a0f3c2 100644 --- a/BookPlayer/de.lproj/Localizable.strings +++ b/BookPlayer/de.lproj/Localizable.strings @@ -319,3 +319,5 @@ "Rewind ${interval}" = "Zurückspulen ${interval}"; "settings_lock_orientation_title" = "Ausrichtung gesperrt"; "more_title" = "Mehr"; +"repeat_turn_on_title" = "Aktivieren Sie die Option „Wiederholen“ für dieses Buch"; +"repeat_turn_off_title" = "Deaktivieren Sie „Wiederholen“ für dieses Buch"; diff --git a/BookPlayer/el.lproj/Localizable.strings b/BookPlayer/el.lproj/Localizable.strings index 9ecec33d..8a78c930 100644 --- a/BookPlayer/el.lproj/Localizable.strings +++ b/BookPlayer/el.lproj/Localizable.strings @@ -185,7 +185,7 @@ "storage_duplicate_item_description" = "Το επιλεγμένο αρχείο ήταν διπλότυπο, το υπάρχον αρχείο βρίσκεται στη διεύθυνση: %@"; "chapters_previous_title" = "Προηγούμενο Κεφάλαιο"; "chapters_next_title" = "Επόμενο κεφάλαιο"; -"book_time_current_title" = "Τρέχουσα ώρα κράτησης: %@"; +"book_time_current_title" = "Τρέχουσα ώρα βιβλίου: %@"; "storage_fix_files_description" = "Ο σύνδεσμος μεταξύ των αρχείων και των στοιχείων του ψηφιακού βιβλίου λείπει, εάν δεν μπορεί να βρεθεί το αντίστοιχο στοιχείο για κάθε αρχείο, θα δημιουργηθεί ένα νέο"; "storage_fix_all_title" = "Διορθώστε όλα"; "settings_backup_title" = "Αντίγραφα ασφαλείας iCloud"; @@ -318,4 +318,6 @@ "intent_custom_skiprewind_title" = "Πίσω με μεσοδιάστημα"; "Rewind ${interval}" = "Επαναφορά ${interval}"; "settings_lock_orientation_title" = "Ο προσανατολισμός είναι κλειδωμένος"; -"more_title" = "Περισσότερο"; +"more_title" = "Περισσότερα"; +"repeat_turn_on_title" = "Ενεργοποιήστε το Repeat για αυτό το βιβλίο"; +"repeat_turn_off_title" = "Απενεργοποιήστε το Repeat για αυτό το βιβλίο"; diff --git a/BookPlayer/en.lproj/Localizable.strings b/BookPlayer/en.lproj/Localizable.strings index d0ed8472..33fc34fa 100644 --- a/BookPlayer/en.lproj/Localizable.strings +++ b/BookPlayer/en.lproj/Localizable.strings @@ -319,3 +319,5 @@ We're working hard on providing a seamless experience, if possible, please conta "Rewind ${interval}" = "Rewind ${interval}"; "settings_lock_orientation_title" = "Orientation Locked"; "more_title" = "More"; +"repeat_turn_on_title" = "Turn on Repeat for this book"; +"repeat_turn_off_title" = "Turn off Repeat for this book"; diff --git a/BookPlayer/es.lproj/Localizable.strings b/BookPlayer/es.lproj/Localizable.strings index 4752db4e..ccd7d010 100644 --- a/BookPlayer/es.lproj/Localizable.strings +++ b/BookPlayer/es.lproj/Localizable.strings @@ -319,3 +319,5 @@ "Rewind ${interval}" = "Rebobinar ${interval}"; "settings_lock_orientation_title" = "Orientación Bloqueada"; "more_title" = "Más"; +"repeat_turn_on_title" = "Activar repetición para este libro"; +"repeat_turn_off_title" = "Desactivar la repetición para este libro"; diff --git a/BookPlayer/fi.lproj/Localizable.strings b/BookPlayer/fi.lproj/Localizable.strings index b0265f03..def93f8a 100644 --- a/BookPlayer/fi.lproj/Localizable.strings +++ b/BookPlayer/fi.lproj/Localizable.strings @@ -319,3 +319,5 @@ "Rewind ${interval}" = "Kelaa taaksepäin ${interval}"; "settings_lock_orientation_title" = "Suunta lukittu"; "more_title" = "Lisää"; +"repeat_turn_on_title" = "Ota Toista käyttöön tälle kirjalle"; +"repeat_turn_off_title" = "Poista Toista käytöstä tästä kirjasta"; diff --git a/BookPlayer/fr.lproj/Localizable.strings b/BookPlayer/fr.lproj/Localizable.strings index b55aa939..52705a40 100644 --- a/BookPlayer/fr.lproj/Localizable.strings +++ b/BookPlayer/fr.lproj/Localizable.strings @@ -319,3 +319,5 @@ "Rewind ${interval}" = "Rembobiner ${interval}"; "settings_lock_orientation_title" = "Orientation verrouillée"; "more_title" = "Plus"; +"repeat_turn_on_title" = "Activer la répétition pour ce livre"; +"repeat_turn_off_title" = "Désactiver la répétition pour ce livre"; diff --git a/BookPlayer/hu.lproj/Localizable.strings b/BookPlayer/hu.lproj/Localizable.strings index 48fd6026..e893e54a 100644 --- a/BookPlayer/hu.lproj/Localizable.strings +++ b/BookPlayer/hu.lproj/Localizable.strings @@ -319,3 +319,5 @@ "Rewind ${interval}" = "Visszatekerés ${interval}"; "settings_lock_orientation_title" = "Tájolás zárolva"; "more_title" = "Több"; +"repeat_turn_on_title" = "Kapcsolja be az Ismétlés funkciót ennél a könyvnél"; +"repeat_turn_off_title" = "Kapcsolja ki az Ismétlés funkciót ennél a könyvnél"; diff --git a/BookPlayer/it.lproj/Localizable.strings b/BookPlayer/it.lproj/Localizable.strings index 0cedc567..c7ee6ecf 100644 --- a/BookPlayer/it.lproj/Localizable.strings +++ b/BookPlayer/it.lproj/Localizable.strings @@ -319,3 +319,5 @@ "Rewind ${interval}" = "Riavvolgi ${interval}"; "settings_lock_orientation_title" = "Orientamento bloccato"; "more_title" = "Di più"; +"repeat_turn_on_title" = "Attiva Ripeti per questo libro"; +"repeat_turn_off_title" = "Disattiva Ripeti per questo libro"; diff --git a/BookPlayer/nb.lproj/Localizable.strings b/BookPlayer/nb.lproj/Localizable.strings index cf916f02..c7fde3e3 100644 --- a/BookPlayer/nb.lproj/Localizable.strings +++ b/BookPlayer/nb.lproj/Localizable.strings @@ -319,3 +319,5 @@ Vi jobber hardt for å gi deg en sømløs opplevelse. Hvis mulig, kontakt oss p "Rewind ${interval}" = "Spol tilbake ${interval}"; "settings_lock_orientation_title" = "Orientering låst"; "more_title" = "Flere"; +"repeat_turn_on_title" = "Slå på Gjenta for denne boken"; +"repeat_turn_off_title" = "Slå av Gjenta for denne boken"; diff --git a/BookPlayer/nl.lproj/Localizable.strings b/BookPlayer/nl.lproj/Localizable.strings index 879c3c89..6c4ed0ce 100644 --- a/BookPlayer/nl.lproj/Localizable.strings +++ b/BookPlayer/nl.lproj/Localizable.strings @@ -319,3 +319,5 @@ "Rewind ${interval}" = "Terugspoelen ${interval}"; "settings_lock_orientation_title" = "Oriëntatie vergrendeld"; "more_title" = "Meer"; +"repeat_turn_on_title" = "Schakel Herhalen in voor dit boek"; +"repeat_turn_off_title" = "Herhalen voor dit boek uitschakelen"; diff --git a/BookPlayer/pl.lproj/Localizable.strings b/BookPlayer/pl.lproj/Localizable.strings index f2362b5d..de5b281d 100644 --- a/BookPlayer/pl.lproj/Localizable.strings +++ b/BookPlayer/pl.lproj/Localizable.strings @@ -319,3 +319,5 @@ "Rewind ${interval}" = "Przewiń ${interval}"; "settings_lock_orientation_title" = "Zablokowana orientacja"; "more_title" = "Więcej"; +"repeat_turn_on_title" = "Włącz opcję Powtórz dla tej książki"; +"repeat_turn_off_title" = "Wyłącz opcję Powtórz dla tej książki"; diff --git a/BookPlayer/pt-BR.lproj/Localizable.strings b/BookPlayer/pt-BR.lproj/Localizable.strings index 8f05e3ae..c42271b7 100644 --- a/BookPlayer/pt-BR.lproj/Localizable.strings +++ b/BookPlayer/pt-BR.lproj/Localizable.strings @@ -319,3 +319,5 @@ "Rewind ${interval}" = "Retroceder ${interval}"; "settings_lock_orientation_title" = "Orientação bloqueada"; "more_title" = "Mais"; +"repeat_turn_on_title" = "Ativar repetição para este livro"; +"repeat_turn_off_title" = "Desativar Repetir para este livro"; diff --git a/BookPlayer/pt-PT.lproj/Localizable.strings b/BookPlayer/pt-PT.lproj/Localizable.strings index 1e49b420..9e740907 100644 --- a/BookPlayer/pt-PT.lproj/Localizable.strings +++ b/BookPlayer/pt-PT.lproj/Localizable.strings @@ -319,3 +319,5 @@ "Rewind ${interval}" = "Retroceder ${interval}"; "settings_lock_orientation_title" = "Orientação bloqueada"; "more_title" = "Mais"; +"repeat_turn_on_title" = "Ativar repetição para este livro"; +"repeat_turn_off_title" = "Desativar Repetir para este livro"; diff --git a/BookPlayer/ro.lproj/Localizable.strings b/BookPlayer/ro.lproj/Localizable.strings index 60922429..8aade4fb 100644 --- a/BookPlayer/ro.lproj/Localizable.strings +++ b/BookPlayer/ro.lproj/Localizable.strings @@ -319,3 +319,5 @@ "Rewind ${interval}" = "Derulați înapoi ${interval}"; "settings_lock_orientation_title" = "Orientare blocată"; "more_title" = "Mai mult"; +"repeat_turn_on_title" = "Activați Repetare pentru această carte"; +"repeat_turn_off_title" = "Dezactivează Repetarea pentru această carte"; diff --git a/BookPlayer/ru.lproj/Localizable.strings b/BookPlayer/ru.lproj/Localizable.strings index c4327c6a..2f903cba 100644 --- a/BookPlayer/ru.lproj/Localizable.strings +++ b/BookPlayer/ru.lproj/Localizable.strings @@ -319,3 +319,5 @@ "Rewind ${interval}" = "Перемотка назад ${interval}"; "settings_lock_orientation_title" = "Ориентация заблокирована"; "more_title" = "Более"; +"repeat_turn_on_title" = "Включить повтор для этой книги"; +"repeat_turn_off_title" = "Отключить повтор для этой книги"; diff --git a/BookPlayer/sk-SK.lproj/Localizable.strings b/BookPlayer/sk-SK.lproj/Localizable.strings index ff765a8d..7def0f4d 100644 --- a/BookPlayer/sk-SK.lproj/Localizable.strings +++ b/BookPlayer/sk-SK.lproj/Localizable.strings @@ -319,3 +319,5 @@ Usilovne pracujeme na poskytovaní bezproblémového zážitku, ak je to možné "Rewind ${interval}" = "Pretočiť ${interval}"; "settings_lock_orientation_title" = "Orientácia uzamknutá"; "more_title" = "Viac"; +"repeat_turn_on_title" = "Pre túto knihu zapnite možnosť Opakovať"; +"repeat_turn_off_title" = "Vypnúť Opakovať pre túto knihu"; diff --git a/BookPlayer/sv.lproj/Localizable.strings b/BookPlayer/sv.lproj/Localizable.strings index e2cfbd14..4716b30b 100644 --- a/BookPlayer/sv.lproj/Localizable.strings +++ b/BookPlayer/sv.lproj/Localizable.strings @@ -319,3 +319,5 @@ "Rewind ${interval}" = "Spola tillbaka ${interval}"; "settings_lock_orientation_title" = "Orientering låst"; "more_title" = "Mer"; +"repeat_turn_on_title" = "Aktivera Upprepa för den här boken"; +"repeat_turn_off_title" = "Stäng av Upprepa för den här boken"; diff --git a/BookPlayer/tr.lproj/Localizable.strings b/BookPlayer/tr.lproj/Localizable.strings index a9bd3b1b..ddf39d37 100644 --- a/BookPlayer/tr.lproj/Localizable.strings +++ b/BookPlayer/tr.lproj/Localizable.strings @@ -319,3 +319,5 @@ "Rewind ${interval}" = "${interval} geri sar"; "settings_lock_orientation_title" = "Yönlendirme Kilitli"; "more_title" = "Daha"; +"repeat_turn_on_title" = "Bu kitap için Tekrarı açın"; +"repeat_turn_off_title" = "Bu kitap için Tekrarı kapatın"; diff --git a/BookPlayer/uk.lproj/Localizable.strings b/BookPlayer/uk.lproj/Localizable.strings index 17e8529f..36d48668 100644 --- a/BookPlayer/uk.lproj/Localizable.strings +++ b/BookPlayer/uk.lproj/Localizable.strings @@ -319,3 +319,5 @@ "Rewind ${interval}" = "Перемотати ${interval}"; "settings_lock_orientation_title" = "Орієнтація заблокована"; "more_title" = "більше"; +"repeat_turn_on_title" = "Увімкніть повтор для цієї книги"; +"repeat_turn_off_title" = "Вимкніть повтор для цієї книги"; diff --git a/BookPlayer/zh-Hans.lproj/Localizable.strings b/BookPlayer/zh-Hans.lproj/Localizable.strings index 5b1d569c..5fb6c31d 100644 --- a/BookPlayer/zh-Hans.lproj/Localizable.strings +++ b/BookPlayer/zh-Hans.lproj/Localizable.strings @@ -319,3 +319,5 @@ "Rewind ${interval}" = "后退 ${interval}"; "settings_lock_orientation_title" = "方向已锁定"; "more_title" = "更多的"; +"repeat_turn_on_title" = "为这本书开启重复"; +"repeat_turn_off_title" = "关闭此书的重复功能"; diff --git a/Shared/Constants.swift b/Shared/Constants.swift index 0d89d3b7..4fdabd0a 100644 --- a/Shared/Constants.swift +++ b/Shared/Constants.swift @@ -40,6 +40,7 @@ public enum Constants { public static let customSleepTimerDuration = "userSettingsCustomSleepTimerDuration" public static let autoTimerEnabled = "userSettingsAutoTimerEnabled" public static let lastEnabledTimer = "userSettingsLastEnabledTimer" + public static let repeatEnabledSuffix = "_repeatEnabled" public static let rewindInterval = "userSettingsRewindInterval" public static let forwardInterval = "userSettingsForwardInterval" diff --git a/Shared/CoreData/Lightweight-Models/PlayableItem.swift b/Shared/CoreData/Lightweight-Models/PlayableItem.swift index 0d925b89..c4e43e6c 100644 --- a/Shared/CoreData/Lightweight-Models/PlayableItem.swift +++ b/Shared/CoreData/Lightweight-Models/PlayableItem.swift @@ -38,6 +38,10 @@ public final class PlayableItem: NSObject, Identifiable { return DataManager.getProcessedFolderURL().appendingPathComponent(self.relativePath) } + public lazy var filename: String = { + fileURL.lastPathComponent + }() + enum CodingKeys: String, CodingKey { case title, author, chapters, currentTime, duration, relativePath, parentFolder, percentCompleted, lastPlayDate, isFinished, isBoundBook