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
|
||||
|
||||
**Datum:** 27. November 2025
|
||||
**Datum:** 5. Dezember 2025
|
||||
**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)
|
||||
- ❌ **Monospace:** Menlo (Apple)
|
||||
|
||||
### Neue Situation (11 Fonts)
|
||||
- ✅ **5 Apple System Fonts** (bereits in iOS enthalten, 0 KB)
|
||||
- ✅ **8 Google Fonts** (OFL 1.1 lizenziert, ~1-2 MB)
|
||||
### Neue Situation (10 Fonts)
|
||||
- ✅ **4 Apple System Fonts** (bereits in iOS enthalten, 0 KB)
|
||||
- ✅ **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
|
||||
- **Lizenz:** Apple proprietär (frei für iOS Apps)
|
||||
- **Eigenschaften:**
|
||||
- 6 Gewichte
|
||||
- Variable optische Größen
|
||||
- Unterstützt Latin, Greek, Cyrillic
|
||||
- Wird in Apple Books und News verwendet
|
||||
- **Verwendung:** Premium Serif für Apple-native Ästhetik
|
||||
- **App-Größe:** 0 KB (bereits in iOS)
|
||||
|
||||
#### 2. **Lora** (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) ⭐
|
||||
#### 2. **Literata** (Google Font) ⭐
|
||||
- **Quelle:** [GitHub - googlefonts/literata](https://github.com/googlefonts/literata)
|
||||
- **Google Fonts:** [fonts.google.com/specimen/Literata](https://fonts.google.com/specimen/Literata)
|
||||
- **Lizenz:** SIL Open Font License 1.1
|
||||
@ -67,7 +56,7 @@
|
||||
- **Verwendung:** **Readeck Web-UI Match** - Hauptschrift für Artikel
|
||||
- **App-Größe:** ~250-350 KB
|
||||
|
||||
#### 4. **Merriweather** (Google Font)
|
||||
#### 3. **Merriweather** (Google Font)
|
||||
- **Quelle:** [GitHub - SorkinType/Merriweather](https://github.com/SorkinType/Merriweather)
|
||||
- **Google Fonts:** [fonts.google.com/specimen/Merriweather](https://fonts.google.com/specimen/Merriweather)
|
||||
- **Lizenz:** SIL Open Font License 1.1
|
||||
@ -79,7 +68,7 @@
|
||||
- **Verwendung:** **Readeck Web-UI Match** - Alternative Serif
|
||||
- **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)
|
||||
- **Google Fonts:** [fonts.google.com/specimen/Source+Serif+4](https://fonts.google.com/specimen/Source+Serif+4)
|
||||
- **Lizenz:** SIL Open Font License 1.1
|
||||
@ -96,7 +85,7 @@
|
||||
|
||||
### 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
|
||||
- **Lizenz:** Apple proprietär (frei für iOS Apps)
|
||||
- **Eigenschaften:**
|
||||
@ -108,6 +97,17 @@
|
||||
- **Verwendung:** Standard UI Font
|
||||
- **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)
|
||||
- **Quelle:** [GitHub - latofonts/lato-source](https://github.com/latofonts/lato-source)
|
||||
- **Google Fonts:** [fonts.google.com/specimen/Lato](https://fonts.google.com/specimen/Lato)
|
||||
|
||||
@ -103,9 +103,9 @@
|
||||
UI/Components/UnifiedLabelChip.swift,
|
||||
UI/Extension/FontSizeExtension.swift,
|
||||
UI/Models/AppSettings.swift,
|
||||
"UI/Utils 2/Logger.swift",
|
||||
"UI/Utils 2/LogStore.swift",
|
||||
UI/Utils/NotificationNames.swift,
|
||||
Utils/Logger.swift,
|
||||
Utils/LogStore.swift,
|
||||
);
|
||||
target = 5D2B7FAE2DFA27A400EBDB2B /* URLShare */;
|
||||
};
|
||||
@ -640,7 +640,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = readeck/readeck.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 37;
|
||||
CURRENT_PROJECT_VERSION = 40;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"readeck/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = 8J69P655GN;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
@ -684,7 +684,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = readeck/readeck.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 37;
|
||||
CURRENT_PROJECT_VERSION = 40;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"readeck/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = 8J69P655GN;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
|
||||
@ -2,7 +2,13 @@ import Foundation
|
||||
import CoreData
|
||||
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()
|
||||
|
||||
@Published var isSyncing = false
|
||||
@ -10,36 +16,21 @@ class OfflineSyncManager: ObservableObject, @unchecked Sendable {
|
||||
|
||||
private let coreDataManager = CoreDataManager.shared
|
||||
private let api: PAPI
|
||||
private let checkServerReachabilityUseCase: PCheckServerReachabilityUseCase
|
||||
|
||||
init(api: PAPI = API(),
|
||||
checkServerReachabilityUseCase: PCheckServerReachabilityUseCase = DefaultUseCaseFactory.shared.makeCheckServerReachabilityUseCase()) {
|
||||
init(api: PAPI = API()) {
|
||||
self.api = api
|
||||
self.checkServerReachabilityUseCase = checkServerReachabilityUseCase
|
||||
}
|
||||
|
||||
// MARK: - Sync Methods
|
||||
|
||||
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 {
|
||||
isSyncing = true
|
||||
syncStatus = "Syncing bookmarks with server..."
|
||||
}
|
||||
|
||||
|
||||
let offlineBookmarks = getOfflineBookmarks()
|
||||
|
||||
|
||||
guard !offlineBookmarks.isEmpty else {
|
||||
await MainActor.run {
|
||||
isSyncing = false
|
||||
@ -50,48 +41,61 @@ class OfflineSyncManager: ObservableObject, @unchecked Sendable {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
var successCount = 0
|
||||
var failedCount = 0
|
||||
|
||||
|
||||
for bookmark in offlineBookmarks {
|
||||
guard let url = bookmark.url else {
|
||||
failedCount += 1
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
let tags = bookmark.tags?.components(separatedBy: ",").filter { !$0.isEmpty } ?? []
|
||||
let title = bookmark.title ?? ""
|
||||
|
||||
|
||||
do {
|
||||
// Try to upload via API
|
||||
let dto = CreateBookmarkRequestDto(url: url, title: title, labels: tags.isEmpty ? nil : tags)
|
||||
_ = try await api.createBookmark(createRequest: dto)
|
||||
|
||||
// If successful, delete from offline storage
|
||||
|
||||
deleteOfflineBookmark(bookmark)
|
||||
successCount += 1
|
||||
|
||||
|
||||
await MainActor.run {
|
||||
syncStatus = "Synced \(successCount) bookmarks..."
|
||||
}
|
||||
|
||||
|
||||
} catch {
|
||||
print("Failed to sync bookmark: \(url) - \(error)")
|
||||
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 {
|
||||
isSyncing = false
|
||||
if failedCount == 0 {
|
||||
syncStatus = "✅ Successfully synced \(successCount) bookmarks"
|
||||
} else {
|
||||
syncStatus = "⚠️ Synced \(successCount), failed \(failedCount) bookmarks"
|
||||
if successCount > 0 {
|
||||
if failedCount == 0 {
|
||||
syncStatus = "✅ Successfully synced \(successCount) bookmarks"
|
||||
} else {
|
||||
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) {
|
||||
self.syncStatus = nil
|
||||
}
|
||||
@ -100,8 +104,8 @@ class OfflineSyncManager: ObservableObject, @unchecked Sendable {
|
||||
func getOfflineBookmarksCount() -> Int {
|
||||
return getOfflineBookmarks().count
|
||||
}
|
||||
|
||||
private func getOfflineBookmarks() -> [ArticleURLEntity] {
|
||||
|
||||
open func getOfflineBookmarks() -> [ArticleURLEntity] {
|
||||
do {
|
||||
let fetchRequest: NSFetchRequest<ArticleURLEntity> = ArticleURLEntity.fetchRequest()
|
||||
return try coreDataManager.context.safeFetch(fetchRequest)
|
||||
@ -110,12 +114,12 @@ class OfflineSyncManager: ObservableObject, @unchecked Sendable {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
private func deleteOfflineBookmark(_ entity: ArticleURLEntity) {
|
||||
|
||||
open func deleteOfflineBookmark(_ entity: ArticleURLEntity) {
|
||||
do {
|
||||
try coreDataManager.context.safePerform { [weak self] in
|
||||
guard let self = self else { return }
|
||||
|
||||
|
||||
self.coreDataManager.context.delete(entity)
|
||||
self.coreDataManager.save()
|
||||
}
|
||||
|
||||
@ -5,19 +5,74 @@
|
||||
// Created by Ilyas Hallak on 06.11.25.
|
||||
//
|
||||
|
||||
|
||||
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 sansSerif = "sansSerif"
|
||||
case monospace = "monospace"
|
||||
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .system: return "System"
|
||||
case .serif: return "Serif"
|
||||
case .sansSerif: return "Sans Serif"
|
||||
case .monospace: return "Monospace"
|
||||
// Apple
|
||||
case .system: return "SF Pro"
|
||||
case .newYork: return "New York"
|
||||
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>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
<key>NSAllowsLocalNetworking</key>
|
||||
<true/>
|
||||
<key>NSExceptionRequiresForwardSecrecy</key>
|
||||
@ -31,5 +33,20 @@
|
||||
<key>UIImageName</key>
|
||||
<string>splash</string>
|
||||
</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>
|
||||
</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";
|
||||
"Use 'Sync Now' to download articles" = "Verwende 'Jetzt synchronisieren', um Artikel herunterzuladen";
|
||||
"Simulate Offline Mode" = "Offline-Modus simulieren";
|
||||
|
||||
/* Font Settings */
|
||||
"font.web.match.hint" = "* Entspricht den Readeck Web-Schriften";
|
||||
|
||||
"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";
|
||||
"Use 'Sync Now' to download articles" = "Use 'Sync Now' to download articles";
|
||||
"Simulate Offline Mode" = "Simulate Offline Mode";
|
||||
|
||||
/* Font Settings */
|
||||
"font.web.match.hint" = "* Matches Readeck Web fonts";
|
||||
"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)
|
||||
.cornerRadius(14)
|
||||
.padding(.horizontal, 4)
|
||||
.id("\(settings.fontFamily?.rawValue ?? "system")-\(settings.fontSize?.rawValue ?? "medium")")
|
||||
} else if viewModel.isLoadingArticle {
|
||||
ProgressView("Loading article...")
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
@ -392,6 +393,7 @@ struct BookmarkDetailLegacyView: View {
|
||||
.cornerRadius(14)
|
||||
.padding(.horizontal, 4)
|
||||
.animation(.easeInOut, value: webViewHeight)
|
||||
.id("\(settings.fontFamily?.rawValue ?? "system")-\(settings.fontSize?.rawValue ?? "medium")")
|
||||
} else if viewModel.isLoadingArticle {
|
||||
ProgressView("Loading article...")
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
|
||||
@ -496,6 +496,7 @@ struct BookmarkDetailView2: View {
|
||||
.frame(height: webViewHeight)
|
||||
.cornerRadius(14)
|
||||
.padding(.horizontal, 4)
|
||||
.id("\(settings.fontFamily?.rawValue ?? "system")-\(settings.fontSize?.rawValue ?? "medium")")
|
||||
}
|
||||
} else if viewModel.isLoadingArticle {
|
||||
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="color-scheme" content="\(isDarkMode ? "dark" : "light")">
|
||||
<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%;
|
||||
box-sizing: border-box;
|
||||
@ -394,10 +456,25 @@ struct NativeWebView: View {
|
||||
|
||||
private func getFontFamily(from fontFamily: FontFamily) -> String {
|
||||
switch fontFamily {
|
||||
// Apple System Fonts
|
||||
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 .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="color-scheme" content="\(isDarkMode ? "dark" : "light")">
|
||||
<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 {
|
||||
--background-color: \(isDarkMode ? "#000000" : "#ffffff");
|
||||
--text-color: \(isDarkMode ? "#ffffff" : "#1a1a1a");
|
||||
@ -356,14 +418,37 @@ struct WebView: UIViewRepresentable {
|
||||
|
||||
private func getFontFamily(from fontFamily: FontFamily) -> String {
|
||||
switch fontFamily {
|
||||
// Apple System Fonts
|
||||
case .system:
|
||||
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:
|
||||
return "'Times New Roman', Times, 'Liberation Serif', serif"
|
||||
case .sansSerif:
|
||||
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) {
|
||||
Text("Font size")
|
||||
.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) {
|
||||
ForEach(FontSize.allCases, id: \.self) { size in
|
||||
Text(size.displayName).tag(size)
|
||||
|
||||
@ -24,42 +24,116 @@ class FontSettingsViewModel {
|
||||
|
||||
// MARK: - Computed Font Properties for Preview
|
||||
var previewTitleFont: Font {
|
||||
let size = selectedFontSize.size
|
||||
|
||||
switch selectedFontFamily {
|
||||
// Apple System Fonts
|
||||
case .system:
|
||||
return selectedFontSize.systemFont.weight(.semibold)
|
||||
case .serif:
|
||||
return Font.custom("Times New Roman", size: selectedFontSize.size).weight(.semibold)
|
||||
case .sansSerif:
|
||||
return Font.custom("Helvetica Neue", size: selectedFontSize.size).weight(.semibold)
|
||||
return Font.system(size: size).weight(.semibold)
|
||||
case .newYork:
|
||||
return Font.system(size: size, design: .serif).weight(.semibold)
|
||||
case .avenirNext:
|
||||
return Font.custom("AvenirNext-DemiBold", size: size)
|
||||
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 {
|
||||
let size = selectedFontSize.size
|
||||
|
||||
switch selectedFontFamily {
|
||||
// Apple System Fonts
|
||||
case .system:
|
||||
return selectedFontSize.systemFont
|
||||
case .serif:
|
||||
return Font.custom("Times New Roman", size: selectedFontSize.size)
|
||||
case .sansSerif:
|
||||
return Font.custom("Helvetica Neue", size: selectedFontSize.size)
|
||||
return Font.system(size: size)
|
||||
case .newYork:
|
||||
return Font.system(size: size, design: .serif)
|
||||
case .avenirNext:
|
||||
return Font.custom("AvenirNext-Regular", size: size)
|
||||
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 {
|
||||
let captionSize = selectedFontSize.size * 0.85
|
||||
|
||||
switch selectedFontFamily {
|
||||
// Apple System Fonts
|
||||
case .system:
|
||||
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:
|
||||
return Font.custom("Times New Roman", size: captionSize)
|
||||
case .sansSerif:
|
||||
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 showingLegalNotice = false
|
||||
@State private var showReleaseNotes = false
|
||||
@State private var showingLicenses = false
|
||||
|
||||
var body: some View {
|
||||
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: {
|
||||
if let url = URL(string: "https://github.com/ilyas-hallak/readeck-ios/issues") {
|
||||
UIApplication.shared.open(url)
|
||||
@ -87,6 +100,9 @@ struct LegalPrivacySettingsView: View {
|
||||
.sheet(isPresented: $showReleaseNotes) {
|
||||
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()
|
||||
}
|
||||
|
||||
SettingsRowNavigationLink(
|
||||
icon: "textformat",
|
||||
iconColor: .green,
|
||||
title: "Font Debug",
|
||||
subtitle: "View available fonts"
|
||||
) {
|
||||
FontDebugView()
|
||||
}
|
||||
} header: {
|
||||
HStack {
|
||||
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