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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