diff --git a/Vernissage.xcodeproj/project.pbxproj b/Vernissage.xcodeproj/project.pbxproj
index 0bb9ea6..39cebe8 100644
--- a/Vernissage.xcodeproj/project.pbxproj
+++ b/Vernissage.xcodeproj/project.pbxproj
@@ -7,22 +7,33 @@
objects = {
/* Begin PBXBuildFile section */
+ F8341F90295C636C009C8EE6 /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8341F8F295C636C009C8EE6 /* UIImage.swift */; };
+ F8341F92295C63BB009C8EE6 /* ImageStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8341F91295C63BB009C8EE6 /* ImageStatus.swift */; };
F88C246C295C37B80006098B /* VernissageApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88C246B295C37B80006098B /* VernissageApp.swift */; };
- F88C246E295C37B80006098B /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88C246D295C37B80006098B /* ContentView.swift */; };
+ F88C246E295C37B80006098B /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88C246D295C37B80006098B /* MainView.swift */; };
F88C2470295C37BB0006098B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F88C246F295C37BB0006098B /* Assets.xcassets */; };
F88C2473295C37BB0006098B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F88C2472295C37BB0006098B /* Preview Assets.xcassets */; };
F88C2475295C37BB0006098B /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88C2474295C37BB0006098B /* Persistence.swift */; };
F88C2478295C37BB0006098B /* Vernissage.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = F88C2476295C37BB0006098B /* Vernissage.xcdatamodeld */; };
+ F88C2480295C38400006098B /* MastodonSwift in Frameworks */ = {isa = PBXBuildFile; productRef = F88C247F295C38400006098B /* MastodonSwift */; };
+ F88C2482295C3A4F0006098B /* ImageDetailsModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88C2481295C3A4F0006098B /* ImageDetailsModalView.swift */; };
+ F88C2484295C3A870006098B /* DismissButtonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88C2483295C3A870006098B /* DismissButtonView.swift */; };
+ F88C2486295C48030006098B /* HTMLFotmattedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = F88C2485295C48030006098B /* HTMLFotmattedText.swift */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
+ F8341F8F295C636C009C8EE6 /* UIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImage.swift; sourceTree = ""; };
+ F8341F91295C63BB009C8EE6 /* ImageStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageStatus.swift; sourceTree = ""; };
F88C2468295C37B80006098B /* Vernissage.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Vernissage.app; sourceTree = BUILT_PRODUCTS_DIR; };
F88C246B295C37B80006098B /* VernissageApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VernissageApp.swift; sourceTree = ""; };
- F88C246D295C37B80006098B /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
+ F88C246D295C37B80006098B /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; };
F88C246F295C37BB0006098B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
F88C2472295C37BB0006098B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
F88C2474295C37BB0006098B /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; };
F88C2477295C37BB0006098B /* Vernissage.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Vernissage.xcdatamodel; sourceTree = ""; };
+ F88C2481295C3A4F0006098B /* ImageDetailsModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDetailsModalView.swift; sourceTree = ""; };
+ F88C2483295C3A870006098B /* DismissButtonView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DismissButtonView.swift; sourceTree = ""; };
+ F88C2485295C48030006098B /* HTMLFotmattedText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTMLFotmattedText.swift; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -30,12 +41,55 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ F88C2480295C38400006098B /* MastodonSwift in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ F8341F93295C63E2009C8EE6 /* Views */ = {
+ isa = PBXGroup;
+ children = (
+ F88C2483295C3A870006098B /* DismissButtonView.swift */,
+ F88C2481295C3A4F0006098B /* ImageDetailsModalView.swift */,
+ F88C246D295C37B80006098B /* MainView.swift */,
+ );
+ path = Views;
+ sourceTree = "";
+ };
+ F8341F94295C63FE009C8EE6 /* Extensions */ = {
+ isa = PBXGroup;
+ children = (
+ F8341F8F295C636C009C8EE6 /* UIImage.swift */,
+ );
+ path = Extensions;
+ sourceTree = "";
+ };
+ F8341F95295C640C009C8EE6 /* Models */ = {
+ isa = PBXGroup;
+ children = (
+ F8341F91295C63BB009C8EE6 /* ImageStatus.swift */,
+ );
+ path = Models;
+ sourceTree = "";
+ };
+ F8341F96295C6427009C8EE6 /* CoreData */ = {
+ isa = PBXGroup;
+ children = (
+ F88C2474295C37BB0006098B /* Persistence.swift */,
+ );
+ path = CoreData;
+ sourceTree = "";
+ };
+ F8341F97295C6434009C8EE6 /* Formatters */ = {
+ isa = PBXGroup;
+ children = (
+ F88C2485295C48030006098B /* HTMLFotmattedText.swift */,
+ );
+ path = Formatters;
+ sourceTree = "";
+ };
F88C245F295C37B80006098B = {
isa = PBXGroup;
children = (
@@ -55,10 +109,13 @@
F88C246A295C37B80006098B /* Vernissage */ = {
isa = PBXGroup;
children = (
+ F8341F97295C6434009C8EE6 /* Formatters */,
+ F8341F96295C6427009C8EE6 /* CoreData */,
+ F8341F95295C640C009C8EE6 /* Models */,
+ F8341F94295C63FE009C8EE6 /* Extensions */,
+ F8341F93295C63E2009C8EE6 /* Views */,
F88C246B295C37B80006098B /* VernissageApp.swift */,
- F88C246D295C37B80006098B /* ContentView.swift */,
F88C246F295C37BB0006098B /* Assets.xcassets */,
- F88C2474295C37BB0006098B /* Persistence.swift */,
F88C2476295C37BB0006098B /* Vernissage.xcdatamodeld */,
F88C2471295C37BB0006098B /* Preview Content */,
);
@@ -89,6 +146,9 @@
dependencies = (
);
name = Vernissage;
+ packageProductDependencies = (
+ F88C247F295C38400006098B /* MastodonSwift */,
+ );
productName = Vernissage;
productReference = F88C2468295C37B80006098B /* Vernissage.app */;
productType = "com.apple.product-type.application";
@@ -117,6 +177,9 @@
Base,
);
mainGroup = F88C245F295C37B80006098B;
+ packageReferences = (
+ F88C247E295C38400006098B /* XCRemoteSwiftPackageReference "Mastodon" */,
+ );
productRefGroup = F88C2469295C37B80006098B /* Products */;
projectDirPath = "";
projectRoot = "";
@@ -144,8 +207,13 @@
buildActionMask = 2147483647;
files = (
F88C2475295C37BB0006098B /* Persistence.swift in Sources */,
- F88C246E295C37B80006098B /* ContentView.swift in Sources */,
+ F8341F92295C63BB009C8EE6 /* ImageStatus.swift in Sources */,
+ F8341F90295C636C009C8EE6 /* UIImage.swift in Sources */,
+ F88C2484295C3A870006098B /* DismissButtonView.swift in Sources */,
+ F88C246E295C37B80006098B /* MainView.swift in Sources */,
F88C2478295C37BB0006098B /* Vernissage.xcdatamodeld in Sources */,
+ F88C2482295C3A4F0006098B /* ImageDetailsModalView.swift in Sources */,
+ F88C2486295C48030006098B /* HTMLFotmattedText.swift in Sources */,
F88C246C295C37B80006098B /* VernissageApp.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -283,6 +351,7 @@
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -312,6 +381,7 @@
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -348,6 +418,25 @@
};
/* End XCConfigurationList section */
+/* Begin XCRemoteSwiftPackageReference section */
+ F88C247E295C38400006098B /* XCRemoteSwiftPackageReference "Mastodon" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/Swiftodon/Mastodon.swift";
+ requirement = {
+ branch = main;
+ kind = branch;
+ };
+ };
+/* End XCRemoteSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+ F88C247F295C38400006098B /* MastodonSwift */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = F88C247E295C38400006098B /* XCRemoteSwiftPackageReference "Mastodon" */;
+ productName = MastodonSwift;
+ };
+/* End XCSwiftPackageProductDependency section */
+
/* Begin XCVersionGroup section */
F88C2476295C37BB0006098B /* Vernissage.xcdatamodeld */ = {
isa = XCVersionGroup;
diff --git a/Vernissage/Assets.xcassets/AppIcon.appiconset/Contents.json b/Vernissage/Assets.xcassets/AppIcon.appiconset/Contents.json
index 13613e3..a657e33 100644
--- a/Vernissage/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ b/Vernissage/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -1,6 +1,7 @@
{
"images" : [
{
+ "filename" : "icon.png",
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
diff --git a/Vernissage/Assets.xcassets/AppIcon.appiconset/icon.png b/Vernissage/Assets.xcassets/AppIcon.appiconset/icon.png
new file mode 100644
index 0000000..5155859
Binary files /dev/null and b/Vernissage/Assets.xcassets/AppIcon.appiconset/icon.png differ
diff --git a/Vernissage/Assets.xcassets/displayNameColor.colorset/Contents.json b/Vernissage/Assets.xcassets/displayNameColor.colorset/Contents.json
new file mode 100644
index 0000000..d890719
--- /dev/null
+++ b/Vernissage/Assets.xcassets/displayNameColor.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0.000",
+ "green" : "0.000",
+ "red" : "0.000"
+ }
+ },
+ "idiom" : "universal"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "1.000",
+ "green" : "1.000",
+ "red" : "1.000"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Vernissage/Assets.xcassets/linkColor.colorset/Contents.json b/Vernissage/Assets.xcassets/linkColor.colorset/Contents.json
new file mode 100644
index 0000000..5ff8429
--- /dev/null
+++ b/Vernissage/Assets.xcassets/linkColor.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "255",
+ "green" : "143",
+ "red" : "0"
+ }
+ },
+ "idiom" : "universal"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "255",
+ "green" : "164",
+ "red" : "0"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Vernissage/Assets.xcassets/mainTextColor.colorset/Contents.json b/Vernissage/Assets.xcassets/mainTextColor.colorset/Contents.json
new file mode 100644
index 0000000..1592500
--- /dev/null
+++ b/Vernissage/Assets.xcassets/mainTextColor.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "0",
+ "green" : "0",
+ "red" : "0"
+ }
+ },
+ "idiom" : "universal"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "255",
+ "green" : "255",
+ "red" : "255"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Vernissage/Assets.xcassets/userNameColor.colorset/Contents.json b/Vernissage/Assets.xcassets/userNameColor.colorset/Contents.json
new file mode 100644
index 0000000..f07e8ce
--- /dev/null
+++ b/Vernissage/Assets.xcassets/userNameColor.colorset/Contents.json
@@ -0,0 +1,38 @@
+{
+ "colors" : [
+ {
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "160",
+ "green" : "160",
+ "red" : "160"
+ }
+ },
+ "idiom" : "universal"
+ },
+ {
+ "appearances" : [
+ {
+ "appearance" : "luminosity",
+ "value" : "dark"
+ }
+ ],
+ "color" : {
+ "color-space" : "srgb",
+ "components" : {
+ "alpha" : "1.000",
+ "blue" : "160",
+ "green" : "160",
+ "red" : "160"
+ }
+ },
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Vernissage/ContentView.swift b/Vernissage/ContentView.swift
deleted file mode 100644
index ef1e237..0000000
--- a/Vernissage/ContentView.swift
+++ /dev/null
@@ -1,88 +0,0 @@
-//
-// https://mczachurski.dev
-// Copyright © 2022 Marcin Czachurski and the repository contributors.
-// Licensed under the MIT License.
-//
-
-
-import SwiftUI
-import CoreData
-
-struct ContentView: View {
- @Environment(\.managedObjectContext) private var viewContext
-
- @FetchRequest(
- sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
- animation: .default)
- private var items: FetchedResults-
-
- var body: some View {
- NavigationView {
- List {
- ForEach(items) { item in
- NavigationLink {
- Text("Item at \(item.timestamp!, formatter: itemFormatter)")
- } label: {
- Text(item.timestamp!, formatter: itemFormatter)
- }
- }
- .onDelete(perform: deleteItems)
- }
- .toolbar {
- ToolbarItem(placement: .navigationBarTrailing) {
- EditButton()
- }
- ToolbarItem {
- Button(action: addItem) {
- Label("Add Item", systemImage: "plus")
- }
- }
- }
- Text("Select an item")
- }
- }
-
- private func addItem() {
- withAnimation {
- let newItem = Item(context: viewContext)
- newItem.timestamp = Date()
-
- do {
- try viewContext.save()
- } catch {
- // Replace this implementation with code to handle the error appropriately.
- // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
- let nsError = error as NSError
- fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
- }
- }
- }
-
- private func deleteItems(offsets: IndexSet) {
- withAnimation {
- offsets.map { items[$0] }.forEach(viewContext.delete)
-
- do {
- try viewContext.save()
- } catch {
- // Replace this implementation with code to handle the error appropriately.
- // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
- let nsError = error as NSError
- fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
- }
- }
- }
-}
-
-private let itemFormatter: DateFormatter = {
- let formatter = DateFormatter()
- formatter.dateStyle = .short
- formatter.timeStyle = .medium
- return formatter
-}()
-
-struct ContentView_Previews: PreviewProvider {
- static var previews: some View {
- ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
- }
-}
diff --git a/Vernissage/Persistence.swift b/Vernissage/CoreData/Persistence.swift
similarity index 100%
rename from Vernissage/Persistence.swift
rename to Vernissage/CoreData/Persistence.swift
diff --git a/Vernissage/Extensions/UIImage.swift b/Vernissage/Extensions/UIImage.swift
new file mode 100644
index 0000000..bcb8475
--- /dev/null
+++ b/Vernissage/Extensions/UIImage.swift
@@ -0,0 +1,27 @@
+//
+// https://mczachurski.dev
+// Copyright © 2022 Marcin Czachurski and the repository contributors.
+// Licensed under the MIT License.
+//
+
+
+import Foundation
+import UIKit
+
+public extension UIImage {
+ func getExifData() -> CFDictionary? {
+ var exifData: CFDictionary? = nil
+
+ if let data = self.jpegData(compressionQuality: 1.0) {
+ data.withUnsafeBytes {
+ let bytes = $0.baseAddress?.assumingMemoryBound(to: UInt8.self)
+ if let cfData = CFDataCreate(kCFAllocatorDefault, bytes, data.count),
+ let source = CGImageSourceCreateWithData(cfData, nil) {
+ exifData = CGImageSourceCopyPropertiesAtIndex(source, 0, nil)
+ }
+ }
+ }
+
+ return exifData
+ }
+}
diff --git a/Vernissage/Formatters/HTMLFotmattedText.swift b/Vernissage/Formatters/HTMLFotmattedText.swift
new file mode 100644
index 0000000..74f9722
--- /dev/null
+++ b/Vernissage/Formatters/HTMLFotmattedText.swift
@@ -0,0 +1,76 @@
+//
+// https://mczachurski.dev
+// Copyright © 2022 Marcin Czachurski and the repository contributors.
+// Licensed under the MIT License.
+//
+
+
+import UIKit
+import SwiftUI
+
+struct HTMLFormattedText: UIViewRepresentable {
+
+ let text: String
+ private let textView = UITextView()
+
+ init(_ content: String) {
+ self.text = content
+ }
+
+ func makeUIView(context: UIViewRepresentableContext) -> UITextView {
+ textView.widthAnchor.constraint(equalToConstant:UIScreen.main.bounds.width - 16).isActive = true
+ textView.isSelectable = false
+ textView.isUserInteractionEnabled = false
+ textView.translatesAutoresizingMaskIntoConstraints = false
+ textView.isScrollEnabled = false
+
+ return textView
+ }
+
+ func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext) {
+ DispatchQueue.main.async {
+ if let attributeText = self.converHTML(text: text) {
+ textView.attributedText = attributeText
+ } else {
+ textView.text = ""
+ }
+ }
+ }
+
+ private func converHTML(text: String) -> NSAttributedString?{
+ guard let data = text.data(using: .utf8) else {
+ return nil
+ }
+
+ let largeAttributes = [
+ NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16),
+ NSAttributedString.Key.foregroundColor: UIColor(Color("mainTextColor"))
+ ]
+
+ let linkAttributes = [
+ NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16),
+ NSAttributedString.Key.foregroundColor: UIColor(Color("linkColor"))
+ ]
+
+ if let attributedString = try? NSMutableAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) {
+
+ attributedString.enumerateAttributes(in: NSRange(0..Danish-made 1st class kebab
Say yes thanks to 2kg. delicious kebab, which is confused and cooked.
Yes thanks for 149.95
Now you can make the most delicious sandwiches, kebab mix and much more at home
")
+ }
+}
diff --git a/Vernissage/Models/ImageStatus.swift b/Vernissage/Models/ImageStatus.swift
new file mode 100644
index 0000000..ba29463
--- /dev/null
+++ b/Vernissage/Models/ImageStatus.swift
@@ -0,0 +1,15 @@
+//
+// https://mczachurski.dev
+// Copyright © 2022 Marcin Czachurski and the repository contributors.
+// Licensed under the MIT License.
+//
+
+import Foundation
+import UIKit
+import MastodonSwift
+
+public struct ImageStatus: Identifiable {
+ public let id: String
+ public let image: UIImage
+ public let status: Status
+}
diff --git a/Vernissage/VernissageApp.swift b/Vernissage/VernissageApp.swift
index 6db0171..3fe2fc8 100644
--- a/Vernissage/VernissageApp.swift
+++ b/Vernissage/VernissageApp.swift
@@ -13,7 +13,7 @@ struct VernissageApp: App {
var body: some Scene {
WindowGroup {
- ContentView()
+ MainView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
diff --git a/Vernissage/Views/DismissButtonView.swift b/Vernissage/Views/DismissButtonView.swift
new file mode 100644
index 0000000..5c37b7b
--- /dev/null
+++ b/Vernissage/Views/DismissButtonView.swift
@@ -0,0 +1,30 @@
+//
+// https://mczachurski.dev
+// Copyright © 2022 Marcin Czachurski and the repository contributors.
+// Licensed under the MIT License.
+//
+
+
+import SwiftUI
+
+struct DismissButtonView: View {
+ var action: () -> ()
+
+ public init(_ action: @escaping () -> ()) {
+ self.action = action
+ }
+
+ public var body: some View {
+ Button(action: action) {
+ RoundedRectangle(cornerRadius: 16)
+ .fill(Color.gray)
+ .frame(width: 50, height: 5)
+ }
+ }
+}
+
+struct DismissButtonView_Previews: PreviewProvider {
+ static var previews: some View {
+ DismissButtonView { }
+ }
+}
diff --git a/Vernissage/Views/ImageDetailsModalView.swift b/Vernissage/Views/ImageDetailsModalView.swift
new file mode 100644
index 0000000..ac2c686
--- /dev/null
+++ b/Vernissage/Views/ImageDetailsModalView.swift
@@ -0,0 +1,84 @@
+//
+// https://mczachurski.dev
+// Copyright © 2022 Marcin Czachurski and the repository contributors.
+// Licensed under the MIT License.
+//
+
+
+import SwiftUI
+import MastodonSwift
+
+struct ImageDetailsModalView: View {
+ @Environment(\.dismiss) private var dismiss
+ @State public var current: ImageStatus
+
+ var body: some View {
+ VStack {
+ DismissButtonView { dismiss() }
+
+ ScrollView {
+ VStack (alignment: .leading) {
+ Image(uiImage: current.image)
+ .resizable().aspectRatio(contentMode: .fit)
+ .frame(maxWidth: .infinity)
+ .onTapGesture {
+ dismiss()
+ }
+
+ VStack(alignment: .leading) {
+ HStack (alignment: .center) {
+ AsyncImage(url: current.status.account?.avatar) { image in
+ image
+ .resizable()
+ .clipShape(Circle())
+ .shadow(radius: 10)
+ .aspectRatio(contentMode: .fit)
+ } placeholder: {
+ Color.gray
+ }
+ .frame(height: 60)
+ .frame(width: 60)
+
+ VStack (alignment: .leading) {
+ Text(current.status.account?.displayName ?? current.status.account?.username ?? "")
+ .foregroundColor(Color("displayNameColor"))
+ Text("@\(current.status.account?.username ?? "unknown")")
+ .foregroundColor(Color("userNameColor"))
+ .font(.footnote)
+ }
+ .padding(.leading, 8)
+ }
+
+ HTMLFormattedText(current.status.content)
+
+ HStack (alignment: .top) {
+ Image(systemName: current.status.favourited ? "heart.fill" : "heart")
+ Text("\(current.status.favouritesCount) likes")
+
+ Image(systemName: "arrow.2.squarepath")
+ .padding(.leading, 16)
+ Text("\(current.status.reblogsCount) boosts")
+ }
+ .font(.footnote)
+ .foregroundColor(Color("mainTextColor"))
+ }
+ .padding(8)
+ }
+ }
+ }
+ .gesture(
+ DragGesture().onEnded { value in
+ if value.location.y - value.startLocation.y > 150 {
+ dismiss()
+ }
+ }
+ )
+ }
+}
+
+struct FullScreenModalView_Previews: PreviewProvider {
+ static var previews: some View {
+ Text("")
+ // FullScreenModalView(current: ImageStatus(id: "123", image: UIImage(), status: Status(from: <#T##Decoder#>)))
+ }
+}
diff --git a/Vernissage/Views/MainView.swift b/Vernissage/Views/MainView.swift
new file mode 100644
index 0000000..0b40d76
--- /dev/null
+++ b/Vernissage/Views/MainView.swift
@@ -0,0 +1,109 @@
+//
+// https://mczachurski.dev
+// Copyright © 2022 Marcin Czachurski and the repository contributors.
+// Licensed under the MIT License.
+//
+
+import SwiftUI
+import UIKit
+import CoreData
+import MastodonSwift
+
+struct MainView: View {
+ @Environment(\.managedObjectContext) private var viewContext
+
+ @FetchRequest(
+ sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
+ animation: .default)
+ private var items: FetchedResults-
+
+ @State private var statuses: [Status] = []
+ @State private var images: [ImageStatus] = []
+ @State private var showDetails: Bool = false
+ @State private var current: ImageStatus? = nil
+
+ var body: some View {
+ ScrollView {
+ ForEach(images) { item in
+ Image(uiImage: item.image)
+ .resizable().aspectRatio(contentMode: .fit)
+ .onTapGesture {
+ current = item
+ showDetails.toggle()
+ }
+ .fullScreenCover(item: $current) { item in
+ ImageDetailsModalView(current: item)
+ }
+ }
+ }
+ .foregroundColor(Color.black)
+ .background(Color.black)
+ .edgesIgnoringSafeArea(.all)
+ .task {
+ do {
+ try await loadData()
+ } catch {
+ print("Error", error)
+ }
+ }
+ }
+
+ private func loadData() async throws {
+ let accessToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiI2MTQwOCIsImp0aSI6IjZjMDg4N2ZlZThjZjBmZjQ1N2RjZDQ5MTU2YjE2NzYyYmQ2MDQ1NDQ1MmEwYzEyMzVmNDA3YjY2YjFhYjU3ZjBjMTY2YjFmZmIyNjJlZDg2IiwiaWF0IjoxNjcyMDU5MDYwLjI3NDgyMSwibmJmIjoxNjcyMDU5MDYwLjI3NDgyNCwiZXhwIjoxNzAzNTk1MDYwLjI1MzM1Nywic3ViIjoiNjc4MjMiLCJzY29wZXMiOlsicmVhZCIsIndyaXRlIiwiZm9sbG93Il19.kGvg3lW8lF1X1mOTdgGgoXNyzwUIJz5hz5RJKK_WiSoBWDQNadhZDty7XMNF0IAPjxOSi6UaIx2av7_eH_65aNlKFw89bkm8bT_zFQW2V0KbADJ-NmE6X0B_NgU2CNoF5IPn6bhCFHCKMtV6MWAQ_db6DT-LXaGemMY3QimcJzCqQuXI_1ouiZ235T297uEPNTrLwtLq-x_UoO-wx254LStBalDIGDVHAa4by9IT-mvu-QXz7k8pH2NHKoX-9Ql_Y3G9RJJNqoOmWMU45Dyo2HaJKKEb1tkeJ9tA3LIYgbwnEbG2PJ7CE8CXxtakiCIflJZpzzOmq1jXLAsCJ1mHnc77o7NfMaB_hY-f8PEI6d2ttOdH8bNlreF2avznNAIVHg_bf-yv_4wKUCUe0QZMG_yWqOwOk6lyruvboSGKuI5RnYsJbXBoJTGMLON6jVmtiKPbHy-9jNcfFgShAc3D5kTO-8Avj9_RquqEh1TQF_S4ljmganxKzMihyMDLK1OVcXzCFO6FKlCw7YKvbfJk1Qrn9kPBrVDM5jzIyXAmqRd1ivcE9nAdYb2l7KnxW_pi31uT0IdJMpTkZrUQSDMyEnj0HgV6Yd5BDlLG6Cnk8GXATTcU-a1pgE13OtWsCpD2cZQm-tOsFHWBDvY-BA0RtTvQAyEUxRIP9NjHe8rSR90"
+
+ let client = MastodonClient(baseURL: URL(string: "https://pixelfed.social")!)
+ .getAuthenticated(token: accessToken)
+
+ self.statuses = try await client.getHomeTimeline()
+
+ var imagesCache: [ImageStatus] = []
+ for item in self.statuses {
+ let imageStatus = try await self.fetchImage(status: item, accessToken: accessToken)
+
+ if let imageStatus {
+ imagesCache.append(imageStatus)
+ }
+ }
+
+ self.images = imagesCache
+ }
+
+ public func fetchImage(status: Status, accessToken: String) async throws -> ImageStatus? {
+ guard let url = status.mediaAttachments.first?.url, let id = status.mediaAttachments.first?.id else {
+ return nil
+ }
+
+ let urlRequest = URLRequest(url: url)
+
+ // Fetching data.
+ let (data, response) = try await URLSession.shared.data(for: urlRequest)
+ guard (response as? HTTPURLResponse)?.statusCode == 200 else {
+ return nil
+ }
+
+ // Decoding JSON.
+ let image = UIImage(data: data)
+ guard let image = image else {
+ return nil
+ }
+
+ /*
+ var exif = image.getExifData()
+ if let dict = exif as? [String: AnyObject] {
+ dict.keys.map { key in
+ print(key)
+ print(dict[key])
+ }
+ }
+ */
+
+ return ImageStatus(id: id,image: image, status: status)
+ }
+}
+
+
+struct MainView_Previews: PreviewProvider {
+ static var previews: some View {
+ MainView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
+ }
+}
diff --git a/icon.afdesign b/icon.afdesign
new file mode 100644
index 0000000..826ea2d
Binary files /dev/null and b/icon.afdesign differ