From b8e5766cb117b4a5312514c83e1cd9360d116394 Mon Sep 17 00:00:00 2001 From: Ilyas Hallak Date: Tue, 14 Oct 2025 14:04:28 +0200 Subject: [PATCH] feat: Add release notes system with auto-popup on version updates Implement comprehensive release notes feature: - RELEASE_NOTES.md with version 1.0 and 1.1 content in English - VersionManager to track app versions and detect updates - ReleaseNotesView with native markdown rendering - Auto-popup sheet on first launch after version update - Manual access via "What's New" button in General Settings Features: - Markdown-based release notes stored in app bundle - Automatic version detection using CFBundleShortVersionString - UserDefaults tracking of last seen version - Dismissable sheet with "Done" button - Settings button shows current version number Technical implementation: - VersionManager singleton for version tracking - Sheet presentation in MainTabView on new version - Settings integration with sparkles icon - Native SwiftUI Text markdown rendering - Bundle resource loading for RELEASE_NOTES.md Release notes content: - Version 1.1: iOS 26 features, floating buttons, progress tracking - Version 1.0: Initial release features and capabilities --- readeck/Resources/RELEASE_NOTES.md | 68 +++++++++++++++++++ readeck/UI/Menu/TabView.swift | 34 +++++++--- readeck/UI/Settings/ReleaseNotesView.swift | 44 ++++++++++++ readeck/UI/Settings/SettingsGeneralView.swift | 26 ++++++- readeck/Utils/VersionManager.swift | 38 +++++++++++ 5 files changed, 199 insertions(+), 11 deletions(-) create mode 100644 readeck/Resources/RELEASE_NOTES.md create mode 100644 readeck/UI/Settings/ReleaseNotesView.swift create mode 100644 readeck/Utils/VersionManager.swift diff --git a/readeck/Resources/RELEASE_NOTES.md b/readeck/Resources/RELEASE_NOTES.md new file mode 100644 index 0000000..6021b18 --- /dev/null +++ b/readeck/Resources/RELEASE_NOTES.md @@ -0,0 +1,68 @@ +# Release Notes + +## Version 1.1 (Build 1) + +### iOS 26+ Native WebView +- **New native SwiftUI WebView implementation** for iOS 26 and later +- Improved performance with native WebKit integration +- Better memory management and rendering + +### Floating Action Buttons +- **Contextual action buttons** appear when reaching 90% of article +- Beautiful glass effect design with liquid interactions +- Smooth slide-up animation +- Quick access to favorite and archive actions + +### Reading Progress Improvements +- **Accurate progress tracking** using optimized PreferenceKey approach +- Progress bar reflects entire article length (header, content, metadata) +- Automatic progress sync every 3% to reduce API calls +- Progress locked at 100% to prevent fluctuations + +### Image Header Enhancement +- **Better image display** with aspect fit and blurred background +- No more random cropping - full image visibility +- Maintains header space while showing complete images + +### Performance Optimizations +- Replaced onScrollGeometryChange with PreferenceKey for smoother scrolling +- Reduced state updates during scroll +- Optimized WebView height detection +- Improved CSS rendering for web content + +### Bug Fixes +- Fixed content width overflow in native WebView +- Fixed excessive spacing between header and content +- Fixed read progress calculation to include all content sections +- Fixed JavaScript height detection with simplified approach + +--- + +## Version 1.0 (Initial Release) + +### Core Features +- Browse and read saved articles +- Bookmark management with labels +- Full article view with custom fonts +- Text-to-speech support (Beta) +- Archive and favorite functionality + +### Reading Experience +- Clean, distraction-free reading interface +- Customizable font settings +- Image viewer with zoom support +- Progress tracking per article +- Dark mode support + +### Organization +- Label system for categorization +- Search and filter bookmarks +- Archive completed articles +- Jump to last read position + +### Share Extension +- Save articles from other apps +- Quick access to save and label bookmarks +- Save Bookmarks offline if your server is not reachable and sync later + + diff --git a/readeck/UI/Menu/TabView.swift b/readeck/UI/Menu/TabView.swift index d3350ef..5e816ab 100644 --- a/readeck/UI/Menu/TabView.swift +++ b/readeck/UI/Menu/TabView.swift @@ -5,21 +5,37 @@ struct MainTabView: View { @State private var selectedTab: SidebarTab = .unread @State var selectedBookmark: Bookmark? @StateObject private var playerUIState = PlayerUIState() - + @State private var showReleaseNotes = false + // sizeClass @Environment(\.horizontalSizeClass) var horizontalSizeClass - + @Environment(\.verticalSizeClass) var verticalSizeClass - + var body: some View { - if UIDevice.isPhone { - PhoneTabView() - .environmentObject(playerUIState) - } else { - PadSidebarView() - .environmentObject(playerUIState) + Group { + if UIDevice.isPhone { + PhoneTabView() + .environmentObject(playerUIState) + } else { + PadSidebarView() + .environmentObject(playerUIState) + } + } + .sheet(isPresented: $showReleaseNotes) { + ReleaseNotesView() + } + .onAppear { + checkForNewVersion() + } + } + + private func checkForNewVersion() { + if VersionManager.shared.isNewVersion { + showReleaseNotes = true + VersionManager.shared.markVersionAsSeen() } } } diff --git a/readeck/UI/Settings/ReleaseNotesView.swift b/readeck/UI/Settings/ReleaseNotesView.swift new file mode 100644 index 0000000..3d7240a --- /dev/null +++ b/readeck/UI/Settings/ReleaseNotesView.swift @@ -0,0 +1,44 @@ +import SwiftUI + +struct ReleaseNotesView: View { + @Environment(\.dismiss) private var dismiss + + var body: some View { + NavigationView { + ScrollView { + VStack(alignment: .leading, spacing: 16) { + if let markdownContent = loadReleaseNotes() { + Text(.init(markdownContent)) + .font(.body) + .padding() + } else { + Text("Unable to load release notes") + .foregroundColor(.secondary) + .padding() + } + } + } + .navigationTitle("What's New") + .navigationBarTitleDisplayMode(.large) + .toolbar { + ToolbarItem(placement: .confirmationAction) { + Button("Done") { + dismiss() + } + } + } + } + } + + private func loadReleaseNotes() -> String? { + guard let url = Bundle.main.url(forResource: "RELEASE_NOTES", withExtension: "md"), + let content = try? String(contentsOf: url) else { + return nil + } + return content + } +} + +#Preview { + ReleaseNotesView() +} diff --git a/readeck/UI/Settings/SettingsGeneralView.swift b/readeck/UI/Settings/SettingsGeneralView.swift index 2f5a758..0911db4 100644 --- a/readeck/UI/Settings/SettingsGeneralView.swift +++ b/readeck/UI/Settings/SettingsGeneralView.swift @@ -9,11 +9,12 @@ import SwiftUI struct SettingsGeneralView: View { @State private var viewModel: SettingsGeneralViewModel - + @State private var showReleaseNotes = false + init(viewModel: SettingsGeneralViewModel = SettingsGeneralViewModel()) { self.viewModel = viewModel } - + var body: some View { VStack(spacing: 20) { SectionHeader(title: "General Settings".localized, icon: "gear") @@ -22,6 +23,24 @@ struct SettingsGeneralView: View { VStack(alignment: .leading, spacing: 12) { Text("General") .font(.headline) + + // What's New Button + Button(action: { + showReleaseNotes = true + }) { + HStack { + Label("What's New", systemImage: "sparkles") + Spacer() + Text("Version \(VersionManager.shared.currentVersion)") + .font(.caption) + .foregroundColor(.secondary) + Image(systemName: "chevron.right") + .font(.caption) + .foregroundColor(.secondary) + } + } + .buttonStyle(.plain) + Toggle("Read Aloud Feature", isOn: $viewModel.enableTTS) .toggleStyle(.switch) .onChange(of: viewModel.enableTTS) { @@ -98,6 +117,9 @@ struct SettingsGeneralView: View { #endif } + .sheet(isPresented: $showReleaseNotes) { + ReleaseNotesView() + } .task { await viewModel.loadGeneralSettings() } diff --git a/readeck/Utils/VersionManager.swift b/readeck/Utils/VersionManager.swift new file mode 100644 index 0000000..4feb0de --- /dev/null +++ b/readeck/Utils/VersionManager.swift @@ -0,0 +1,38 @@ +import Foundation + +class VersionManager { + static let shared = VersionManager() + + private let lastSeenVersionKey = "lastSeenAppVersion" + private let userDefaults = UserDefaults.standard + + var currentVersion: String { + Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "1.0" + } + + var currentBuild: String { + Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as? String ?? "1" + } + + var fullVersion: String { + "\(currentVersion) (\(currentBuild))" + } + + var lastSeenVersion: String? { + userDefaults.string(forKey: lastSeenVersionKey) + } + + var isNewVersion: Bool { + guard let lastSeen = lastSeenVersion else { + // First launch + return true + } + return lastSeen != currentVersion + } + + func markVersionAsSeen() { + userDefaults.set(currentVersion, forKey: lastSeenVersionKey) + } + + private init() {} +}