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
This commit is contained in:
parent
e61dbc7d72
commit
b8e5766cb1
68
readeck/Resources/RELEASE_NOTES.md
Normal file
68
readeck/Resources/RELEASE_NOTES.md
Normal file
@ -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
|
||||
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
44
readeck/UI/Settings/ReleaseNotesView.swift
Normal file
44
readeck/UI/Settings/ReleaseNotesView.swift
Normal file
@ -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()
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
|
||||
38
readeck/Utils/VersionManager.swift
Normal file
38
readeck/Utils/VersionManager.swift
Normal file
@ -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() {}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user