diff --git a/Bilbary for iPad/Bilbary.swift b/Bilbary for iPad/Bilbary.swift index 8648c35..b832e63 100644 --- a/Bilbary for iPad/Bilbary.swift +++ b/Bilbary for iPad/Bilbary.swift @@ -17,7 +17,7 @@ struct Bilbary: App { init() { do { - dataModel.container = try ModelContainer(for: Book.self, BookProgress.self) + dataModel.container = try ModelContainer(for: Book.self, BookSession.self) } catch { fatalError( "Something happened when creating the model container." diff --git a/Bilbary for iPad/Views/Components/BlurTransition.swift b/Bilbary for iPad/Views/Components/BlurTransition.swift new file mode 100644 index 0000000..fb5e56b --- /dev/null +++ b/Bilbary for iPad/Views/Components/BlurTransition.swift @@ -0,0 +1,26 @@ +// +// BlurTransition.swift +// Bilbary +// +// Created by Guillaume Coquard on 06/03/24. +// + +import SwiftUI + +struct BlurTransition: ViewModifier { + let blurValue: CGFloat + + func body(content: Content) -> some View { + content + .blur(radius: blurValue) + } +} + +extension AnyTransition { + static var blur: AnyTransition { + .modifier( + active: BlurTransition(blurValue: 20), + identity: BlurTransition(blurValue: 0) + ) + } +} diff --git a/Bilbary for iPad/Views/EPUB/EPUBScrollableContent.swift b/Bilbary for iPad/Views/EPUB/EPUBScrollableContent.swift index 85e5d33..868db6b 100644 --- a/Bilbary for iPad/Views/EPUB/EPUBScrollableContent.swift +++ b/Bilbary for iPad/Views/EPUB/EPUBScrollableContent.swift @@ -6,6 +6,7 @@ // import SwiftUI +import OSLog struct EPUBScrollableContent: View { @@ -16,36 +17,82 @@ struct EPUBScrollableContent: View { var book: Book - private var session: BookProgress? { + private var session: BookSession? { read.currentSession } var body: some View { VStack { - ScrollView { - VStack { - ForEach(book.content.strings.prefix(10), id: \.self) { paragraph in - VStack(alignment: .leading, spacing: 0) { - Text( - cust.style.present( - paragraph - ) - ) - .lineSpacing( cust.lineHeight ) - .padding(.bottom, cust.paragraphSpacing) + VStack(alignment: .leading) { + if read.selectedBook != nil { + ScrollView(.vertical) { + LazyVStack(alignment: .leading, spacing: cust.paragraphSpacing) { + ForEach(book.content.formatted, id: \.id) { paragraph in + HStack { + Text( + cust.style.present( + paragraph.content + ) + ) + .lineSpacing( cust.lineHeight ) + .multilineTextAlignment(.leading) + + Spacer() + } + .padding(.horizontal, 80) + .frame(width: view.screenWidth) + .scrollTransition(axis: .vertical, transition: { + $0 + .blur(radius: $1.isIdentity ? 0 : 10) + .opacity( $1.isIdentity ? 1 : 0.5 ) + }) + .tag(paragraph.id) + .id(paragraph.id) + } + } + .scrollTargetLayout() + } + .overlay { + ZStack { + VStack { + LinearGradient(colors: [ + Color.userDefinedBackground, + Color.clear, + Color.clear, + Color.clear + ], startPoint: .top, endPoint: .center) + Spacer() + } + .frame(maxWidth: .infinity) + + VStack { + Spacer() + LinearGradient(colors: [ + Color.userDefinedBackground, + Color.clear, + Color.clear + ], startPoint: .bottom, endPoint: .center) + } + .frame(maxWidth: .infinity) } - .frame(maxWidth: .infinity) - .padding(.vertical) + .allowsHitTesting(false) } + .defaultScrollAnchor(.top) + .scrollPosition(id: $read.currentParagraphId, anchor: .center) + .scrollIndicators(.never) + .frame(minHeight: 200) + .padding(0) + .padding(.vertical) + } else { + Spacer() + EmptyView() + Spacer() } - .frame(minHeight: 200) - .frame(maxWidth: .infinity) - .padding(0) } .padding(0) } + .defersSystemGestures(on: .horizontal) .padding(.vertical, 0) - .padding(.horizontal, 80) .frame(width: view.screenWidth) .onAppear { if let session = self.session { diff --git a/Bilbary for iPad/Views/EPUB/EPUBSwipeView.swift b/Bilbary for iPad/Views/EPUB/EPUBSwipeView.swift index 34dc0ca..8bdd960 100644 --- a/Bilbary for iPad/Views/EPUB/EPUBSwipeView.swift +++ b/Bilbary for iPad/Views/EPUB/EPUBSwipeView.swift @@ -26,7 +26,7 @@ struct EPUBSwipeView: View { .scrollTargetLayout() } .scrollDisabled(view.isAnyPopoverDisplayed) - .scrollPosition(id: $readModel.selectedBookHashValue, anchor: .center) + .scrollPosition(id: $readModel.selectedBookHashValue, anchor: .trailing) .scrollClipDisabled() .scrollTargetBehavior(.paging) .scrollIndicators(.never) diff --git a/Bilbary for iPad/Views/Library/Book/BBookInfoCardStyle.swift b/Bilbary for iPad/Views/Library/Book/BBookInfoCardStyle.swift index c8837a4..4eefdac 100644 --- a/Bilbary for iPad/Views/Library/Book/BBookInfoCardStyle.swift +++ b/Bilbary for iPad/Views/Library/Book/BBookInfoCardStyle.swift @@ -14,6 +14,12 @@ struct BBookInfoCardStyle: ViewModifier { .frame(maxWidth: .infinity) .background(.regularMaterial) .clipShape(.rect(cornerRadius: 12)) + .overlay { + RoundedRectangle(cornerRadius: 12) + .stroke(Color.black.opacity(0.1), lineWidth: 0.1) + .foregroundStyle(.clear) + } + .shadow(color: Color.black.opacity(0.2), radius: 12, y: 4) } } diff --git a/Bilbary for iPad/Views/Library/LibraryContentView.swift b/Bilbary for iPad/Views/Library/LibraryContentView.swift index daee337..acdf47d 100644 --- a/Bilbary for iPad/Views/Library/LibraryContentView.swift +++ b/Bilbary for iPad/Views/Library/LibraryContentView.swift @@ -32,6 +32,7 @@ struct LibraryContentView: View { } Spacer() } + .scrollClipDisabled() .padding(.horizontal) .navigationTitle("Library") .navigationBarTitleDisplayMode(.automatic) diff --git a/Bilbary for iPad/Views/Streak/BStreakValidation.swift b/Bilbary for iPad/Views/Streak/BStreakValidation.swift index f6e5480..44124a9 100644 --- a/Bilbary for iPad/Views/Streak/BStreakValidation.swift +++ b/Bilbary for iPad/Views/Streak/BStreakValidation.swift @@ -49,7 +49,7 @@ struct BStreakValidation: View { if !goalModel.isStreakValidated { DispatchQueue.main.async { withAnimation(.linear(duration: BReadModel.timerIncrement)) { - goalModel.streakValidationProgressRemaining = goalModel.streakValidationProgressRemaining - BReadModel.timerIncrement + goalModel.streakValidationProgressRemaining -= BReadModel.timerIncrement } } } diff --git a/Bilbary.xcodeproj/project.pbxproj b/Bilbary.xcodeproj/project.pbxproj index 54a7e90..77fd6ac 100644 --- a/Bilbary.xcodeproj/project.pbxproj +++ b/Bilbary.xcodeproj/project.pbxproj @@ -63,10 +63,10 @@ 463CEDFA2B8569BE001D951C /* BilbaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 463CEDF92B8569BE001D951C /* BilbaryView.swift */; }; 463EBC182B91CE9400BB9A57 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 463EBC172B91CE9400BB9A57 /* Utilities.swift */; }; 463EBC192B91CE9400BB9A57 /* Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 463EBC172B91CE9400BB9A57 /* Utilities.swift */; }; - 463EBC1B2B91DD4800BB9A57 /* Book+BookCover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 463EBC1A2B91DD4800BB9A57 /* Book+BookCover.swift */; }; - 463EBC1C2B91DD4800BB9A57 /* Book+BookCover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 463EBC1A2B91DD4800BB9A57 /* Book+BookCover.swift */; }; - 463EBC1E2B91F7A400BB9A57 /* Book+BookContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 463EBC1D2B91F7A400BB9A57 /* Book+BookContent.swift */; }; - 463EBC1F2B91F7A400BB9A57 /* Book+BookContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 463EBC1D2B91F7A400BB9A57 /* Book+BookContent.swift */; }; + 463EBC1B2B91DD4800BB9A57 /* BookCover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 463EBC1A2B91DD4800BB9A57 /* BookCover.swift */; }; + 463EBC1C2B91DD4800BB9A57 /* BookCover.swift in Sources */ = {isa = PBXBuildFile; fileRef = 463EBC1A2B91DD4800BB9A57 /* BookCover.swift */; }; + 463EBC1E2B91F7A400BB9A57 /* BookContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 463EBC1D2B91F7A400BB9A57 /* BookContent.swift */; }; + 463EBC1F2B91F7A400BB9A57 /* BookContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 463EBC1D2B91F7A400BB9A57 /* BookContent.swift */; }; 463EBC212B91FA3500BB9A57 /* Book+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 463EBC202B91FA3500BB9A57 /* Book+Equatable.swift */; }; 463EBC222B91FA3500BB9A57 /* Book+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 463EBC202B91FA3500BB9A57 /* Book+Equatable.swift */; }; 463EBC242B91FA5900BB9A57 /* Book+Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 463EBC232B91FA5900BB9A57 /* Book+Hashable.swift */; }; @@ -123,8 +123,8 @@ 464F8F2F2B913BB4009102CB /* UIWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 464F8F262B913BB4009102CB /* UIWindow.swift */; }; 464F8F302B913BB4009102CB /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 464F8F272B913BB4009102CB /* Color.swift */; }; 464F8F312B913BB4009102CB /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 464F8F272B913BB4009102CB /* Color.swift */; }; - 464F8F362B913DB0009102CB /* Book+Static.swift in Sources */ = {isa = PBXBuildFile; fileRef = 464F8F352B913DB0009102CB /* Book+Static.swift */; }; - 464F8F372B913DB0009102CB /* Book+Static.swift in Sources */ = {isa = PBXBuildFile; fileRef = 464F8F352B913DB0009102CB /* Book+Static.swift */; }; + 464F8F362B913DB0009102CB /* Book+Formatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 464F8F352B913DB0009102CB /* Book+Formatter.swift */; }; + 464F8F372B913DB0009102CB /* Book+Formatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 464F8F352B913DB0009102CB /* Book+Formatter.swift */; }; 464F8F3F2B913EC4009102CB /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 464F8F3E2B913EC4009102CB /* Logger.swift */; }; 464F8F402B913EC4009102CB /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 464F8F3E2B913EC4009102CB /* Logger.swift */; }; 464F8F452B9144DA009102CB /* Notification.Name.swift in Sources */ = {isa = PBXBuildFile; fileRef = 464F8F432B9144DA009102CB /* Notification.Name.swift */; }; @@ -143,12 +143,12 @@ 4659B25C2B968DA6000BD724 /* Book+SwiftData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4659B25A2B968DA6000BD724 /* Book+SwiftData.swift */; }; 4659B25E2B968DD1000BD724 /* Book+ReadingProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4659B25D2B968DD1000BD724 /* Book+ReadingProgress.swift */; }; 4659B25F2B968DD1000BD724 /* Book+ReadingProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4659B25D2B968DD1000BD724 /* Book+ReadingProgress.swift */; }; - 4659B2612B968E09000BD724 /* Book+Static+Local.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4659B2602B968E09000BD724 /* Book+Static+Local.swift */; }; - 4659B2622B968E09000BD724 /* Book+Static+Local.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4659B2602B968E09000BD724 /* Book+Static+Local.swift */; }; - 4659B2642B968E90000BD724 /* BookProgress+SwiftData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4659B2632B968E90000BD724 /* BookProgress+SwiftData.swift */; }; - 4659B2652B968E90000BD724 /* BookProgress+SwiftData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4659B2632B968E90000BD724 /* BookProgress+SwiftData.swift */; }; - 4659B2672B968EBE000BD724 /* BookProgress+ComputedProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4659B2662B968EBE000BD724 /* BookProgress+ComputedProperties.swift */; }; - 4659B2682B968EBE000BD724 /* BookProgress+ComputedProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4659B2662B968EBE000BD724 /* BookProgress+ComputedProperties.swift */; }; + 4659B2612B968E09000BD724 /* Book+Static.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4659B2602B968E09000BD724 /* Book+Static.swift */; }; + 4659B2622B968E09000BD724 /* Book+Static.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4659B2602B968E09000BD724 /* Book+Static.swift */; }; + 4659B2642B968E90000BD724 /* BookSession+SwiftData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4659B2632B968E90000BD724 /* BookSession+SwiftData.swift */; }; + 4659B2652B968E90000BD724 /* BookSession+SwiftData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4659B2632B968E90000BD724 /* BookSession+SwiftData.swift */; }; + 4659B2672B968EBE000BD724 /* BookSession+ComputedProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4659B2662B968EBE000BD724 /* BookSession+ComputedProperties.swift */; }; + 4659B2682B968EBE000BD724 /* BookSession+ComputedProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4659B2662B968EBE000BD724 /* BookSession+ComputedProperties.swift */; }; 465E0F0C2B94DCFB00D83BE5 /* BClickableModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 465E0F0B2B94DCFB00D83BE5 /* BClickableModifier.swift */; }; 465E0F0D2B94DCFB00D83BE5 /* BClickableModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 465E0F0B2B94DCFB00D83BE5 /* BClickableModifier.swift */; }; 465E0F0F2B94E08300D83BE5 /* BReadSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 465E0F0E2B94E08300D83BE5 /* BReadSize.swift */; }; @@ -194,6 +194,8 @@ 46A5FE4D2B92381C00C46368 /* Bilbary.plist in Resources */ = {isa = PBXBuildFile; fileRef = 464F8F112B90E034009102CB /* Bilbary.plist */; }; 46A5FE502B923CBD00C46368 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = 46A5FE4F2B923CBD00C46368 /* SwiftSoup */; }; 46AE3DB02B973E9000B2FD5E /* steve-jobs-4-lives.epub in Resources */ = {isa = PBXBuildFile; fileRef = 464C8B4B2B97052000876EAD /* steve-jobs-4-lives.epub */; }; + 46B170BA2B9921B1009F5CF8 /* BlurTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46B170B92B9921B1009F5CF8 /* BlurTransition.swift */; }; + 46B170BB2B9921B1009F5CF8 /* BlurTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46B170B92B9921B1009F5CF8 /* BlurTransition.swift */; }; 46ED8AB92B9283AB00EA292A /* BViewModel+Methods.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46ED8AB82B9283AB00EA292A /* BViewModel+Methods.swift */; }; 46ED8ABA2B9283AB00EA292A /* BViewModel+Methods.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46ED8AB82B9283AB00EA292A /* BViewModel+Methods.swift */; }; 46ED8ABC2B9289CD00EA292A /* BVMScreenSizeUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46ED8ABB2B9289CD00EA292A /* BVMScreenSizeUpdate.swift */; }; @@ -206,12 +208,14 @@ 46ED8AC62B92955500EA292A /* LibraryFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46ED8AC42B92955500EA292A /* LibraryFilter.swift */; }; 46ED8AC82B929BBF00EA292A /* LibraryContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46ED8AC72B929BBF00EA292A /* LibraryContentView.swift */; }; 46ED8AC92B929BBF00EA292A /* LibraryContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46ED8AC72B929BBF00EA292A /* LibraryContentView.swift */; }; - 46ED8ACE2B92BA5200EA292A /* BookProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46ED8ACD2B92BA5200EA292A /* BookProgress.swift */; }; - 46ED8ACF2B92BA5200EA292A /* BookProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46ED8ACD2B92BA5200EA292A /* BookProgress.swift */; }; - 46ED8AD12B92BD5A00EA292A /* Book+BookReadingStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46ED8AD02B92BD5A00EA292A /* Book+BookReadingStatus.swift */; }; - 46ED8AD22B92BD5A00EA292A /* Book+BookReadingStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46ED8AD02B92BD5A00EA292A /* Book+BookReadingStatus.swift */; }; - 46ED8AD42B92BE4700EA292A /* Book+BookOpinion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46ED8AD32B92BE4700EA292A /* Book+BookOpinion.swift */; }; - 46ED8AD52B92BE4700EA292A /* Book+BookOpinion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46ED8AD32B92BE4700EA292A /* Book+BookOpinion.swift */; }; + 46ED8ACE2B92BA5200EA292A /* BookSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46ED8ACD2B92BA5200EA292A /* BookSession.swift */; }; + 46ED8ACF2B92BA5200EA292A /* BookSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46ED8ACD2B92BA5200EA292A /* BookSession.swift */; }; + 46ED8AD12B92BD5A00EA292A /* BookReadingStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46ED8AD02B92BD5A00EA292A /* BookReadingStatus.swift */; }; + 46ED8AD22B92BD5A00EA292A /* BookReadingStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46ED8AD02B92BD5A00EA292A /* BookReadingStatus.swift */; }; + 46ED8AD42B92BE4700EA292A /* BookOpinion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46ED8AD32B92BE4700EA292A /* BookOpinion.swift */; }; + 46ED8AD52B92BE4700EA292A /* BookOpinion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46ED8AD32B92BE4700EA292A /* BookOpinion.swift */; }; + 46F835A82B988AC400B2E81A /* BookParagraph.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46F835A72B988AC400B2E81A /* BookParagraph.swift */; }; + 46F835A92B988AC400B2E81A /* BookParagraph.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46F835A72B988AC400B2E81A /* BookParagraph.swift */; }; 46FF9D512B97EF390008AE35 /* RCPreset+Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46FF9D502B97EF390008AE35 /* RCPreset+Hashable.swift */; }; 46FF9D522B97EF390008AE35 /* RCPreset+Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46FF9D502B97EF390008AE35 /* RCPreset+Hashable.swift */; }; 46FF9D542B97EFD50008AE35 /* RCPreset+Static.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46FF9D532B97EFD50008AE35 /* RCPreset+Static.swift */; }; @@ -295,8 +299,8 @@ 463CEDF12B855D00001D951C /* TabButtonItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabButtonItem.swift; sourceTree = ""; }; 463CEDF92B8569BE001D951C /* BilbaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BilbaryView.swift; sourceTree = ""; }; 463EBC172B91CE9400BB9A57 /* Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utilities.swift; sourceTree = ""; }; - 463EBC1A2B91DD4800BB9A57 /* Book+BookCover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Book+BookCover.swift"; sourceTree = ""; }; - 463EBC1D2B91F7A400BB9A57 /* Book+BookContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Book+BookContent.swift"; sourceTree = ""; }; + 463EBC1A2B91DD4800BB9A57 /* BookCover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookCover.swift; sourceTree = ""; }; + 463EBC1D2B91F7A400BB9A57 /* BookContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookContent.swift; sourceTree = ""; }; 463EBC202B91FA3500BB9A57 /* Book+Equatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Book+Equatable.swift"; sourceTree = ""; }; 463EBC232B91FA5900BB9A57 /* Book+Hashable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Book+Hashable.swift"; sourceTree = ""; }; 463EBC262B92011400BB9A57 /* BookDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookDelegate.swift; sourceTree = ""; }; @@ -316,7 +320,7 @@ 464F8F242B913BB4009102CB /* NavigationSplitViewVisibility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationSplitViewVisibility.swift; sourceTree = ""; }; 464F8F262B913BB4009102CB /* UIWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIWindow.swift; sourceTree = ""; }; 464F8F272B913BB4009102CB /* Color.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = ""; }; - 464F8F352B913DB0009102CB /* Book+Static.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Book+Static.swift"; sourceTree = ""; }; + 464F8F352B913DB0009102CB /* Book+Formatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Book+Formatter.swift"; sourceTree = ""; }; 464F8F3E2B913EC4009102CB /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; 464F8F432B9144DA009102CB /* Notification.Name.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Notification.Name.swift; sourceTree = ""; }; 464F8F442B9144DA009102CB /* NotificationCenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationCenter.swift; sourceTree = ""; }; @@ -329,9 +333,9 @@ 4656F4312B8CD920004DD8BB /* BPopover.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BPopover.swift; sourceTree = ""; }; 4659B25A2B968DA6000BD724 /* Book+SwiftData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Book+SwiftData.swift"; sourceTree = ""; }; 4659B25D2B968DD1000BD724 /* Book+ReadingProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Book+ReadingProgress.swift"; sourceTree = ""; }; - 4659B2602B968E09000BD724 /* Book+Static+Local.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Book+Static+Local.swift"; sourceTree = ""; }; - 4659B2632B968E90000BD724 /* BookProgress+SwiftData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BookProgress+SwiftData.swift"; sourceTree = ""; }; - 4659B2662B968EBE000BD724 /* BookProgress+ComputedProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BookProgress+ComputedProperties.swift"; sourceTree = ""; }; + 4659B2602B968E09000BD724 /* Book+Static.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Book+Static.swift"; sourceTree = ""; }; + 4659B2632B968E90000BD724 /* BookSession+SwiftData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BookSession+SwiftData.swift"; sourceTree = ""; }; + 4659B2662B968EBE000BD724 /* BookSession+ComputedProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BookSession+ComputedProperties.swift"; sourceTree = ""; }; 465E0F0B2B94DCFB00D83BE5 /* BClickableModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BClickableModifier.swift; sourceTree = ""; }; 465E0F0E2B94E08300D83BE5 /* BReadSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BReadSize.swift; sourceTree = ""; }; 465E0F112B950AA200D83BE5 /* EPUBSwipeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EPUBSwipeView.swift; sourceTree = ""; }; @@ -357,15 +361,17 @@ 467FA7D32B962DAE00BA04DA /* BBookProgressUpdateModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BBookProgressUpdateModifier.swift; sourceTree = ""; }; 467FA7D92B96317700BA04DA /* BReadModel+Book.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BReadModel+Book.swift"; sourceTree = ""; }; 46A485B82B87392B00C6C7E3 /* EPUBView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EPUBView.swift; sourceTree = ""; }; + 46B170B92B9921B1009F5CF8 /* BlurTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurTransition.swift; sourceTree = ""; }; 46ED8AB82B9283AB00EA292A /* BViewModel+Methods.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BViewModel+Methods.swift"; sourceTree = ""; }; 46ED8ABB2B9289CD00EA292A /* BVMScreenSizeUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BVMScreenSizeUpdate.swift; sourceTree = ""; }; 46ED8ABE2B92945700EA292A /* LibraryToolbarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryToolbarView.swift; sourceTree = ""; }; 46ED8AC12B9294F200EA292A /* LibraryModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryModel.swift; sourceTree = ""; }; 46ED8AC42B92955500EA292A /* LibraryFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryFilter.swift; sourceTree = ""; }; 46ED8AC72B929BBF00EA292A /* LibraryContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibraryContentView.swift; sourceTree = ""; }; - 46ED8ACD2B92BA5200EA292A /* BookProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookProgress.swift; sourceTree = ""; }; - 46ED8AD02B92BD5A00EA292A /* Book+BookReadingStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Book+BookReadingStatus.swift"; sourceTree = ""; }; - 46ED8AD32B92BE4700EA292A /* Book+BookOpinion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Book+BookOpinion.swift"; sourceTree = ""; }; + 46ED8ACD2B92BA5200EA292A /* BookSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookSession.swift; sourceTree = ""; }; + 46ED8AD02B92BD5A00EA292A /* BookReadingStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookReadingStatus.swift; sourceTree = ""; }; + 46ED8AD32B92BE4700EA292A /* BookOpinion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookOpinion.swift; sourceTree = ""; }; + 46F835A72B988AC400B2E81A /* BookParagraph.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookParagraph.swift; sourceTree = ""; }; 46FF9D502B97EF390008AE35 /* RCPreset+Hashable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RCPreset+Hashable.swift"; sourceTree = ""; }; 46FF9D532B97EFD50008AE35 /* RCPreset+Static.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RCPreset+Static.swift"; sourceTree = ""; }; 46FF9D562B97F0370008AE35 /* RCPreset+Equatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RCPreset+Equatable.swift"; sourceTree = ""; }; @@ -570,6 +576,7 @@ 4656F4312B8CD920004DD8BB /* BPopover.swift */, 462E0DF42B88A90D001D2161 /* Protocols */, 462E0DC42B877C78001D2161 /* RBarRow.swift */, + 46B170B92B9921B1009F5CF8 /* BlurTransition.swift */, ); path = Components; sourceTree = ""; @@ -673,9 +680,14 @@ isa = PBXGroup; children = ( 4659B26A2B968ED7000BD724 /* Book */, - 4659B2692B968ECB000BD724 /* BookProgress */, + 4659B2692B968ECB000BD724 /* BookSession */, + 463EBC1D2B91F7A400BB9A57 /* BookContent.swift */, + 463EBC1A2B91DD4800BB9A57 /* BookCover.swift */, 463EBC262B92011400BB9A57 /* BookDelegate.swift */, 464310202B8F852300B5B2CD /* BookError.swift */, + 46ED8AD32B92BE4700EA292A /* BookOpinion.swift */, + 46F835A72B988AC400B2E81A /* BookParagraph.swift */, + 46ED8AD02B92BD5A00EA292A /* BookReadingStatus.swift */, 465E0F172B9514C200D83BE5 /* Books.swift */, ); path = Book; @@ -729,29 +741,25 @@ path = workflows; sourceTree = ""; }; - 4659B2692B968ECB000BD724 /* BookProgress */ = { + 4659B2692B968ECB000BD724 /* BookSession */ = { isa = PBXGroup; children = ( - 46ED8ACD2B92BA5200EA292A /* BookProgress.swift */, - 4659B2662B968EBE000BD724 /* BookProgress+ComputedProperties.swift */, - 4659B2632B968E90000BD724 /* BookProgress+SwiftData.swift */, + 46ED8ACD2B92BA5200EA292A /* BookSession.swift */, + 4659B2662B968EBE000BD724 /* BookSession+ComputedProperties.swift */, + 4659B2632B968E90000BD724 /* BookSession+SwiftData.swift */, ); - path = BookProgress; + path = BookSession; sourceTree = ""; }; 4659B26A2B968ED7000BD724 /* Book */ = { isa = PBXGroup; children = ( 463CEDD72B854579001D951C /* Book.swift */, - 463EBC1D2B91F7A400BB9A57 /* Book+BookContent.swift */, - 463EBC1A2B91DD4800BB9A57 /* Book+BookCover.swift */, - 46ED8AD32B92BE4700EA292A /* Book+BookOpinion.swift */, - 46ED8AD02B92BD5A00EA292A /* Book+BookReadingStatus.swift */, 463EBC202B91FA3500BB9A57 /* Book+Equatable.swift */, 463EBC232B91FA5900BB9A57 /* Book+Hashable.swift */, 4659B25D2B968DD1000BD724 /* Book+ReadingProgress.swift */, - 464F8F352B913DB0009102CB /* Book+Static.swift */, - 4659B2602B968E09000BD724 /* Book+Static+Local.swift */, + 464F8F352B913DB0009102CB /* Book+Formatter.swift */, + 4659B2602B968E09000BD724 /* Book+Static.swift */, 4659B25A2B968DA6000BD724 /* Book+SwiftData.swift */, 464C8B3F2B96A29C00876EAD /* Book+Metadata.swift */, ); @@ -1082,7 +1090,7 @@ 460B36272B7B7D8300D99FFF /* BilbaryView.swift in Sources */, 462E0DF72B88B83E001D2161 /* RCPreset.swift in Sources */, 464F8F2E2B913BB4009102CB /* UIWindow.swift in Sources */, - 46ED8AD42B92BE4700EA292A /* Book+BookOpinion.swift in Sources */, + 46ED8AD42B92BE4700EA292A /* BookOpinion.swift in Sources */, 462D501D2B8409B2001ED44E /* ReadTime+Extensions.swift in Sources */, 462E0DD82B87E9B1001D2161 /* PressActions.swift in Sources */, 4656F4322B8CD920004DD8BB /* RButton.swift in Sources */, @@ -1101,7 +1109,7 @@ 462D502B2B841405001ED44E /* RCustomizerModel+RCustomizerModelStorage.swift in Sources */, 465E0F212B95B65700D83BE5 /* BBookInfoCardHeaderImage.swift in Sources */, 462E0DC72B879EBF001D2161 /* BottomBar.swift in Sources */, - 463EBC1B2B91DD4800BB9A57 /* Book+BookCover.swift in Sources */, + 463EBC1B2B91DD4800BB9A57 /* BookCover.swift in Sources */, 46406D502B97B98200500A8F /* UIContentSizeCategory.swift in Sources */, 46FF9D512B97EF390008AE35 /* RCPreset+Hashable.swift in Sources */, 465E0F372B95D5BE00D83BE5 /* BBookInfoCardMainHeader.swift in Sources */, @@ -1111,7 +1119,7 @@ 4606E06F2B92321D00D77205 /* BConstants.swift in Sources */, 467FA7CC2B96269E00BA04DA /* BReadModel.swift in Sources */, 465E0F0F2B94E08300D83BE5 /* BReadSize.swift in Sources */, - 4659B2672B968EBE000BD724 /* BookProgress+ComputedProperties.swift in Sources */, + 4659B2672B968EBE000BD724 /* BookSession+ComputedProperties.swift in Sources */, 467FA7CA2B96097800BA04DA /* BStreakValidation.swift in Sources */, 463CEDDA2B8545E0001D951C /* BookInformationView.swift in Sources */, 46406D562B97D01000500A8F /* RCustomizerModel+UINavigation.swift in Sources */, @@ -1124,17 +1132,17 @@ 464C8B402B96A29C00876EAD /* Book+Metadata.swift in Sources */, 465E0F2D2B95BC6600D83BE5 /* BBookInfoCardHeaderImageStyle.swift in Sources */, 46ED8AC52B92955500EA292A /* LibraryFilter.swift in Sources */, - 46ED8AD12B92BD5A00EA292A /* Book+BookReadingStatus.swift in Sources */, + 46ED8AD12B92BD5A00EA292A /* BookReadingStatus.swift in Sources */, 465E0F122B950AA200D83BE5 /* EPUBSwipeView.swift in Sources */, 465E0F272B95BADF00D83BE5 /* BBookInfoProgressCard.swift in Sources */, 465E0F342B95D5A100D83BE5 /* BBookInfoCardMain.swift in Sources */, 462DD8E22B84946500ECF5D2 /* UNHelper.swift in Sources */, - 464F8F362B913DB0009102CB /* Book+Static.swift in Sources */, + 464F8F362B913DB0009102CB /* Book+Formatter.swift in Sources */, 46ED8AC22B9294F200EA292A /* LibraryModel.swift in Sources */, 462E0DDE2B8893A4001D2161 /* RReadingTimeMenu.swift in Sources */, 463CEDD22B853AE9001D951C /* BViewModel.swift in Sources */, 463CEDCB2B85018E001D951C /* ContentView.swift in Sources */, - 4659B2642B968E90000BD724 /* BookProgress+SwiftData.swift in Sources */, + 4659B2642B968E90000BD724 /* BookSession+SwiftData.swift in Sources */, 462D50172B8407D1001ED44E /* ReadTime.swift in Sources */, 46ED8AB92B9283AB00EA292A /* BViewModel+Methods.swift in Sources */, 464F8F452B9144DA009102CB /* Notification.Name.swift in Sources */, @@ -1144,6 +1152,7 @@ 464F8F3F2B913EC4009102CB /* Logger.swift in Sources */, 462D50152B8407AD001ED44E /* GoalModelStorage.swift in Sources */, 463CEDC02B84DAAC001D951C /* PermissionsModel.swift in Sources */, + 46B170BA2B9921B1009F5CF8 /* BlurTransition.swift in Sources */, 462E0DD52B87E216001D2161 /* RActionable.swift in Sources */, 46FF9D542B97EFD50008AE35 /* RCPreset+Static.swift in Sources */, 462E0DC52B877C78001D2161 /* RBarRow.swift in Sources */, @@ -1155,6 +1164,7 @@ 463CEDD42B853E59001D951C /* BookView.swift in Sources */, 462D502D2B8416AE001ED44E /* RCColor.swift in Sources */, 467FA7C62B95F7B500BA04DA /* BStreakPopover.swift in Sources */, + 46F835A82B988AC400B2E81A /* BookParagraph.swift in Sources */, 4659B25B2B968DA6000BD724 /* Book+SwiftData.swift in Sources */, 4659B25E2B968DD1000BD724 /* Book+ReadingProgress.swift in Sources */, 462D502F2B841794001ED44E /* RCColor+Color.swift in Sources */, @@ -1168,15 +1178,15 @@ 462D50102B840365001ED44E /* GoalModel.swift in Sources */, 462D501B2B84084A001ED44E /* GoalStreakDuration+String.swift in Sources */, 462D50272B8413D6001ED44E /* CustomizerView.swift in Sources */, - 46ED8ACE2B92BA5200EA292A /* BookProgress.swift in Sources */, + 46ED8ACE2B92BA5200EA292A /* BookSession.swift in Sources */, 462D50332B842056001ED44E /* RCFontFace+Font.swift in Sources */, 46ED8ABF2B92945700EA292A /* LibraryToolbarView.swift in Sources */, 462E0DFD2B8CD5A1001D2161 /* BViewModel+ViewModelStorage.swift in Sources */, 464C8B492B96ABA400876EAD /* BBookProgressCircle.swift in Sources */, 467FA7CE2B96291E00BA04DA /* DataModel.swift in Sources */, 463EBC242B91FA5900BB9A57 /* Book+Hashable.swift in Sources */, - 4659B2612B968E09000BD724 /* Book+Static+Local.swift in Sources */, - 463EBC1E2B91F7A400BB9A57 /* Book+BookContent.swift in Sources */, + 4659B2612B968E09000BD724 /* Book+Static.swift in Sources */, + 463EBC1E2B91F7A400BB9A57 /* BookContent.swift in Sources */, 467FA7DA2B96317700BA04DA /* BReadModel+Book.swift in Sources */, 464F8F4D2B9144F7009102CB /* EPUBDocument.swift in Sources */, 462D50232B841236001ED44E /* StreakWeekRowView.swift in Sources */, @@ -1197,7 +1207,7 @@ 463EBC3E2B9223F300BB9A57 /* BViewModel+ColumnType.swift in Sources */, 463EBC192B91CE9400BB9A57 /* Utilities.swift in Sources */, 4659B25F2B968DD1000BD724 /* Book+ReadingProgress.swift in Sources */, - 464F8F372B913DB0009102CB /* Book+Static.swift in Sources */, + 464F8F372B913DB0009102CB /* Book+Formatter.swift in Sources */, 46ED8AC02B92945700EA292A /* LibraryToolbarView.swift in Sources */, 465E0F102B94E08300D83BE5 /* BReadSize.swift in Sources */, 465E0F132B950AA200D83BE5 /* EPUBSwipeView.swift in Sources */, @@ -1233,12 +1243,14 @@ 464C8B442B96AAF300876EAD /* BBookInformationPopover.swift in Sources */, 46406D512B97B98200500A8F /* UIContentSizeCategory.swift in Sources */, 46406D542B97CC5E00500A8F /* View.swift in Sources */, - 463EBC1F2B91F7A400BB9A57 /* Book+BookContent.swift in Sources */, + 46B170BB2B9921B1009F5CF8 /* BlurTransition.swift in Sources */, + 463EBC1F2B91F7A400BB9A57 /* BookContent.swift in Sources */, 463EBC222B91FA3500BB9A57 /* Book+Equatable.swift in Sources */, 465E0F162B950BC100D83BE5 /* EPUBScrollableContent.swift in Sources */, - 46ED8AD22B92BD5A00EA292A /* Book+BookReadingStatus.swift in Sources */, + 46ED8AD22B92BD5A00EA292A /* BookReadingStatus.swift in Sources */, 463EBC392B9223E300BB9A57 /* GoalStreakDuration.swift in Sources */, 46406D5A2B97DCB900500A8F /* GoalStreakDuration+Tag.swift in Sources */, + 46F835A92B988AC400B2E81A /* BookParagraph.swift in Sources */, 463EBC312B9223D900BB9A57 /* RCPreset.swift in Sources */, 46FF9D522B97EF390008AE35 /* RCPreset+Hashable.swift in Sources */, 464F8F2B2B913BB4009102CB /* NavigationSplitViewVisibility.swift in Sources */, @@ -1247,13 +1259,13 @@ 463EBC2F2B9223D900BB9A57 /* RCustomizerModel.swift in Sources */, 465E0F382B95D5BE00D83BE5 /* BBookInfoCardMainHeader.swift in Sources */, 463EBC382B9223E300BB9A57 /* GoalStreakDuration+String.swift in Sources */, - 46ED8AD52B92BE4700EA292A /* Book+BookOpinion.swift in Sources */, + 46ED8AD52B92BE4700EA292A /* BookOpinion.swift in Sources */, 465E0F3E2B95D75000D83BE5 /* BBookInfoCardStyle.swift in Sources */, 467FA7D52B962DAE00BA04DA /* BBookProgressUpdateModifier.swift in Sources */, - 4659B2652B968E90000BD724 /* BookProgress+SwiftData.swift in Sources */, + 4659B2652B968E90000BD724 /* BookSession+SwiftData.swift in Sources */, 465E0F322B95D53400D83BE5 /* BBookCoverViewStyle.swift in Sources */, 464F8F402B913EC4009102CB /* Logger.swift in Sources */, - 463EBC1C2B91DD4800BB9A57 /* Book+BookCover.swift in Sources */, + 463EBC1C2B91DD4800BB9A57 /* BookCover.swift in Sources */, 4606E0702B92321D00D77205 /* BConstants.swift in Sources */, 463EBC3C2B9223EC00BB9A57 /* ReadTime+Extensions.swift in Sources */, 464F8F482B9144DA009102CB /* NotificationCenter.swift in Sources */, @@ -1263,7 +1275,7 @@ 46ED8AC92B929BBF00EA292A /* LibraryContentView.swift in Sources */, 464F8F4E2B9144F7009102CB /* EPUBDocument.swift in Sources */, 463EBC402B9223F300BB9A57 /* BViewModel+ViewModelStorage.swift in Sources */, - 46ED8ACF2B92BA5200EA292A /* BookProgress.swift in Sources */, + 46ED8ACF2B92BA5200EA292A /* BookSession.swift in Sources */, 462E0DFF2B8CD6EE001D2161 /* OnboardingView.swift in Sources */, 464C8B4A2B96ABA400876EAD /* BBookProgressCircle.swift in Sources */, 46ED8AC32B9294F200EA292A /* LibraryModel.swift in Sources */, @@ -1271,8 +1283,8 @@ 463EBC3D2B9223EF00BB9A57 /* UNHelper.swift in Sources */, 463EBC302B9223D900BB9A57 /* RCColor.swift in Sources */, 463EBC352B9223D900BB9A57 /* RCFontFace.swift in Sources */, - 4659B2682B968EBE000BD724 /* BookProgress+ComputedProperties.swift in Sources */, - 4659B2622B968E09000BD724 /* Book+Static+Local.swift in Sources */, + 4659B2682B968EBE000BD724 /* BookSession+ComputedProperties.swift in Sources */, + 4659B2622B968E09000BD724 /* Book+Static.swift in Sources */, 463EBC252B91FA5900BB9A57 /* Book+Hashable.swift in Sources */, 46FF9D602B97FFEC0008AE35 /* BReadModel+BRMStorage.swift in Sources */, 467FA7CF2B96291E00BA04DA /* DataModel.swift in Sources */, @@ -1433,7 +1445,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = Bilbary/Bilbary.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - CURRENT_PROJECT_VERSION = 4; + CURRENT_PROJECT_VERSION = 5; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = NP94WB3P75; ENABLE_HARDENED_RUNTIME = YES; @@ -1458,7 +1470,7 @@ "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; LINKER_DISPLAYS_MANGLED_NAMES = YES; MACOSX_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 0.0.4; + MARKETING_VERSION = 0.0.5; PRODUCT_BUNDLE_IDENTIFIER = studio.aemi.ada23.trial.gcqd.0.iits.Read; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; @@ -1479,7 +1491,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = Bilbary/Bilbary.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - CURRENT_PROJECT_VERSION = 4; + CURRENT_PROJECT_VERSION = 5; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = NP94WB3P75; ENABLE_HARDENED_RUNTIME = YES; @@ -1504,7 +1516,7 @@ "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; LINKER_DISPLAYS_MANGLED_NAMES = YES; MACOSX_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 0.0.4; + MARKETING_VERSION = 0.0.5; PRODUCT_BUNDLE_IDENTIFIER = studio.aemi.ada23.trial.gcqd.0.iits.Read; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; diff --git a/Bilbary/Extensions/EPUB/EPUBDocument.swift b/Bilbary/Extensions/EPUB/EPUBDocument.swift index 889eecb..328dc78 100644 --- a/Bilbary/Extensions/EPUB/EPUBDocument.swift +++ b/Bilbary/Extensions/EPUB/EPUBDocument.swift @@ -16,27 +16,45 @@ extension EPUBDocument { Logger.epub.error("EPUB bundle at unresolved.") return [] } - var items = self.spine.items.compactMap { item in + return self.spine.items.compactMap { item in return if let manifestItem = self.manifest.items.first(where: { (_, value) in item.idref == value.id }) { bundle.bundleURL.appendingPathComponent(manifestItem.value.path) } else { nil } } - return items } public var content: [String] { - self.contentFiles.flatMap { url in - do { - return try SwiftSoup - .parse(String(contentsOf: url, encoding: .utf8)) - .select("p,h1,h2,h3,h4,h5,h6,pre") - .compactMap { try $0.text().trimmingCharacters(in: .whitespacesAndNewlines) } - .filter { !$0.isEmpty } - } catch { - Logger.epub.error("XML File not loaded") + self.contentFiles + .filter { + let strippedSections: [String] = [ + "title", + "section", + "cover", + "colophon", + "imprint", + "endnote", + "copyright" + ] + let lastPathComponent = $0.deletingPathExtension().lastPathComponent.lowercased() + for strippedSection in strippedSections { + if lastPathComponent.contains(strippedSection) { + return false + } + } + return true + } + .flatMap { url in + do { + return try SwiftSoup + .parse(String(contentsOf: url, encoding: .utf8)) + .select("p,h1,h2,h3,h4,h5,h6,pre") + .compactMap { try $0.text().trimmingCharacters(in: .whitespacesAndNewlines) } + .filter { !$0.isEmpty } + } catch { + Logger.epub.error("XML File not loaded") + } + return [] } - return [] - } } } diff --git a/Bilbary/Models/Book/Book/Book+BookContent.swift b/Bilbary/Models/Book/Book/Book+BookContent.swift deleted file mode 100644 index 6c51a76..0000000 --- a/Bilbary/Models/Book/Book/Book+BookContent.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// Book+BookContent.swift -// Bilbary -// -// Created by Guillaume Coquard on 01/03/24. -// - -import Foundation - -extension Book { - struct BookContent { - - private let rawContent: [String] - - init(from content: [String]) { - self.rawContent = content - } - - public var strings: [String] { - self.rawContent - } - } -} diff --git a/Bilbary/Models/Book/Book/Book+BookCover.swift b/Bilbary/Models/Book/Book/Book+BookCover.swift deleted file mode 100644 index aa57e3d..0000000 --- a/Bilbary/Models/Book/Book/Book+BookCover.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// Book+BookCover.swift -// Bilbary -// -// Created by Guillaume Coquard on 01/03/24. -// - -import Foundation -import OSLog -import UIKit -import SwiftUI - -extension Book { - - struct BookCover { - - public let url: URL - - init?(url: URL?) { - guard let url = url else { - Logger.book.warning("URL is nil.") - return nil - } - self.url = url - } - - public func `as`(_ output: T.Type) async -> T? { - switch output { - case is Data.Type: - return await Util.getImageData(from: self.url) as? T - case is UIImage.Type: - return await Util.getUIImage(from: self.url) as? T - case is Image.Type: - return await Util.getImage(from: self.url) as? T - default: - return nil - } - } - - public var asyncImage: some View { - AsyncImage( - url: self.url, - content: { image in - image - .resizable() - }, - placeholder: { - Rectangle() - .foregroundStyle(.thinMaterial) - } - ) - } - } -} diff --git a/Bilbary/Models/Book/Book/Book+BookOpinion.swift b/Bilbary/Models/Book/Book/Book+BookOpinion.swift deleted file mode 100644 index 7d76dc1..0000000 --- a/Bilbary/Models/Book/Book/Book+BookOpinion.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// Book+BookOpinion.swift -// Bilbary -// -// Created by Guillaume Coquard on 02/03/24. -// - -import Foundation - -extension Book { - - enum BookOpinion: Int, CaseIterable, Codable { - case undefined = 0 - case liked = 1 - case skipped = 2 - case disliked = 3 - - @discardableResult - mutating func toggle() -> Self { - if self != .liked { - self = .liked - } else { - self = .undefined - } - return self - } - - var icon: String { - self == .liked ? "heart.fill" : "heart" - } - } - -} diff --git a/Bilbary/Models/Book/Book/Book+BookReadingStatus.swift b/Bilbary/Models/Book/Book/Book+BookReadingStatus.swift deleted file mode 100644 index ccf0b46..0000000 --- a/Bilbary/Models/Book/Book/Book+BookReadingStatus.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// Book+BookReadingStatus.swift -// Bilbary -// -// Created by Guillaume Coquard on 02/03/24. -// - -import Foundation - -extension Book { - - enum BookReadingStatus: Int, CaseIterable, Codable { - case undefined = 0 - case started = 1 - case finished = 2 - case abandonned = 3 - } - -} diff --git a/Bilbary/Models/Book/Book/Book+Equatable.swift b/Bilbary/Models/Book/Book/Book+Equatable.swift index 93ba4d4..a37cea2 100644 --- a/Bilbary/Models/Book/Book/Book+Equatable.swift +++ b/Bilbary/Models/Book/Book/Book+Equatable.swift @@ -9,6 +9,7 @@ import Foundation extension Book: Equatable { static func == (lhs: Book, rhs: Book) -> Bool { - lhs.url == rhs.url + lhs.url == rhs.url && + lhs.content == rhs.content } } diff --git a/Bilbary/Models/Book/Book/Book+Formatter.swift b/Bilbary/Models/Book/Book/Book+Formatter.swift new file mode 100644 index 0000000..a7b96c2 --- /dev/null +++ b/Bilbary/Models/Book/Book/Book+Formatter.swift @@ -0,0 +1,40 @@ +// +// Book+Static.swift +// Bilbary +// +// Created by Guillaume Coquard on 29/02/24. +// + +import Foundation +import OSLog +import EPUBKit + +// MARK: Formatter +extension Book { + + static private var timeIntervalFomatter: DateComponentsFormatter { + let formatter = DateComponentsFormatter() + formatter.unitsStyle = .abbreviated + formatter.zeroFormattingBehavior = .dropAll + formatter.allowedUnits = [.hour, .minute] + return formatter + } + + static private var dateFormatter: DateFormatter { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: Locale.preferredLanguages[0]) + formatter.dateStyle = .short + return formatter + } + + static func format(_ component: Any) -> String { + return switch component.self { + case is TimeInterval: + Self.timeIntervalFomatter.string(for: component) ?? "" + case is Date: + Self.dateFormatter.string(from: component as! Date) + default: + String(describing: component) + } + } +} diff --git a/Bilbary/Models/Book/Book/Book+Hashable.swift b/Bilbary/Models/Book/Book/Book+Hashable.swift index 9b112ce..d926e5f 100644 --- a/Bilbary/Models/Book/Book/Book+Hashable.swift +++ b/Bilbary/Models/Book/Book/Book+Hashable.swift @@ -11,5 +11,7 @@ extension Book: Hashable { hasher.combine(self.title) hasher.combine(self.author) hasher.combine(self.publisher) + hasher.combine(self.content) + hasher.combine(self.readingSessions) } } diff --git a/Bilbary/Models/Book/Book/Book+Static+Local.swift b/Bilbary/Models/Book/Book/Book+Static+Local.swift deleted file mode 100644 index ac98f8c..0000000 --- a/Bilbary/Models/Book/Book/Book+Static+Local.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// Book+Static+Local.swift -// Bilbary -// -// Created by Guillaume Coquard on 05/03/24. -// - -import SwiftUI - -import UniformTypeIdentifiers -extension Book { - - public static var localBooksUrls: [URL] { - Bundle.main.urls( - forResourcesWithExtension: UTType.epub.preferredFilenameExtension, - subdirectory: nil - ) ?? [] - } - - public static var localBooks: [Book] { - self.localBooksUrls - .compactMap({Book(from: $0)}) - } - - public static func randomBooksURL(_ count: Int) -> [URL] { - let uniqueURLs = Set(Self.localBooksUrls) - let finalCount = min(count, uniqueURLs.count) - return Array(uniqueURLs.shuffled().prefix(finalCount)) - } - - public static func books(from urls: [URL], count: Int = 3) -> [Book] { - urls.shuffled().prefix(count).compactMap { - Book(from: $0) - } - } -} diff --git a/Bilbary/Models/Book/Book/Book+Static.swift b/Bilbary/Models/Book/Book/Book+Static.swift index d1d08f8..431d65d 100644 --- a/Bilbary/Models/Book/Book/Book+Static.swift +++ b/Bilbary/Models/Book/Book/Book+Static.swift @@ -1,41 +1,40 @@ // -// Book+Static.swift +// Book+Static+Local.swift // Bilbary // -// Created by Guillaume Coquard on 29/02/24. +// Created by Guillaume Coquard on 05/03/24. // -import Foundation -import OSLog +import SwiftUI import EPUBKit +import UniformTypeIdentifiers extension Book { + static internal let parser: EPUBParser = .init() static internal let delegate: BookDelegate = .default - static private var timeIntervalFomatter: DateComponentsFormatter { - let formatter = DateComponentsFormatter() - formatter.unitsStyle = .abbreviated - formatter.zeroFormattingBehavior = .dropAll - formatter.allowedUnits = [.hour, .minute] - return formatter + public static var localBooksUrls: [URL] { + Bundle.main.urls( + forResourcesWithExtension: UTType.epub.preferredFilenameExtension, + subdirectory: nil + ) ?? [] + } + + public static var localBooks: [Book] { + self.localBooksUrls + .compactMap({Book(from: $0)}) } - static private var dateFormatter: DateFormatter { - let formatter = DateFormatter() - formatter.locale = Locale(identifier: Locale.preferredLanguages[0]) - formatter.dateStyle = .short - return formatter + public static func randomBooksURL(_ count: Int) -> [URL] { + let uniqueURLs = Set(Self.localBooksUrls) + let finalCount = min(count, uniqueURLs.count) + return Array(uniqueURLs.shuffled().prefix(finalCount)) } - static func format(_ component: Any) -> String { - return switch component.self { - case is TimeInterval: - Self.timeIntervalFomatter.string(for: component) ?? "" - case is Date: - Self.dateFormatter.string(from: component as! Date) - default: - String(describing: component) + public static func books(from urls: [URL], count: Int = 3) -> [Book] { + urls.shuffled().prefix(count).compactMap { + Book(from: $0) } } } diff --git a/Bilbary/Models/Book/Book/Book.swift b/Bilbary/Models/Book/Book/Book.swift index a7df7f2..4d96e15 100644 --- a/Bilbary/Models/Book/Book/Book.swift +++ b/Bilbary/Models/Book/Book/Book.swift @@ -22,24 +22,20 @@ final class Book: Identifiable { @Transient internal var document: EPUBDocument? @Transient public private(set) var cover: BookCover? - @Transient public private(set) var content: BookContent = .init(from: []) + @Transient public private(set) var content: BookContent = .init() + @Transient public var currentParagraph: Int? - @Relationship(inverse: \BookProgress.book) - public var readingSessions: [BookProgress] + public var readingSessions: [BookSession] = [] public var readingStatus: BookReadingStatus public var opinion: BookOpinion init?(from url: URL?, id: UUID = .init()) { - self.id = id - guard let url = url else { Logger.book.warning("<\(String(describing: url))> is nil") return nil } - self.url = url - do { Self.parser.delegate = Self.delegate self.document = try Self.parser.parse(documentAt: url) @@ -48,10 +44,9 @@ final class Book: Identifiable { return nil } - self.cover = BookCover(url: document!.cover) - self.content = BookContent(from: document!.content) - - self.readingSessions = .init() + self.cover = BookCover(document!.cover) + self.content = BookContent(document!.content) + self.currentParagraph = self.content.formatted.first?.index self.readingStatus = BookReadingStatus.undefined self.opinion = BookOpinion.undefined } diff --git a/Bilbary/Models/Book/BookContent.swift b/Bilbary/Models/Book/BookContent.swift new file mode 100644 index 0000000..f9e5387 --- /dev/null +++ b/Bilbary/Models/Book/BookContent.swift @@ -0,0 +1,25 @@ +// +// Book+BookContent.swift +// Bilbary +// +// Created by Guillaume Coquard on 01/03/24. +// + +import Foundation + +struct BookContent: Hashable { + + private let rawContent: [String] + + public let formatted: [BookParagraph] + + init(_ content: [String] = []) { + self.rawContent = content + self.formatted = BookParagraph.from(self.rawContent) + } + +} + +extension BookContent: Equatable { + +} diff --git a/Bilbary/Models/Book/BookCover.swift b/Bilbary/Models/Book/BookCover.swift new file mode 100644 index 0000000..31b1941 --- /dev/null +++ b/Bilbary/Models/Book/BookCover.swift @@ -0,0 +1,51 @@ +// +// Book+BookCover.swift +// Bilbary +// +// Created by Guillaume Coquard on 01/03/24. +// + +import Foundation +import OSLog +import UIKit +import SwiftUI + +struct BookCover { + + public let url: URL + + init?(_ url: URL?) { + guard let url = url else { + Logger.book.warning("URL is nil.") + return nil + } + self.url = url + } + + public func `as`(_ output: T.Type) async -> T? { + switch output { + case is Data.Type: + return await Util.getImageData(from: self.url) as? T + case is UIImage.Type: + return await Util.getUIImage(from: self.url) as? T + case is Image.Type: + return await Util.getImage(from: self.url) as? T + default: + return nil + } + } + + public var asyncImage: some View { + AsyncImage( + url: self.url, + content: { image in + image + .resizable() + }, + placeholder: { + Rectangle() + .foregroundStyle(.thinMaterial) + } + ) + } +} diff --git a/Bilbary/Models/Book/BookOpinion.swift b/Bilbary/Models/Book/BookOpinion.swift new file mode 100644 index 0000000..4aaa1ba --- /dev/null +++ b/Bilbary/Models/Book/BookOpinion.swift @@ -0,0 +1,29 @@ +// +// Book+BookOpinion.swift +// Bilbary +// +// Created by Guillaume Coquard on 02/03/24. +// + +import Foundation + +enum BookOpinion: Int, CaseIterable, Codable { + case undefined = 0 + case liked = 1 + case skipped = 2 + case disliked = 3 + + @discardableResult + mutating func toggle() -> Self { + if self != .liked { + self = .liked + } else { + self = .undefined + } + return self + } + + var icon: String { + self == .liked ? "heart.fill" : "heart" + } +} diff --git a/Bilbary/Models/Book/BookParagraph.swift b/Bilbary/Models/Book/BookParagraph.swift new file mode 100644 index 0000000..946180e --- /dev/null +++ b/Bilbary/Models/Book/BookParagraph.swift @@ -0,0 +1,98 @@ +// +// Book+BookParagraph.swift +// Bilbary +// +// Created by Guillaume Coquard on 06/03/24. +// + +import Foundation + +struct BookParagraph: Identifiable, Hashable, Equatable { + static private let averageWordCountPerMinute: Int = 180 + static private func getWords(_ text: String) -> Int { + text + .split(separator: " ") + .map { $0.trimmingCharacters(in: .punctuationCharacters) } + .filter({ !$0.isEmpty }) + .count + } + static private func littlePieces(_ text: String) -> [String] { + // Regular expression to match sentence endings along with the sentence + let pattern = #"(.*?[.!?])\s?"# + let regex = try! NSRegularExpression(pattern: pattern) + let range = NSRange(location: 0, length: text.utf16.count) + + var sentences = [String]() + regex.enumerateMatches(in: text, options: [], range: range) { (match, _, _) in + if let matchRange = match?.range(at: 1), let swiftRange = Range(matchRange, in: text) { + let sentence = String(text[swiftRange]) + sentences.append(sentence) + } + } + + var chunks: [String] = [] + var currentChunk: [String] = [] + var currentWordCount = 0 + + for sentence in sentences { + let words = sentence.split(separator: " ") + let sentenceWordCount = words.count + + // If adding the current sentence would exceed the limit, add the current chunk to chunks and start a new one + if currentWordCount + sentenceWordCount > Self.averageWordCountPerMinute { + chunks.append(currentChunk.joined(separator: " ")) + currentChunk = [] + currentWordCount = 0 + } + + // Add the sentence to the current chunk and update the word count + currentChunk.append(sentence) + currentWordCount += sentenceWordCount + } + + // Don't forget to add the last chunk if it's not empty + if !currentChunk.isEmpty { + chunks.append(currentChunk.joined(separator: " ")) + } + + return chunks + } + private let wrappedValue: String + public let id: UUID = UUID() + public let index: Int + public let wordCount: Int + public let approximatedTime: TimeInterval + public var content: String { + self.wrappedValue + } + init(_ text: String, index: Int) { + self.index = index + self.wrappedValue = text + .trimmingCharacters(in: .whitespacesAndNewlines) + self.wordCount = self.wrappedValue + .split(separator: " ") + .map { $0.trimmingCharacters(in: .punctuationCharacters) } + .filter({ !$0.isEmpty }) + .count + self.approximatedTime = (Double(self.wordCount) * 60.0) / Double(Self.averageWordCountPerMinute) + } + static func from(_ stringLiterals: String...) -> [Self] { + self.from( stringLiterals ) + } + static func from(_ strings: [String]) -> [Self] { + var paragraphs: [Self] = [] + let elements = strings + .map { literal in + literal + .split(separator: "\n") + .flatMap { part in + Self.littlePieces(String(part)) + } + } + .flatMap { $0 } + for (index, element) in elements.enumerated() { + paragraphs.append(.init(element, index: index)) + } + return paragraphs + } +} diff --git a/Bilbary/Models/Book/BookReadingStatus.swift b/Bilbary/Models/Book/BookReadingStatus.swift new file mode 100644 index 0000000..ace4935 --- /dev/null +++ b/Bilbary/Models/Book/BookReadingStatus.swift @@ -0,0 +1,15 @@ +// +// Book+BookReadingStatus.swift +// Bilbary +// +// Created by Guillaume Coquard on 02/03/24. +// + +import Foundation + +enum BookReadingStatus: Int, CaseIterable, Codable { + case undefined = 0 + case started = 1 + case finished = 2 + case abandonned = 3 +} diff --git a/Bilbary/Models/Book/BookProgress/BookProgress+ComputedProperties.swift b/Bilbary/Models/Book/BookSession/BookSession+ComputedProperties.swift similarity index 71% rename from Bilbary/Models/Book/BookProgress/BookProgress+ComputedProperties.swift rename to Bilbary/Models/Book/BookSession/BookSession+ComputedProperties.swift index a2bd703..3efbc44 100644 --- a/Bilbary/Models/Book/BookProgress/BookProgress+ComputedProperties.swift +++ b/Bilbary/Models/Book/BookSession/BookSession+ComputedProperties.swift @@ -1,5 +1,5 @@ // -// BookProgress+ComputedProperties.swift +// BookSession+ComputedProperties.swift // Bilbary // // Created by Guillaume Coquard on 05/03/24. @@ -7,7 +7,7 @@ import Foundation -extension BookProgress { +extension BookSession { var progress: CGFloat { self.endProgress - self.startProgress } diff --git a/Bilbary/Models/Book/BookProgress/BookProgress+SwiftData.swift b/Bilbary/Models/Book/BookSession/BookSession+SwiftData.swift similarity index 95% rename from Bilbary/Models/Book/BookProgress/BookProgress+SwiftData.swift rename to Bilbary/Models/Book/BookSession/BookSession+SwiftData.swift index 14c7389..2da1a6e 100644 --- a/Bilbary/Models/Book/BookProgress/BookProgress+SwiftData.swift +++ b/Bilbary/Models/Book/BookSession/BookSession+SwiftData.swift @@ -1,5 +1,5 @@ // -// BookProgress+SwiftData.swift +// BookSession+SwiftData.swift // Bilbary // // Created by Guillaume Coquard on 05/03/24. @@ -9,7 +9,7 @@ import Foundation import SwiftData import OSLog -extension BookProgress { +extension BookSession { @discardableResult public func update(to book: Book) -> Self { diff --git a/Bilbary/Models/Book/BookProgress/BookProgress.swift b/Bilbary/Models/Book/BookSession/BookSession.swift similarity index 86% rename from Bilbary/Models/Book/BookProgress/BookProgress.swift rename to Bilbary/Models/Book/BookSession/BookSession.swift index 0971b22..f711e47 100644 --- a/Bilbary/Models/Book/BookProgress/BookProgress.swift +++ b/Bilbary/Models/Book/BookSession/BookSession.swift @@ -10,16 +10,20 @@ import SwiftData import OSLog @Model -final class BookProgress: Identifiable, Hashable { +final class BookSession: Identifiable, Hashable { @Attribute(.unique) public let id: UUID = UUID() + @Relationship(inverse: \Book.readingSessions) public internal(set) var book: Book + public var startDate: Date = Date.now public var endDate: Date = Date.now public var startProgress: CGFloat = 0.0 public var endProgress: CGFloat = 0.0 + public var paragraph: Int = 0 + public private(set) var readTime: TimeInterval = 0.0 @discardableResult diff --git a/Bilbary/Models/Read/BReadModel+Book.swift b/Bilbary/Models/Read/BReadModel+Book.swift index 474971a..944b50d 100644 --- a/Bilbary/Models/Read/BReadModel+Book.swift +++ b/Bilbary/Models/Read/BReadModel+Book.swift @@ -36,16 +36,16 @@ extension BReadModel { } @discardableResult - public func create(for book: Book? = nil) -> BookProgress? { + public func create(for book: Book? = nil) -> BookSession? { if let book = book { - book.readingSessions.append(BookProgress( + book.readingSessions.append(BookSession( for: book, startedAt: .now, from: book.readingProgress )) } else { if let book = self.selectedBook { - book.readingSessions.append(BookProgress( + book.readingSessions.append(BookSession( for: book, startedAt: .now, from: book.readingProgress diff --git a/Bilbary/Models/Read/BReadModel.swift b/Bilbary/Models/Read/BReadModel.swift index 6bbeb7b..1f258a6 100644 --- a/Bilbary/Models/Read/BReadModel.swift +++ b/Bilbary/Models/Read/BReadModel.swift @@ -24,9 +24,9 @@ final class BReadModel { private let storage: BRMStorage = .init() - internal var currentSession: BookProgress? + internal var currentSession: BookSession? - public var sessions: [BookProgress]? + public var sessions: [BookSession]? public var contentLength: ReadTime.ID = ReadTime.aMin.id { didSet { self.storage.brmContentLength = self.contentLength @@ -45,11 +45,19 @@ final class BReadModel { } } } + public var currentParagraphId: Int? { + get { + self.selectedBook?.currentParagraph + } + set { + self.selectedBook?.currentParagraph = newValue + } + } private init() { self.selectedBook = self.selectedBooks.first if self.selectedBook != nil { - self.currentSession = BookProgress(for: self.selectedBook!, startedAt: .now) + self.currentSession = BookSession(for: self.selectedBook!, startedAt: .now) } self.contentLength = self.storage.brmContentLength }