feat: Implement extended font system and offline sync improvements
- Add 10 new fonts (Literata, Merriweather, Source Serif, Lato, Montserrat, Source Sans) - Support Apple system fonts and Google Fonts (OFL 1.1 licensed) - Extend FontFamily enum with new fonts and categories - Update FontSettingsViewModel and WebView with font support - Force WebView reload when font settings change - Refactor OfflineSyncManager with protocol and improved error handling - Add test mocks and OfflineSyncManagerTests with 9 test cases - Add OpenSourceLicensesView and FontDebugView - Bump build number - Update localization strings
This commit is contained in:
parent
75200e472c
commit
a227c275f3
@ -1,8 +1,8 @@
|
|||||||
# Font System Erweiterung - Konzept & Implementierungsplan
|
# Font System Erweiterung - Konzept & Implementierungsplan
|
||||||
|
|
||||||
**Datum:** 27. November 2025
|
**Datum:** 5. Dezember 2025
|
||||||
**Status:** Geplant
|
**Status:** Geplant
|
||||||
**Ziel:** Erweiterte Font-Auswahl mit 11 hochwertigen Schriftarten für bessere Lesbarkeit
|
**Ziel:** Erweiterte Font-Auswahl mit 10 hochwertigen Schriftarten für bessere Lesbarkeit
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -14,9 +14,9 @@
|
|||||||
- ❌ **Sans Serif:** Helvetica Neue (Standard)
|
- ❌ **Sans Serif:** Helvetica Neue (Standard)
|
||||||
- ❌ **Monospace:** Menlo (Apple)
|
- ❌ **Monospace:** Menlo (Apple)
|
||||||
|
|
||||||
### Neue Situation (11 Fonts)
|
### Neue Situation (10 Fonts)
|
||||||
- ✅ **5 Apple System Fonts** (bereits in iOS enthalten, 0 KB)
|
- ✅ **4 Apple System Fonts** (bereits in iOS enthalten, 0 KB)
|
||||||
- ✅ **8 Google Fonts** (OFL 1.1 lizenziert, ~1-2 MB)
|
- ✅ **6 Google Fonts** (OFL 1.1 lizenziert, ~1.5 MB)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -29,33 +29,22 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📚 Font-Übersicht (11 Fonts Total)
|
## 📚 Font-Übersicht (10 Fonts Total)
|
||||||
|
|
||||||
### Serif Fonts (5 Schriftarten)
|
### Serif Fonts (4 Schriftarten)
|
||||||
|
|
||||||
#### 1. **New York** (Apple System Font)
|
#### 1. **New York** (Apple System Font) ⭐
|
||||||
- **Quelle:** In iOS 13+ enthalten
|
- **Quelle:** In iOS 13+ enthalten
|
||||||
- **Lizenz:** Apple proprietär (frei für iOS Apps)
|
- **Lizenz:** Apple proprietär (frei für iOS Apps)
|
||||||
- **Eigenschaften:**
|
- **Eigenschaften:**
|
||||||
- 6 Gewichte
|
- 6 Gewichte
|
||||||
- Variable optische Größen
|
- Variable optische Größen
|
||||||
- Unterstützt Latin, Greek, Cyrillic
|
- Unterstützt Latin, Greek, Cyrillic
|
||||||
|
- Wird in Apple Books und News verwendet
|
||||||
- **Verwendung:** Premium Serif für Apple-native Ästhetik
|
- **Verwendung:** Premium Serif für Apple-native Ästhetik
|
||||||
- **App-Größe:** 0 KB (bereits in iOS)
|
- **App-Größe:** 0 KB (bereits in iOS)
|
||||||
|
|
||||||
#### 2. **Lora** (Google Font)
|
#### 2. **Literata** (Google Font) ⭐
|
||||||
- **Quelle:** [GitHub - cyrealtype/Lora-Cyrillic](https://github.com/cyrealtype/Lora-Cyrillic)
|
|
||||||
- **Google Fonts:** [fonts.google.com/specimen/Lora](https://fonts.google.com/specimen/Lora)
|
|
||||||
- **Lizenz:** SIL Open Font License 1.1
|
|
||||||
- **Designer:** Olga Karpushina, Alexei Vanyashin (Cyreal)
|
|
||||||
- **Eigenschaften:**
|
|
||||||
- Gut ausbalancierte Brushes
|
|
||||||
- Optimiert für Bildschirm-Lesbarkeit
|
|
||||||
- Variable Font verfügbar
|
|
||||||
- **Verwendung:** Elegante, lesbare Serif für Artikel
|
|
||||||
- **App-Größe:** ~200-300 KB
|
|
||||||
|
|
||||||
#### 3. **Literata** (Google Font) ⭐
|
|
||||||
- **Quelle:** [GitHub - googlefonts/literata](https://github.com/googlefonts/literata)
|
- **Quelle:** [GitHub - googlefonts/literata](https://github.com/googlefonts/literata)
|
||||||
- **Google Fonts:** [fonts.google.com/specimen/Literata](https://fonts.google.com/specimen/Literata)
|
- **Google Fonts:** [fonts.google.com/specimen/Literata](https://fonts.google.com/specimen/Literata)
|
||||||
- **Lizenz:** SIL Open Font License 1.1
|
- **Lizenz:** SIL Open Font License 1.1
|
||||||
@ -67,7 +56,7 @@
|
|||||||
- **Verwendung:** **Readeck Web-UI Match** - Hauptschrift für Artikel
|
- **Verwendung:** **Readeck Web-UI Match** - Hauptschrift für Artikel
|
||||||
- **App-Größe:** ~250-350 KB
|
- **App-Größe:** ~250-350 KB
|
||||||
|
|
||||||
#### 4. **Merriweather** (Google Font)
|
#### 3. **Merriweather** (Google Font)
|
||||||
- **Quelle:** [GitHub - SorkinType/Merriweather](https://github.com/SorkinType/Merriweather)
|
- **Quelle:** [GitHub - SorkinType/Merriweather](https://github.com/SorkinType/Merriweather)
|
||||||
- **Google Fonts:** [fonts.google.com/specimen/Merriweather](https://fonts.google.com/specimen/Merriweather)
|
- **Google Fonts:** [fonts.google.com/specimen/Merriweather](https://fonts.google.com/specimen/Merriweather)
|
||||||
- **Lizenz:** SIL Open Font License 1.1
|
- **Lizenz:** SIL Open Font License 1.1
|
||||||
@ -79,7 +68,7 @@
|
|||||||
- **Verwendung:** **Readeck Web-UI Match** - Alternative Serif
|
- **Verwendung:** **Readeck Web-UI Match** - Alternative Serif
|
||||||
- **App-Größe:** ~200-300 KB
|
- **App-Größe:** ~200-300 KB
|
||||||
|
|
||||||
#### 5. **Source Serif** (Adobe/Google Font)
|
#### 4. **Source Serif** (Adobe/Google Font)
|
||||||
- **Quelle:** [GitHub - adobe-fonts/source-serif](https://github.com/adobe-fonts/source-serif)
|
- **Quelle:** [GitHub - adobe-fonts/source-serif](https://github.com/adobe-fonts/source-serif)
|
||||||
- **Google Fonts:** [fonts.google.com/specimen/Source+Serif+4](https://fonts.google.com/specimen/Source+Serif+4)
|
- **Google Fonts:** [fonts.google.com/specimen/Source+Serif+4](https://fonts.google.com/specimen/Source+Serif+4)
|
||||||
- **Lizenz:** SIL Open Font License 1.1
|
- **Lizenz:** SIL Open Font License 1.1
|
||||||
@ -96,7 +85,7 @@
|
|||||||
|
|
||||||
### Sans Serif Fonts (5 Schriftarten)
|
### Sans Serif Fonts (5 Schriftarten)
|
||||||
|
|
||||||
#### 6. **SF Pro** (San Francisco - Apple System Font)
|
#### 5. **SF Pro** (San Francisco - Apple System Font) ⭐
|
||||||
- **Quelle:** In iOS enthalten
|
- **Quelle:** In iOS enthalten
|
||||||
- **Lizenz:** Apple proprietär (frei für iOS Apps)
|
- **Lizenz:** Apple proprietär (frei für iOS Apps)
|
||||||
- **Eigenschaften:**
|
- **Eigenschaften:**
|
||||||
@ -108,6 +97,17 @@
|
|||||||
- **Verwendung:** Standard UI Font
|
- **Verwendung:** Standard UI Font
|
||||||
- **App-Größe:** 0 KB (bereits in iOS)
|
- **App-Größe:** 0 KB (bereits in iOS)
|
||||||
|
|
||||||
|
#### 6. **Avenir Next** (Apple System Font) ⭐
|
||||||
|
- **Quelle:** In iOS enthalten
|
||||||
|
- **Lizenz:** Apple proprietär (frei für iOS Apps)
|
||||||
|
- **Eigenschaften:**
|
||||||
|
- Moderne geometrische Sans
|
||||||
|
- 12 Gewichte
|
||||||
|
- Sehr beliebt (Apple Marketing)
|
||||||
|
- Optimiert für Lesbarkeit
|
||||||
|
- **Verwendung:** Premium Sans für moderne Ästhetik
|
||||||
|
- **App-Größe:** 0 KB (bereits in iOS)
|
||||||
|
|
||||||
#### 7. **Lato** (Google Font)
|
#### 7. **Lato** (Google Font)
|
||||||
- **Quelle:** [GitHub - latofonts/lato-source](https://github.com/latofonts/lato-source)
|
- **Quelle:** [GitHub - latofonts/lato-source](https://github.com/latofonts/lato-source)
|
||||||
- **Google Fonts:** [fonts.google.com/specimen/Lato](https://fonts.google.com/specimen/Lato)
|
- **Google Fonts:** [fonts.google.com/specimen/Lato](https://fonts.google.com/specimen/Lato)
|
||||||
|
|||||||
@ -103,9 +103,9 @@
|
|||||||
UI/Components/UnifiedLabelChip.swift,
|
UI/Components/UnifiedLabelChip.swift,
|
||||||
UI/Extension/FontSizeExtension.swift,
|
UI/Extension/FontSizeExtension.swift,
|
||||||
UI/Models/AppSettings.swift,
|
UI/Models/AppSettings.swift,
|
||||||
|
"UI/Utils 2/Logger.swift",
|
||||||
|
"UI/Utils 2/LogStore.swift",
|
||||||
UI/Utils/NotificationNames.swift,
|
UI/Utils/NotificationNames.swift,
|
||||||
Utils/Logger.swift,
|
|
||||||
Utils/LogStore.swift,
|
|
||||||
);
|
);
|
||||||
target = 5D2B7FAE2DFA27A400EBDB2B /* URLShare */;
|
target = 5D2B7FAE2DFA27A400EBDB2B /* URLShare */;
|
||||||
};
|
};
|
||||||
@ -640,7 +640,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = readeck/readeck.entitlements;
|
CODE_SIGN_ENTITLEMENTS = readeck/readeck.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 37;
|
CURRENT_PROJECT_VERSION = 40;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"readeck/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"readeck/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = 8J69P655GN;
|
DEVELOPMENT_TEAM = 8J69P655GN;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
@ -684,7 +684,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = readeck/readeck.entitlements;
|
CODE_SIGN_ENTITLEMENTS = readeck/readeck.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 37;
|
CURRENT_PROJECT_VERSION = 40;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"readeck/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"readeck/Preview Content\"";
|
||||||
DEVELOPMENT_TEAM = 8J69P655GN;
|
DEVELOPMENT_TEAM = 8J69P655GN;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
|
|||||||
@ -2,7 +2,13 @@ import Foundation
|
|||||||
import CoreData
|
import CoreData
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
class OfflineSyncManager: ObservableObject, @unchecked Sendable {
|
protocol POfflineSyncManager {
|
||||||
|
func syncOfflineBookmarks() async
|
||||||
|
func getOfflineBookmarks() -> [ArticleURLEntity]
|
||||||
|
func deleteOfflineBookmark(_ entity: ArticleURLEntity)
|
||||||
|
}
|
||||||
|
|
||||||
|
open class OfflineSyncManager: ObservableObject, @unchecked Sendable {
|
||||||
static let shared = OfflineSyncManager()
|
static let shared = OfflineSyncManager()
|
||||||
|
|
||||||
@Published var isSyncing = false
|
@Published var isSyncing = false
|
||||||
@ -10,29 +16,14 @@ class OfflineSyncManager: ObservableObject, @unchecked Sendable {
|
|||||||
|
|
||||||
private let coreDataManager = CoreDataManager.shared
|
private let coreDataManager = CoreDataManager.shared
|
||||||
private let api: PAPI
|
private let api: PAPI
|
||||||
private let checkServerReachabilityUseCase: PCheckServerReachabilityUseCase
|
|
||||||
|
|
||||||
init(api: PAPI = API(),
|
init(api: PAPI = API()) {
|
||||||
checkServerReachabilityUseCase: PCheckServerReachabilityUseCase = DefaultUseCaseFactory.shared.makeCheckServerReachabilityUseCase()) {
|
|
||||||
self.api = api
|
self.api = api
|
||||||
self.checkServerReachabilityUseCase = checkServerReachabilityUseCase
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Sync Methods
|
// MARK: - Sync Methods
|
||||||
|
|
||||||
func syncOfflineBookmarks() async {
|
func syncOfflineBookmarks() async {
|
||||||
// First check if server is reachable
|
|
||||||
guard await checkServerReachabilityUseCase.execute() else {
|
|
||||||
await MainActor.run {
|
|
||||||
isSyncing = false
|
|
||||||
syncStatus = "Server not reachable. Cannot sync."
|
|
||||||
}
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
|
|
||||||
self.syncStatus = nil
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
isSyncing = true
|
isSyncing = true
|
||||||
syncStatus = "Syncing bookmarks with server..."
|
syncStatus = "Syncing bookmarks with server..."
|
||||||
@ -64,11 +55,9 @@ class OfflineSyncManager: ObservableObject, @unchecked Sendable {
|
|||||||
let title = bookmark.title ?? ""
|
let title = bookmark.title ?? ""
|
||||||
|
|
||||||
do {
|
do {
|
||||||
// Try to upload via API
|
|
||||||
let dto = CreateBookmarkRequestDto(url: url, title: title, labels: tags.isEmpty ? nil : tags)
|
let dto = CreateBookmarkRequestDto(url: url, title: title, labels: tags.isEmpty ? nil : tags)
|
||||||
_ = try await api.createBookmark(createRequest: dto)
|
_ = try await api.createBookmark(createRequest: dto)
|
||||||
|
|
||||||
// If successful, delete from offline storage
|
|
||||||
deleteOfflineBookmark(bookmark)
|
deleteOfflineBookmark(bookmark)
|
||||||
successCount += 1
|
successCount += 1
|
||||||
|
|
||||||
@ -79,19 +68,34 @@ class OfflineSyncManager: ObservableObject, @unchecked Sendable {
|
|||||||
} catch {
|
} catch {
|
||||||
print("Failed to sync bookmark: \(url) - \(error)")
|
print("Failed to sync bookmark: \(url) - \(error)")
|
||||||
failedCount += 1
|
failedCount += 1
|
||||||
|
|
||||||
|
// If first sync attempt fails, server is likely unreachable - abort
|
||||||
|
if successCount == 0 && failedCount == 1 {
|
||||||
|
await MainActor.run {
|
||||||
|
isSyncing = false
|
||||||
|
syncStatus = "Server not reachable. Cannot sync."
|
||||||
|
}
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
|
||||||
|
self.syncStatus = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
isSyncing = false
|
isSyncing = false
|
||||||
|
if successCount > 0 {
|
||||||
if failedCount == 0 {
|
if failedCount == 0 {
|
||||||
syncStatus = "✅ Successfully synced \(successCount) bookmarks"
|
syncStatus = "✅ Successfully synced \(successCount) bookmarks"
|
||||||
} else {
|
} else {
|
||||||
syncStatus = "⚠️ Synced \(successCount), failed \(failedCount) bookmarks"
|
syncStatus = "⚠️ Synced \(successCount), failed \(failedCount) bookmarks"
|
||||||
}
|
}
|
||||||
|
} else if failedCount > 0 {
|
||||||
|
syncStatus = "❌ Sync failed - check your connection"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear status after a few seconds
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
|
||||||
self.syncStatus = nil
|
self.syncStatus = nil
|
||||||
}
|
}
|
||||||
@ -101,7 +105,7 @@ class OfflineSyncManager: ObservableObject, @unchecked Sendable {
|
|||||||
return getOfflineBookmarks().count
|
return getOfflineBookmarks().count
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getOfflineBookmarks() -> [ArticleURLEntity] {
|
open func getOfflineBookmarks() -> [ArticleURLEntity] {
|
||||||
do {
|
do {
|
||||||
let fetchRequest: NSFetchRequest<ArticleURLEntity> = ArticleURLEntity.fetchRequest()
|
let fetchRequest: NSFetchRequest<ArticleURLEntity> = ArticleURLEntity.fetchRequest()
|
||||||
return try coreDataManager.context.safeFetch(fetchRequest)
|
return try coreDataManager.context.safeFetch(fetchRequest)
|
||||||
@ -111,7 +115,7 @@ class OfflineSyncManager: ObservableObject, @unchecked Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func deleteOfflineBookmark(_ entity: ArticleURLEntity) {
|
open func deleteOfflineBookmark(_ entity: ArticleURLEntity) {
|
||||||
do {
|
do {
|
||||||
try coreDataManager.context.safePerform { [weak self] in
|
try coreDataManager.context.safePerform { [weak self] in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
|||||||
@ -5,19 +5,74 @@
|
|||||||
// Created by Ilyas Hallak on 06.11.25.
|
// Created by Ilyas Hallak on 06.11.25.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
|
||||||
enum FontFamily: String, CaseIterable {
|
enum FontFamily: String, CaseIterable {
|
||||||
case system = "system"
|
// Apple System Fonts
|
||||||
|
case system = "system" // SF Pro
|
||||||
|
case newYork = "newYork" // New York
|
||||||
|
case avenirNext = "avenirNext" // Avenir Next
|
||||||
|
case monospace = "monospace" // SF Mono
|
||||||
|
|
||||||
|
// Google Serif Fonts
|
||||||
|
case literata = "literata"
|
||||||
|
case merriweather = "merriweather"
|
||||||
|
case sourceSerif = "sourceSerif"
|
||||||
|
|
||||||
|
// Google Sans Serif Fonts
|
||||||
|
case lato = "lato"
|
||||||
|
case montserrat = "montserrat"
|
||||||
|
case sourceSans = "sourceSans"
|
||||||
|
|
||||||
|
// Legacy (for backwards compatibility)
|
||||||
case serif = "serif"
|
case serif = "serif"
|
||||||
case sansSerif = "sansSerif"
|
case sansSerif = "sansSerif"
|
||||||
case monospace = "monospace"
|
|
||||||
|
|
||||||
var displayName: String {
|
var displayName: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .system: return "System"
|
// Apple
|
||||||
case .serif: return "Serif"
|
case .system: return "SF Pro"
|
||||||
case .sansSerif: return "Sans Serif"
|
case .newYork: return "New York"
|
||||||
case .monospace: return "Monospace"
|
case .avenirNext: return "Avenir Next"
|
||||||
|
case .monospace: return "SF Mono"
|
||||||
|
|
||||||
|
// Serif
|
||||||
|
case .literata: return "Literata *"
|
||||||
|
case .merriweather: return "Merriweather *"
|
||||||
|
case .sourceSerif: return "Source Serif *"
|
||||||
|
|
||||||
|
// Sans Serif
|
||||||
|
case .lato: return "Lato"
|
||||||
|
case .montserrat: return "Montserrat"
|
||||||
|
case .sourceSans: return "Source Sans *"
|
||||||
|
|
||||||
|
// Legacy
|
||||||
|
case .serif: return "Serif (Legacy)"
|
||||||
|
case .sansSerif: return "Sans Serif (Legacy)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var category: FontCategory {
|
||||||
|
switch self {
|
||||||
|
case .system, .avenirNext, .lato, .montserrat, .sourceSans, .sansSerif:
|
||||||
|
return .sansSerif
|
||||||
|
case .newYork, .literata, .merriweather, .sourceSerif, .serif:
|
||||||
|
return .serif
|
||||||
|
case .monospace:
|
||||||
|
return .monospace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isReadeckWebMatch: Bool {
|
||||||
|
switch self {
|
||||||
|
case .literata, .merriweather, .sourceSerif, .sourceSans:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum FontCategory {
|
||||||
|
case serif
|
||||||
|
case sansSerif
|
||||||
|
case monospace
|
||||||
|
}
|
||||||
@ -15,6 +15,8 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>NSAppTransportSecurity</key>
|
<key>NSAppTransportSecurity</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
|
<true/>
|
||||||
<key>NSAllowsLocalNetworking</key>
|
<key>NSAllowsLocalNetworking</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSExceptionRequiresForwardSecrecy</key>
|
<key>NSExceptionRequiresForwardSecrecy</key>
|
||||||
@ -31,5 +33,20 @@
|
|||||||
<key>UIImageName</key>
|
<key>UIImageName</key>
|
||||||
<string>splash</string>
|
<string>splash</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>UIAppFonts</key>
|
||||||
|
<array>
|
||||||
|
<string>Literata-Regular.ttf</string>
|
||||||
|
<string>Literata-Bold.ttf</string>
|
||||||
|
<string>Merriweather-Regular.ttf</string>
|
||||||
|
<string>Merriweather-Bold.ttf</string>
|
||||||
|
<string>SourceSerif4-Regular.ttf</string>
|
||||||
|
<string>SourceSerif4-Bold.ttf</string>
|
||||||
|
<string>Lato-Regular.ttf</string>
|
||||||
|
<string>Lato-Bold.ttf</string>
|
||||||
|
<string>Montserrat-Regular.ttf</string>
|
||||||
|
<string>Montserrat-Bold.ttf</string>
|
||||||
|
<string>SourceSans3-Regular.ttf</string>
|
||||||
|
<string>SourceSans3-Bold.ttf</string>
|
||||||
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@ -188,5 +188,9 @@
|
|||||||
"Enable offline reading and sync to cache articles for offline access" = "Aktiviere Offline-Lesen und synchronisiere, um Artikel für den Offline-Zugriff zu cachen";
|
"Enable offline reading and sync to cache articles for offline access" = "Aktiviere Offline-Lesen und synchronisiere, um Artikel für den Offline-Zugriff zu cachen";
|
||||||
"Use 'Sync Now' to download articles" = "Verwende 'Jetzt synchronisieren', um Artikel herunterzuladen";
|
"Use 'Sync Now' to download articles" = "Verwende 'Jetzt synchronisieren', um Artikel herunterzuladen";
|
||||||
"Simulate Offline Mode" = "Offline-Modus simulieren";
|
"Simulate Offline Mode" = "Offline-Modus simulieren";
|
||||||
|
|
||||||
|
/* Font Settings */
|
||||||
|
"font.web.match.hint" = "* Entspricht den Readeck Web-Schriften";
|
||||||
|
|
||||||
"DEBUG: Toggle network status" = "DEBUG: Netzwerkstatus umschalten";
|
"DEBUG: Toggle network status" = "DEBUG: Netzwerkstatus umschalten";
|
||||||
|
|
||||||
|
|||||||
@ -184,4 +184,7 @@
|
|||||||
"Enable offline reading and sync to cache articles for offline access" = "Enable offline reading and sync to cache articles for offline access";
|
"Enable offline reading and sync to cache articles for offline access" = "Enable offline reading and sync to cache articles for offline access";
|
||||||
"Use 'Sync Now' to download articles" = "Use 'Sync Now' to download articles";
|
"Use 'Sync Now' to download articles" = "Use 'Sync Now' to download articles";
|
||||||
"Simulate Offline Mode" = "Simulate Offline Mode";
|
"Simulate Offline Mode" = "Simulate Offline Mode";
|
||||||
|
|
||||||
|
/* Font Settings */
|
||||||
|
"font.web.match.hint" = "* Matches Readeck Web fonts";
|
||||||
"DEBUG: Toggle network status" = "DEBUG: Toggle network status";
|
"DEBUG: Toggle network status" = "DEBUG: Toggle network status";
|
||||||
BIN
readeck/Resources/Fonts/Lato-Bold.ttf
Normal file
BIN
readeck/Resources/Fonts/Lato-Bold.ttf
Normal file
Binary file not shown.
BIN
readeck/Resources/Fonts/Lato-Regular.ttf
Normal file
BIN
readeck/Resources/Fonts/Lato-Regular.ttf
Normal file
Binary file not shown.
71
readeck/Resources/Fonts/Licenses/FONTS-LICENSE.md
Normal file
71
readeck/Resources/Fonts/Licenses/FONTS-LICENSE.md
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
# Open Source Fonts Used in Readeck
|
||||||
|
|
||||||
|
This app uses the following open-source fonts:
|
||||||
|
|
||||||
|
## Google Fonts (SIL Open Font License 1.1)
|
||||||
|
|
||||||
|
### Serif Fonts
|
||||||
|
|
||||||
|
- **Literata** by TypeTogether for Google
|
||||||
|
- License: SIL OFL 1.1
|
||||||
|
- Source: https://github.com/googlefonts/literata
|
||||||
|
|
||||||
|
- **Merriweather** by Sorkin Type
|
||||||
|
- License: SIL OFL 1.1
|
||||||
|
- Source: https://github.com/SorkinType/Merriweather
|
||||||
|
|
||||||
|
- **Source Serif** by Adobe (Frank Grießhammer)
|
||||||
|
- License: SIL OFL 1.1
|
||||||
|
- Source: https://github.com/adobe-fonts/source-serif
|
||||||
|
|
||||||
|
### Sans Serif Fonts
|
||||||
|
|
||||||
|
- **Lato** by Łukasz Dziedzic
|
||||||
|
- License: SIL OFL 1.1
|
||||||
|
- Source: https://github.com/latofonts/lato-source
|
||||||
|
|
||||||
|
- **Montserrat** by Julieta Ulanovsky
|
||||||
|
- License: SIL OFL 1.1
|
||||||
|
- Source: https://github.com/JulietaUla/Montserrat
|
||||||
|
|
||||||
|
- **Source Sans** by Adobe (Paul D. Hunt)
|
||||||
|
- License: SIL OFL 1.1
|
||||||
|
- Source: https://github.com/adobe-fonts/source-sans
|
||||||
|
|
||||||
|
## Apple System Fonts
|
||||||
|
|
||||||
|
- **New York** - Apple proprietary (free for iOS apps)
|
||||||
|
- **SF Pro** - Apple proprietary (free for iOS apps)
|
||||||
|
- **Avenir Next** - Apple proprietary (free for iOS apps)
|
||||||
|
- **SF Mono** - Apple proprietary (free for iOS apps)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SIL Open Font License 1.1
|
||||||
|
|
||||||
|
Full license text: https://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
|
||||||
|
**Permitted:**
|
||||||
|
✅ Private use
|
||||||
|
✅ Commercial use
|
||||||
|
✅ Modification
|
||||||
|
✅ Distribution (embedded in App)
|
||||||
|
✅ Sale of App in AppStore
|
||||||
|
|
||||||
|
**Forbidden:**
|
||||||
|
❌ Sale of fonts as standalone product
|
||||||
|
|
||||||
|
**Requirements:**
|
||||||
|
- Copyright notice must be retained (already in font files)
|
||||||
|
- License text should be included (see individual license files)
|
||||||
|
|
||||||
|
### Individual License Files
|
||||||
|
|
||||||
|
- Literata: `Literata-OFL.txt`
|
||||||
|
- Merriweather: `Merriweather-OFL.txt`
|
||||||
|
- Source Serif: `SourceSerif-LICENSE.md`
|
||||||
|
- Lato: `Lato-LICENSE.txt`
|
||||||
|
- Montserrat: `Montserrat-OFL.txt`
|
||||||
|
- Source Sans: `SourceSans-LICENSE.md`
|
||||||
94
readeck/Resources/Fonts/Licenses/Lato-LICENSE.txt
Executable file
94
readeck/Resources/Fonts/Licenses/Lato-LICENSE.txt
Executable file
@ -0,0 +1,94 @@
|
|||||||
|
Copyright (c) 2010-2019, Łukasz Dziedzic (dziedzic@typoland.com),
|
||||||
|
with Reserved Font Name Lato.
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
93
readeck/Resources/Fonts/Licenses/Literata-OFL.txt
Normal file
93
readeck/Resources/Fonts/Licenses/Literata-OFL.txt
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
Copyright 2017 The Literata Project Authors (https://github.com/googlefonts/literata)
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
93
readeck/Resources/Fonts/Licenses/Merriweather-OFL.txt
Normal file
93
readeck/Resources/Fonts/Licenses/Merriweather-OFL.txt
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
Copyright 2016 The Merriweather Project Authors (https://github.com/EbenSorkin/Merriweather), with Reserved Font Name "Merriweather".
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
93
readeck/Resources/Fonts/Licenses/Montserrat-OFL.txt
Normal file
93
readeck/Resources/Fonts/Licenses/Montserrat-OFL.txt
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
Copyright 2024 The Montserrat.Git Project Authors (https://github.com/JulietaUla/Montserrat.git)
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
https://openfontlicense.org
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
93
readeck/Resources/Fonts/Licenses/SourceSans-LICENSE.md
Normal file
93
readeck/Resources/Fonts/Licenses/SourceSans-LICENSE.md
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
Copyright 2010-2024 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries.
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
|
||||||
|
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
93
readeck/Resources/Fonts/Licenses/SourceSerif-LICENSE.md
Normal file
93
readeck/Resources/Fonts/Licenses/SourceSerif-LICENSE.md
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
Copyright 2014 - 2023 Adobe (http://www.adobe.com/), with Reserved Font Name ‘Source’. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries.
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
|
||||||
|
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
BIN
readeck/Resources/Fonts/Literata-Bold.ttf
Normal file
BIN
readeck/Resources/Fonts/Literata-Bold.ttf
Normal file
Binary file not shown.
BIN
readeck/Resources/Fonts/Literata-Regular.ttf
Normal file
BIN
readeck/Resources/Fonts/Literata-Regular.ttf
Normal file
Binary file not shown.
BIN
readeck/Resources/Fonts/Merriweather-Bold.ttf
Normal file
BIN
readeck/Resources/Fonts/Merriweather-Bold.ttf
Normal file
Binary file not shown.
BIN
readeck/Resources/Fonts/Merriweather-Regular.ttf
Normal file
BIN
readeck/Resources/Fonts/Merriweather-Regular.ttf
Normal file
Binary file not shown.
BIN
readeck/Resources/Fonts/Montserrat-Bold.ttf
Normal file
BIN
readeck/Resources/Fonts/Montserrat-Bold.ttf
Normal file
Binary file not shown.
BIN
readeck/Resources/Fonts/Montserrat-Regular.ttf
Normal file
BIN
readeck/Resources/Fonts/Montserrat-Regular.ttf
Normal file
Binary file not shown.
BIN
readeck/Resources/Fonts/SourceSans3-Bold.ttf
Normal file
BIN
readeck/Resources/Fonts/SourceSans3-Bold.ttf
Normal file
Binary file not shown.
BIN
readeck/Resources/Fonts/SourceSans3-Regular.ttf
Normal file
BIN
readeck/Resources/Fonts/SourceSans3-Regular.ttf
Normal file
Binary file not shown.
BIN
readeck/Resources/Fonts/SourceSerif4-Bold.ttf
Normal file
BIN
readeck/Resources/Fonts/SourceSerif4-Bold.ttf
Normal file
Binary file not shown.
BIN
readeck/Resources/Fonts/SourceSerif4-Regular.ttf
Normal file
BIN
readeck/Resources/Fonts/SourceSerif4-Regular.ttf
Normal file
Binary file not shown.
@ -116,6 +116,7 @@ struct BookmarkDetailLegacyView: View {
|
|||||||
.frame(height: webViewHeight)
|
.frame(height: webViewHeight)
|
||||||
.cornerRadius(14)
|
.cornerRadius(14)
|
||||||
.padding(.horizontal, 4)
|
.padding(.horizontal, 4)
|
||||||
|
.id("\(settings.fontFamily?.rawValue ?? "system")-\(settings.fontSize?.rawValue ?? "medium")")
|
||||||
} else if viewModel.isLoadingArticle {
|
} else if viewModel.isLoadingArticle {
|
||||||
ProgressView("Loading article...")
|
ProgressView("Loading article...")
|
||||||
.frame(maxWidth: .infinity, alignment: .center)
|
.frame(maxWidth: .infinity, alignment: .center)
|
||||||
@ -392,6 +393,7 @@ struct BookmarkDetailLegacyView: View {
|
|||||||
.cornerRadius(14)
|
.cornerRadius(14)
|
||||||
.padding(.horizontal, 4)
|
.padding(.horizontal, 4)
|
||||||
.animation(.easeInOut, value: webViewHeight)
|
.animation(.easeInOut, value: webViewHeight)
|
||||||
|
.id("\(settings.fontFamily?.rawValue ?? "system")-\(settings.fontSize?.rawValue ?? "medium")")
|
||||||
} else if viewModel.isLoadingArticle {
|
} else if viewModel.isLoadingArticle {
|
||||||
ProgressView("Loading article...")
|
ProgressView("Loading article...")
|
||||||
.frame(maxWidth: .infinity, alignment: .center)
|
.frame(maxWidth: .infinity, alignment: .center)
|
||||||
|
|||||||
@ -496,6 +496,7 @@ struct BookmarkDetailView2: View {
|
|||||||
.frame(height: webViewHeight)
|
.frame(height: webViewHeight)
|
||||||
.cornerRadius(14)
|
.cornerRadius(14)
|
||||||
.padding(.horizontal, 4)
|
.padding(.horizontal, 4)
|
||||||
|
.id("\(settings.fontFamily?.rawValue ?? "system")-\(settings.fontSize?.rawValue ?? "medium")")
|
||||||
}
|
}
|
||||||
} else if viewModel.isLoadingArticle {
|
} else if viewModel.isLoadingArticle {
|
||||||
ProgressView("Loading article...")
|
ProgressView("Loading article...")
|
||||||
|
|||||||
@ -189,6 +189,68 @@ struct NativeWebView: View {
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
<meta name="color-scheme" content="\(isDarkMode ? "dark" : "light")">
|
<meta name="color-scheme" content="\(isDarkMode ? "dark" : "light")">
|
||||||
<style>
|
<style>
|
||||||
|
/* Load custom fonts from app bundle */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Literata';
|
||||||
|
src: local('Literata-Regular');
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Literata';
|
||||||
|
src: local('Literata-Bold');
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Merriweather';
|
||||||
|
src: local('Merriweather-Regular');
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Merriweather';
|
||||||
|
src: local('Merriweather-Bold');
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Source Serif 4';
|
||||||
|
src: local('SourceSerif4-Regular');
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Source Serif 4';
|
||||||
|
src: local('SourceSerif4-Bold');
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Lato';
|
||||||
|
src: local('Lato-Regular');
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Lato';
|
||||||
|
src: local('Lato-Bold');
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Montserrat';
|
||||||
|
src: local('Montserrat-Regular');
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Montserrat';
|
||||||
|
src: local('Montserrat-Bold');
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Source Sans 3';
|
||||||
|
src: local('SourceSans3-Regular');
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Source Sans 3';
|
||||||
|
src: local('SourceSans3-Bold');
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@ -394,10 +456,25 @@ struct NativeWebView: View {
|
|||||||
|
|
||||||
private func getFontFamily(from fontFamily: FontFamily) -> String {
|
private func getFontFamily(from fontFamily: FontFamily) -> String {
|
||||||
switch fontFamily {
|
switch fontFamily {
|
||||||
|
// Apple System Fonts
|
||||||
case .system: return "-apple-system, BlinkMacSystemFont, sans-serif"
|
case .system: return "-apple-system, BlinkMacSystemFont, sans-serif"
|
||||||
|
case .newYork: return "'New York', 'Times New Roman', Georgia, serif"
|
||||||
|
case .avenirNext: return "'Avenir Next', Avenir, 'Helvetica Neue', sans-serif"
|
||||||
|
case .monospace: return "'SF Mono', Menlo, Monaco, monospace"
|
||||||
|
|
||||||
|
// Google Serif Fonts
|
||||||
|
case .literata: return "'Literata', Georgia, 'Times New Roman', serif"
|
||||||
|
case .merriweather: return "'Merriweather', Georgia, 'Times New Roman', serif"
|
||||||
|
case .sourceSerif: return "'Source Serif 4', 'Source Serif Pro', Georgia, serif"
|
||||||
|
|
||||||
|
// Google Sans Serif Fonts
|
||||||
|
case .lato: return "'Lato', 'Helvetica Neue', Arial, sans-serif"
|
||||||
|
case .montserrat: return "'Montserrat', 'Helvetica Neue', Arial, sans-serif"
|
||||||
|
case .sourceSans: return "'Source Sans 3', 'Source Sans Pro', 'Helvetica Neue', sans-serif"
|
||||||
|
|
||||||
|
// Legacy
|
||||||
case .serif: return "'Times New Roman', Times, serif"
|
case .serif: return "'Times New Roman', Times, serif"
|
||||||
case .sansSerif: return "'Helvetica Neue', Helvetica, Arial, sans-serif"
|
case .sansSerif: return "'Helvetica Neue', Helvetica, Arial, sans-serif"
|
||||||
case .monospace: return "'SF Mono', Menlo, Monaco, monospace"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -74,6 +74,68 @@ struct WebView: UIViewRepresentable {
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<meta name="color-scheme" content="\(isDarkMode ? "dark" : "light")">
|
<meta name="color-scheme" content="\(isDarkMode ? "dark" : "light")">
|
||||||
<style>
|
<style>
|
||||||
|
/* Load custom fonts from app bundle */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Literata';
|
||||||
|
src: local('Literata-Regular');
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Literata';
|
||||||
|
src: local('Literata-Bold');
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Merriweather';
|
||||||
|
src: local('Merriweather-Regular');
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Merriweather';
|
||||||
|
src: local('Merriweather-Bold');
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Source Serif 4';
|
||||||
|
src: local('SourceSerif4-Regular');
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Source Serif 4';
|
||||||
|
src: local('SourceSerif4-Bold');
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Lato';
|
||||||
|
src: local('Lato-Regular');
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Lato';
|
||||||
|
src: local('Lato-Bold');
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Montserrat';
|
||||||
|
src: local('Montserrat-Regular');
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Montserrat';
|
||||||
|
src: local('Montserrat-Bold');
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Source Sans 3';
|
||||||
|
src: local('SourceSans3-Regular');
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Source Sans 3';
|
||||||
|
src: local('SourceSans3-Bold');
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--background-color: \(isDarkMode ? "#000000" : "#ffffff");
|
--background-color: \(isDarkMode ? "#000000" : "#ffffff");
|
||||||
--text-color: \(isDarkMode ? "#ffffff" : "#1a1a1a");
|
--text-color: \(isDarkMode ? "#ffffff" : "#1a1a1a");
|
||||||
@ -356,14 +418,37 @@ struct WebView: UIViewRepresentable {
|
|||||||
|
|
||||||
private func getFontFamily(from fontFamily: FontFamily) -> String {
|
private func getFontFamily(from fontFamily: FontFamily) -> String {
|
||||||
switch fontFamily {
|
switch fontFamily {
|
||||||
|
// Apple System Fonts
|
||||||
case .system:
|
case .system:
|
||||||
return "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
|
return "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
|
||||||
|
case .newYork:
|
||||||
|
return "'New York', 'Times New Roman', Georgia, serif"
|
||||||
|
case .avenirNext:
|
||||||
|
return "'Avenir Next', Avenir, 'Helvetica Neue', sans-serif"
|
||||||
|
case .monospace:
|
||||||
|
return "'SF Mono', Menlo, Monaco, Consolas, 'Liberation Mono', monospace"
|
||||||
|
|
||||||
|
// Google Serif Fonts
|
||||||
|
case .literata:
|
||||||
|
return "'Literata', Georgia, 'Times New Roman', serif"
|
||||||
|
case .merriweather:
|
||||||
|
return "'Merriweather', Georgia, 'Times New Roman', serif"
|
||||||
|
case .sourceSerif:
|
||||||
|
return "'Source Serif 4', 'Source Serif Pro', Georgia, serif"
|
||||||
|
|
||||||
|
// Google Sans Serif Fonts
|
||||||
|
case .lato:
|
||||||
|
return "'Lato', 'Helvetica Neue', Arial, sans-serif"
|
||||||
|
case .montserrat:
|
||||||
|
return "'Montserrat', 'Helvetica Neue', Arial, sans-serif"
|
||||||
|
case .sourceSans:
|
||||||
|
return "'Source Sans 3', 'Source Sans Pro', 'Helvetica Neue', sans-serif"
|
||||||
|
|
||||||
|
// Legacy
|
||||||
case .serif:
|
case .serif:
|
||||||
return "'Times New Roman', Times, 'Liberation Serif', serif"
|
return "'Times New Roman', Times, 'Liberation Serif', serif"
|
||||||
case .sansSerif:
|
case .sansSerif:
|
||||||
return "'Helvetica Neue', Helvetica, Arial, sans-serif"
|
return "'Helvetica Neue', Helvetica, Arial, sans-serif"
|
||||||
case .monospace:
|
|
||||||
return "'SF Mono', Menlo, Monaco, Consolas, 'Liberation Mono', monospace"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
71
readeck/UI/Settings/FontDebugView.swift
Normal file
71
readeck/UI/Settings/FontDebugView.swift
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
//
|
||||||
|
// FontDebugView.swift
|
||||||
|
// readeck
|
||||||
|
//
|
||||||
|
// Created by Ilyas Hallak on 05.12.25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
struct FontDebugView: View {
|
||||||
|
@State private var availableFonts: [String: [String]] = [:]
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationStack {
|
||||||
|
List {
|
||||||
|
Section {
|
||||||
|
Text("This view shows all available font families and their font names. Use this to verify that custom fonts are loaded correctly.")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
} header: {
|
||||||
|
Text("Debug Info")
|
||||||
|
}
|
||||||
|
|
||||||
|
ForEach(availableFonts.keys.sorted(), id: \.self) { family in
|
||||||
|
Section {
|
||||||
|
ForEach(availableFonts[family] ?? [], id: \.self) { fontName in
|
||||||
|
Text(fontName)
|
||||||
|
.font(.caption)
|
||||||
|
.textSelection(.enabled)
|
||||||
|
}
|
||||||
|
} header: {
|
||||||
|
Text(family)
|
||||||
|
.textSelection(.enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationTitle("Available Fonts")
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .confirmationAction) {
|
||||||
|
Button("Refresh") {
|
||||||
|
loadFonts()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
loadFonts()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadFonts() {
|
||||||
|
var fonts: [String: [String]] = [:]
|
||||||
|
|
||||||
|
for family in UIFont.familyNames.sorted() {
|
||||||
|
let names = UIFont.fontNames(forFamilyName: family)
|
||||||
|
if !names.isEmpty {
|
||||||
|
fonts[family] = names
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
availableFonts = fonts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
FontDebugView()
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@ -61,6 +61,10 @@ struct FontSelectionView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Text("font.web.match.hint".localized)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
Text("Font size")
|
Text("Font size")
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
|
|||||||
@ -28,6 +28,10 @@ struct FontSettingsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Text("font.web.match.hint".localized)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
|
||||||
Picker("Font size", selection: $viewModel.selectedFontSize) {
|
Picker("Font size", selection: $viewModel.selectedFontSize) {
|
||||||
ForEach(FontSize.allCases, id: \.self) { size in
|
ForEach(FontSize.allCases, id: \.self) { size in
|
||||||
Text(size.displayName).tag(size)
|
Text(size.displayName).tag(size)
|
||||||
|
|||||||
@ -24,42 +24,116 @@ class FontSettingsViewModel {
|
|||||||
|
|
||||||
// MARK: - Computed Font Properties for Preview
|
// MARK: - Computed Font Properties for Preview
|
||||||
var previewTitleFont: Font {
|
var previewTitleFont: Font {
|
||||||
|
let size = selectedFontSize.size
|
||||||
|
|
||||||
switch selectedFontFamily {
|
switch selectedFontFamily {
|
||||||
|
// Apple System Fonts
|
||||||
case .system:
|
case .system:
|
||||||
return selectedFontSize.systemFont.weight(.semibold)
|
return Font.system(size: size).weight(.semibold)
|
||||||
case .serif:
|
case .newYork:
|
||||||
return Font.custom("Times New Roman", size: selectedFontSize.size).weight(.semibold)
|
return Font.system(size: size, design: .serif).weight(.semibold)
|
||||||
case .sansSerif:
|
case .avenirNext:
|
||||||
return Font.custom("Helvetica Neue", size: selectedFontSize.size).weight(.semibold)
|
return Font.custom("AvenirNext-DemiBold", size: size)
|
||||||
case .monospace:
|
case .monospace:
|
||||||
return Font.custom("Menlo", size: selectedFontSize.size).weight(.semibold)
|
return Font.system(size: size, design: .monospaced).weight(.semibold)
|
||||||
|
|
||||||
|
// Google Serif Fonts
|
||||||
|
case .literata:
|
||||||
|
return Font.custom("Literata-Bold", size: size)
|
||||||
|
case .merriweather:
|
||||||
|
return Font.custom("Merriweather-Bold", size: size)
|
||||||
|
case .sourceSerif:
|
||||||
|
return Font.custom("SourceSerif4-Bold", size: size)
|
||||||
|
|
||||||
|
// Google Sans Serif Fonts
|
||||||
|
case .lato:
|
||||||
|
return Font.custom("Lato-Bold", size: size)
|
||||||
|
case .montserrat:
|
||||||
|
return Font.custom("Montserrat-Bold", size: size)
|
||||||
|
case .sourceSans:
|
||||||
|
return Font.custom("SourceSans3-Bold", size: size)
|
||||||
|
|
||||||
|
// Legacy
|
||||||
|
case .serif:
|
||||||
|
return Font.custom("Times New Roman", size: size).weight(.semibold)
|
||||||
|
case .sansSerif:
|
||||||
|
return Font.custom("Helvetica Neue", size: size).weight(.semibold)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var previewBodyFont: Font {
|
var previewBodyFont: Font {
|
||||||
|
let size = selectedFontSize.size
|
||||||
|
|
||||||
switch selectedFontFamily {
|
switch selectedFontFamily {
|
||||||
|
// Apple System Fonts
|
||||||
case .system:
|
case .system:
|
||||||
return selectedFontSize.systemFont
|
return Font.system(size: size)
|
||||||
case .serif:
|
case .newYork:
|
||||||
return Font.custom("Times New Roman", size: selectedFontSize.size)
|
return Font.system(size: size, design: .serif)
|
||||||
case .sansSerif:
|
case .avenirNext:
|
||||||
return Font.custom("Helvetica Neue", size: selectedFontSize.size)
|
return Font.custom("AvenirNext-Regular", size: size)
|
||||||
case .monospace:
|
case .monospace:
|
||||||
return Font.custom("Menlo", size: selectedFontSize.size)
|
return Font.system(size: size, design: .monospaced)
|
||||||
|
|
||||||
|
// Google Serif Fonts
|
||||||
|
case .literata:
|
||||||
|
return Font.custom("Literata-Regular", size: size)
|
||||||
|
case .merriweather:
|
||||||
|
return Font.custom("Merriweather-Regular", size: size)
|
||||||
|
case .sourceSerif:
|
||||||
|
return Font.custom("SourceSerif4-Regular", size: size)
|
||||||
|
|
||||||
|
// Google Sans Serif Fonts
|
||||||
|
case .lato:
|
||||||
|
return Font.custom("Lato-Regular", size: size)
|
||||||
|
case .montserrat:
|
||||||
|
return Font.custom("Montserrat-Regular", size: size)
|
||||||
|
case .sourceSans:
|
||||||
|
return Font.custom("SourceSans3-Regular", size: size)
|
||||||
|
|
||||||
|
// Legacy
|
||||||
|
case .serif:
|
||||||
|
return Font.custom("Times New Roman", size: size)
|
||||||
|
case .sansSerif:
|
||||||
|
return Font.custom("Helvetica Neue", size: size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var previewCaptionFont: Font {
|
var previewCaptionFont: Font {
|
||||||
let captionSize = selectedFontSize.size * 0.85
|
let captionSize = selectedFontSize.size * 0.85
|
||||||
|
|
||||||
switch selectedFontFamily {
|
switch selectedFontFamily {
|
||||||
|
// Apple System Fonts
|
||||||
case .system:
|
case .system:
|
||||||
return Font.system(size: captionSize)
|
return Font.system(size: captionSize)
|
||||||
|
case .newYork:
|
||||||
|
return Font.system(size: captionSize, design: .serif)
|
||||||
|
case .avenirNext:
|
||||||
|
return Font.custom("AvenirNext-Regular", size: captionSize)
|
||||||
|
case .monospace:
|
||||||
|
return Font.system(size: captionSize, design: .monospaced)
|
||||||
|
|
||||||
|
// Google Serif Fonts
|
||||||
|
case .literata:
|
||||||
|
return Font.custom("Literata-Regular", size: captionSize)
|
||||||
|
case .merriweather:
|
||||||
|
return Font.custom("Merriweather-Regular", size: captionSize)
|
||||||
|
case .sourceSerif:
|
||||||
|
return Font.custom("SourceSerif4-Regular", size: captionSize)
|
||||||
|
|
||||||
|
// Google Sans Serif Fonts
|
||||||
|
case .lato:
|
||||||
|
return Font.custom("Lato-Regular", size: captionSize)
|
||||||
|
case .montserrat:
|
||||||
|
return Font.custom("Montserrat-Regular", size: captionSize)
|
||||||
|
case .sourceSans:
|
||||||
|
return Font.custom("SourceSans3-Regular", size: captionSize)
|
||||||
|
|
||||||
|
// Legacy
|
||||||
case .serif:
|
case .serif:
|
||||||
return Font.custom("Times New Roman", size: captionSize)
|
return Font.custom("Times New Roman", size: captionSize)
|
||||||
case .sansSerif:
|
case .sansSerif:
|
||||||
return Font.custom("Helvetica Neue", size: captionSize)
|
return Font.custom("Helvetica Neue", size: captionSize)
|
||||||
case .monospace:
|
|
||||||
return Font.custom("Menlo", size: captionSize)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ struct LegalPrivacySettingsView: View {
|
|||||||
@State private var showingPrivacyPolicy = false
|
@State private var showingPrivacyPolicy = false
|
||||||
@State private var showingLegalNotice = false
|
@State private var showingLegalNotice = false
|
||||||
@State private var showReleaseNotes = false
|
@State private var showReleaseNotes = false
|
||||||
|
@State private var showingLicenses = false
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
@ -47,6 +48,18 @@ struct LegalPrivacySettingsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
showingLicenses = true
|
||||||
|
}) {
|
||||||
|
HStack {
|
||||||
|
Text("Open Source Licenses")
|
||||||
|
Spacer()
|
||||||
|
Image(systemName: "chevron.right")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
if let url = URL(string: "https://github.com/ilyas-hallak/readeck-ios/issues") {
|
if let url = URL(string: "https://github.com/ilyas-hallak/readeck-ios/issues") {
|
||||||
UIApplication.shared.open(url)
|
UIApplication.shared.open(url)
|
||||||
@ -87,6 +100,9 @@ struct LegalPrivacySettingsView: View {
|
|||||||
.sheet(isPresented: $showReleaseNotes) {
|
.sheet(isPresented: $showReleaseNotes) {
|
||||||
ReleaseNotesView()
|
ReleaseNotesView()
|
||||||
}
|
}
|
||||||
|
.sheet(isPresented: $showingLicenses) {
|
||||||
|
OpenSourceLicensesView()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
135
readeck/UI/Settings/OpenSourceLicensesView.swift
Normal file
135
readeck/UI/Settings/OpenSourceLicensesView.swift
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
//
|
||||||
|
// OpenSourceLicensesView.swift
|
||||||
|
// readeck
|
||||||
|
//
|
||||||
|
// Created by Ilyas Hallak on 05.12.25.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct OpenSourceLicensesView: View {
|
||||||
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
NavigationStack {
|
||||||
|
List {
|
||||||
|
Section {
|
||||||
|
Text("This app uses the following open-source fonts under the SIL Open Font License 1.1.")
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
} header: {
|
||||||
|
Text("Open Source Fonts")
|
||||||
|
}
|
||||||
|
|
||||||
|
Section {
|
||||||
|
FontLicenseRow(
|
||||||
|
name: "Literata",
|
||||||
|
author: "TypeTogether for Google",
|
||||||
|
license: "SIL OFL 1.1"
|
||||||
|
)
|
||||||
|
|
||||||
|
FontLicenseRow(
|
||||||
|
name: "Merriweather",
|
||||||
|
author: "Sorkin Type",
|
||||||
|
license: "SIL OFL 1.1"
|
||||||
|
)
|
||||||
|
|
||||||
|
FontLicenseRow(
|
||||||
|
name: "Source Serif",
|
||||||
|
author: "Adobe (Frank Grießhammer)",
|
||||||
|
license: "SIL OFL 1.1"
|
||||||
|
)
|
||||||
|
|
||||||
|
FontLicenseRow(
|
||||||
|
name: "Lato",
|
||||||
|
author: "Łukasz Dziedzic",
|
||||||
|
license: "SIL OFL 1.1"
|
||||||
|
)
|
||||||
|
|
||||||
|
FontLicenseRow(
|
||||||
|
name: "Montserrat",
|
||||||
|
author: "Julieta Ulanovsky",
|
||||||
|
license: "SIL OFL 1.1"
|
||||||
|
)
|
||||||
|
|
||||||
|
FontLicenseRow(
|
||||||
|
name: "Source Sans",
|
||||||
|
author: "Adobe (Paul D. Hunt)",
|
||||||
|
license: "SIL OFL 1.1"
|
||||||
|
)
|
||||||
|
} header: {
|
||||||
|
Text("Font Licenses")
|
||||||
|
}
|
||||||
|
|
||||||
|
Section {
|
||||||
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
|
Text("SIL Open Font License 1.1")
|
||||||
|
.font(.headline)
|
||||||
|
|
||||||
|
Text("The SIL Open Font License allows the fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves.")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
if let url = URL(string: "https://scripts.sil.org/OFL") {
|
||||||
|
UIApplication.shared.open(url)
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
HStack {
|
||||||
|
Text("View Full License")
|
||||||
|
.font(.caption)
|
||||||
|
Image(systemName: "arrow.up.right")
|
||||||
|
.font(.caption2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.top, 4)
|
||||||
|
}
|
||||||
|
.padding(.vertical, 4)
|
||||||
|
} header: {
|
||||||
|
Text("License Information")
|
||||||
|
}
|
||||||
|
|
||||||
|
Section {
|
||||||
|
Text("Apple System Fonts (SF Pro, New York, Avenir Next, SF Mono) are proprietary to Apple Inc. and are free to use within iOS applications.")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
} header: {
|
||||||
|
Text("Apple Fonts")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationTitle("Open Source Licenses")
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .confirmationAction) {
|
||||||
|
Button("Done") {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FontLicenseRow: View {
|
||||||
|
let name: String
|
||||||
|
let author: String
|
||||||
|
let license: String
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
Text(name)
|
||||||
|
.font(.headline)
|
||||||
|
Text(author)
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
Text(license)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
.padding(.vertical, 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
OpenSourceLicensesView()
|
||||||
|
}
|
||||||
@ -134,6 +134,15 @@ struct SettingsContainerView: View {
|
|||||||
) {
|
) {
|
||||||
LoggingConfigurationView()
|
LoggingConfigurationView()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingsRowNavigationLink(
|
||||||
|
icon: "textformat",
|
||||||
|
iconColor: .green,
|
||||||
|
title: "Font Debug",
|
||||||
|
subtitle: "View available fonts"
|
||||||
|
) {
|
||||||
|
FontDebugView()
|
||||||
|
}
|
||||||
} header: {
|
} header: {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Debug Settings")
|
Text("Debug Settings")
|
||||||
|
|||||||
167
readeckTests/Helpers/TestMocks.swift
Normal file
167
readeckTests/Helpers/TestMocks.swift
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
//
|
||||||
|
// TestMocks.swift
|
||||||
|
// readeckTests
|
||||||
|
//
|
||||||
|
// Created by Ilyas Hallak
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CoreData
|
||||||
|
@testable import readeck
|
||||||
|
|
||||||
|
// MARK: - Mock API
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
class TestMockAPI: PAPI {
|
||||||
|
var tokenProvider: TokenProvider = TestMockTokenProvider()
|
||||||
|
|
||||||
|
var createBookmarkCalls: [(CreateBookmarkRequestDto, Result<CreateBookmarkResponseDto, Error>)] = []
|
||||||
|
var createBookmarkResults: [Result<CreateBookmarkResponseDto, Error>] = []
|
||||||
|
private var callIndex = 0
|
||||||
|
|
||||||
|
func createBookmark(createRequest: CreateBookmarkRequestDto) async throws -> CreateBookmarkResponseDto {
|
||||||
|
guard callIndex < createBookmarkResults.count else {
|
||||||
|
throw APIError.serverError(500)
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = createBookmarkResults[callIndex]
|
||||||
|
callIndex += 1
|
||||||
|
|
||||||
|
createBookmarkCalls.append((createRequest, result))
|
||||||
|
|
||||||
|
switch result {
|
||||||
|
case .success(let response):
|
||||||
|
return response
|
||||||
|
case .failure(let error):
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func reset() {
|
||||||
|
createBookmarkCalls.removeAll()
|
||||||
|
callIndex = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Unimplemented Methods
|
||||||
|
|
||||||
|
func login(endpoint: String, username: String, password: String) async throws -> UserDto {
|
||||||
|
fatalError("Not implemented for tests")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBookmarks(state: BookmarkState?, limit: Int?, offset: Int?, search: String?, type: [BookmarkType]?, tag: String?) async throws -> BookmarksPageDto {
|
||||||
|
fatalError("Not implemented for tests")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBookmark(id: String) async throws -> BookmarkDetailDto {
|
||||||
|
fatalError("Not implemented for tests")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBookmarkArticle(id: String) async throws -> String {
|
||||||
|
fatalError("Not implemented for tests")
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateBookmark(id: String, updateRequest: UpdateBookmarkRequestDto) async throws {
|
||||||
|
fatalError("Not implemented for tests")
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteBookmark(id: String) async throws {
|
||||||
|
fatalError("Not implemented for tests")
|
||||||
|
}
|
||||||
|
|
||||||
|
func searchBookmarks(search: String) async throws -> BookmarksPageDto {
|
||||||
|
fatalError("Not implemented for tests")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBookmarkLabels() async throws -> [BookmarkLabelDto] {
|
||||||
|
fatalError("Not implemented for tests")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBookmarkAnnotations(bookmarkId: String) async throws -> [AnnotationDto] {
|
||||||
|
fatalError("Not implemented for tests")
|
||||||
|
}
|
||||||
|
|
||||||
|
func createAnnotation(bookmarkId: String, color: String, startOffset: Int, endOffset: Int, startSelector: String, endSelector: String) async throws -> AnnotationDto {
|
||||||
|
fatalError("Not implemented for tests")
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteAnnotation(bookmarkId: String, annotationId: String) async throws {
|
||||||
|
fatalError("Not implemented for tests")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Mock Token Provider
|
||||||
|
|
||||||
|
class TestMockTokenProvider: TokenProvider {
|
||||||
|
func getToken() async -> String? { return "mock-token" }
|
||||||
|
func setToken(_ token: String) async {}
|
||||||
|
func clearToken() async {}
|
||||||
|
func getEndpoint() async -> String? { return "https://mock.example.com" }
|
||||||
|
func setEndpoint(_ endpoint: String) async {}
|
||||||
|
func clearEndpoint() async {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Test CoreData Manager
|
||||||
|
|
||||||
|
class TestCoreDataManager {
|
||||||
|
let context: NSManagedObjectContext
|
||||||
|
|
||||||
|
init() {
|
||||||
|
let managedObjectModel = NSManagedObjectModel.mergedModel(from: [Bundle.main])!
|
||||||
|
let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)
|
||||||
|
|
||||||
|
try! persistentStoreCoordinator.addPersistentStore(
|
||||||
|
ofType: NSInMemoryStoreType,
|
||||||
|
configurationName: nil,
|
||||||
|
at: nil,
|
||||||
|
options: nil
|
||||||
|
)
|
||||||
|
|
||||||
|
context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
|
||||||
|
context.persistentStoreCoordinator = persistentStoreCoordinator
|
||||||
|
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTestBookmark(url: String, title: String, tags: String? = nil) -> ArticleURLEntity {
|
||||||
|
let entity = ArticleURLEntity(context: context)
|
||||||
|
entity.url = url
|
||||||
|
entity.title = title
|
||||||
|
entity.tags = tags
|
||||||
|
entity.id = UUID()
|
||||||
|
|
||||||
|
try! context.save()
|
||||||
|
return entity
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchAllBookmarks() -> [ArticleURLEntity] {
|
||||||
|
let fetchRequest: NSFetchRequest<ArticleURLEntity> = ArticleURLEntity.fetchRequest()
|
||||||
|
return (try? context.fetch(fetchRequest)) ?? []
|
||||||
|
}
|
||||||
|
|
||||||
|
func clearAll() {
|
||||||
|
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = ArticleURLEntity.fetchRequest()
|
||||||
|
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
|
||||||
|
try? context.execute(deleteRequest)
|
||||||
|
try? context.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Testable OfflineSyncManager
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
class TestableOfflineSyncManager: OfflineSyncManager {
|
||||||
|
let mockCoreDataManager: TestCoreDataManager
|
||||||
|
|
||||||
|
init(api: PAPI, coreDataManager: TestCoreDataManager) {
|
||||||
|
self.mockCoreDataManager = coreDataManager
|
||||||
|
super.init(api: api)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func getOfflineBookmarks() -> [ArticleURLEntity] {
|
||||||
|
return mockCoreDataManager.fetchAllBookmarks()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func deleteOfflineBookmark(_ entity: ArticleURLEntity) {
|
||||||
|
mockCoreDataManager.context.delete(entity)
|
||||||
|
try? mockCoreDataManager.context.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
165
readeckTests/OfflineSyncManagerTests.swift
Normal file
165
readeckTests/OfflineSyncManagerTests.swift
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
//
|
||||||
|
// OfflineSyncManagerTests.swift
|
||||||
|
// readeckTests
|
||||||
|
//
|
||||||
|
// Created by Ilyas Hallak
|
||||||
|
//
|
||||||
|
|
||||||
|
import Testing
|
||||||
|
import Foundation
|
||||||
|
@testable import readeck
|
||||||
|
|
||||||
|
@Suite("OfflineSyncManager Tests")
|
||||||
|
@MainActor
|
||||||
|
struct OfflineSyncManagerTests {
|
||||||
|
|
||||||
|
// MARK: - Test: Empty Queue
|
||||||
|
|
||||||
|
@Test("Should handle empty bookmark queue")
|
||||||
|
func testEmptyQueue() async throws {
|
||||||
|
let (syncManager, mockAPI, _) = createTestEnvironment()
|
||||||
|
|
||||||
|
await syncManager.syncOfflineBookmarks()
|
||||||
|
|
||||||
|
#expect(syncManager.isSyncing == false)
|
||||||
|
#expect(syncManager.syncStatus == "No bookmarks to sync")
|
||||||
|
#expect(mockAPI.createBookmarkCalls.isEmpty)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Test: Successful Sync
|
||||||
|
|
||||||
|
@Test("Should successfully sync all bookmarks")
|
||||||
|
func testSuccessfulSync() async throws {
|
||||||
|
let (syncManager, mockAPI, mockCoreData) = createTestEnvironment()
|
||||||
|
|
||||||
|
_ = mockCoreData.createTestBookmark(url: "https://example.com/1", title: "Article 1", tags: "tag1,tag2")
|
||||||
|
_ = mockCoreData.createTestBookmark(url: "https://example.com/2", title: "Article 2")
|
||||||
|
_ = mockCoreData.createTestBookmark(url: "https://example.com/3", title: "Article 3", tags: "tag3")
|
||||||
|
|
||||||
|
mockAPI.createBookmarkResults = Array(repeating: .success(mockSuccessResponse()), count: 3)
|
||||||
|
|
||||||
|
await syncManager.syncOfflineBookmarks()
|
||||||
|
try await Task.sleep(for: .milliseconds(100))
|
||||||
|
|
||||||
|
#expect(syncManager.isSyncing == false)
|
||||||
|
#expect(syncManager.syncStatus?.contains("Successfully synced 3 bookmarks") == true)
|
||||||
|
#expect(mockAPI.createBookmarkCalls.count == 3)
|
||||||
|
#expect(mockCoreData.fetchAllBookmarks().isEmpty)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Test: Server Unreachable
|
||||||
|
|
||||||
|
@Test("Should abort on first failure (server unreachable)")
|
||||||
|
func testServerUnreachable() async throws {
|
||||||
|
let (syncManager, mockAPI, mockCoreData) = createTestEnvironment()
|
||||||
|
|
||||||
|
_ = mockCoreData.createTestBookmark(url: "https://example.com/1", title: "Article 1")
|
||||||
|
_ = mockCoreData.createTestBookmark(url: "https://example.com/2", title: "Article 2")
|
||||||
|
_ = mockCoreData.createTestBookmark(url: "https://example.com/3", title: "Article 3")
|
||||||
|
|
||||||
|
mockAPI.createBookmarkResults = [.failure(APIError.serverError(503))]
|
||||||
|
|
||||||
|
await syncManager.syncOfflineBookmarks()
|
||||||
|
try await Task.sleep(for: .milliseconds(100))
|
||||||
|
|
||||||
|
#expect(syncManager.isSyncing == false)
|
||||||
|
#expect(syncManager.syncStatus == "Server not reachable. Cannot sync.")
|
||||||
|
#expect(mockAPI.createBookmarkCalls.count == 1)
|
||||||
|
#expect(mockCoreData.fetchAllBookmarks().count == 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Test: Partial Success
|
||||||
|
|
||||||
|
@Test("Should handle partial sync success")
|
||||||
|
func testPartialSuccess() async throws {
|
||||||
|
let (syncManager, mockAPI, mockCoreData) = createTestEnvironment()
|
||||||
|
|
||||||
|
for i in 1...4 {
|
||||||
|
_ = mockCoreData.createTestBookmark(url: "https://example.com/\(i)", title: "Article \(i)")
|
||||||
|
}
|
||||||
|
|
||||||
|
mockAPI.createBookmarkResults = [
|
||||||
|
.success(mockSuccessResponse()),
|
||||||
|
.failure(APIError.serverError(400)),
|
||||||
|
.success(mockSuccessResponse()),
|
||||||
|
.failure(APIError.serverError(400))
|
||||||
|
]
|
||||||
|
|
||||||
|
await syncManager.syncOfflineBookmarks()
|
||||||
|
try await Task.sleep(for: .milliseconds(100))
|
||||||
|
|
||||||
|
#expect(syncManager.isSyncing == false)
|
||||||
|
#expect(syncManager.syncStatus?.contains("Synced 2, failed 2") == true)
|
||||||
|
#expect(mockAPI.createBookmarkCalls.count == 4)
|
||||||
|
#expect(mockCoreData.fetchAllBookmarks().count == 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Test: Bookmark Without URL
|
||||||
|
|
||||||
|
@Test("Should skip bookmarks without URL")
|
||||||
|
func testBookmarkWithoutURL() async throws {
|
||||||
|
let (syncManager, mockAPI, mockCoreData) = createTestEnvironment()
|
||||||
|
|
||||||
|
let invalidEntity = ArticleURLEntity(context: mockCoreData.context)
|
||||||
|
invalidEntity.url = nil
|
||||||
|
invalidEntity.title = "Invalid Bookmark"
|
||||||
|
try! mockCoreData.context.save()
|
||||||
|
|
||||||
|
_ = mockCoreData.createTestBookmark(url: "https://example.com/1", title: "Valid Article")
|
||||||
|
|
||||||
|
mockAPI.createBookmarkResults = [.success(mockSuccessResponse())]
|
||||||
|
|
||||||
|
await syncManager.syncOfflineBookmarks()
|
||||||
|
try await Task.sleep(for: .milliseconds(100))
|
||||||
|
|
||||||
|
#expect(syncManager.isSyncing == false)
|
||||||
|
#expect(mockAPI.createBookmarkCalls.count == 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Test: Tags Parsing
|
||||||
|
|
||||||
|
@Test("Should correctly parse and send tags")
|
||||||
|
func testTagsParsing() async throws {
|
||||||
|
let (syncManager, mockAPI, mockCoreData) = createTestEnvironment()
|
||||||
|
|
||||||
|
_ = mockCoreData.createTestBookmark(url: "https://example.com/1", title: "Article", tags: "swift,ios,testing")
|
||||||
|
|
||||||
|
mockAPI.createBookmarkResults = [.success(mockSuccessResponse())]
|
||||||
|
|
||||||
|
await syncManager.syncOfflineBookmarks()
|
||||||
|
try await Task.sleep(for: .milliseconds(100))
|
||||||
|
|
||||||
|
#expect(mockAPI.createBookmarkCalls.count == 1)
|
||||||
|
#expect(mockAPI.createBookmarkCalls[0].0.labels == ["swift", "ios", "testing"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Test: Empty Tags
|
||||||
|
|
||||||
|
@Test("Should handle bookmarks without tags")
|
||||||
|
func testEmptyTags() async throws {
|
||||||
|
let (syncManager, mockAPI, mockCoreData) = createTestEnvironment()
|
||||||
|
|
||||||
|
_ = mockCoreData.createTestBookmark(url: "https://example.com/1", title: "Article")
|
||||||
|
|
||||||
|
mockAPI.createBookmarkResults = [.success(mockSuccessResponse())]
|
||||||
|
|
||||||
|
await syncManager.syncOfflineBookmarks()
|
||||||
|
try await Task.sleep(for: .milliseconds(100))
|
||||||
|
|
||||||
|
#expect(mockAPI.createBookmarkCalls.count == 1)
|
||||||
|
#expect(mockAPI.createBookmarkCalls[0].0.labels == nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Test Helpers
|
||||||
|
|
||||||
|
private func createTestEnvironment() -> (TestableOfflineSyncManager, TestMockAPI, TestCoreDataManager) {
|
||||||
|
let mockAPI = TestMockAPI()
|
||||||
|
let mockCoreData = TestCoreDataManager()
|
||||||
|
let syncManager = TestableOfflineSyncManager(api: mockAPI, coreDataManager: mockCoreData)
|
||||||
|
return (syncManager, mockAPI, mockCoreData)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func mockSuccessResponse() -> CreateBookmarkResponseDto {
|
||||||
|
CreateBookmarkResponseDto(message: "Bookmark created", status: 200)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user