diff --git a/BookPlayer.xcodeproj/project.pbxproj b/BookPlayer.xcodeproj/project.pbxproj
index 9bdee6e4..fe6356a6 100644
--- a/BookPlayer.xcodeproj/project.pbxproj
+++ b/BookPlayer.xcodeproj/project.pbxproj
@@ -4894,7 +4894,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
- MARKETING_VERSION = 5.15.2;
+ MARKETING_VERSION = 5.15.3;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).BookPlayerIntents";
@@ -4928,7 +4928,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
- MARKETING_VERSION = 5.15.2;
+ MARKETING_VERSION = 5.15.3;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).BookPlayerIntents";
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -4960,7 +4960,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
- MARKETING_VERSION = 5.15.2;
+ MARKETING_VERSION = 5.15.3;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).BookPlayerIntents";
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -4997,7 +4997,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
- MARKETING_VERSION = 5.15.2;
+ MARKETING_VERSION = 5.15.3;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).watchkitapp";
@@ -5039,7 +5039,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
- MARKETING_VERSION = 5.15.2;
+ MARKETING_VERSION = 5.15.3;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).watchkitapp";
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -5078,7 +5078,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
- MARKETING_VERSION = 5.15.2;
+ MARKETING_VERSION = 5.15.3;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).watchkitapp";
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -5247,7 +5247,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
- MARKETING_VERSION = 5.15.2;
+ MARKETING_VERSION = 5.15.3;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).BookPlayerWidgetUI";
@@ -5285,7 +5285,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
- MARKETING_VERSION = 5.15.2;
+ MARKETING_VERSION = 5.15.3;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).BookPlayerWidgetUI";
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -5321,7 +5321,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
- MARKETING_VERSION = 5.15.2;
+ MARKETING_VERSION = 5.15.3;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).BookPlayerWidgetUI";
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -5475,7 +5475,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 5.15.2;
+ MARKETING_VERSION = 5.15.3;
PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = BookPlayer;
PROVISIONING_PROFILE_SPECIFIER = "$(BP_PROVISIONING_MAIN)";
@@ -5514,7 +5514,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 5.15.2;
+ MARKETING_VERSION = 5.15.3;
PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = BookPlayer;
PROVISIONING_PROFILE_SPECIFIER = "$(BP_PROVISIONING_MAIN)";
@@ -5736,7 +5736,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
- MARKETING_VERSION = 5.15.2;
+ MARKETING_VERSION = 5.15.3;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).watchkitapp.widgets";
@@ -5774,7 +5774,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
- MARKETING_VERSION = 5.15.2;
+ MARKETING_VERSION = 5.15.3;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).watchkitapp.widgets";
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -5810,7 +5810,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
- MARKETING_VERSION = 5.15.2;
+ MARKETING_VERSION = 5.15.3;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).watchkitapp.widgets";
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -5849,7 +5849,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
- MARKETING_VERSION = 5.15.2;
+ MARKETING_VERSION = 5.15.3;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).BookPlayerShareExtension";
@@ -5889,7 +5889,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
- MARKETING_VERSION = 5.15.2;
+ MARKETING_VERSION = 5.15.3;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).BookPlayerShareExtension";
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -5927,7 +5927,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
- MARKETING_VERSION = 5.15.2;
+ MARKETING_VERSION = 5.15.3;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER).BookPlayerShareExtension";
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -6020,7 +6020,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 5.15.2;
+ MARKETING_VERSION = 5.15.3;
PRODUCT_BUNDLE_IDENTIFIER = "$(BP_BUNDLE_IDENTIFIER)";
PRODUCT_NAME = BookPlayer;
PROVISIONING_PROFILE_SPECIFIER = "$(BP_PROVISIONING_MAIN)";
diff --git a/BookPlayer/Assets.xcassets/audiobookshelf-icon.imageset/Contents.json b/BookPlayer/Assets.xcassets/audiobookshelf-icon.imageset/Contents.json
index 069cdf4d..90b58dbe 100644
--- a/BookPlayer/Assets.xcassets/audiobookshelf-icon.imageset/Contents.json
+++ b/BookPlayer/Assets.xcassets/audiobookshelf-icon.imageset/Contents.json
@@ -1,7 +1,7 @@
{
"images" : [
{
- "filename" : "audiobookshelf-icon.png",
+ "filename" : "abs.svg",
"idiom" : "universal"
}
],
diff --git a/BookPlayer/Assets.xcassets/audiobookshelf-icon.imageset/abs.svg b/BookPlayer/Assets.xcassets/audiobookshelf-icon.imageset/abs.svg
new file mode 100644
index 00000000..54cb1849
--- /dev/null
+++ b/BookPlayer/Assets.xcassets/audiobookshelf-icon.imageset/abs.svg
@@ -0,0 +1,31 @@
+
\ No newline at end of file
diff --git a/BookPlayer/Assets.xcassets/audiobookshelf-icon.imageset/audiobookshelf-icon.png b/BookPlayer/Assets.xcassets/audiobookshelf-icon.imageset/audiobookshelf-icon.png
deleted file mode 100644
index 87fcee04..00000000
Binary files a/BookPlayer/Assets.xcassets/audiobookshelf-icon.imageset/audiobookshelf-icon.png and /dev/null differ
diff --git a/BookPlayer/Assets.xcassets/jellyfin-icon.imageset/Contents.json b/BookPlayer/Assets.xcassets/jellyfin-icon.imageset/Contents.json
index da19810b..7385abcb 100644
--- a/BookPlayer/Assets.xcassets/jellyfin-icon.imageset/Contents.json
+++ b/BookPlayer/Assets.xcassets/jellyfin-icon.imageset/Contents.json
@@ -1,7 +1,7 @@
{
"images" : [
{
- "filename" : "icon-transparent.png",
+ "filename" : "jellyfin.svg",
"idiom" : "universal"
}
],
diff --git a/BookPlayer/Assets.xcassets/jellyfin-icon.imageset/icon-transparent.png b/BookPlayer/Assets.xcassets/jellyfin-icon.imageset/icon-transparent.png
deleted file mode 100644
index 50c881b3..00000000
Binary files a/BookPlayer/Assets.xcassets/jellyfin-icon.imageset/icon-transparent.png and /dev/null differ
diff --git a/BookPlayer/Assets.xcassets/jellyfin-icon.imageset/jellyfin.svg b/BookPlayer/Assets.xcassets/jellyfin-icon.imageset/jellyfin.svg
new file mode 100644
index 00000000..5c75f1c0
--- /dev/null
+++ b/BookPlayer/Assets.xcassets/jellyfin-icon.imageset/jellyfin.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/BookPlayer/Library/ItemList/ItemListViewModel.swift b/BookPlayer/Library/ItemList/ItemListViewModel.swift
index bfe5316f..9e5bfe50 100644
--- a/BookPlayer/Library/ItemList/ItemListViewModel.swift
+++ b/BookPlayer/Library/ItemList/ItemListViewModel.swift
@@ -375,8 +375,14 @@ extension ItemListViewModel {
func createFolder(with title: String, items: [String]? = nil, type: SimpleItemType) {
Task { @MainActor in
do {
+ let trimmedTitle = title.trimmingCharacters(in: .whitespacesAndNewlines)
+
+ guard !trimmedTitle.isEmpty else {
+ return
+ }
+
let folder = try libraryService.createFolder(
- with: title,
+ with: trimmedTitle,
inside: libraryNode.folderRelativePath
)
await syncService.scheduleUpload(items: [folder])
diff --git a/BookPlayer/Player/PlayerManager.swift b/BookPlayer/Player/PlayerManager.swift
index 0fb6e32b..207d4724 100755
--- a/BookPlayer/Player/PlayerManager.swift
+++ b/BookPlayer/Player/PlayerManager.swift
@@ -590,7 +590,7 @@ final class PlayerManager: NSObject, PlayerManagerProtocol, ObservableObject {
self.nowPlayingInfo[MPMediaItemPropertyTitle] = chapter.title
/// If the chapter title is the same as the current item, show the author instead
- if chapter.title == currentItem.title {
+ if currentItem.isBoundBook || chapter.title == currentItem.title {
self.nowPlayingInfo[MPMediaItemPropertyArtist] = currentItem.author
} else {
self.nowPlayingInfo[MPMediaItemPropertyArtist] = currentItem.title
diff --git a/BookPlayer/Settings/Sections/SettingsTipView.swift b/BookPlayer/Settings/Sections/SettingsTipView.swift
index 48ee17d1..61d7c4df 100644
--- a/BookPlayer/Settings/Sections/SettingsTipView.swift
+++ b/BookPlayer/Settings/Sections/SettingsTipView.swift
@@ -19,9 +19,26 @@ struct SettingsTipView: View {
@State private var loadProductTask: Task<(), Error>?
@EnvironmentObject private var theme: ThemeViewModel
@Environment(\.loadingState) var loadingState
+ @Environment(\.accountService) var accountService
let purchaseCompleted: () -> Void
+ /// Whether this is the user's first donation (uses non-consumable) or a repeat donation (uses consumable)
+ private var isFirstDonation: Bool {
+ return accountService.getAccount()?.donationMade != true
+ }
+
+ /// Returns the appropriate product ID based on donation history
+ private var productId: String {
+ if isFirstDonation {
+ // First time donation uses non-consumable
+ return tipOption.rawValue
+ } else {
+ // Subsequent donations use consumable
+ return tipOption.rawValue + ".consumable"
+ }
+ }
+
var buttonBackgroundColor: Color {
switch tipOption {
case .kind:
@@ -93,7 +110,8 @@ struct SettingsTipView: View {
func loadProduct() {
loadProductTask = Task {
self.isLoading = true
- let products = await Purchases.shared.products([tipOption.rawValue])
+ // Use non-consumable for first donation, consumable for repeat donations
+ let products = await Purchases.shared.products([productId])
self.product = products.first
self.buttonTitle = self.product?.localizedPriceString ?? ""
self.isLoading = false
@@ -104,7 +122,7 @@ struct SettingsTipView: View {
guard AppEnvironment.isPurchaseEnabled else {
throw PurchaseError.testFlightPurchasesDisabled
}
-
+
_ = await loadProductTask?.result
guard let product else {
diff --git a/Shared/Services/PlaybackService.swift b/Shared/Services/PlaybackService.swift
index 17d91f36..e368b9c0 100644
--- a/Shared/Services/PlaybackService.swift
+++ b/Shared/Services/PlaybackService.swift
@@ -260,7 +260,7 @@ public final class PlaybackService: PlaybackServiceProtocol {
return PlayableItem(
title: folder.title,
- author: chapters.first?.author ?? "voiceover_unknown_author".localized,
+ author: chapters.first?.author ?? folder.details,
chapters: chapters,
currentTime: folder.currentTime,
duration: duration ?? folder.duration,