From 2ee44fac78303bb2c074529f711ae8d0f725e68a Mon Sep 17 00:00:00 2001
From: Marcin Czachursk
Date: Thu, 29 Dec 2022 08:06:21 +0100
Subject: [PATCH] Initial commit
---
Vernissage.xcodeproj/project.pbxproj | 99 +++++++++++++++-
.../AppIcon.appiconset/Contents.json | 1 +
.../AppIcon.appiconset/icon.png | Bin 0 -> 12319 bytes
.../displayNameColor.colorset/Contents.json | 38 ++++++
.../linkColor.colorset/Contents.json | 38 ++++++
.../mainTextColor.colorset/Contents.json | 38 ++++++
.../userNameColor.colorset/Contents.json | 38 ++++++
Vernissage/ContentView.swift | 88 --------------
Vernissage/{ => CoreData}/Persistence.swift | 0
Vernissage/Extensions/UIImage.swift | 27 +++++
Vernissage/Formatters/HTMLFotmattedText.swift | 76 ++++++++++++
Vernissage/Models/ImageStatus.swift | 15 +++
Vernissage/VernissageApp.swift | 2 +-
Vernissage/Views/DismissButtonView.swift | 30 +++++
Vernissage/Views/ImageDetailsModalView.swift | 84 ++++++++++++++
Vernissage/Views/MainView.swift | 109 ++++++++++++++++++
icon.afdesign | Bin 0 -> 15918 bytes
17 files changed, 589 insertions(+), 94 deletions(-)
create mode 100644 Vernissage/Assets.xcassets/AppIcon.appiconset/icon.png
create mode 100644 Vernissage/Assets.xcassets/displayNameColor.colorset/Contents.json
create mode 100644 Vernissage/Assets.xcassets/linkColor.colorset/Contents.json
create mode 100644 Vernissage/Assets.xcassets/mainTextColor.colorset/Contents.json
create mode 100644 Vernissage/Assets.xcassets/userNameColor.colorset/Contents.json
delete mode 100644 Vernissage/ContentView.swift
rename Vernissage/{ => CoreData}/Persistence.swift (100%)
create mode 100644 Vernissage/Extensions/UIImage.swift
create mode 100644 Vernissage/Formatters/HTMLFotmattedText.swift
create mode 100644 Vernissage/Models/ImageStatus.swift
create mode 100644 Vernissage/Views/DismissButtonView.swift
create mode 100644 Vernissage/Views/ImageDetailsModalView.swift
create mode 100644 Vernissage/Views/MainView.swift
create mode 100644 icon.afdesign
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 0000000000000000000000000000000000000000..5155859fdfb168f8134fa8123de0b71673ada41e
GIT binary patch
literal 12319
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
KUSG6n_kRIkL!)f~
literal 0
HcmV?d00001
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 0000000000000000000000000000000000000000..826ea2d151fcb71f19f600767440319e3e0a1979
GIT binary patch
literal 15918
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