forked from lucxjo/leganto-apple
Compare commits
6 commits
Author | SHA1 | Date | |
---|---|---|---|
Louis Hollingworth | ef6ed994e2 | ||
Louis Hollingworth | abfb97d07b | ||
Louis Hollingworth | c2cc4ff5b1 | ||
Louis Hollingworth | d6f1f93fb3 | ||
Louis Hollingworth | feb658d43f | ||
Louis Hollingworth | 9345b91573 |
|
@ -7,6 +7,7 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
860920F32A1E917600CED32F /* FeedParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 860920F22A1E917600CED32F /* FeedParser.swift */; };
|
||||
86B4633C2A1A382800CBBE76 /* LegantoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86B4633B2A1A382800CBBE76 /* LegantoApp.swift */; };
|
||||
86B463412A1A382800CBBE76 /* Leganto.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 86B4633F2A1A382800CBBE76 /* Leganto.xcdatamodeld */; };
|
||||
86B463432A1A382800CBBE76 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86B463422A1A382800CBBE76 /* ContentView.swift */; };
|
||||
|
@ -17,9 +18,11 @@
|
|||
86B4635A2A1A499300CBBE76 /* AddFeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86B463592A1A499300CBBE76 /* AddFeedView.swift */; };
|
||||
86B4635C2A1D1C6C00CBBE76 /* FeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86B4635B2A1D1C6C00CBBE76 /* FeedView.swift */; };
|
||||
86B4635E2A1D217B00CBBE76 /* FeedViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86B4635D2A1D217B00CBBE76 /* FeedViewCell.swift */; };
|
||||
86B463612A1D346700CBBE76 /* FeedKit in Frameworks */ = {isa = PBXBuildFile; productRef = 86B463602A1D346700CBBE76 /* FeedKit */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
860920F22A1E917600CED32F /* FeedParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedParser.swift; sourceTree = "<group>"; };
|
||||
86B463382A1A382800CBBE76 /* Leganto.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Leganto.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
86B4633B2A1A382800CBBE76 /* LegantoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegantoApp.swift; sourceTree = "<group>"; };
|
||||
86B463402A1A382800CBBE76 /* Leganto.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Leganto.xcdatamodel; sourceTree = "<group>"; };
|
||||
|
@ -32,6 +35,8 @@
|
|||
86B463592A1A499300CBBE76 /* AddFeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddFeedView.swift; sourceTree = "<group>"; };
|
||||
86B4635B2A1D1C6C00CBBE76 /* FeedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedView.swift; sourceTree = "<group>"; };
|
||||
86B4635D2A1D217B00CBBE76 /* FeedViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedViewCell.swift; sourceTree = "<group>"; };
|
||||
86B463622A1D37C400CBBE76 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
86B463632A1D387100CBBE76 /* eo */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eo; path = eo.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -39,6 +44,7 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
86B463612A1D346700CBBE76 /* FeedKit in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -64,6 +70,7 @@
|
|||
86B4633A2A1A382800CBBE76 /* Leganto */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
86B463622A1D37C400CBBE76 /* Info.plist */,
|
||||
86B4635D2A1D217B00CBBE76 /* FeedViewCell.swift */,
|
||||
86B4633B2A1A382800CBBE76 /* LegantoApp.swift */,
|
||||
86B463422A1A382800CBBE76 /* ContentView.swift */,
|
||||
|
@ -75,6 +82,7 @@
|
|||
86B463472A1A382B00CBBE76 /* Preview Content */,
|
||||
86B463592A1A499300CBBE76 /* AddFeedView.swift */,
|
||||
86B4635B2A1D1C6C00CBBE76 /* FeedView.swift */,
|
||||
860920F22A1E917600CED32F /* FeedParser.swift */,
|
||||
);
|
||||
path = Leganto;
|
||||
sourceTree = "<group>";
|
||||
|
@ -103,6 +111,9 @@
|
|||
dependencies = (
|
||||
);
|
||||
name = Leganto;
|
||||
packageProductDependencies = (
|
||||
86B463602A1D346700CBBE76 /* FeedKit */,
|
||||
);
|
||||
productName = Leganto;
|
||||
productReference = 86B463382A1A382800CBBE76 /* Leganto.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
|
@ -129,8 +140,12 @@
|
|||
knownRegions = (
|
||||
Base,
|
||||
"en-GB",
|
||||
eo,
|
||||
);
|
||||
mainGroup = 86B4632F2A1A382800CBBE76;
|
||||
packageReferences = (
|
||||
86B4635F2A1D346700CBBE76 /* XCRemoteSwiftPackageReference "FeedKit" */,
|
||||
);
|
||||
productRefGroup = 86B463392A1A382800CBBE76 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
|
@ -162,6 +177,7 @@
|
|||
86B463582A1A42D100CBBE76 /* DataController.swift in Sources */,
|
||||
86B4635A2A1A499300CBBE76 /* AddFeedView.swift in Sources */,
|
||||
86B4633C2A1A382800CBBE76 /* LegantoApp.swift in Sources */,
|
||||
860920F32A1E917600CED32F /* FeedParser.swift in Sources */,
|
||||
86B4635C2A1D1C6C00CBBE76 /* FeedView.swift in Sources */,
|
||||
86B4635E2A1D217B00CBBE76 /* FeedViewCell.swift in Sources */,
|
||||
86B463432A1A382800CBBE76 /* ContentView.swift in Sources */,
|
||||
|
@ -175,6 +191,7 @@
|
|||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
86B463562A1A3ECE00CBBE76 /* en-GB */,
|
||||
86B463632A1D387100CBBE76 /* eo */,
|
||||
);
|
||||
name = Localizable.strings;
|
||||
sourceTree = "<group>";
|
||||
|
@ -306,7 +323,9 @@
|
|||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Leganto/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Leganto;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.reference";
|
||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
|
||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
|
||||
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
|
||||
|
@ -326,6 +345,8 @@
|
|||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
|
@ -345,7 +366,9 @@
|
|||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = Leganto/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Leganto;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.reference";
|
||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES;
|
||||
"INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES;
|
||||
"INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES;
|
||||
|
@ -365,6 +388,8 @@
|
|||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = auto;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
|
||||
SUPPORTS_MACCATALYST = NO;
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
|
@ -394,6 +419,25 @@
|
|||
};
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
/* Begin XCRemoteSwiftPackageReference section */
|
||||
86B4635F2A1D346700CBBE76 /* XCRemoteSwiftPackageReference "FeedKit" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/nmdias/FeedKit.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 9.0.0;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
86B463602A1D346700CBBE76 /* FeedKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 86B4635F2A1D346700CBBE76 /* XCRemoteSwiftPackageReference "FeedKit" */;
|
||||
productName = FeedKit;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
|
||||
/* Begin XCVersionGroup section */
|
||||
86B4633F2A1A382800CBBE76 /* Leganto.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "feedkit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/nmdias/FeedKit.git",
|
||||
"state" : {
|
||||
"revision" : "68493a33d862c33c9a9f67ec729b3b7df1b20ade",
|
||||
"version" : "9.1.2"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
}
|
|
@ -25,6 +25,7 @@ struct AddFeed: View {
|
|||
}
|
||||
#else
|
||||
AddFeedContent()
|
||||
.padding(.all)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
@ -52,6 +53,14 @@ struct AddFeedContent: View {
|
|||
}
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem {
|
||||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
Text("cancel")
|
||||
}
|
||||
|
||||
}
|
||||
ToolbarItem {
|
||||
Button(action: addItem) {
|
||||
Text("save")
|
||||
|
|
|
@ -10,7 +10,7 @@ import CoreData
|
|||
|
||||
struct ContentView: View {
|
||||
@Environment(\.managedObjectContext) var moc
|
||||
@FetchRequest(sortDescriptors: [], animation: .default) var feeds: FetchedResults<Feed>
|
||||
@FetchRequest(sortDescriptors: [SortDescriptor(\.name)], animation: .default) var feeds: FetchedResults<Feed>
|
||||
|
||||
@State private var showAddScreen = false
|
||||
|
||||
|
@ -19,7 +19,7 @@ struct ContentView: View {
|
|||
List {
|
||||
ForEach(feeds) { item in
|
||||
NavigationLink {
|
||||
FeedView(source: item)
|
||||
FeedView(feedURL: URL(string: item.url!)!, feedName: item.name!)
|
||||
} label: {
|
||||
HStack {
|
||||
if Genre(rawValue: item.genre) == .news {
|
||||
|
|
|
@ -52,6 +52,7 @@ class DataController: ObservableObject {
|
|||
|
||||
init(inMemory: Bool = false) {
|
||||
container = NSPersistentCloudKitContainer(name: "Leganto")
|
||||
|
||||
if inMemory {
|
||||
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
|
||||
}
|
||||
|
@ -72,7 +73,17 @@ class DataController: ObservableObject {
|
|||
fatalError("Unresolved error \(error), \(error.userInfo)")
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
/*
|
||||
#if DEBUG
|
||||
do {
|
||||
try container.initializeCloudKitSchema()
|
||||
} catch {
|
||||
let err = error as NSError
|
||||
fatalError("Unresolved error \(err), \(err.userInfo)")
|
||||
}
|
||||
#endif
|
||||
*/
|
||||
container.viewContext.automaticallyMergesChangesFromParent = true
|
||||
}
|
||||
}
|
||||
|
|
48
Leganto/FeedParser.swift
Normal file
48
Leganto/FeedParser.swift
Normal file
|
@ -0,0 +1,48 @@
|
|||
//
|
||||
// FeedParser.swift
|
||||
// Leganto
|
||||
//
|
||||
// Created by Louis Hollingworth on 2023-05-24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import FeedKit
|
||||
|
||||
class LFeedParser: ObservableObject {
|
||||
var urlStr: String
|
||||
@Published var rssFeed: RSSFeed?
|
||||
@Published var atomFeed: AtomFeed?
|
||||
@Published var jsonFeed: JSONFeed?
|
||||
|
||||
init(urlStr: String) {
|
||||
self.urlStr = urlStr
|
||||
}
|
||||
|
||||
func loadData() {
|
||||
if let url = URL(string: urlStr) {
|
||||
let parser = FeedParser(URL: url)
|
||||
|
||||
parser.parseAsync { (result) in
|
||||
|
||||
switch result {
|
||||
case .success(let feed):
|
||||
switch feed {
|
||||
case let .atom(feed):
|
||||
self.atomFeed = feed
|
||||
case let .json(feed):
|
||||
self.jsonFeed = feed
|
||||
case let .rss(feed):
|
||||
self.rssFeed = feed
|
||||
}
|
||||
case .failure(let err):
|
||||
print(err)
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -6,16 +6,21 @@
|
|||
//
|
||||
|
||||
import SwiftUI
|
||||
import FeedKit
|
||||
|
||||
struct FeedView: View {
|
||||
var source: Feed
|
||||
var feed: Feed
|
||||
@ObservedObject var parsed: LFeedParser = LFeedParser(urlStr: "https://git.ludoviko.ch/lucxjo/leganto-apple.rss")
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
ForEach(1..<20) { item in
|
||||
FeedViewCell()
|
||||
if let feed = parsed.jsonFeed {
|
||||
ForEach(feed.items!, id: \.id!) { item in
|
||||
FeedViewCell(title: item.title!, author: item.author!, date: Date(),
|
||||
content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle(source.name!)
|
||||
.navigationTitle(feed.name!)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,25 +8,41 @@
|
|||
import SwiftUI
|
||||
|
||||
struct FeedViewCell: View {
|
||||
let title: String
|
||||
let author: String
|
||||
let date: Date
|
||||
let content: String
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Title")
|
||||
Text(title)
|
||||
.font(.title)
|
||||
HStack {
|
||||
Text("Author")
|
||||
Text(author)
|
||||
Spacer()
|
||||
Text("Date")
|
||||
Text(date, formatter: dateFormatter)
|
||||
}
|
||||
.font(.footnote)
|
||||
Text("Content")
|
||||
Text(content)
|
||||
.font(.body)
|
||||
}
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
}
|
||||
|
||||
private let dateFormatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .short
|
||||
formatter.timeStyle = .short
|
||||
return formatter
|
||||
}()
|
||||
|
||||
|
||||
struct FeedViewCell_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
FeedViewCell()
|
||||
FeedViewCell(
|
||||
title: "Test", author: "Test Author", date: Date(),
|
||||
content: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
14
Leganto/Info.plist
Normal file
14
Leganto/Info.plist
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict/>
|
||||
</array>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
|
@ -2,9 +2,21 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
<key>aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.icloud-container-identifiers</key>
|
||||
<array>
|
||||
<string>iCloud.ch.ludoviko.Leganto</string>
|
||||
</array>
|
||||
<key>com.apple.developer.icloud-services</key>
|
||||
<array>
|
||||
<string>CloudKit</string>
|
||||
</array>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-only</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
1
Leganto/de.lproj/Localizable.strings
Normal file
1
Leganto/de.lproj/Localizable.strings
Normal file
|
@ -0,0 +1 @@
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
// General
|
||||
"feeds" = "Feeds";
|
||||
"save" = "Save";
|
||||
"cancel" = "Cancel";
|
||||
"name" = "Name";
|
||||
"desc" = "Description";
|
||||
"url" = "URL";
|
||||
|
|
22
Leganto/eo.lproj/Localizable.strings
Normal file
22
Leganto/eo.lproj/Localizable.strings
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
Localizable.strings
|
||||
Leganto
|
||||
|
||||
Created by Louis Hollingworth on 2023-05-21.
|
||||
|
||||
*/
|
||||
|
||||
// General
|
||||
"feeds" = "Fluoj";
|
||||
"save" = "Konservi";
|
||||
"cancel" = "Nuligi";
|
||||
"name" = "Nomo";
|
||||
"desc" = "Priskribo";
|
||||
"url" = "URL";
|
||||
"genre" = "Ĝenro";
|
||||
"fadd" = "Aldoni Fluon";
|
||||
|
||||
// Genre categories
|
||||
"uncategorised" = "Nekategoriigita";
|
||||
"technology" = "Teĥnologio";
|
||||
"news" = "Novaĵoj";
|
1
Leganto/es.lproj/Localizable.strings
Normal file
1
Leganto/es.lproj/Localizable.strings
Normal file
|
@ -0,0 +1 @@
|
|||
|
1
Leganto/fr.lproj/Localizable.strings
Normal file
1
Leganto/fr.lproj/Localizable.strings
Normal file
|
@ -0,0 +1 @@
|
|||
|
1
Leganto/nl.lproj/Localizable.strings
Normal file
1
Leganto/nl.lproj/Localizable.strings
Normal file
|
@ -0,0 +1 @@
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
# Leganto
|
||||
[![Weblate project translated](https://img.shields.io/weblate/progress/leganto?color=ef9f76&logo=weblate&logoColor=ef9f76&server=https%3A%2F%2Fi18n.ludoviko.ch&style=for-the-badge&labelColor=414559)](https://i18n.ludoviko.ch/projects/leganto/leganto-apple/)
|
||||
An RSS reader.
|
||||
|
|
Loading…
Reference in a new issue