diff --git a/.ruby-version b/.ruby-version
new file mode 100644
index 0000000..15a2799
--- /dev/null
+++ b/.ruby-version
@@ -0,0 +1 @@
+3.3.0
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000..7a118b4
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,3 @@
+source "https://rubygems.org"
+
+gem "fastlane"
diff --git a/Gemfile.lock b/Gemfile.lock
new file mode 100644
index 0000000..3eae243
--- /dev/null
+++ b/Gemfile.lock
@@ -0,0 +1,227 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ CFPropertyList (3.0.7)
+ base64
+ nkf
+ rexml
+ addressable (2.8.7)
+ public_suffix (>= 2.0.2, < 7.0)
+ artifactory (3.0.17)
+ atomos (0.1.3)
+ aws-eventstream (1.4.0)
+ aws-partitions (1.1129.0)
+ aws-sdk-core (3.226.2)
+ aws-eventstream (~> 1, >= 1.3.0)
+ aws-partitions (~> 1, >= 1.992.0)
+ aws-sigv4 (~> 1.9)
+ base64
+ jmespath (~> 1, >= 1.6.1)
+ logger
+ aws-sdk-kms (1.106.0)
+ aws-sdk-core (~> 3, >= 3.225.0)
+ aws-sigv4 (~> 1.5)
+ aws-sdk-s3 (1.193.0)
+ aws-sdk-core (~> 3, >= 3.225.0)
+ aws-sdk-kms (~> 1)
+ aws-sigv4 (~> 1.5)
+ aws-sigv4 (1.12.1)
+ aws-eventstream (~> 1, >= 1.0.2)
+ babosa (1.0.4)
+ base64 (0.3.0)
+ claide (1.1.0)
+ colored (1.2)
+ colored2 (3.1.2)
+ commander (4.6.0)
+ highline (~> 2.0.0)
+ declarative (0.0.20)
+ digest-crc (0.7.0)
+ rake (>= 12.0.0, < 14.0.0)
+ domain_name (0.6.20240107)
+ dotenv (2.8.1)
+ emoji_regex (3.2.3)
+ excon (0.112.0)
+ faraday (1.10.4)
+ faraday-em_http (~> 1.0)
+ faraday-em_synchrony (~> 1.0)
+ faraday-excon (~> 1.1)
+ faraday-httpclient (~> 1.0)
+ faraday-multipart (~> 1.0)
+ faraday-net_http (~> 1.0)
+ faraday-net_http_persistent (~> 1.0)
+ faraday-patron (~> 1.0)
+ faraday-rack (~> 1.0)
+ faraday-retry (~> 1.0)
+ ruby2_keywords (>= 0.0.4)
+ faraday-cookie_jar (0.0.7)
+ faraday (>= 0.8.0)
+ http-cookie (~> 1.0.0)
+ faraday-em_http (1.0.0)
+ faraday-em_synchrony (1.0.1)
+ faraday-excon (1.1.0)
+ faraday-httpclient (1.0.1)
+ faraday-multipart (1.1.1)
+ multipart-post (~> 2.0)
+ faraday-net_http (1.0.2)
+ faraday-net_http_persistent (1.2.0)
+ faraday-patron (1.0.0)
+ faraday-rack (1.0.0)
+ faraday-retry (1.0.3)
+ faraday_middleware (1.2.1)
+ faraday (~> 1.0)
+ fastimage (2.4.0)
+ fastlane (2.228.0)
+ CFPropertyList (>= 2.3, < 4.0.0)
+ addressable (>= 2.8, < 3.0.0)
+ artifactory (~> 3.0)
+ aws-sdk-s3 (~> 1.0)
+ babosa (>= 1.0.3, < 2.0.0)
+ bundler (>= 1.12.0, < 3.0.0)
+ colored (~> 1.2)
+ commander (~> 4.6)
+ dotenv (>= 2.1.1, < 3.0.0)
+ emoji_regex (>= 0.1, < 4.0)
+ excon (>= 0.71.0, < 1.0.0)
+ faraday (~> 1.0)
+ faraday-cookie_jar (~> 0.0.6)
+ faraday_middleware (~> 1.0)
+ fastimage (>= 2.1.0, < 3.0.0)
+ fastlane-sirp (>= 1.0.0)
+ gh_inspector (>= 1.1.2, < 2.0.0)
+ google-apis-androidpublisher_v3 (~> 0.3)
+ google-apis-playcustomapp_v1 (~> 0.1)
+ google-cloud-env (>= 1.6.0, < 2.0.0)
+ google-cloud-storage (~> 1.31)
+ highline (~> 2.0)
+ http-cookie (~> 1.0.5)
+ json (< 3.0.0)
+ jwt (>= 2.1.0, < 3)
+ mini_magick (>= 4.9.4, < 5.0.0)
+ multipart-post (>= 2.0.0, < 3.0.0)
+ naturally (~> 2.2)
+ optparse (>= 0.1.1, < 1.0.0)
+ plist (>= 3.1.0, < 4.0.0)
+ rubyzip (>= 2.0.0, < 3.0.0)
+ security (= 0.1.5)
+ simctl (~> 1.6.3)
+ terminal-notifier (>= 2.0.0, < 3.0.0)
+ terminal-table (~> 3)
+ tty-screen (>= 0.6.3, < 1.0.0)
+ tty-spinner (>= 0.8.0, < 1.0.0)
+ word_wrap (~> 1.0.0)
+ xcodeproj (>= 1.13.0, < 2.0.0)
+ xcpretty (~> 0.4.1)
+ xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
+ fastlane-sirp (1.0.0)
+ sysrandom (~> 1.0)
+ gh_inspector (1.1.3)
+ google-apis-androidpublisher_v3 (0.54.0)
+ google-apis-core (>= 0.11.0, < 2.a)
+ google-apis-core (0.11.3)
+ addressable (~> 2.5, >= 2.5.1)
+ googleauth (>= 0.16.2, < 2.a)
+ httpclient (>= 2.8.1, < 3.a)
+ mini_mime (~> 1.0)
+ representable (~> 3.0)
+ retriable (>= 2.0, < 4.a)
+ rexml
+ google-apis-iamcredentials_v1 (0.17.0)
+ google-apis-core (>= 0.11.0, < 2.a)
+ google-apis-playcustomapp_v1 (0.13.0)
+ google-apis-core (>= 0.11.0, < 2.a)
+ google-apis-storage_v1 (0.31.0)
+ google-apis-core (>= 0.11.0, < 2.a)
+ google-cloud-core (1.8.0)
+ google-cloud-env (>= 1.0, < 3.a)
+ google-cloud-errors (~> 1.0)
+ google-cloud-env (1.6.0)
+ faraday (>= 0.17.3, < 3.0)
+ google-cloud-errors (1.5.0)
+ google-cloud-storage (1.47.0)
+ addressable (~> 2.8)
+ digest-crc (~> 0.4)
+ google-apis-iamcredentials_v1 (~> 0.1)
+ google-apis-storage_v1 (~> 0.31.0)
+ google-cloud-core (~> 1.6)
+ googleauth (>= 0.16.2, < 2.a)
+ mini_mime (~> 1.0)
+ googleauth (1.8.1)
+ faraday (>= 0.17.3, < 3.a)
+ jwt (>= 1.4, < 3.0)
+ multi_json (~> 1.11)
+ os (>= 0.9, < 2.0)
+ signet (>= 0.16, < 2.a)
+ highline (2.0.3)
+ http-cookie (1.0.8)
+ domain_name (~> 0.5)
+ httpclient (2.9.0)
+ mutex_m
+ jmespath (1.6.2)
+ json (2.13.0)
+ jwt (2.10.2)
+ base64
+ logger (1.7.0)
+ mini_magick (4.13.2)
+ mini_mime (1.1.5)
+ multi_json (1.16.0)
+ multipart-post (2.4.1)
+ mutex_m (0.3.0)
+ nanaimo (0.4.0)
+ naturally (2.3.0)
+ nkf (0.2.0)
+ optparse (0.6.0)
+ os (1.1.4)
+ plist (3.7.2)
+ public_suffix (6.0.2)
+ rake (13.3.0)
+ representable (3.2.0)
+ declarative (< 0.1.0)
+ trailblazer-option (>= 0.1.1, < 0.2.0)
+ uber (< 0.2.0)
+ retriable (3.1.2)
+ rexml (3.4.1)
+ rouge (3.28.0)
+ ruby2_keywords (0.0.5)
+ rubyzip (2.4.1)
+ security (0.1.5)
+ signet (0.20.0)
+ addressable (~> 2.8)
+ faraday (>= 0.17.5, < 3.a)
+ jwt (>= 1.5, < 3.0)
+ multi_json (~> 1.10)
+ simctl (1.6.10)
+ CFPropertyList
+ naturally
+ sysrandom (1.0.5)
+ terminal-notifier (2.0.0)
+ terminal-table (3.0.2)
+ unicode-display_width (>= 1.1.1, < 3)
+ trailblazer-option (0.1.2)
+ tty-cursor (0.7.1)
+ tty-screen (0.8.2)
+ tty-spinner (0.9.3)
+ tty-cursor (~> 0.7)
+ uber (0.1.0)
+ unicode-display_width (2.6.0)
+ word_wrap (1.0.0)
+ xcodeproj (1.27.0)
+ CFPropertyList (>= 2.3.3, < 4.0)
+ atomos (~> 0.1.3)
+ claide (>= 1.0.2, < 2.0)
+ colored2 (~> 3.1)
+ nanaimo (~> 0.4.0)
+ rexml (>= 3.3.6, < 4.0)
+ xcpretty (0.4.1)
+ rouge (~> 3.28.0)
+ xcpretty-travis-formatter (1.0.1)
+ xcpretty (~> 0.2, >= 0.0.7)
+
+PLATFORMS
+ arm64-darwin-24
+ ruby
+
+DEPENDENCIES
+ fastlane
+
+BUNDLED WITH
+ 2.6.9
diff --git a/Localizable.xcstrings b/Localizable.xcstrings
index 1e7c160..3694e57 100644
--- a/Localizable.xcstrings
+++ b/Localizable.xcstrings
@@ -61,6 +61,9 @@
}
}
}
+ },
+ "Als Favorit markieren" : {
+
},
"Anmelden & speichern" : {
@@ -82,9 +85,6 @@
},
"Bookmark archivieren" : {
- },
- "Bookmark ist archiviert" : {
-
},
"Bookmark speichern" : {
@@ -287,9 +287,6 @@
},
"Sync-Intervall" : {
- },
- "Tags" : {
-
},
"Theme" : {
diff --git a/URLShare/URLShare.entitlements b/URLShare/URLShare.entitlements
index 264629d..1e5156a 100644
--- a/URLShare/URLShare.entitlements
+++ b/URLShare/URLShare.entitlements
@@ -4,7 +4,7 @@
keychain-access-groups
- $(AppIdentifierPrefix)de.ilyashallak.readeck2
+ $(AppIdentifierPrefix)de.ilyashallak.readeck
diff --git a/fastlane/Appfile b/fastlane/Appfile
new file mode 100644
index 0000000..d7455ae
--- /dev/null
+++ b/fastlane/Appfile
@@ -0,0 +1,6 @@
+app_identifier("de.ilyashallak.readeck2") # The bundle identifier of your app
+# apple_id("[[APPLE_ID]]") # Your Apple Developer Portal username
+
+
+# For more information about the Appfile, see:
+# https://docs.fastlane.tools/advanced/#appfile
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
new file mode 100644
index 0000000..138edf3
--- /dev/null
+++ b/fastlane/Fastfile
@@ -0,0 +1,32 @@
+# This file contains the fastlane.tools configuration
+# You can find the documentation at https://docs.fastlane.tools
+#
+# For a list of all available actions, check out
+#
+# https://docs.fastlane.tools/actions
+#
+# For a list of all available plugins, check out
+#
+# https://docs.fastlane.tools/plugins/available-plugins
+#
+
+# Uncomment the line if you want fastlane to automatically update itself
+# update_fastlane
+
+default_platform(:ios)
+xcversion(version: "16.4.0")
+
+platform :ios do
+ #desc "Generate new localized screenshots"
+ #lane :screenshots do
+ # capture_screenshots(scheme: "readeck")
+ # end
+
+ desc "Build and upload to TestFlight"
+ lane :beta do
+ build_app(scheme: "readeck")
+ upload_to_testflight
+ end
+end
+
+
diff --git a/fastlane/Snapfile b/fastlane/Snapfile
new file mode 100644
index 0000000..809379b
--- /dev/null
+++ b/fastlane/Snapfile
@@ -0,0 +1,32 @@
+# Uncomment the lines below you want to change by removing the # in the beginning
+
+# A list of devices you want to take the screenshots from
+devices([
+ "iPhone 15 Pro",
+ #"iPad Pro (11-inch) (4th generation)"
+])
+
+ languages([
+ "en-US",
+ "de-DE",
+# "it-IT",
+# ["pt", "pt_BR"] # Portuguese with Brazilian locale
+ ])
+
+# The name of the scheme which contains the UI Tests
+scheme("readeck")
+
+# Where should the resulting screenshots be stored?
+output_directory("./screenshots")
+
+# remove the '#' to clear all previously generated screenshots before creating new ones
+clear_previous_screenshots(true)
+
+# Remove the '#' to set the status bar to 9:41 AM, and show full battery and reception. See also override_status_bar_arguments for custom options.
+ override_status_bar(true)
+
+# Arguments to pass to the app on launch. See https://docs.fastlane.tools/actions/snapshot/#launch-arguments
+# launch_arguments(["-favColor red"])
+
+# For more information about all available options run
+# fastlane action snapshot
diff --git a/fastlane/SnapshotHelper.swift b/fastlane/SnapshotHelper.swift
new file mode 100644
index 0000000..6dec130
--- /dev/null
+++ b/fastlane/SnapshotHelper.swift
@@ -0,0 +1,313 @@
+//
+// SnapshotHelper.swift
+// Example
+//
+// Created by Felix Krause on 10/8/15.
+//
+
+// -----------------------------------------------------
+// IMPORTANT: When modifying this file, make sure to
+// increment the version number at the very
+// bottom of the file to notify users about
+// the new SnapshotHelper.swift
+// -----------------------------------------------------
+
+import Foundation
+import XCTest
+
+@MainActor
+func setupSnapshot(_ app: XCUIApplication, waitForAnimations: Bool = true) {
+ Snapshot.setupSnapshot(app, waitForAnimations: waitForAnimations)
+}
+
+@MainActor
+func snapshot(_ name: String, waitForLoadingIndicator: Bool) {
+ if waitForLoadingIndicator {
+ Snapshot.snapshot(name)
+ } else {
+ Snapshot.snapshot(name, timeWaitingForIdle: 0)
+ }
+}
+
+/// - Parameters:
+/// - name: The name of the snapshot
+/// - timeout: Amount of seconds to wait until the network loading indicator disappears. Pass `0` if you don't want to wait.
+@MainActor
+func snapshot(_ name: String, timeWaitingForIdle timeout: TimeInterval = 20) {
+ Snapshot.snapshot(name, timeWaitingForIdle: timeout)
+}
+
+enum SnapshotError: Error, CustomDebugStringConvertible {
+ case cannotFindSimulatorHomeDirectory
+ case cannotRunOnPhysicalDevice
+
+ var debugDescription: String {
+ switch self {
+ case .cannotFindSimulatorHomeDirectory:
+ return "Couldn't find simulator home location. Please, check SIMULATOR_HOST_HOME env variable."
+ case .cannotRunOnPhysicalDevice:
+ return "Can't use Snapshot on a physical device."
+ }
+ }
+}
+
+@objcMembers
+@MainActor
+open class Snapshot: NSObject {
+ static var app: XCUIApplication?
+ static var waitForAnimations = true
+ static var cacheDirectory: URL?
+ static var screenshotsDirectory: URL? {
+ return cacheDirectory?.appendingPathComponent("screenshots", isDirectory: true)
+ }
+ static var deviceLanguage = ""
+ static var currentLocale = ""
+
+ open class func setupSnapshot(_ app: XCUIApplication, waitForAnimations: Bool = true) {
+
+ Snapshot.app = app
+ Snapshot.waitForAnimations = waitForAnimations
+
+ do {
+ let cacheDir = try getCacheDirectory()
+ Snapshot.cacheDirectory = cacheDir
+ setLanguage(app)
+ setLocale(app)
+ setLaunchArguments(app)
+ } catch let error {
+ NSLog(error.localizedDescription)
+ }
+ }
+
+ class func setLanguage(_ app: XCUIApplication) {
+ guard let cacheDirectory = self.cacheDirectory else {
+ NSLog("CacheDirectory is not set - probably running on a physical device?")
+ return
+ }
+
+ let path = cacheDirectory.appendingPathComponent("language.txt")
+
+ do {
+ let trimCharacterSet = CharacterSet.whitespacesAndNewlines
+ deviceLanguage = try String(contentsOf: path, encoding: .utf8).trimmingCharacters(in: trimCharacterSet)
+ app.launchArguments += ["-AppleLanguages", "(\(deviceLanguage))"]
+ } catch {
+ NSLog("Couldn't detect/set language...")
+ }
+ }
+
+ class func setLocale(_ app: XCUIApplication) {
+ guard let cacheDirectory = self.cacheDirectory else {
+ NSLog("CacheDirectory is not set - probably running on a physical device?")
+ return
+ }
+
+ let path = cacheDirectory.appendingPathComponent("locale.txt")
+
+ do {
+ let trimCharacterSet = CharacterSet.whitespacesAndNewlines
+ currentLocale = try String(contentsOf: path, encoding: .utf8).trimmingCharacters(in: trimCharacterSet)
+ } catch {
+ NSLog("Couldn't detect/set locale...")
+ }
+
+ if currentLocale.isEmpty && !deviceLanguage.isEmpty {
+ currentLocale = Locale(identifier: deviceLanguage).identifier
+ }
+
+ if !currentLocale.isEmpty {
+ app.launchArguments += ["-AppleLocale", "\"\(currentLocale)\""]
+ }
+ }
+
+ class func setLaunchArguments(_ app: XCUIApplication) {
+ guard let cacheDirectory = self.cacheDirectory else {
+ NSLog("CacheDirectory is not set - probably running on a physical device?")
+ return
+ }
+
+ let path = cacheDirectory.appendingPathComponent("snapshot-launch_arguments.txt")
+ app.launchArguments += ["-FASTLANE_SNAPSHOT", "YES", "-ui_testing"]
+
+ do {
+ let launchArguments = try String(contentsOf: path, encoding: String.Encoding.utf8)
+ let regex = try NSRegularExpression(pattern: "(\\\".+?\\\"|\\S+)", options: [])
+ let matches = regex.matches(in: launchArguments, options: [], range: NSRange(location: 0, length: launchArguments.count))
+ let results = matches.map { result -> String in
+ (launchArguments as NSString).substring(with: result.range)
+ }
+ app.launchArguments += results
+ } catch {
+ NSLog("Couldn't detect/set launch_arguments...")
+ }
+ }
+
+ open class func snapshot(_ name: String, timeWaitingForIdle timeout: TimeInterval = 20) {
+ if timeout > 0 {
+ waitForLoadingIndicatorToDisappear(within: timeout)
+ }
+
+ NSLog("snapshot: \(name)") // more information about this, check out https://docs.fastlane.tools/actions/snapshot/#how-does-it-work
+
+ if Snapshot.waitForAnimations {
+ sleep(1) // Waiting for the animation to be finished (kind of)
+ }
+
+ #if os(OSX)
+ guard let app = self.app else {
+ NSLog("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().")
+ return
+ }
+
+ app.typeKey(XCUIKeyboardKeySecondaryFn, modifierFlags: [])
+ #else
+
+ guard self.app != nil else {
+ NSLog("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().")
+ return
+ }
+
+ let screenshot = XCUIScreen.main.screenshot()
+ #if os(iOS) && !targetEnvironment(macCatalyst)
+ let image = XCUIDevice.shared.orientation.isLandscape ? fixLandscapeOrientation(image: screenshot.image) : screenshot.image
+ #else
+ let image = screenshot.image
+ #endif
+
+ guard var simulator = ProcessInfo().environment["SIMULATOR_DEVICE_NAME"], let screenshotsDir = screenshotsDirectory else { return }
+
+ do {
+ // The simulator name contains "Clone X of " inside the screenshot file when running parallelized UI Tests on concurrent devices
+ let regex = try NSRegularExpression(pattern: "Clone [0-9]+ of ")
+ let range = NSRange(location: 0, length: simulator.count)
+ simulator = regex.stringByReplacingMatches(in: simulator, range: range, withTemplate: "")
+
+ let path = screenshotsDir.appendingPathComponent("\(simulator)-\(name).png")
+ #if swift(<5.0)
+ try UIImagePNGRepresentation(image)?.write(to: path, options: .atomic)
+ #else
+ try image.pngData()?.write(to: path, options: .atomic)
+ #endif
+ } catch let error {
+ NSLog("Problem writing screenshot: \(name) to \(screenshotsDir)/\(simulator)-\(name).png")
+ NSLog(error.localizedDescription)
+ }
+ #endif
+ }
+
+ class func fixLandscapeOrientation(image: UIImage) -> UIImage {
+ #if os(watchOS)
+ return image
+ #else
+ if #available(iOS 10.0, *) {
+ let format = UIGraphicsImageRendererFormat()
+ format.scale = image.scale
+ let renderer = UIGraphicsImageRenderer(size: image.size, format: format)
+ return renderer.image { context in
+ image.draw(in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height))
+ }
+ } else {
+ return image
+ }
+ #endif
+ }
+
+ class func waitForLoadingIndicatorToDisappear(within timeout: TimeInterval) {
+ #if os(tvOS)
+ return
+ #endif
+
+ guard let app = self.app else {
+ NSLog("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().")
+ return
+ }
+
+ let networkLoadingIndicator = app.otherElements.deviceStatusBars.networkLoadingIndicators.element
+ let networkLoadingIndicatorDisappeared = XCTNSPredicateExpectation(predicate: NSPredicate(format: "exists == false"), object: networkLoadingIndicator)
+ _ = XCTWaiter.wait(for: [networkLoadingIndicatorDisappeared], timeout: timeout)
+ }
+
+ class func getCacheDirectory() throws -> URL {
+ let cachePath = "Library/Caches/tools.fastlane"
+ // on OSX config is stored in /Users//Library
+ // and on iOS/tvOS/WatchOS it's in simulator's home dir
+ #if os(OSX)
+ let homeDir = URL(fileURLWithPath: NSHomeDirectory())
+ return homeDir.appendingPathComponent(cachePath)
+ #elseif arch(i386) || arch(x86_64) || arch(arm64)
+ guard let simulatorHostHome = ProcessInfo().environment["SIMULATOR_HOST_HOME"] else {
+ throw SnapshotError.cannotFindSimulatorHomeDirectory
+ }
+ let homeDir = URL(fileURLWithPath: simulatorHostHome)
+ return homeDir.appendingPathComponent(cachePath)
+ #else
+ throw SnapshotError.cannotRunOnPhysicalDevice
+ #endif
+ }
+}
+
+private extension XCUIElementAttributes {
+ var isNetworkLoadingIndicator: Bool {
+ if hasAllowListedIdentifier { return false }
+
+ let hasOldLoadingIndicatorSize = frame.size == CGSize(width: 10, height: 20)
+ let hasNewLoadingIndicatorSize = frame.size.width.isBetween(46, and: 47) && frame.size.height.isBetween(2, and: 3)
+
+ return hasOldLoadingIndicatorSize || hasNewLoadingIndicatorSize
+ }
+
+ var hasAllowListedIdentifier: Bool {
+ let allowListedIdentifiers = ["GeofenceLocationTrackingOn", "StandardLocationTrackingOn"]
+
+ return allowListedIdentifiers.contains(identifier)
+ }
+
+ func isStatusBar(_ deviceWidth: CGFloat) -> Bool {
+ if elementType == .statusBar { return true }
+ guard frame.origin == .zero else { return false }
+
+ let oldStatusBarSize = CGSize(width: deviceWidth, height: 20)
+ let newStatusBarSize = CGSize(width: deviceWidth, height: 44)
+
+ return [oldStatusBarSize, newStatusBarSize].contains(frame.size)
+ }
+}
+
+private extension XCUIElementQuery {
+ var networkLoadingIndicators: XCUIElementQuery {
+ let isNetworkLoadingIndicator = NSPredicate { (evaluatedObject, _) in
+ guard let element = evaluatedObject as? XCUIElementAttributes else { return false }
+
+ return element.isNetworkLoadingIndicator
+ }
+
+ return self.containing(isNetworkLoadingIndicator)
+ }
+
+ @MainActor
+ var deviceStatusBars: XCUIElementQuery {
+ guard let app = Snapshot.app else {
+ fatalError("XCUIApplication is not set. Please call setupSnapshot(app) before snapshot().")
+ }
+
+ let deviceWidth = app.windows.firstMatch.frame.width
+
+ let isStatusBar = NSPredicate { (evaluatedObject, _) in
+ guard let element = evaluatedObject as? XCUIElementAttributes else { return false }
+
+ return element.isStatusBar(deviceWidth)
+ }
+
+ return self.containing(isStatusBar)
+ }
+}
+
+private extension CGFloat {
+ func isBetween(_ numberA: CGFloat, and numberB: CGFloat) -> Bool {
+ return numberA...numberB ~= self
+ }
+}
+
+// Please don't remove the lines below
+// They are used to detect outdated configuration files
+// SnapshotHelperVersion [1.30]
diff --git a/readeck.xcodeproj/project.pbxproj b/readeck.xcodeproj/project.pbxproj
index 2918e14..452ceda 100644
--- a/readeck.xcodeproj/project.pbxproj
+++ b/readeck.xcodeproj/project.pbxproj
@@ -10,7 +10,6 @@
5D2B7FB92DFA27A400EBDB2B /* URLShare.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 5D2B7FAF2DFA27A400EBDB2B /* URLShare.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
5D348CC32E0C9F4F00D0AF21 /* netfox in Frameworks */ = {isa = PBXBuildFile; productRef = 5D348CC22E0C9F4F00D0AF21 /* netfox */; };
5DA241FB2E17C3B3007531C3 /* RswiftLibrary in Frameworks */ = {isa = PBXBuildFile; productRef = 5DA241FA2E17C3B3007531C3 /* RswiftLibrary */; };
- 5DA241FD2E17C3B3007531C3 /* rswift in Frameworks */ = {isa = PBXBuildFile; productRef = 5DA241FC2E17C3B3007531C3 /* rswift */; };
5DA242132E17D31A007531C3 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 5DA242122E17D31A007531C3 /* Localizable.xcstrings */; };
5DA242142E17D31A007531C3 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 5DA242122E17D31A007531C3 /* Localizable.xcstrings */; };
/* End PBXBuildFile section */
@@ -143,7 +142,6 @@
files = (
5D348CC32E0C9F4F00D0AF21 /* netfox in Frameworks */,
5DA241FB2E17C3B3007531C3 /* RswiftLibrary in Frameworks */,
- 5DA241FD2E17C3B3007531C3 /* rswift in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -235,7 +233,6 @@
packageProductDependencies = (
5D348CC22E0C9F4F00D0AF21 /* netfox */,
5DA241FA2E17C3B3007531C3 /* RswiftLibrary */,
- 5DA241FC2E17C3B3007531C3 /* rswift */,
);
productName = readeck;
productReference = 5D45F9C82DF858680048D5B8 /* readeck.app */;
@@ -446,7 +443,7 @@
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = de.ilyashallak.readeck2.URLShare;
+ PRODUCT_BUNDLE_IDENTIFIER = de.ilyashallak.readeck.URLShare;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
@@ -475,7 +472,7 @@
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = de.ilyashallak.readeck2.URLShare;
+ PRODUCT_BUNDLE_IDENTIFIER = de.ilyashallak.readeck.URLShare;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
@@ -542,6 +539,7 @@
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -597,6 +595,7 @@
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
+ SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_EMIT_LOC_STRINGS = YES;
};
@@ -610,13 +609,15 @@
CODE_SIGN_ENTITLEMENTS = readeck/readeck.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
+ CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_ASSET_PATHS = "\"readeck/Preview Content\"";
DEVELOPMENT_TEAM = 8J69P655GN;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = readeck/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = "";
+ INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
@@ -625,21 +626,21 @@
"INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
- INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
- INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 15.1;
MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = de.ilyashallak.readeck2;
+ PRODUCT_BUNDLE_IDENTIFIER = de.ilyashallak.readeck;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = auto;
- SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
+ SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = "1,2,7";
+ TARGETED_DEVICE_FAMILY = "1,2";
XROS_DEPLOYMENT_TARGET = 2.1;
};
name = Debug;
@@ -652,13 +653,15 @@
CODE_SIGN_ENTITLEMENTS = readeck/readeck.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 1;
+ CURRENT_PROJECT_VERSION = 2;
DEVELOPMENT_ASSET_PATHS = "\"readeck/Preview Content\"";
DEVELOPMENT_TEAM = 8J69P655GN;
ENABLE_HARDENED_RUNTIME = YES;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = readeck/Info.plist;
+ INFOPLIST_KEY_CFBundleDisplayName = "";
+ INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.productivity";
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
@@ -667,21 +670,21 @@
"INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES;
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphoneos*]" = UIStatusBarStyleDefault;
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
- INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
- INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
MACOSX_DEPLOYMENT_TARGET = 15.1;
MARKETING_VERSION = 1.0;
- PRODUCT_BUNDLE_IDENTIFIER = de.ilyashallak.readeck2;
+ PRODUCT_BUNDLE_IDENTIFIER = de.ilyashallak.readeck;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SDKROOT = auto;
- SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator";
+ SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
- TARGETED_DEVICE_FAMILY = "1,2,7";
+ TARGETED_DEVICE_FAMILY = "1,2";
XROS_DEPLOYMENT_TARGET = 2.1;
};
name = Release;
@@ -856,11 +859,6 @@
package = 5DA241F92E17C3B3007531C3 /* XCRemoteSwiftPackageReference "R.swift" */;
productName = RswiftLibrary;
};
- 5DA241FC2E17C3B3007531C3 /* rswift */ = {
- isa = XCSwiftPackageProductDependency;
- package = 5DA241F92E17C3B3007531C3 /* XCRemoteSwiftPackageReference "R.swift" */;
- productName = rswift;
- };
5DA241FE2E17C3CE007531C3 /* RswiftGenerateInternalResources */ = {
isa = XCSwiftPackageProductDependency;
package = 5DA241F92E17C3B3007531C3 /* XCRemoteSwiftPackageReference "R.swift" */;
diff --git a/readeck.xcodeproj/xcshareddata/xcschemes/URLShare.xcscheme b/readeck.xcodeproj/xcshareddata/xcschemes/URLShare.xcscheme
new file mode 100644
index 0000000..4a2cf8a
--- /dev/null
+++ b/readeck.xcodeproj/xcshareddata/xcschemes/URLShare.xcscheme
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/readeck.xcodeproj/xcshareddata/xcschemes/readeck.xcscheme b/readeck.xcodeproj/xcshareddata/xcschemes/readeck.xcscheme
new file mode 100644
index 0000000..1264820
--- /dev/null
+++ b/readeck.xcodeproj/xcshareddata/xcschemes/readeck.xcscheme
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/readeck/Data/KeychainHelper.swift b/readeck/Data/KeychainHelper.swift
index 57cc0ef..4e9fce2 100644
--- a/readeck/Data/KeychainHelper.swift
+++ b/readeck/Data/KeychainHelper.swift
@@ -5,7 +5,7 @@ class KeychainHelper {
static let shared = KeychainHelper()
private init() {}
- private static let accessGroup = "8J69P655GN.de.ilyashallak.readeck2"
+ private static let accessGroup = "8J69P655GN.de.ilyashallak.readeck"
@discardableResult
func saveToken(_ token: String) -> Bool {
diff --git a/readeck/Domain/Model/BookmarkDetail.swift b/readeck/Domain/Model/BookmarkDetail.swift
index 62c1f3b..ef70101 100644
--- a/readeck/Domain/Model/BookmarkDetail.swift
+++ b/readeck/Domain/Model/BookmarkDetail.swift
@@ -12,7 +12,7 @@ struct BookmarkDetail {
let wordCount: Int?
let readingTime: Int?
let hasArticle: Bool
- let isMarked: Bool
+ var isMarked: Bool
var isArchived: Bool
let labels: [String]
let thumbnailUrl: String
diff --git a/readeck/Info.plist b/readeck/Info.plist
index 29aeaca..81ffd01 100644
--- a/readeck/Info.plist
+++ b/readeck/Info.plist
@@ -13,6 +13,15 @@
+ NSAppTransportSecurity
+
+ NSAllowsLocalNetworking
+
+
+ UIBackgroundModes
+
+ audio
+
UILaunchScreen
UIColorName
@@ -20,9 +29,5 @@
UIImageName
readeck
- UIBackgroundModes
-
- audio
-
diff --git a/readeck/UI/BookmarkDetail/BookmarkDetailView.swift b/readeck/UI/BookmarkDetail/BookmarkDetailView.swift
index 0481f92..e89f918 100644
--- a/readeck/UI/BookmarkDetail/BookmarkDetailView.swift
+++ b/readeck/UI/BookmarkDetail/BookmarkDetailView.swift
@@ -16,13 +16,17 @@ struct BookmarkDetailView: View {
ScrollView {
ZStack(alignment: .top) {
headerView(geometry: geometry)
- VStack(alignment: .leading, spacing: 16) {
+ VStack(alignment: .center, spacing: 16) {
Color.clear.frame(height: viewModel.bookmarkDetail.imageUrl.isEmpty ? 84 : headerHeight)
titleSection
Divider().padding(.horizontal)
contentSection
Spacer(minLength: 40)
- archiveSection
+ if viewModel.isLoadingArticle == false {
+ archiveSection
+ .transition(.opacity.combined(with: .move(edge: .bottom)))
+ .animation(.easeInOut, value: viewModel.articleContent)
+ }
}
}
}
@@ -152,11 +156,14 @@ struct BookmarkDetailView: View {
private var contentSection: some View {
if let settings = viewModel.settings, !viewModel.articleContent.isEmpty {
WebView(htmlContent: viewModel.articleContent, settings: settings) { height in
- webViewHeight = height
+ withAnimation(.easeInOut(duration: 0.3)) {
+ webViewHeight = height
+ }
}
.frame(height: webViewHeight)
.cornerRadius(14)
.padding(.horizontal)
+ .animation(.easeInOut, value: webViewHeight)
} else if viewModel.isLoadingArticle {
ProgressView("Lade Artikel...")
.frame(maxWidth: .infinity, alignment: .center)
@@ -174,7 +181,7 @@ struct BookmarkDetailView: View {
}
.buttonStyle(.borderedProminent)
.padding(.horizontal)
- .padding(.top, 32)
+ .padding(.top, 4)
}
}
@@ -280,13 +287,29 @@ struct BookmarkDetailView: View {
}
private var archiveSection: some View {
- VStack(spacing: 12) {
+ VStack(alignment: .center, spacing: 12) {
Text("Fertig mit Lesen?")
.font(.headline)
.padding(.top, 24)
- if viewModel.bookmarkDetail.isArchived {
- Label("Bookmark ist archiviert", systemImage: "archivebox.fill")
- } else {
+ VStack(alignment: .center, spacing: 16) {
+ Button(action: {
+ Task {
+ await viewModel.toggleFavorite(id: bookmarkId)
+ }
+ }) {
+ HStack {
+ Image(systemName: viewModel.bookmarkDetail.isMarked ? "star.fill" : "star")
+ .foregroundColor(viewModel.bookmarkDetail.isMarked ? .yellow : .gray)
+ Text(viewModel.bookmarkDetail.isMarked ? "Favorit" : "Als Favorit markieren")
+ }
+ .font(.title3.bold())
+ .frame(maxHeight: 60)
+ .padding(10)
+ }
+ .buttonStyle(.bordered)
+ .disabled(viewModel.isLoading)
+
+ // Archivieren-Button
Button(action: {
Task {
await viewModel.archiveBookmark(id: bookmarkId)
@@ -297,7 +320,8 @@ struct BookmarkDetailView: View {
Text("Bookmark archivieren")
}
.font(.title3.bold())
- .frame(maxWidth: .infinity, maxHeight: 40)
+ .frame(maxHeight: 60)
+ .padding(10)
}
.buttonStyle(.borderedProminent)
.disabled(viewModel.isLoading)
diff --git a/readeck/UI/BookmarkDetail/BookmarkDetailViewModel.swift b/readeck/UI/BookmarkDetail/BookmarkDetailViewModel.swift
index ba064a2..1bad4e1 100644
--- a/readeck/UI/BookmarkDetail/BookmarkDetailViewModel.swift
+++ b/readeck/UI/BookmarkDetail/BookmarkDetailViewModel.swift
@@ -13,7 +13,7 @@ class BookmarkDetailViewModel {
var articleParagraphs: [String] = []
var bookmark: Bookmark? = nil
var isLoading = false
- var isLoadingArticle = false
+ var isLoadingArticle = true
var errorMessage: String?
var settings: Settings?
@@ -85,4 +85,18 @@ class BookmarkDetailViewModel {
bookmarkDetail.content = articleContent
addTextToSpeechQueueUseCase.execute(bookmarkDetail: bookmarkDetail)
}
+
+ @MainActor
+ func toggleFavorite(id: String) async {
+ isLoading = true
+ errorMessage = nil
+ do {
+ let newValue = !bookmarkDetail.isMarked
+ try await updateBookmarkUseCase.toggleFavorite(bookmarkId: id, isMarked: newValue)
+ bookmarkDetail.isMarked = newValue
+ } catch {
+ errorMessage = "Fehler beim Aktualisieren des Favoriten-Status"
+ }
+ isLoading = false
+ }
}
diff --git a/readeck/UI/Menu/PhoneTabView.swift b/readeck/UI/Menu/PhoneTabView.swift
index 2835a9c..6303f30 100644
--- a/readeck/UI/Menu/PhoneTabView.swift
+++ b/readeck/UI/Menu/PhoneTabView.swift
@@ -28,28 +28,28 @@ struct PhoneTabView: View {
}
NavigationStack {
- if let selectedTab = selectedMoreTab {
- tabView(for: selectedTab)
- .navigationTitle(selectedTab.label)
- } else {
- VStack(alignment: .leading) {
- List(moreTabs, id: \.self, selection: $selectedMoreTab) { tab in
- NavigationLink {
- tabView(for: tab)
- .navigationTitle(tab.label)
- } label: {
- Label(tab.label, systemImage: tab.systemImage)
+ List(moreTabs, id: \.self) { tab in
+
+ NavigationLink {
+ tabView(for: tab)
+ .navigationTitle(tab.label)
+ .onDisappear {
+ // tags and search handle navigation by own
+ if tab != .tags && tab != .search {
+ selectedMoreTab = nil
+ }
}
- .listRowBackground(Color(R.color.bookmark_list_bg))
- }
- .navigationTitle("Mehr")
- .scrollContentBackground(.hidden)
- .background(Color(R.color.bookmark_list_bg))
-
- PlayerQueueResumeButton()
- .padding(.bottom, 16)
+ } label: {
+ Label(tab.label, systemImage: tab.systemImage)
}
+ .listRowBackground(Color(R.color.bookmark_list_bg))
}
+ .navigationTitle("Mehr")
+ .scrollContentBackground(.hidden)
+ .background(Color(R.color.bookmark_list_bg))
+
+ PlayerQueueResumeButton()
+ .padding(.bottom, 16)
}
.tabItem {
Label("Mehr", systemImage: "ellipsis")
diff --git a/readeck/UI/Search/SearchBookmarksView.swift b/readeck/UI/Search/SearchBookmarksView.swift
index dd673fa..3cc2eba 100644
--- a/readeck/UI/Search/SearchBookmarksView.swift
+++ b/readeck/UI/Search/SearchBookmarksView.swift
@@ -45,8 +45,16 @@ struct SearchBookmarksView: View {
if let bookmarks = viewModel.bookmarks?.bookmarks, !bookmarks.isEmpty {
List(bookmarks) { bookmark in
- Button(action: {
-
+ NavigationLink {
+ BookmarkDetailView(bookmarkId: bookmark.id)
+ } label: {
+ BookmarkCardView(bookmark: bookmark, currentState: .all, onArchive: {_ in }, onDelete: {_ in }, onToggleFavorite: {_ in })
+ .listRowBackground(Color(.systemBackground))
+ .padding(.vertical, 4)
+ }
+
+
+ /*Button(action: {
if UIDevice.isPhone {
selectedBookmarkId = bookmark.id
} else {
@@ -66,6 +74,7 @@ struct SearchBookmarksView: View {
}
.buttonStyle(.plain)
.listRowSeparator(.hidden)
+ */
}
.listStyle(.plain)
} else if !viewModel.isLoading && viewModel.bookmarks != nil {
diff --git a/readeck/readeck.entitlements b/readeck/readeck.entitlements
index 92613a3..900d8a5 100644
--- a/readeck/readeck.entitlements
+++ b/readeck/readeck.entitlements
@@ -8,7 +8,7 @@
keychain-access-groups
- $(AppIdentifierPrefix)de.ilyashallak.readeck2
+ $(AppIdentifierPrefix)de.ilyashallak.readeck
diff --git a/readeckUITests/readeckUITests.swift b/readeckUITests/readeckUITests.swift
index 097bf37..f8c0422 100644
--- a/readeckUITests/readeckUITests.swift
+++ b/readeckUITests/readeckUITests.swift
@@ -6,6 +6,7 @@
//
import XCTest
+import SnapshotHelper
final class readeckUITests: XCTestCase {
@@ -26,7 +27,9 @@ final class readeckUITests: XCTestCase {
func testExample() throws {
// UI tests must launch the application that they test.
let app = XCUIApplication()
+ setupSnapshot(app)
app.launch()
+ snapshot("01LaunchScreen")
// Use XCTAssert and related functions to verify your tests produce the correct results.
}