Merge pull request #77 from VernissageApp/feature/iPad
iPad version of the application.
This commit is contained in:
commit
3f44a6b350
|
@ -9,6 +9,13 @@ import PixelfedKit
|
|||
|
||||
extension Client {
|
||||
public class PublicTimeline: BaseClient {
|
||||
public func getHomeTimeline(maxId: String? = nil,
|
||||
sinceId: String? = nil,
|
||||
minId: String? = nil,
|
||||
limit: Int = 40) async throws -> [Status] {
|
||||
return try await pixelfedClient.getHomeTimeline(maxId: maxId, sinceId: sinceId, minId: minId, limit: limit)
|
||||
}
|
||||
|
||||
public func getStatuses(local: Bool? = nil,
|
||||
remote: Bool? = nil,
|
||||
maxId: String? = nil,
|
||||
|
|
|
@ -8,7 +8,6 @@ import Foundation
|
|||
import PixelfedKit
|
||||
|
||||
public class StatusModel: ObservableObject {
|
||||
|
||||
public let id: EntityId
|
||||
public let content: Html
|
||||
|
||||
|
@ -107,6 +106,21 @@ public extension StatusModel {
|
|||
}
|
||||
}
|
||||
|
||||
extension StatusModel: Equatable {
|
||||
public static func == (lhs: StatusModel, rhs: StatusModel) -> Bool {
|
||||
lhs.id == rhs.id
|
||||
}
|
||||
}
|
||||
|
||||
extension StatusModel: Hashable {
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
return hasher.combine(self.id)
|
||||
}
|
||||
}
|
||||
|
||||
extension StatusModel: Identifiable {
|
||||
}
|
||||
|
||||
public extension [StatusModel] {
|
||||
func getAllImagesUrls() -> [URL] {
|
||||
var urls: [URL] = []
|
||||
|
|
|
@ -120,20 +120,6 @@ class StatusDataHandler {
|
|||
}
|
||||
}
|
||||
|
||||
func remove(accountId: String, statuses: [StatusData], viewContext: NSManagedObjectContext? = nil) {
|
||||
let context = viewContext ?? CoreDataHandler.shared.container.viewContext
|
||||
|
||||
for status in statuses {
|
||||
context.delete(status)
|
||||
}
|
||||
|
||||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
CoreDataError.shared.handle(error, message: "Error during deleting status (remove).")
|
||||
}
|
||||
}
|
||||
|
||||
func setFavourited(accountId: String, statusId: String) {
|
||||
let backgroundContext = CoreDataHandler.shared.newBackgroundContext()
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ public extension Color {
|
|||
|
||||
// MARK: - Text Colors
|
||||
static let dangerColor = Color("DangerColor")
|
||||
static let lightGrayColor = Color("LightGrayColor")
|
||||
static let customGrayColor = Color("CustomGrayColor")
|
||||
static let mainTextColor = Color("MainTextColor")
|
||||
static let selectedRowColor = Color("SelectedRowColor")
|
||||
static let viewBackgroundColor = Color("ViewBackgroundColor")
|
||||
|
|
|
@ -32,3 +32,9 @@ In the name of the folders you have to put the code of the new language ([here](
|
|||
Then you have to open files in these folders and translate them 🇯🇵🇫🇷🇨🇮🇧🇪. After translation create a Pull Request 👍.
|
||||
|
||||
From time to time you have to come back and translate lines which has been added since the last translation.
|
||||
|
||||
## Technical debt
|
||||
- Use auto generated resources (Color/Images) instead static extensions (how to do this in separete Swift Packages?)
|
||||
- Enable swiftlint (https://github.com/realm/SwiftLint/issues/5053)
|
||||
- Investigate new (iOS 17) Observables
|
||||
- Check how to migrate to SwiftData (iOS 17)
|
||||
|
|
|
@ -20,7 +20,6 @@ let package = Package(
|
|||
// Dependencies declare other packages that this package depends on.
|
||||
.package(url: "https://github.com/omaralbeik/Drops", .upToNextMajor(from: "1.6.1")),
|
||||
.package(url: "https://github.com/kean/Nuke", .upToNextMajor(from: "12.0.0")),
|
||||
.package(name: "PixelfedKit", path: "../PixelfedKit"),
|
||||
.package(name: "EnvironmentKit", path: "../EnvironmentKit")
|
||||
],
|
||||
targets: [
|
||||
|
|
|
@ -7,40 +7,86 @@
|
|||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
/// Service is storing orginal image sizes.
|
||||
/// Very often images doesn't have size in metadataa (especially for services other then Pixelfed).
|
||||
/// After download image from server we can check his size and remember in the cache.
|
||||
///
|
||||
/// When we want to prepare placeholder for specfic image and container witdh we have to use special method.
|
||||
public class ImageSizeService {
|
||||
public static let shared = ImageSizeService()
|
||||
private init() { }
|
||||
|
||||
/// Cache with orginal image sizes.
|
||||
private var memoryCacheData = MemoryCache<URL, CGSize>(entryLifetime: 3600)
|
||||
private let staticImageHeight = 500.0
|
||||
|
||||
public func get(for url: URL) -> CGSize? {
|
||||
return self.memoryCacheData[url]
|
||||
}
|
||||
|
||||
public func calculate(for url: URL, width: Int32, height: Int32) -> CGSize {
|
||||
return calculate(for: url, width: Double(width), height: Double(height))
|
||||
public func save(for url: URL, width: Int32, height: Int32) {
|
||||
save(for: url, width: Double(width), height: Double(height))
|
||||
}
|
||||
|
||||
public func calculate(for url: URL, width: Int, height: Int) -> CGSize {
|
||||
return calculate(for: url, width: Double(width), height: Double(height))
|
||||
public func save(for url: URL, width: Int, height: Int) {
|
||||
save(for: url, width: Double(width), height: Double(height))
|
||||
}
|
||||
|
||||
public func save(for url: URL, width: Double, height: Double) {
|
||||
self.memoryCacheData.insert(CGSize(width: width, height: height), forKey: url)
|
||||
}
|
||||
}
|
||||
|
||||
extension ImageSizeService {
|
||||
public func calculate(for url: URL) -> CGSize {
|
||||
return UIDevice.current.userInterfaceIdiom == .phone
|
||||
? ImageSizeService.shared.calculate(for: url, andContainerWidth: UIScreen.main.bounds.size.width)
|
||||
: ImageSizeService.shared.calculate(for: url, andContainerHeight: self.staticImageHeight)
|
||||
}
|
||||
|
||||
public func calculate(for url: URL, andContainerWidth containerWidth: Double) -> CGSize {
|
||||
guard let size = self.get(for: url) else {
|
||||
return CGSize(width: containerWidth, height: containerWidth)
|
||||
}
|
||||
|
||||
return self.calculate(width: size.width, height: size.height, andContainerWidth: containerWidth)
|
||||
}
|
||||
|
||||
public func calculate(for url: URL, andContainerHeight containerHeight: Double) -> CGSize {
|
||||
guard let size = self.get(for: url) else {
|
||||
return CGSize(width: containerHeight, height: containerHeight)
|
||||
}
|
||||
|
||||
return self.calculate(width: size.width, height: size.height, andContainerHeight: containerHeight)
|
||||
}
|
||||
|
||||
public func calculate(width: Double, height: Double) -> CGSize {
|
||||
let divider = Double(width) / UIScreen.main.bounds.size.width
|
||||
return UIDevice.current.userInterfaceIdiom == .phone
|
||||
? ImageSizeService.shared.calculate(width: width, height: height, andContainerWidth: UIScreen.main.bounds.size.width)
|
||||
: ImageSizeService.shared.calculate(width: width, height: height, andContainerHeight: self.staticImageHeight)
|
||||
}
|
||||
|
||||
public func calculate(width: Double, height: Double, andContainerWidth containerWidth: Double) -> CGSize {
|
||||
let divider = Double(width) / containerWidth
|
||||
let calculatedHeight = Double(height) / divider
|
||||
|
||||
let size = CGSize(
|
||||
width: UIScreen.main.bounds.width,
|
||||
height: (calculatedHeight > 0 && calculatedHeight < .infinity) ? calculatedHeight : UIScreen.main.bounds.width
|
||||
width: containerWidth,
|
||||
height: (calculatedHeight > 0 && calculatedHeight < .infinity) ? calculatedHeight : containerWidth
|
||||
)
|
||||
|
||||
return size
|
||||
}
|
||||
|
||||
public func calculate(for url: URL, width: Double, height: Double) -> CGSize {
|
||||
let size = self.calculate(width: width, height: height)
|
||||
public func calculate(width: Double, height: Double, andContainerHeight containerHeight: Double) -> CGSize {
|
||||
let divider = Double(height) / containerHeight
|
||||
let calculatedWidth = Double(width) / divider
|
||||
|
||||
let size = CGSize(
|
||||
width: (calculatedWidth > 0 && calculatedWidth < .infinity) ? calculatedWidth : containerHeight,
|
||||
height: containerHeight
|
||||
)
|
||||
|
||||
self.memoryCacheData.insert(size, forKey: url)
|
||||
return size
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
F84625F829FE2C2F002D3AF4 /* AccountFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = F84625F729FE2C2F002D3AF4 /* AccountFetcher.swift */; };
|
||||
F84625FB29FE393B002D3AF4 /* QRCode in Frameworks */ = {isa = PBXBuildFile; productRef = F84625FA29FE393B002D3AF4 /* QRCode */; };
|
||||
F858906B29E1CC7A00D4BDED /* UIApplication+Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = F858906A29E1CC7A00D4BDED /* UIApplication+Window.swift */; };
|
||||
F85D0C652ABA08F9002B3577 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F85D0C642ABA08F9002B3577 /* Assets.xcassets */; };
|
||||
F85D4971296402DC00751DF7 /* AuthorizationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85D4970296402DC00751DF7 /* AuthorizationService.swift */; };
|
||||
F85D4975296407F100751DF7 /* HomeTimelineService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85D4974296407F100751DF7 /* HomeTimelineService.swift */; };
|
||||
F85D497729640A5200751DF7 /* ImageRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = F85D497629640A5200751DF7 /* ImageRow.swift */; };
|
||||
|
@ -54,7 +55,6 @@
|
|||
F864F76129BB91B400B13921 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F864F76029BB91B400B13921 /* SwiftUI.framework */; };
|
||||
F864F76429BB91B400B13921 /* VernissageWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F864F76329BB91B400B13921 /* VernissageWidgetBundle.swift */; };
|
||||
F864F76629BB91B400B13921 /* PhotoWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = F864F76529BB91B400B13921 /* PhotoWidget.swift */; };
|
||||
F864F76829BB91B600B13921 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F864F76729BB91B600B13921 /* Assets.xcassets */; };
|
||||
F864F76C29BB91B600B13921 /* VernissageWidgetExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = F864F75D29BB91B400B13921 /* VernissageWidgetExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
F864F77529BB92CE00B13921 /* PhotoProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F864F77329BB929A00B13921 /* PhotoProvider.swift */; };
|
||||
F864F77629BB92CE00B13921 /* PhotoWidgetEntryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F864F77129BB924D00B13921 /* PhotoWidgetEntryView.swift */; };
|
||||
|
@ -89,6 +89,7 @@
|
|||
F866F6A729604629002E8F88 /* SignInView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F866F6A629604629002E8F88 /* SignInView.swift */; };
|
||||
F866F6AA29605AFA002E8F88 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F866F6A929605AFA002E8F88 /* SceneDelegate.swift */; };
|
||||
F866F6AE29606367002E8F88 /* ApplicationViewMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F866F6AD29606367002E8F88 /* ApplicationViewMode.swift */; };
|
||||
F8675DD02A1FA40500A89959 /* WaterfallGrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8675DCF2A1FA40500A89959 /* WaterfallGrid.swift */; };
|
||||
F86A42FD299A8B8E00DF7645 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F86A42FC299A8B8E00DF7645 /* StoreKit.framework */; };
|
||||
F86A42FF299A8C5500DF7645 /* InAppPurchaseStoreKitConfiguration.storekit in Resources */ = {isa = PBXBuildFile; fileRef = F86A42FE299A8C5500DF7645 /* InAppPurchaseStoreKitConfiguration.storekit */; };
|
||||
F86A4301299A97F500DF7645 /* ProductIdentifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = F86A4300299A97F500DF7645 /* ProductIdentifiers.swift */; };
|
||||
|
@ -182,6 +183,7 @@
|
|||
F89D6C4629718193001DA3D4 /* GeneralSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89D6C4529718193001DA3D4 /* GeneralSectionView.swift */; };
|
||||
F89D6C4A297196FF001DA3D4 /* ImageViewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89D6C49297196FF001DA3D4 /* ImageViewer.swift */; };
|
||||
F89F57B029D1C11200001EE3 /* RelationshipModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F89F57AF29D1C11200001EE3 /* RelationshipModel.swift */; };
|
||||
F8A192102ABB322E00C2599A /* Semaphore in Frameworks */ = {isa = PBXBuildFile; productRef = F8A1920F2ABB322E00C2599A /* Semaphore */; };
|
||||
F8A93D7E2965FD89001D8331 /* UserProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A93D7D2965FD89001D8331 /* UserProfileView.swift */; };
|
||||
F8AFF7C129B259150087D083 /* HashtagsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8AFF7C029B259150087D083 /* HashtagsView.swift */; };
|
||||
F8AFF7C429B25EF40087D083 /* ImagesGrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8AFF7C329B25EF40087D083 /* ImagesGrid.swift */; };
|
||||
|
@ -190,16 +192,19 @@
|
|||
F8B0885E29942E31002AB40A /* ThirdPartyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B0885D29942E31002AB40A /* ThirdPartyView.swift */; };
|
||||
F8B0886029943498002AB40A /* OtherSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B0885F29943498002AB40A /* OtherSectionView.swift */; };
|
||||
F8B08862299435C9002AB40A /* SupportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B08861299435C9002AB40A /* SupportView.swift */; };
|
||||
F8C287A32A06B4C90072213F /* ImageScale.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8C287A22A06B4C90072213F /* ImageScale.swift */; };
|
||||
F8B758DE2AB9DD85000C8068 /* ColumnData.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B758DD2AB9DD85000C8068 /* ColumnData.swift */; };
|
||||
F8D5444329D4066C002225D6 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8D5444229D4066C002225D6 /* AppDelegate.swift */; };
|
||||
F8DF38E429DD68820047F1AA /* ViewOffsetKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8DF38E329DD68820047F1AA /* ViewOffsetKey.swift */; };
|
||||
F8DF38E629DDB98A0047F1AA /* SocialsSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8DF38E529DDB98A0047F1AA /* SocialsSectionView.swift */; };
|
||||
F8E36E462AB8745300769C55 /* Sizable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E36E452AB8745300769C55 /* Sizable.swift */; };
|
||||
F8E36E482AB874A500769C55 /* StatusModel+Sizeable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E36E472AB874A500769C55 /* StatusModel+Sizeable.swift */; };
|
||||
F8E6D03329CDD52500416CCA /* EditProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E6D03229CDD52500416CCA /* EditProfileView.swift */; };
|
||||
F8F6E44229BC58F20004795E /* Vernissage.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = F88C2476295C37BB0006098B /* Vernissage.xcdatamodeld */; };
|
||||
F8F6E44C29BCC1F70004795E /* PhotoSmallWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F6E44629BCC0DC0004795E /* PhotoSmallWidgetView.swift */; };
|
||||
F8F6E44D29BCC1F90004795E /* PhotoMediumWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F6E44829BCC0F00004795E /* PhotoMediumWidgetView.swift */; };
|
||||
F8F6E44E29BCC1FB0004795E /* PhotoLargeWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F6E44A29BCC0FF0004795E /* PhotoLargeWidgetView.swift */; };
|
||||
F8F6E45129BCE9190004795E /* UIImage+Resize.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F6E45029BCE9190004795E /* UIImage+Resize.swift */; };
|
||||
F8FAA0AD2AB0BCB400FD78BD /* View+ContainerBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8FAA0AC2AB0BCB400FD78BD /* View+ContainerBackground.swift */; };
|
||||
F8FB8ABA29EB2ED400342C04 /* NavigationMenuButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8FB8AB929EB2ED400342C04 /* NavigationMenuButtons.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
|
@ -267,6 +272,7 @@
|
|||
F84625F729FE2C2F002D3AF4 /* AccountFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountFetcher.swift; sourceTree = "<group>"; };
|
||||
F858906A29E1CC7A00D4BDED /* UIApplication+Window.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIApplication+Window.swift"; sourceTree = "<group>"; };
|
||||
F85B586C29ED169B00A16D12 /* Vernissage-010.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-010.xcdatamodel"; sourceTree = "<group>"; };
|
||||
F85D0C642ABA08F9002B3577 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
F85D4970296402DC00751DF7 /* AuthorizationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationService.swift; sourceTree = "<group>"; };
|
||||
F85D4974296407F100751DF7 /* HomeTimelineService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTimelineService.swift; sourceTree = "<group>"; };
|
||||
F85D497629640A5200751DF7 /* ImageRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRow.swift; sourceTree = "<group>"; };
|
||||
|
@ -284,7 +290,6 @@
|
|||
F864F76029BB91B400B13921 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
|
||||
F864F76329BB91B400B13921 /* VernissageWidgetBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VernissageWidgetBundle.swift; sourceTree = "<group>"; };
|
||||
F864F76529BB91B400B13921 /* PhotoWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoWidget.swift; sourceTree = "<group>"; };
|
||||
F864F76729BB91B600B13921 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
F864F76929BB91B600B13921 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
F864F77129BB924D00B13921 /* PhotoWidgetEntryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoWidgetEntryView.swift; sourceTree = "<group>"; };
|
||||
F864F77329BB929A00B13921 /* PhotoProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoProvider.swift; sourceTree = "<group>"; };
|
||||
|
@ -302,6 +307,7 @@
|
|||
F866F6A829604FFF002E8F88 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||
F866F6A929605AFA002E8F88 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||
F866F6AD29606367002E8F88 /* ApplicationViewMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationViewMode.swift; sourceTree = "<group>"; };
|
||||
F8675DCF2A1FA40500A89959 /* WaterfallGrid.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaterfallGrid.swift; sourceTree = "<group>"; };
|
||||
F86A42FC299A8B8E00DF7645 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
|
||||
F86A42FE299A8C5500DF7645 /* InAppPurchaseStoreKitConfiguration.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = InAppPurchaseStoreKitConfiguration.storekit; sourceTree = "<group>"; };
|
||||
F86A4300299A97F500DF7645 /* ProductIdentifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductIdentifiers.swift; sourceTree = "<group>"; };
|
||||
|
@ -391,13 +397,15 @@
|
|||
F8B08861299435C9002AB40A /* SupportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportView.swift; sourceTree = "<group>"; };
|
||||
F8B3699A29D86EB600BE3808 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = .swiftlint.yml; sourceTree = "<group>"; };
|
||||
F8B3699B29D86EBD00BE3808 /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; path = .gitignore; sourceTree = "<group>"; };
|
||||
F8C287A22A06B4C90072213F /* ImageScale.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageScale.swift; sourceTree = "<group>"; };
|
||||
F8B758DD2AB9DD85000C8068 /* ColumnData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColumnData.swift; sourceTree = "<group>"; };
|
||||
F8C937A929882CA90004D782 /* Vernissage-001.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-001.xcdatamodel"; sourceTree = "<group>"; };
|
||||
F8CAE64129B8F1AF001E0372 /* Vernissage-005.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-005.xcdatamodel"; sourceTree = "<group>"; };
|
||||
F8D5444229D4066C002225D6 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
F8DF38E329DD68820047F1AA /* ViewOffsetKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewOffsetKey.swift; sourceTree = "<group>"; };
|
||||
F8DF38E529DDB98A0047F1AA /* SocialsSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialsSectionView.swift; sourceTree = "<group>"; };
|
||||
F8DF38E729DDC3D20047F1AA /* eu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = eu; path = eu.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
F8E36E452AB8745300769C55 /* Sizable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sizable.swift; sourceTree = "<group>"; };
|
||||
F8E36E472AB874A500769C55 /* StatusModel+Sizeable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusModel+Sizeable.swift"; sourceTree = "<group>"; };
|
||||
F8E6D03229CDD52500416CCA /* EditProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditProfileView.swift; sourceTree = "<group>"; };
|
||||
F8EF371429C624DA00669F45 /* Vernissage-006.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-006.xcdatamodel"; sourceTree = "<group>"; };
|
||||
F8EF3C8B29FC3A5F00CBFF7C /* Vernissage-012.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-012.xcdatamodel"; sourceTree = "<group>"; };
|
||||
|
@ -407,6 +415,7 @@
|
|||
F8F6E44829BCC0F00004795E /* PhotoMediumWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoMediumWidgetView.swift; sourceTree = "<group>"; };
|
||||
F8F6E44A29BCC0FF0004795E /* PhotoLargeWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoLargeWidgetView.swift; sourceTree = "<group>"; };
|
||||
F8F6E45029BCE9190004795E /* UIImage+Resize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Resize.swift"; sourceTree = "<group>"; };
|
||||
F8FAA0AC2AB0BCB400FD78BD /* View+ContainerBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+ContainerBackground.swift"; sourceTree = "<group>"; };
|
||||
F8FB8AB929EB2ED400342C04 /* NavigationMenuButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationMenuButtons.swift; sourceTree = "<group>"; };
|
||||
F8FFBD4929E99BEE0047EE80 /* Vernissage-009.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-009.xcdatamodel"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
@ -447,6 +456,7 @@
|
|||
F8210DD72966BB7E001D9973 /* NukeExtensions in Frameworks */,
|
||||
F88BC52D29E04BB600CE6141 /* EnvironmentKit in Frameworks */,
|
||||
F8210DD92966BB7E001D9973 /* NukeUI in Frameworks */,
|
||||
F8A192102ABB322E00C2599A /* Semaphore in Frameworks */,
|
||||
F89B5CC029D019B600549F2F /* HTMLString in Frameworks */,
|
||||
F88BC52A29E046D700CE6141 /* WidgetsKit in Frameworks */,
|
||||
);
|
||||
|
@ -533,7 +543,8 @@
|
|||
F8DF38E329DD68820047F1AA /* ViewOffsetKey.swift */,
|
||||
F871F21C29EF0D7000A351EF /* NavigationMenuItemDetails.swift */,
|
||||
F8624D3C29F2D3AC00204986 /* SelectedMenuItemDetails.swift */,
|
||||
F8C287A22A06B4C90072213F /* ImageScale.swift */,
|
||||
F8E36E452AB8745300769C55 /* Sizable.swift */,
|
||||
F8B758DD2AB9DD85000C8068 /* ColumnData.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
|
@ -590,6 +601,7 @@
|
|||
F8AFF7C329B25EF40087D083 /* ImagesGrid.swift */,
|
||||
F89D6C49297196FF001DA3D4 /* ImageViewer.swift */,
|
||||
F870EE5129F1645C00A2D43B /* MainNavigationOptions.swift */,
|
||||
F8675DCF2A1FA40500A89959 /* WaterfallGrid.swift */,
|
||||
);
|
||||
path = Widgets;
|
||||
sourceTree = "<group>";
|
||||
|
@ -651,6 +663,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
F858906A29E1CC7A00D4BDED /* UIApplication+Window.swift */,
|
||||
F8E36E472AB874A500769C55 /* StatusModel+Sizeable.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
|
@ -665,7 +678,7 @@
|
|||
F84625EE29FE2ABA002D3AF4 /* PhotoWidget */,
|
||||
F8F6E44F29BCE9030004795E /* Extensions */,
|
||||
F864F76329BB91B400B13921 /* VernissageWidgetBundle.swift */,
|
||||
F864F76729BB91B600B13921 /* Assets.xcassets */,
|
||||
F85D0C642ABA08F9002B3577 /* Assets.xcassets */,
|
||||
);
|
||||
path = VernissageWidget;
|
||||
sourceTree = "<group>";
|
||||
|
@ -909,6 +922,7 @@
|
|||
children = (
|
||||
F815F60B29E49CF20044566B /* Avatar.swift */,
|
||||
F8F6E45029BCE9190004795E /* UIImage+Resize.swift */,
|
||||
F8FAA0AC2AB0BCB400FD78BD /* View+ContainerBackground.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
|
@ -969,7 +983,6 @@
|
|||
F88C2465295C37B80006098B /* Frameworks */,
|
||||
F88C2466295C37B80006098B /* Resources */,
|
||||
F864F76D29BB91B600B13921 /* Embed Foundation Extensions */,
|
||||
F8CB3DF129D8267C00CDAE5A /* ShellScript */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
|
@ -988,6 +1001,7 @@
|
|||
F88BC52629E0431D00CE6141 /* ServicesKit */,
|
||||
F88BC52929E046D700CE6141 /* WidgetsKit */,
|
||||
F88BC52C29E04BB600CE6141 /* EnvironmentKit */,
|
||||
F8A1920F2ABB322E00C2599A /* Semaphore */,
|
||||
);
|
||||
productName = Vernissage;
|
||||
productReference = F88C2468295C37B80006098B /* Vernissage.app */;
|
||||
|
@ -1001,7 +1015,7 @@
|
|||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastSwiftUpdateCheck = 1430;
|
||||
LastUpgradeCheck = 1420;
|
||||
LastUpgradeCheck = 1500;
|
||||
TargetAttributes = {
|
||||
F864F75C29BB91B400B13921 = {
|
||||
CreatedOnToolsVersion = 14.2;
|
||||
|
@ -1031,6 +1045,7 @@
|
|||
F88E4D4B297EA4290057491A /* XCRemoteSwiftPackageReference "EmojiText" */,
|
||||
F89B5CBE29D019B600549F2F /* XCRemoteSwiftPackageReference "HTMLString" */,
|
||||
F84625F929FE393B002D3AF4 /* XCRemoteSwiftPackageReference "QRCode" */,
|
||||
F8A1920E2ABB322E00C2599A /* XCRemoteSwiftPackageReference "Semaphore" */,
|
||||
);
|
||||
productRefGroup = F88C2469295C37B80006098B /* Products */;
|
||||
projectDirPath = "";
|
||||
|
@ -1048,7 +1063,7 @@
|
|||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F864F76829BB91B600B13921 /* Assets.xcassets in Resources */,
|
||||
F85D0C652ABA08F9002B3577 /* Assets.xcassets in Resources */,
|
||||
F835082429BEF9C400DE3247 /* Localizable.strings in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -1076,27 +1091,6 @@
|
|||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
F8CB3DF129D8267C00CDAE5A /* ShellScript */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nif which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https: //github.com/realm/SwiftLint\"\nfi\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
F864F75929BB91B400B13921 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
|
@ -1121,6 +1115,7 @@
|
|||
F864F78229BB9A6500B13921 /* StatusData+CoreDataClass.swift in Sources */,
|
||||
F864F78329BB9A6800B13921 /* StatusData+CoreDataProperties.swift in Sources */,
|
||||
F864F78429BB9A6E00B13921 /* ApplicationSettings+CoreDataClass.swift in Sources */,
|
||||
F8FAA0AD2AB0BCB400FD78BD /* View+ContainerBackground.swift in Sources */,
|
||||
F864F78629BB9A7400B13921 /* AccountData+CoreDataClass.swift in Sources */,
|
||||
F8705A7B29FF872F00DA818A /* QRCodeGenerator.swift in Sources */,
|
||||
F865B4CE2A024AD8008ACDFC /* StatusData+Faulty.swift in Sources */,
|
||||
|
@ -1181,6 +1176,7 @@
|
|||
F87AEB922986C44E00434FB6 /* AuthorizationSession.swift in Sources */,
|
||||
F86A4301299A97F500DF7645 /* ProductIdentifiers.swift in Sources */,
|
||||
F89D6C4229717FDC001DA3D4 /* AccountsSectionView.swift in Sources */,
|
||||
F8E36E482AB874A500769C55 /* StatusModel+Sizeable.swift in Sources */,
|
||||
F80048082961E6DE00E6868A /* StatusDataHandler.swift in Sources */,
|
||||
F866F6A0296040A8002E8F88 /* ApplicationSettings+CoreDataClass.swift in Sources */,
|
||||
F8210DEA2966E4F9001D9973 /* AnimatePlaceholderModifier.swift in Sources */,
|
||||
|
@ -1197,6 +1193,7 @@
|
|||
F883402029B62AE900C3E096 /* SearchView.swift in Sources */,
|
||||
F88FAD2A295F43B8009B20C9 /* AccountData+CoreDataClass.swift in Sources */,
|
||||
F871F21D29EF0D7000A351EF /* NavigationMenuItemDetails.swift in Sources */,
|
||||
F8E36E462AB8745300769C55 /* Sizable.swift in Sources */,
|
||||
F85DBF8F296732E20069BF89 /* AccountsView.swift in Sources */,
|
||||
F805DCF129DBEF83006A1FD9 /* ReportView.swift in Sources */,
|
||||
F8B0886029943498002AB40A /* OtherSectionView.swift in Sources */,
|
||||
|
@ -1216,6 +1213,7 @@
|
|||
F891E7CE29C35BF50022C449 /* ImageRowItem.swift in Sources */,
|
||||
F86B7221296C49A300EE59EC /* EmptyButtonStyle.swift in Sources */,
|
||||
F80048042961850500E6868A /* AttachmentData+CoreDataProperties.swift in Sources */,
|
||||
F8B758DE2AB9DD85000C8068 /* ColumnData.swift in Sources */,
|
||||
F88E4D4A297EA0490057491A /* RouterPath.swift in Sources */,
|
||||
F88E4D48297E90CD0057491A /* TrendStatusesView.swift in Sources */,
|
||||
F800480A2961EA1900E6868A /* AttachmentDataHandler.swift in Sources */,
|
||||
|
@ -1258,7 +1256,6 @@
|
|||
F89B5CC229D01BF700549F2F /* InstanceView.swift in Sources */,
|
||||
F825F0CB29F7CFC4008BD204 /* FollowRequestsView.swift in Sources */,
|
||||
F825F0C929F7A562008BD204 /* UserProfilePrivateAccountView.swift in Sources */,
|
||||
F8C287A32A06B4C90072213F /* ImageScale.swift in Sources */,
|
||||
F89F57B029D1C11200001EE3 /* RelationshipModel.swift in Sources */,
|
||||
F88AB05829B36B8200345EDE /* AccountsPhotoView.swift in Sources */,
|
||||
F85D4971296402DC00751DF7 /* AuthorizationService.swift in Sources */,
|
||||
|
@ -1278,6 +1275,7 @@
|
|||
F83CBEFB298298A1002972C8 /* ImageCarouselPicture.swift in Sources */,
|
||||
F89A46DC296EAACE0062125F /* SettingsView.swift in Sources */,
|
||||
F866F6AE29606367002E8F88 /* ApplicationViewMode.swift in Sources */,
|
||||
F8675DD02A1FA40500A89959 /* WaterfallGrid.swift in Sources */,
|
||||
F85D4DFE29B78C8400345267 /* HashtagModel.swift in Sources */,
|
||||
F866F6AA29605AFA002E8F88 /* SceneDelegate.swift in Sources */,
|
||||
);
|
||||
|
@ -1316,112 +1314,154 @@
|
|||
F864F76E29BB91B600B13921 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = NO;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CODE_SIGN_ENTITLEMENTS = VernissageWidget/VernissageWidgetExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
<<<<<<< HEAD
|
||||
CURRENT_PROJECT_VERSION = 207;
|
||||
=======
|
||||
CURRENT_PROJECT_VERSION = 144;
|
||||
>>>>>>> develop
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = VernissageWidget/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = VernissageWidget;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
<<<<<<< HEAD
|
||||
MARKETING_VERSION = 1.10.0;
|
||||
=======
|
||||
MARKETING_VERSION = 1.9.1;
|
||||
>>>>>>> develop
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.mczachurski.vernissage.widget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
F864F76F29BB91B600B13921 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = NO;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CODE_SIGN_ENTITLEMENTS = VernissageWidget/VernissageWidgetExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
<<<<<<< HEAD
|
||||
CURRENT_PROJECT_VERSION = 207;
|
||||
=======
|
||||
CURRENT_PROJECT_VERSION = 144;
|
||||
>>>>>>> develop
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = VernissageWidget/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = VernissageWidget;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
<<<<<<< HEAD
|
||||
MARKETING_VERSION = 1.10.0;
|
||||
=======
|
||||
MARKETING_VERSION = 1.9.1;
|
||||
>>>>>>> develop
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.mczachurski.vernissage.widget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
F88BC50D29E02F3900CE6141 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = NO;
|
||||
CODE_SIGN_ENTITLEMENTS = VernissageShare/VernissageShareExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
<<<<<<< HEAD
|
||||
CURRENT_PROJECT_VERSION = 207;
|
||||
=======
|
||||
CURRENT_PROJECT_VERSION = 144;
|
||||
>>>>>>> develop
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = VernissageShare/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = VernissageShareExtension;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.4;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
<<<<<<< HEAD
|
||||
MARKETING_VERSION = 1.10.0;
|
||||
=======
|
||||
MARKETING_VERSION = 1.9.1;
|
||||
>>>>>>> develop
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.mczachurski.vernissage.share;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
F88BC50E29E02F3900CE6141 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = NO;
|
||||
CODE_SIGN_ENTITLEMENTS = VernissageShare/VernissageShareExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
<<<<<<< HEAD
|
||||
CURRENT_PROJECT_VERSION = 207;
|
||||
=======
|
||||
CURRENT_PROJECT_VERSION = 144;
|
||||
>>>>>>> develop
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = VernissageShare/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = VernissageShareExtension;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.4;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
<<<<<<< HEAD
|
||||
MARKETING_VERSION = 1.10.0;
|
||||
=======
|
||||
MARKETING_VERSION = 1.9.1;
|
||||
>>>>>>> develop
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.mczachurski.vernissage.share;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
@ -1429,6 +1469,7 @@
|
|||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
|
@ -1462,6 +1503,7 @@
|
|||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
|
@ -1476,7 +1518,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
|
@ -1490,6 +1532,7 @@
|
|||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
|
@ -1523,6 +1566,7 @@
|
|||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
|
@ -1531,7 +1575,7 @@
|
|||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.2;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
|
@ -1547,12 +1591,17 @@
|
|||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "Violet Blue Pride Pride-Camera Blue-Camera Violet-Camera Orange-Camera Orange Yellow-Camera Yellow Gradient-Camera Gradient";
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = Default;
|
||||
ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = NO;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||
CODE_SIGN_ENTITLEMENTS = Vernissage/Vernissage.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
<<<<<<< HEAD
|
||||
CURRENT_PROJECT_VERSION = 207;
|
||||
=======
|
||||
CURRENT_PROJECT_VERSION = 144;
|
||||
>>>>>>> develop
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
@ -1566,12 +1615,16 @@
|
|||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
<<<<<<< HEAD
|
||||
MARKETING_VERSION = 1.10.0;
|
||||
=======
|
||||
MARKETING_VERSION = 1.9.1;
|
||||
>>>>>>> develop
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.mczachurski.vernissage;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
@ -1580,7 +1633,7 @@
|
|||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
|
@ -1590,11 +1643,16 @@
|
|||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "Violet Blue Pride Pride-Camera Blue-Camera Violet-Camera Orange-Camera Orange Yellow-Camera Yellow Gradient-Camera Gradient";
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = Default;
|
||||
ASSETCATALOG_COMPILER_GENERATE_ASSET_SYMBOLS = NO;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = NO;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||
CODE_SIGN_ENTITLEMENTS = Vernissage/Vernissage.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
<<<<<<< HEAD
|
||||
CURRENT_PROJECT_VERSION = 207;
|
||||
=======
|
||||
CURRENT_PROJECT_VERSION = 144;
|
||||
>>>>>>> develop
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
@ -1608,12 +1666,16 @@
|
|||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.6;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
<<<<<<< HEAD
|
||||
MARKETING_VERSION = 1.10.0;
|
||||
=======
|
||||
MARKETING_VERSION = 1.9.1;
|
||||
>>>>>>> develop
|
||||
PRODUCT_BUNDLE_IDENTIFIER = dev.mczachurski.vernissage;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
|
@ -1621,7 +1683,7 @@
|
|||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
@ -1699,6 +1761,14 @@
|
|||
minimumVersion = 6.0.0;
|
||||
};
|
||||
};
|
||||
F8A1920E2ABB322E00C2599A /* XCRemoteSwiftPackageReference "Semaphore" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/groue/Semaphore";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 0.0.8;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
|
@ -1772,6 +1842,11 @@
|
|||
package = F89B5CBE29D019B600549F2F /* XCRemoteSwiftPackageReference "HTMLString" */;
|
||||
productName = HTMLString;
|
||||
};
|
||||
F8A1920F2ABB322E00C2599A /* Semaphore */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = F8A1920E2ABB322E00C2599A /* XCRemoteSwiftPackageReference "Semaphore" */;
|
||||
productName = Semaphore;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
|
||||
/* Begin XCVersionGroup section */
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the Apache License 2.0.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import ClientKit
|
||||
|
||||
extension StatusModel: Sizable {
|
||||
public var height: Double {
|
||||
return Double(self.mediaAttachments.first?.metaImageHeight ?? 500)
|
||||
}
|
||||
|
||||
public var width: Double {
|
||||
return Double(self.mediaAttachments.first?.metaImageWidth ?? 500)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the Apache License 2.0.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class ColumnData<T>: Identifiable where T: Identifiable, T: Hashable, T: Sizable {
|
||||
public let id = UUID().uuidString
|
||||
public var data: [T] = []
|
||||
public var height: Double = 0.0
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
enum ImageScale {
|
||||
case orginalFullWidth
|
||||
case squareHalfWidth
|
||||
public protocol Sizable {
|
||||
var height: Double { get }
|
||||
var width: Double { get }
|
||||
}
|
|
@ -19,6 +19,7 @@ public class HomeTimelineService {
|
|||
private let defaultAmountOfDownloadedStatuses = 40
|
||||
private let imagePrefetcher = ImagePrefetcher(destination: .diskCache)
|
||||
|
||||
@MainActor
|
||||
public func loadOnBottom(for account: AccountModel) async throws -> Int {
|
||||
// Load data from API and operate on CoreData on background context.
|
||||
let backgroundContext = CoreDataHandler.shared.newBackgroundContext()
|
||||
|
@ -43,7 +44,8 @@ public class HomeTimelineService {
|
|||
return allStatusesFromApi.count
|
||||
}
|
||||
|
||||
public func refreshTimeline(for account: AccountModel) async throws -> String? {
|
||||
@MainActor
|
||||
public func refreshTimeline(for account: AccountModel, updateLastSeenStatus: Bool = false) async throws -> String? {
|
||||
// Load data from API and operate on CoreData on background context.
|
||||
let backgroundContext = CoreDataHandler.shared.newBackgroundContext()
|
||||
|
||||
|
@ -55,29 +57,28 @@ public class HomeTimelineService {
|
|||
// When Apple introduce good way to show new items without scroll to top then we can change that method.
|
||||
let allStatusesFromApi = try await self.refresh(for: account, on: backgroundContext)
|
||||
|
||||
// Save data into database.
|
||||
CoreDataHandler.shared.save(viewContext: backgroundContext)
|
||||
|
||||
// Update last seen status.
|
||||
if let lastSeenStatusId, updateLastSeenStatus == true {
|
||||
try self.update(lastSeenStatusId: lastSeenStatusId, for: account, on: backgroundContext)
|
||||
}
|
||||
|
||||
// Start prefetching images.
|
||||
self.prefetch(statuses: allStatusesFromApi)
|
||||
|
||||
// Save data into database.
|
||||
CoreDataHandler.shared.save(viewContext: backgroundContext)
|
||||
|
||||
// Return id of last seen status.
|
||||
return lastSeenStatusId
|
||||
}
|
||||
|
||||
public func save(lastSeenStatusId: String, for account: AccountModel) async throws {
|
||||
// Load data from API and operate on CoreData on background context.
|
||||
let backgroundContext = CoreDataHandler.shared.newBackgroundContext()
|
||||
|
||||
private func update(lastSeenStatusId: String, for account: AccountModel, on backgroundContext: NSManagedObjectContext) throws {
|
||||
// Save information about last seen status.
|
||||
guard let accountDataFromDb = AccountDataHandler.shared.getAccountData(accountId: account.id, viewContext: backgroundContext) else {
|
||||
throw DatabaseError.cannotDownloadAccount
|
||||
}
|
||||
|
||||
accountDataFromDb.lastSeenStatusId = lastSeenStatusId
|
||||
|
||||
// Save data into database.
|
||||
CoreDataHandler.shared.save(viewContext: backgroundContext)
|
||||
}
|
||||
|
||||
public func update(status statusData: StatusData, basedOn status: Status, for account: AccountModel) async throws -> StatusData? {
|
||||
|
@ -191,7 +192,9 @@ public class HomeTimelineService {
|
|||
|
||||
// Delete statuses from database.
|
||||
if !dbStatusesToRemove.isEmpty {
|
||||
StatusDataHandler.shared.remove(accountId: account.id, statuses: dbStatusesToRemove, viewContext: backgroundContext)
|
||||
for dbStatusToRemove in dbStatusesToRemove {
|
||||
backgroundContext.delete(dbStatusToRemove)
|
||||
}
|
||||
}
|
||||
|
||||
// Save statuses in database.
|
||||
|
|
|
@ -142,7 +142,7 @@ struct EditProfileView: View {
|
|||
|
||||
Text("@\(account.acct)")
|
||||
.font(.headline)
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
|
||||
if self.avatarData != nil {
|
||||
HStack {
|
||||
|
@ -151,7 +151,7 @@ struct EditProfileView: View {
|
|||
.foregroundColor(.accentColor)
|
||||
Text("editProfile.title.photoInfo")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
}
|
||||
.padding(.top, 4)
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ struct FollowRequestsView: View {
|
|||
Text(account.displayName ?? account.username)
|
||||
.foregroundColor(.mainTextColor)
|
||||
Text("@\(account.acct)")
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
.font(.footnote)
|
||||
}
|
||||
.padding(.leading, 8)
|
||||
|
|
|
@ -101,12 +101,10 @@ struct HomeFeedView: View {
|
|||
private func refreshData() async {
|
||||
do {
|
||||
if let account = self.applicationState.account {
|
||||
if let lastSeenStatusId = try await HomeTimelineService.shared.refreshTimeline(for: account) {
|
||||
try await HomeTimelineService.shared.save(lastSeenStatusId: lastSeenStatusId, for: account)
|
||||
self.applicationState.lastSeenStatusId = lastSeenStatusId
|
||||
}
|
||||
let lastSeenStatusId = try await HomeTimelineService.shared.refreshTimeline(for: account, updateLastSeenStatus: true)
|
||||
|
||||
asyncAfter(0.35) {
|
||||
self.applicationState.lastSeenStatusId = lastSeenStatusId
|
||||
self.applicationState.amountOfNewStatuses = 0
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ struct InstanceView: View {
|
|||
if let shortDescription = instance.shortDescription {
|
||||
Text(shortDescription)
|
||||
.font(.footnote)
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,7 +119,7 @@ struct InstanceView: View {
|
|||
Text(title, comment: "Title")
|
||||
Spacer()
|
||||
Text(value)
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
.font(.subheadline)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -116,8 +116,13 @@ struct MainView: View {
|
|||
private func getMainView() -> some View {
|
||||
switch self.viewMode {
|
||||
case .home:
|
||||
HomeFeedView(accountId: applicationState.account?.id ?? String.empty())
|
||||
.id(applicationState.account?.id ?? String.empty())
|
||||
if UIDevice.isIPhone {
|
||||
HomeFeedView(accountId: applicationState.account?.id ?? String.empty())
|
||||
.id(applicationState.account?.id ?? String.empty())
|
||||
} else {
|
||||
StatusesView(listType: .home)
|
||||
.id(applicationState.account?.id ?? String.empty())
|
||||
}
|
||||
case .trendingPhotos:
|
||||
TrendStatusesView(accountId: applicationState.account?.id ?? String.empty())
|
||||
.id(applicationState.account?.id ?? String.empty())
|
||||
|
@ -237,7 +242,7 @@ struct MainView: View {
|
|||
.frame(width: 16, height: 16)
|
||||
.foregroundColor(.white)
|
||||
.padding(8)
|
||||
.background(Color.lightGrayColor)
|
||||
.background(Color.customGrayColor)
|
||||
.clipShape(AvatarShape.circle.shape())
|
||||
.background(
|
||||
AvatarShape.circle.shape()
|
||||
|
|
|
@ -58,13 +58,13 @@ struct NotificationRowView: View {
|
|||
|
||||
if let createdAt = self.notification.createdAt.toDate(.isoDateTimeMilliSec) {
|
||||
RelativeTime(date: createdAt)
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
.font(.footnote)
|
||||
}
|
||||
}
|
||||
|
||||
Text(self.getTitle(), comment: "Notification type")
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
.font(.footnote)
|
||||
.fontWeight(.light)
|
||||
|
||||
|
|
|
@ -38,6 +38,11 @@ struct PaginableStatusesView: View {
|
|||
@State private var state: ViewState = .loading
|
||||
@State private var page = 1
|
||||
|
||||
// Gallery parameters.
|
||||
@State private var imageColumns = 3
|
||||
@State private var containerWidth: Double = UIScreen.main.bounds.width
|
||||
@State private var containerHeight: Double = UIScreen.main.bounds.height
|
||||
|
||||
private let defaultLimit = 10
|
||||
private let imagePrefetcher = ImagePrefetcher(destination: .diskCache)
|
||||
|
||||
|
@ -75,27 +80,44 @@ struct PaginableStatusesView: View {
|
|||
@ViewBuilder
|
||||
private func list() -> some View {
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .center) {
|
||||
ForEach(self.statusViewModels, id: \.id) { item in
|
||||
ImageRowAsync(statusViewModel: item)
|
||||
if self.imageColumns > 1 {
|
||||
WaterfallGrid($statusViewModels, columns: $imageColumns, hideLoadMore: $allItemsLoaded) { item in
|
||||
ImageRowAsync(statusViewModel: item, containerWidth: $containerWidth)
|
||||
} onLoadMore: {
|
||||
do {
|
||||
try await self.loadMoreStatuses()
|
||||
} catch {
|
||||
ErrorService.shared.handle(error, message: "statuses.error.loadingStatusesFailed", showToastr: !Task.isCancelled)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LazyVStack(alignment: .center) {
|
||||
ForEach(self.statusViewModels, id: \.id) { item in
|
||||
ImageRowAsync(statusViewModel: item, containerWidth: $containerWidth)
|
||||
}
|
||||
|
||||
if allItemsLoaded == false {
|
||||
HStack {
|
||||
Spacer()
|
||||
LoadingIndicator()
|
||||
.task {
|
||||
do {
|
||||
try await self.loadMoreStatuses()
|
||||
} catch {
|
||||
ErrorService.shared.handle(error, message: "statuses.error.loadingStatusesFailed", showToastr: !Task.isCancelled)
|
||||
if allItemsLoaded == false {
|
||||
HStack {
|
||||
Spacer()
|
||||
LoadingIndicator()
|
||||
.task {
|
||||
do {
|
||||
try await self.loadMoreStatuses()
|
||||
} catch {
|
||||
ErrorService.shared.handle(error, message: "statuses.error.loadingStatusesFailed", showToastr: !Task.isCancelled)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.gallery { galleryProperties in
|
||||
self.imageColumns = galleryProperties.imageColumns
|
||||
self.containerWidth = galleryProperties.containerWidth
|
||||
self.containerHeight = galleryProperties.containerHeight
|
||||
}
|
||||
}
|
||||
|
||||
private func loadData() async {
|
||||
|
|
|
@ -19,7 +19,7 @@ struct MediaSettingsView: View {
|
|||
Text("settings.title.alwaysShowSensitiveTitle", comment: "Always show NSFW")
|
||||
Text("settings.title.alwaysShowSensitiveDescription", comment: "Force show all NFSW (sensitive) media without warnings")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
}
|
||||
}
|
||||
.onChange(of: self.applicationState.showSensitive) { newValue in
|
||||
|
@ -31,7 +31,7 @@ struct MediaSettingsView: View {
|
|||
Text("settings.title.alwaysShowAltTitle", comment: "Show alternative text")
|
||||
Text("settings.title.alwaysShowAltDescription", comment: "Show alternative text if present on status details screen")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
}
|
||||
}
|
||||
.onChange(of: self.applicationState.showPhotoDescription) { newValue in
|
||||
|
@ -43,7 +43,7 @@ struct MediaSettingsView: View {
|
|||
Text("settings.title.showAvatars", comment: "Show avatars")
|
||||
Text("settings.title.showAvatarsOnTimeline", comment: "Show avatars on timeline")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
}
|
||||
}
|
||||
.onChange(of: self.applicationState.showAvatarsOnTimeline) { newValue in
|
||||
|
@ -55,7 +55,7 @@ struct MediaSettingsView: View {
|
|||
Text("settings.title.showFavourite", comment: "Show favourites")
|
||||
Text("settings.title.showFavouriteOnTimeline", comment: "Show favourites on timeline")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
}
|
||||
}
|
||||
.onChange(of: self.applicationState.showFavouritesOnTimeline) { newValue in
|
||||
|
@ -67,7 +67,7 @@ struct MediaSettingsView: View {
|
|||
Text("settings.title.showAltText", comment: "Show ALT icon")
|
||||
Text("settings.title.showAltTextOnTimeline", comment: "ALT icon will be displayed on timelines")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
}
|
||||
}
|
||||
.onChange(of: self.applicationState.showAltIconOnTimeline) { newValue in
|
||||
|
@ -79,7 +79,7 @@ struct MediaSettingsView: View {
|
|||
Text("settings.title.warnAboutMissingAltTitle", comment: "Warn of missing ALT text")
|
||||
Text("settings.title.warnAboutMissingAltDescription", comment: "A warning about missing ALT texts will be displayed before publishing new post.")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
}
|
||||
}
|
||||
.onChange(of: self.applicationState.warnAboutMissingAlt) { newValue in
|
||||
|
|
|
@ -14,7 +14,7 @@ struct SocialsSectionView: View {
|
|||
Text("settings.title.followVernissage", comment: "Follow Vernissage")
|
||||
Text("settings.title.mastodonAccount", comment: "Mastodon account")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
@ -27,7 +27,7 @@ struct SocialsSectionView: View {
|
|||
Text("settings.title.follow", comment: "Follow me")
|
||||
Text("settings.title.mastodonAccount", comment: "Mastodon account")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
@ -40,7 +40,7 @@ struct SocialsSectionView: View {
|
|||
Text("settings.title.follow", comment: "Follow me")
|
||||
Text("settings.title.pixelfedAccount", comment: "Pixelfed account")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
|
|
@ -21,7 +21,7 @@ struct SupportView: View {
|
|||
Text(product.displayName)
|
||||
Text(product.description)
|
||||
.font(.footnote)
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
}
|
||||
Spacer()
|
||||
Button(product.displayPrice) {
|
||||
|
|
|
@ -80,7 +80,7 @@ struct InstanceRowView: View {
|
|||
Spacer()
|
||||
}
|
||||
.padding(.top, 4)
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ struct StatusView: View {
|
|||
private func mainBody() -> some View {
|
||||
switch state {
|
||||
case .loading:
|
||||
StatusPlaceholderView(imageHeight: self.getImageHeight(), imageBlurhash: self.imageBlurhash)
|
||||
StatusPlaceholderView(imageWidth: self.getImageWidth(), imageHeight: self.getImageHeight(), imageBlurhash: self.imageBlurhash)
|
||||
.task {
|
||||
await self.loadData()
|
||||
}
|
||||
|
@ -134,7 +134,7 @@ struct StatusView: View {
|
|||
}
|
||||
}
|
||||
.padding(.bottom, 2)
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
|
||||
HStack {
|
||||
Text("status.title.uploaded", comment: "Uploaded")
|
||||
|
@ -151,7 +151,7 @@ struct StatusView: View {
|
|||
Text(String(format: NSLocalizedString("status.title.via", comment: "via"), applicationName))
|
||||
}
|
||||
}
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
.font(.footnote)
|
||||
|
||||
InteractionRow(statusModel: statusViewModel) {
|
||||
|
@ -236,7 +236,8 @@ struct StatusView: View {
|
|||
|
||||
private func getImageHeight() -> Double {
|
||||
if let highestImageUrl = self.highestImageUrl, let imageSize = ImageSizeService.shared.get(for: highestImageUrl) {
|
||||
return imageSize.height
|
||||
let calculatedSize = ImageSizeService.shared.calculate(width: imageSize.width, height: imageSize.height)
|
||||
return calculatedSize.height
|
||||
}
|
||||
|
||||
if let imageHeight = self.imageHeight, let imageWidth = self.imageWidth, imageHeight > 0 && imageWidth > 0 {
|
||||
|
@ -248,6 +249,21 @@ struct StatusView: View {
|
|||
return UIScreen.main.bounds.width * 0.75
|
||||
}
|
||||
|
||||
private func getImageWidth() -> Double {
|
||||
if let highestImageUrl = self.highestImageUrl, let imageSize = ImageSizeService.shared.get(for: highestImageUrl) {
|
||||
let calculatedSize = ImageSizeService.shared.calculate(width: imageSize.width, height: imageSize.height)
|
||||
return calculatedSize.width
|
||||
}
|
||||
|
||||
if let imageHeight = self.imageHeight, let imageWidth = self.imageWidth, imageHeight > 0 && imageWidth > 0 {
|
||||
let calculatedSize = ImageSizeService.shared.calculate(width: Double(imageWidth), height: Double(imageHeight))
|
||||
return calculatedSize.width
|
||||
}
|
||||
|
||||
// If we don't have image height and width in metadata, we have to use some constant height.
|
||||
return UIScreen.main.bounds.width * 0.75
|
||||
}
|
||||
|
||||
private func getMainStatus(status: StatusModel) async throws -> StatusModel {
|
||||
guard let inReplyToId = status.inReplyToId else {
|
||||
return status
|
||||
|
|
|
@ -36,7 +36,7 @@ struct CommentBodyView: View {
|
|||
|
||||
if let createdAt = self.statusViewModel.createdAt.toDate(.isoDateTimeMilliSec) {
|
||||
RelativeTime(date: createdAt)
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
.font(.footnote)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ struct CommentsSectionView: View {
|
|||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
.background(Color.lightGrayColor.opacity(0.5))
|
||||
.background(Color.customGrayColor.opacity(0.5))
|
||||
.transition(AnyTransition.move(edge: .top).combined(with: .opacity))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import SwiftUI
|
|||
import WidgetsKit
|
||||
|
||||
struct StatusPlaceholderView: View {
|
||||
@State var imageWidth: Double
|
||||
@State var imageHeight: Double
|
||||
@State var imageBlurhash: String?
|
||||
|
||||
|
@ -17,11 +18,11 @@ struct StatusPlaceholderView: View {
|
|||
if let imageBlurhash, let uiImage = UIImage(blurHash: imageBlurhash, size: CGSize(width: 32, height: 32)) {
|
||||
Image(uiImage: uiImage)
|
||||
.resizable()
|
||||
.frame(width: UIScreen.main.bounds.width, height: imageHeight)
|
||||
.frame(width: self.imageWidth, height: self.imageHeight)
|
||||
} else {
|
||||
Rectangle()
|
||||
.fill(Color.placeholderText)
|
||||
.frame(width: UIScreen.main.bounds.width, height: imageHeight)
|
||||
.frame(width: self.imageWidth, height: self.imageHeight)
|
||||
.redacted(reason: .placeholder)
|
||||
}
|
||||
|
||||
|
@ -31,10 +32,10 @@ struct StatusPlaceholderView: View {
|
|||
accountUsername: "@username")
|
||||
|
||||
Text("Lorem ispum text something")
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
.font(.footnote)
|
||||
Text("Lorem ispum text something sdf sdfsdf sdfdsfsdfsdf")
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
.font(.footnote)
|
||||
|
||||
LabelIcon(iconName: "mappin.and.ellipse", value: "Wroclaw, Poland")
|
||||
|
|
|
@ -14,6 +14,7 @@ import WidgetsKit
|
|||
|
||||
struct StatusesView: View {
|
||||
public enum ListType: Hashable {
|
||||
case home
|
||||
case local
|
||||
case federated
|
||||
case favourites
|
||||
|
@ -22,6 +23,8 @@ struct StatusesView: View {
|
|||
|
||||
public var title: LocalizedStringKey {
|
||||
switch self {
|
||||
case .home:
|
||||
return "mainview.tab.homeTimeline"
|
||||
case .local:
|
||||
return "statuses.navigationBar.localTimeline"
|
||||
case .federated:
|
||||
|
@ -50,7 +53,12 @@ struct StatusesView: View {
|
|||
@State private var state: ViewState = .loading
|
||||
@State private var lastStatusId: String?
|
||||
|
||||
private let defaultLimit = 20
|
||||
// Gallery parameters.
|
||||
@State private var imageColumns = 3
|
||||
@State private var containerWidth: Double = UIDevice.isIPad ? UIScreen.main.bounds.width / 3 : UIScreen.main.bounds.width
|
||||
@State private var containerHeight: Double = UIDevice.isIPad ? UIScreen.main.bounds.height / 3 : UIScreen.main.bounds.height
|
||||
|
||||
private let defaultLimit = 40
|
||||
private let imagePrefetcher = ImagePrefetcher(destination: .diskCache)
|
||||
|
||||
var body: some View {
|
||||
|
@ -88,27 +96,44 @@ struct StatusesView: View {
|
|||
@ViewBuilder
|
||||
private func list() -> some View {
|
||||
ScrollView {
|
||||
LazyVStack(alignment: .center) {
|
||||
ForEach(self.statusViewModels, id: \.id) { item in
|
||||
ImageRowAsync(statusViewModel: item)
|
||||
if self.imageColumns > 1 {
|
||||
WaterfallGrid($statusViewModels, columns: $imageColumns, hideLoadMore: $allItemsLoaded) { item in
|
||||
ImageRowAsync(statusViewModel: item, containerWidth: $containerWidth)
|
||||
} onLoadMore: {
|
||||
do {
|
||||
try await self.loadMoreStatuses()
|
||||
} catch {
|
||||
ErrorService.shared.handle(error, message: "statuses.error.loadingStatusesFailed", showToastr: !Task.isCancelled)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LazyVStack(alignment: .center) {
|
||||
ForEach(self.statusViewModels, id: \.id) { item in
|
||||
ImageRowAsync(statusViewModel: item, containerWidth: $containerWidth)
|
||||
}
|
||||
|
||||
if allItemsLoaded == false {
|
||||
HStack {
|
||||
Spacer()
|
||||
LoadingIndicator()
|
||||
.task {
|
||||
do {
|
||||
try await self.loadMoreStatuses()
|
||||
} catch {
|
||||
ErrorService.shared.handle(error, message: "statuses.error.loadingStatusesFailed", showToastr: !Task.isCancelled)
|
||||
if allItemsLoaded == false {
|
||||
HStack {
|
||||
Spacer()
|
||||
LoadingIndicator()
|
||||
.task {
|
||||
do {
|
||||
try await self.loadMoreStatuses()
|
||||
} catch {
|
||||
ErrorService.shared.handle(error, message: "statuses.error.loadingStatusesFailed", showToastr: !Task.isCancelled)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.gallery { galleryProperties in
|
||||
self.imageColumns = galleryProperties.imageColumns
|
||||
self.containerWidth = galleryProperties.containerWidth
|
||||
self.containerHeight = galleryProperties.containerHeight
|
||||
}
|
||||
.refreshable {
|
||||
do {
|
||||
HapticService.shared.fireHaptic(of: .dataRefresh(intensity: 0.3))
|
||||
|
@ -210,6 +235,12 @@ struct StatusesView: View {
|
|||
|
||||
private func loadFromApi(maxId: String? = nil, sinceId: String? = nil, minId: String? = nil) async throws -> [Status] {
|
||||
switch self.listType {
|
||||
case .home:
|
||||
return try await self.client.publicTimeline?.getHomeTimeline(
|
||||
maxId: maxId,
|
||||
sinceId: sinceId,
|
||||
minId: minId,
|
||||
limit: self.defaultLimit) ?? []
|
||||
case .local:
|
||||
return try await self.client.publicTimeline?.getStatuses(
|
||||
local: true,
|
||||
|
|
|
@ -21,6 +21,11 @@ struct TrendStatusesView: View {
|
|||
@State private var statusViewModels: [StatusModel] = []
|
||||
@State private var state: ViewState = .loading
|
||||
|
||||
// Gallery parameters.
|
||||
@State private var imageColumns = 3
|
||||
@State private var containerWidth: Double = UIScreen.main.bounds.width
|
||||
@State private var containerHeight: Double = UIScreen.main.bounds.height
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
Picker(selection: $tabSelectedValue, label: Text("")) {
|
||||
|
@ -45,6 +50,20 @@ struct TrendStatusesView: View {
|
|||
|
||||
self.mainBody()
|
||||
}
|
||||
.gallery { galleryProperties in
|
||||
self.imageColumns = galleryProperties.imageColumns
|
||||
self.containerWidth = galleryProperties.containerWidth
|
||||
self.containerHeight = galleryProperties.containerHeight
|
||||
}
|
||||
.refreshable {
|
||||
do {
|
||||
HapticService.shared.fireHaptic(of: .dataRefresh(intensity: 0.3))
|
||||
try await self.loadStatuses()
|
||||
HapticService.shared.fireHaptic(of: .dataRefresh(intensity: 0.7))
|
||||
} catch {
|
||||
ErrorService.shared.handle(error, message: "trendingStatuses.error.loadingStatusesFailed", showToastr: !Task.isCancelled)
|
||||
}
|
||||
}
|
||||
.navigationTitle("trendingStatuses.navigationBar.title")
|
||||
}
|
||||
|
||||
|
@ -70,18 +89,15 @@ struct TrendStatusesView: View {
|
|||
if self.statusViewModels.isEmpty {
|
||||
NoDataView(imageSystemName: "photo.on.rectangle.angled", text: "trendingStatuses.title.noPhotos")
|
||||
} else {
|
||||
LazyVStack(alignment: .center) {
|
||||
ForEach(self.statusViewModels, id: \.id) { item in
|
||||
ImageRowAsync(statusViewModel: item)
|
||||
}
|
||||
}
|
||||
.refreshable {
|
||||
do {
|
||||
HapticService.shared.fireHaptic(of: .dataRefresh(intensity: 0.3))
|
||||
try await self.loadStatuses()
|
||||
HapticService.shared.fireHaptic(of: .dataRefresh(intensity: 0.7))
|
||||
} catch {
|
||||
ErrorService.shared.handle(error, message: "trendingStatuses.error.loadingStatusesFailed", showToastr: !Task.isCancelled)
|
||||
if self.imageColumns > 1 {
|
||||
WaterfallGrid($statusViewModels, columns: $imageColumns, hideLoadMore: Binding.constant(true)) { item in
|
||||
ImageRowAsync(statusViewModel: item, containerWidth: $containerWidth)
|
||||
} onLoadMore: { }
|
||||
} else {
|
||||
LazyVStack(alignment: .center) {
|
||||
ForEach(self.statusViewModels, id: \.id) { item in
|
||||
ImageRowAsync(statusViewModel: item, containerWidth: Binding.constant(UIScreen.main.bounds.width))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,6 +57,8 @@ struct UserProfileHeaderView: View {
|
|||
.opacity(0.6)
|
||||
}
|
||||
}.foregroundColor(.mainTextColor)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
HStack(alignment: .center) {
|
||||
|
@ -66,7 +68,7 @@ struct UserProfileHeaderView: View {
|
|||
.font(.title3)
|
||||
.fontWeight(.bold)
|
||||
Text("@\(account.acct)")
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
.font(.subheadline)
|
||||
}
|
||||
|
||||
|
@ -99,7 +101,7 @@ struct UserProfileHeaderView: View {
|
|||
self.accountRelationshipPanel()
|
||||
|
||||
Text(String(format: NSLocalizedString("userProfile.title.joined", comment: "Joined"), account.createdAt.toRelative(.isoDateTimeMilliSec)))
|
||||
.foregroundColor(.lightGrayColor.opacity(0.5))
|
||||
.foregroundColor(.customGrayColor.opacity(0.5))
|
||||
.font(.footnote)
|
||||
}
|
||||
.padding([.top, .leading, .trailing])
|
||||
|
|
|
@ -18,65 +18,87 @@ struct UserProfileStatusesView: View {
|
|||
|
||||
@State public var accountId: String
|
||||
|
||||
// Gallery parameters.
|
||||
@Binding private var imageColumns: Int
|
||||
@Binding private var containerWidth: Double
|
||||
@Binding private var containerHeight: Double
|
||||
|
||||
@State private var allItemsLoaded = false
|
||||
@State private var firstLoadFinished = false
|
||||
@State private var statusViewModels: [StatusModel] = []
|
||||
|
||||
private let defaultLimit = 20
|
||||
private let defaultLimit = 40
|
||||
private let imagePrefetcher = ImagePrefetcher(destination: .diskCache)
|
||||
private let singleGrids = [GridItem(.flexible(), spacing: 10)]
|
||||
private let dubleGrid = [GridItem(.flexible(), spacing: 10), GridItem(.flexible(), spacing: 0)]
|
||||
|
||||
init(accountId: String, imageColumns: Binding<Int>, containerWidth: Binding<Double>, containerHeight: Binding<Double>) {
|
||||
self.accountId = accountId
|
||||
self._imageColumns = imageColumns
|
||||
self._containerWidth = containerWidth
|
||||
self._containerHeight = containerHeight
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if firstLoadFinished == true {
|
||||
HStack {
|
||||
Spacer()
|
||||
Button {
|
||||
withAnimation {
|
||||
self.applicationState.showGridOnUserProfile = false
|
||||
ApplicationSettingsHandler.shared.set(showGridOnUserProfile: false)
|
||||
if self.imageColumns > 1 {
|
||||
WaterfallGrid($statusViewModels, columns: $imageColumns, hideLoadMore: $allItemsLoaded) { item in
|
||||
ImageRowAsync(statusViewModel: item, withAvatar: false, containerWidth: $containerWidth)
|
||||
} onLoadMore: {
|
||||
do {
|
||||
try await self.loadMoreStatuses()
|
||||
} catch {
|
||||
ErrorService.shared.handle(error, message: "statuses.error.loadingStatusesFailed", showToastr: !Task.isCancelled)
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "rectangle.grid.1x2.fill")
|
||||
.foregroundColor(self.applicationState.showGridOnUserProfile ? .lightGrayColor : .accentColor)
|
||||
.padding(.trailing, 8)
|
||||
.padding(.bottom, 8)
|
||||
}
|
||||
Button {
|
||||
withAnimation {
|
||||
self.applicationState.showGridOnUserProfile = true
|
||||
ApplicationSettingsHandler.shared.set(showGridOnUserProfile: true)
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "rectangle.grid.2x2.fill")
|
||||
.foregroundColor(self.applicationState.showGridOnUserProfile ? .accentColor : .lightGrayColor)
|
||||
.padding(.trailing, 16)
|
||||
.padding(.bottom, 8)
|
||||
}
|
||||
}
|
||||
|
||||
LazyVGrid(columns: self.applicationState.showGridOnUserProfile ? dubleGrid : singleGrids, spacing: 5) {
|
||||
ForEach(self.statusViewModels, id: \.id) { item in
|
||||
ImageRowAsync(statusViewModel: item,
|
||||
withAvatar: false,
|
||||
imageScale: self.applicationState.showGridOnUserProfile ? .squareHalfWidth : .orginalFullWidth)
|
||||
.if(self.applicationState.showGridOnUserProfile) {
|
||||
$0.frame(width: UIScreen.main.bounds.width / 2, height: UIScreen.main.bounds.width / 2)
|
||||
} else {
|
||||
HStack {
|
||||
Spacer()
|
||||
Button {
|
||||
withAnimation {
|
||||
self.applicationState.showGridOnUserProfile = false
|
||||
ApplicationSettingsHandler.shared.set(showGridOnUserProfile: false)
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "rectangle.grid.1x2.fill")
|
||||
.foregroundColor(self.applicationState.showGridOnUserProfile ? .customGrayColor : .accentColor)
|
||||
.padding(.trailing, 8)
|
||||
.padding(.bottom, 8)
|
||||
}
|
||||
Button {
|
||||
withAnimation {
|
||||
self.applicationState.showGridOnUserProfile = true
|
||||
ApplicationSettingsHandler.shared.set(showGridOnUserProfile: true)
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "rectangle.grid.2x2.fill")
|
||||
.foregroundColor(self.applicationState.showGridOnUserProfile ? .accentColor : .customGrayColor)
|
||||
.padding(.trailing, 16)
|
||||
.padding(.bottom, 8)
|
||||
}
|
||||
}
|
||||
|
||||
if allItemsLoaded == false && firstLoadFinished == true {
|
||||
HStack {
|
||||
Spacer()
|
||||
LoadingIndicator()
|
||||
.task {
|
||||
do {
|
||||
try await self.loadMoreStatuses()
|
||||
} catch {
|
||||
ErrorService.shared.handle(error, message: "global.error.errorDuringDownloadStatuses", showToastr: true)
|
||||
LazyVGrid(columns: self.applicationState.showGridOnUserProfile ? dubleGrid : singleGrids, spacing: 5) {
|
||||
ForEach(self.statusViewModels, id: \.id) { item in
|
||||
ImageRowAsync(statusViewModel: item,
|
||||
withAvatar: false,
|
||||
containerWidth: Binding.constant(self.applicationState.showGridOnUserProfile ? self.containerWidth / 2 : self.containerWidth),
|
||||
clipToRectangle: $applicationState.showGridOnUserProfile)
|
||||
}
|
||||
|
||||
if allItemsLoaded == false && firstLoadFinished == true {
|
||||
HStack {
|
||||
Spacer()
|
||||
LoadingIndicator()
|
||||
.task {
|
||||
do {
|
||||
try await self.loadMoreStatuses()
|
||||
} catch {
|
||||
ErrorService.shared.handle(error, message: "global.error.errorDuringDownloadStatuses", showToastr: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,11 @@ struct UserProfileView: View {
|
|||
@State private var state: ViewState = .loading
|
||||
@State private var viewId = UUID().uuidString
|
||||
|
||||
// Gallery parameters.
|
||||
@State private var imageColumns = 3
|
||||
@State private var containerWidth: Double = UIScreen.main.bounds.width
|
||||
@State private var containerHeight: Double = UIScreen.main.bounds.height
|
||||
|
||||
var body: some View {
|
||||
self.mainBody()
|
||||
.navigationTitle(self.accountDisplayName ?? self.accountUserName)
|
||||
|
@ -68,11 +73,16 @@ struct UserProfileView: View {
|
|||
.id(self.viewId)
|
||||
|
||||
if self.applicationState.account?.id == account.id || self.relationship.haveAccessToPhotos(account: account) {
|
||||
UserProfileStatusesView(accountId: account.id)
|
||||
UserProfileStatusesView(accountId: account.id, imageColumns: $imageColumns, containerWidth: $containerWidth, containerHeight: $containerHeight)
|
||||
} else {
|
||||
UserProfilePrivateAccountView()
|
||||
}
|
||||
}
|
||||
.gallery { galleryProperties in
|
||||
self.imageColumns = galleryProperties.imageColumns
|
||||
self.containerWidth = galleryProperties.containerWidth
|
||||
self.containerHeight = galleryProperties.containerHeight
|
||||
}
|
||||
.onAppear {
|
||||
if let updatedProfile = self.applicationState.updatedProfile {
|
||||
self.account = nil
|
||||
|
|
|
@ -12,11 +12,29 @@ import WidgetsKit
|
|||
struct ImageCarouselPicture: View {
|
||||
@ObservedObject public var attachment: AttachmentModel
|
||||
|
||||
@State private var blurredImageHeight: Double
|
||||
@State private var blurredImageWidth: Double
|
||||
|
||||
private let onImageDownloaded: (AttachmentModel, Data) -> Void
|
||||
|
||||
init(attachment: AttachmentModel, onImageDownloaded: @escaping (_: AttachmentModel, _: Data) -> Void) {
|
||||
self.attachment = attachment
|
||||
self.onImageDownloaded = onImageDownloaded
|
||||
|
||||
if let size = ImageSizeService.shared.get(for: attachment.url) {
|
||||
let imageSize = ImageSizeService.shared.calculate(width: size.width, height: size.height)
|
||||
|
||||
self.blurredImageHeight = imageSize.height
|
||||
self.blurredImageWidth = imageSize.width
|
||||
} else if let imageWidth = attachment.metaImageWidth, let imageHeight = attachment.metaImageHeight {
|
||||
let imageSize = ImageSizeService.shared.calculate(width: Double(imageWidth), height: Double(imageHeight))
|
||||
|
||||
self.blurredImageHeight = imageSize.height
|
||||
self.blurredImageWidth = imageSize.width
|
||||
} else {
|
||||
self.blurredImageHeight = 100.0
|
||||
self.blurredImageWidth = 100.0
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
|
@ -26,6 +44,7 @@ struct ImageCarouselPicture: View {
|
|||
.aspectRatio(contentMode: .fit)
|
||||
} else {
|
||||
BlurredImage(blurhash: attachment.blurhash)
|
||||
.frame(width: self.blurredImageWidth, height: self.blurredImageHeight)
|
||||
.task {
|
||||
do {
|
||||
// Download image and recalculate exif data.
|
||||
|
|
|
@ -25,12 +25,15 @@ struct ImageRow: View {
|
|||
|
||||
// Calculate size of frame (first from cache, then from real image, then from metadata).
|
||||
if let firstAttachment, let size = ImageSizeService.shared.get(for: firstAttachment.url) {
|
||||
self.imageWidth = size.width
|
||||
self.imageHeight = size.height
|
||||
let calculatedSize = ImageSizeService.shared.calculate(width: size.width, height: size.height, andContainerWidth: UIScreen.main.bounds.size.width)
|
||||
self.imageWidth = calculatedSize.width
|
||||
self.imageHeight = calculatedSize.height
|
||||
} else if let firstAttachment, firstAttachment.metaImageWidth > 0 && firstAttachment.metaImageHeight > 0 {
|
||||
let size = ImageSizeService.shared.calculate(for: firstAttachment.url,
|
||||
width: firstAttachment.metaImageWidth,
|
||||
height: firstAttachment.metaImageHeight)
|
||||
ImageSizeService.shared.save(for: firstAttachment.url,
|
||||
width: firstAttachment.metaImageWidth,
|
||||
height: firstAttachment.metaImageHeight)
|
||||
|
||||
let size = ImageSizeService.shared.calculate(for: firstAttachment.url, andContainerWidth: UIScreen.main.bounds.size.width)
|
||||
self.imageWidth = size.width
|
||||
self.imageHeight = size.height
|
||||
} else {
|
||||
|
@ -73,7 +76,9 @@ struct ImageRow: View {
|
|||
}
|
||||
.onChange(of: selected, perform: { attachmentId in
|
||||
if let attachment = attachmentsData.first(where: { item in item.id == attachmentId }) {
|
||||
let size = ImageSizeService.shared.calculate(width: Double(attachment.metaImageWidth), height: Double(attachment.metaImageHeight))
|
||||
let size = ImageSizeService.shared.calculate(width: Double(attachment.metaImageWidth),
|
||||
height: Double(attachment.metaImageHeight),
|
||||
andContainerWidth: UIScreen.main.bounds.size.width)
|
||||
|
||||
if size.width != self.imageWidth || size.height != self.imageHeight {
|
||||
withAnimation(.linear(duration: 0.4)) {
|
||||
|
|
|
@ -9,38 +9,50 @@ import PixelfedKit
|
|||
import ClientKit
|
||||
import ServicesKit
|
||||
import WidgetsKit
|
||||
import EnvironmentKit
|
||||
|
||||
struct ImageRowAsync: View {
|
||||
private let statusViewModel: StatusModel
|
||||
private let firstAttachment: AttachmentModel?
|
||||
private let showAvatar: Bool
|
||||
private let imageScale: ImageScale
|
||||
|
||||
@Binding private var containerWidth: Double
|
||||
@Binding private var clipToRectangle: Bool
|
||||
|
||||
@State private var selected: String
|
||||
@State private var imageHeight: Double
|
||||
@State private var imageWidth: Double
|
||||
|
||||
init(statusViewModel: StatusModel, withAvatar showAvatar: Bool = true, imageScale: ImageScale = .orginalFullWidth) {
|
||||
init(statusViewModel: StatusModel,
|
||||
withAvatar showAvatar: Bool = true,
|
||||
containerWidth: Binding<Double>,
|
||||
clipToRectangle: Binding<Bool> = Binding.constant(false)) {
|
||||
self.showAvatar = showAvatar
|
||||
self.imageScale = imageScale
|
||||
self.statusViewModel = statusViewModel
|
||||
self.firstAttachment = statusViewModel.mediaAttachments.first
|
||||
self.selected = String.empty()
|
||||
|
||||
self._containerWidth = containerWidth
|
||||
self._clipToRectangle = clipToRectangle
|
||||
|
||||
// Calculate size of frame (first from cache, then from metadata).
|
||||
if let firstAttachment, let size = ImageSizeService.shared.get(for: firstAttachment.url) {
|
||||
self.imageWidth = size.width
|
||||
self.imageHeight = size.height
|
||||
let calculatedSize = ImageSizeService.shared.calculate(width: size.width, height: size.height, andContainerWidth: containerWidth.wrappedValue)
|
||||
|
||||
self.imageWidth = calculatedSize.width
|
||||
self.imageHeight = calculatedSize.height
|
||||
} else if let firstAttachment,
|
||||
let imgHeight = (firstAttachment.meta as? ImageMetadata)?.original?.height,
|
||||
let imgWidth = (firstAttachment.meta as? ImageMetadata)?.original?.width {
|
||||
|
||||
let size = ImageSizeService.shared.calculate(for: firstAttachment.url, width: imgWidth, height: imgHeight)
|
||||
self.imageWidth = size.width
|
||||
self.imageHeight = size.height
|
||||
ImageSizeService.shared.save(for: firstAttachment.url, width: imgWidth, height: imgHeight)
|
||||
let calculatedSize = ImageSizeService.shared.calculate(for: firstAttachment.url, andContainerWidth: containerWidth.wrappedValue)
|
||||
|
||||
self.imageWidth = calculatedSize.width
|
||||
self.imageHeight = calculatedSize.height
|
||||
} else {
|
||||
self.imageWidth = UIScreen.main.bounds.width
|
||||
self.imageHeight = UIScreen.main.bounds.width
|
||||
self.imageWidth = containerWidth.wrappedValue
|
||||
self.imageHeight = containerWidth.wrappedValue
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,18 +61,28 @@ struct ImageRowAsync: View {
|
|||
ImageRowItemAsync(statusViewModel: self.statusViewModel,
|
||||
attachment: firstAttachment,
|
||||
withAvatar: self.showAvatar,
|
||||
imageScale: self.imageScale) { (imageWidth, imageHeight) in
|
||||
containerWidth: $containerWidth,
|
||||
clipToRectangle: $clipToRectangle,
|
||||
showSpoilerText: Binding.constant(self.containerWidth > 300)) { (imageWidth, imageHeight) in
|
||||
|
||||
// When we download image and calculate real size we have to change view size.
|
||||
if imageWidth != self.imageWidth || imageHeight != self.imageHeight {
|
||||
let calculatedSize = ImageSizeService.shared.calculate(width: imageWidth, height: imageHeight, andContainerWidth: self.containerWidth)
|
||||
|
||||
if calculatedSize.width != self.imageWidth || calculatedSize.height != self.imageHeight {
|
||||
withAnimation(.linear(duration: 0.4)) {
|
||||
self.imageWidth = imageWidth
|
||||
self.imageHeight = imageHeight
|
||||
self.imageWidth = calculatedSize.width
|
||||
self.imageHeight = calculatedSize.height
|
||||
}
|
||||
}
|
||||
}
|
||||
.if(self.imageScale == .orginalFullWidth) {
|
||||
$0.frame(width: self.imageWidth, height: self.imageHeight)
|
||||
.frame(width: self.clipToRectangle ? self.containerWidth : self.imageWidth,
|
||||
height: self.clipToRectangle ? self.containerWidth : self.imageHeight)
|
||||
.onChange(of: self.containerWidth) { newContainerWidth in
|
||||
let calculatedSize = ImageSizeService.shared.calculate(width: self.imageWidth,
|
||||
height: self.imageHeight,
|
||||
andContainerWidth: newContainerWidth)
|
||||
self.imageWidth = calculatedSize.width
|
||||
self.imageHeight = calculatedSize.height
|
||||
}
|
||||
} else {
|
||||
TabView(selection: $selected) {
|
||||
|
@ -68,14 +90,18 @@ struct ImageRowAsync: View {
|
|||
ImageRowItemAsync(statusViewModel: self.statusViewModel,
|
||||
attachment: attachment,
|
||||
withAvatar: self.showAvatar,
|
||||
imageScale: self.imageScale) { (imageWidth, imageHeight) in
|
||||
containerWidth: $containerWidth,
|
||||
clipToRectangle: $clipToRectangle,
|
||||
showSpoilerText: Binding.constant(self.containerWidth > 300)) { (imageWidth, imageHeight) in
|
||||
|
||||
// When we download image and calculate real size we have to change view size (only when image is now visible).
|
||||
let calculatedSize = ImageSizeService.shared.calculate(width: imageWidth, height: imageHeight, andContainerWidth: self.containerWidth)
|
||||
|
||||
if attachment.id == self.selected {
|
||||
if imageWidth != self.imageWidth || imageHeight != self.imageHeight {
|
||||
if calculatedSize.width != self.imageWidth || calculatedSize.height != self.imageHeight {
|
||||
withAnimation(.linear(duration: 0.4)) {
|
||||
self.imageWidth = imageWidth
|
||||
self.imageHeight = imageHeight
|
||||
self.imageWidth = calculatedSize.width
|
||||
self.imageHeight = calculatedSize.height
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -83,24 +109,34 @@ struct ImageRowAsync: View {
|
|||
.tag(attachment.id)
|
||||
}
|
||||
}
|
||||
.onChange(of: self.containerWidth) { newContainerWidth in
|
||||
let calculatedSize = ImageSizeService.shared.calculate(width: self.imageWidth,
|
||||
height: self.imageHeight,
|
||||
andContainerWidth: newContainerWidth)
|
||||
self.imageWidth = calculatedSize.width
|
||||
self.imageHeight = calculatedSize.height
|
||||
}
|
||||
.onFirstAppear {
|
||||
self.selected = self.statusViewModel.mediaAttachments.first?.id ?? String.empty()
|
||||
}
|
||||
.onChange(of: selected, perform: { attachmentId in
|
||||
if let attachment = self.statusViewModel.mediaAttachments.first(where: { item in item.id == attachmentId }) {
|
||||
if let size = ImageSizeService.shared.get(for: attachment.url) {
|
||||
if size.width != self.imageWidth || size.height != self.imageHeight {
|
||||
let calculatedSize = ImageSizeService.shared.calculate(width: size.width,
|
||||
height: size.height,
|
||||
andContainerWidth: self.containerWidth)
|
||||
|
||||
if calculatedSize.width != self.imageWidth || calculatedSize.height != self.imageHeight {
|
||||
withAnimation(.linear(duration: 0.4)) {
|
||||
self.imageWidth = size.width
|
||||
self.imageHeight = size.height
|
||||
self.imageWidth = calculatedSize.width
|
||||
self.imageHeight = calculatedSize.height
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.if(self.imageScale == .orginalFullWidth) {
|
||||
$0.frame(width: self.imageWidth, height: self.imageHeight)
|
||||
}
|
||||
.frame(width: self.clipToRectangle ? self.containerWidth : self.imageWidth,
|
||||
height: self.clipToRectangle ? self.containerWidth : self.imageHeight)
|
||||
.tabViewStyle(.page(indexDisplayMode: .never))
|
||||
.overlay(CustomPageTabViewStyleView(pages: self.statusViewModel.mediaAttachments, currentId: $selected))
|
||||
}
|
||||
|
|
|
@ -210,14 +210,18 @@ struct ImageRowItem: View {
|
|||
}
|
||||
|
||||
private func setVariables(imageData: Data, downloadedImage: UIImage) {
|
||||
let size = ImageSizeService.shared.calculate(for: attachmentData.url,
|
||||
width: downloadedImage.size.width,
|
||||
height: downloadedImage.size.height)
|
||||
ImageSizeService.shared.save(for: attachmentData.url,
|
||||
width: downloadedImage.size.width,
|
||||
height: downloadedImage.size.height)
|
||||
|
||||
let size = ImageSizeService.shared.calculate(for: attachmentData.url, andContainerWidth: UIScreen.main.bounds.size.width)
|
||||
self.onImageDownloaded(size.width, size.height)
|
||||
self.uiImage = downloadedImage
|
||||
|
||||
HomeTimelineService.shared.update(attachment: attachmentData, withData: imageData, imageWidth: size.width, imageHeight: size.height)
|
||||
self.uiImage = downloadedImage
|
||||
HomeTimelineService.shared.update(attachment: attachmentData,
|
||||
withData: imageData,
|
||||
imageWidth: downloadedImage.size.width,
|
||||
imageHeight: downloadedImage.size.height)
|
||||
self.error = nil
|
||||
self.cancelled = false
|
||||
}
|
||||
|
|
|
@ -22,7 +22,10 @@ struct ImageRowItemAsync: View {
|
|||
private var attachment: AttachmentModel
|
||||
private let showAvatar: Bool
|
||||
private let imageFromCache: Bool
|
||||
private let imageScale: ImageScale
|
||||
|
||||
@Binding private var containerWidth: Double
|
||||
@Binding private var showSpoilerText: Bool
|
||||
@Binding private var clipToRectangle: Bool
|
||||
|
||||
@State private var showThumbImage = false
|
||||
@State private var opacity = 1.0
|
||||
|
@ -33,14 +36,19 @@ struct ImageRowItemAsync: View {
|
|||
init(statusViewModel: StatusModel,
|
||||
attachment: AttachmentModel,
|
||||
withAvatar showAvatar: Bool = true,
|
||||
imageScale: ImageScale = .orginalFullWidth,
|
||||
containerWidth: Binding<Double>,
|
||||
clipToRectangle: Binding<Bool> = Binding.constant(false),
|
||||
showSpoilerText: Binding<Bool> = Binding.constant(true),
|
||||
onImageDownloaded: @escaping (_: Double, _: Double) -> Void) {
|
||||
self.showAvatar = showAvatar
|
||||
self.imageScale = imageScale
|
||||
self.statusViewModel = statusViewModel
|
||||
self.attachment = attachment
|
||||
self.onImageDownloaded = onImageDownloaded
|
||||
|
||||
self._containerWidth = containerWidth
|
||||
self._showSpoilerText = showSpoilerText
|
||||
self._clipToRectangle = clipToRectangle
|
||||
|
||||
self.imageFromCache = ImagePipeline.shared.cache.containsCachedImage(for: ImageRequest(url: attachment.url))
|
||||
}
|
||||
|
||||
|
@ -49,7 +57,7 @@ struct ImageRowItemAsync: View {
|
|||
if let image = state.image {
|
||||
if self.statusViewModel.sensitive && !self.applicationState.showSensitive {
|
||||
ZStack {
|
||||
ContentWarning(spoilerText: self.imageScale == .orginalFullWidth ? self.statusViewModel.spoilerText : nil) {
|
||||
ContentWarning(spoilerText: self.showSpoilerText ? self.statusViewModel.spoilerText : nil) {
|
||||
self.imageContainerView(image: image)
|
||||
.imageContextMenu(statusModel: self.statusViewModel,
|
||||
attachmentModel: self.attachment,
|
||||
|
@ -155,9 +163,9 @@ struct ImageRowItemAsync: View {
|
|||
private func imageView(image: Image) -> some View {
|
||||
image
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.if(self.imageScale == .squareHalfWidth) {
|
||||
$0.frame(width: UIScreen.main.bounds.width / 2, height: UIScreen.main.bounds.width / 2).clipped()
|
||||
.aspectRatio(contentMode: self.clipToRectangle ? .fill : .fit)
|
||||
.if(self.clipToRectangle) {
|
||||
$0.frame(width: self.containerWidth, height: self.containerWidth).clipped()
|
||||
}
|
||||
.onTapGesture(count: 2) {
|
||||
Task {
|
||||
|
@ -194,10 +202,11 @@ struct ImageRowItemAsync: View {
|
|||
}
|
||||
|
||||
private func recalculateSizeOfDownloadedImage(uiImage: UIImage) {
|
||||
let size = ImageSizeService.shared.calculate(for: attachment.url,
|
||||
width: uiImage.size.width,
|
||||
height: uiImage.size.height)
|
||||
ImageSizeService.shared.save(for: attachment.url,
|
||||
width: uiImage.size.width,
|
||||
height: uiImage.size.height)
|
||||
|
||||
let size = ImageSizeService.shared.calculate(for: attachment.url, andContainerWidth: UIScreen.main.bounds.size.width)
|
||||
self.onImageDownloaded(size.width, size.height)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -224,7 +224,9 @@ struct ImageViewer: View {
|
|||
|
||||
private func calculateStartingOffset() -> CGSize {
|
||||
// Image size on the screen.
|
||||
let calculatedSize = ImageSizeService.shared.calculate(width: self.imageWidth, height: self.imageHeight)
|
||||
let calculatedSize = ImageSizeService.shared.calculate(width: self.imageWidth,
|
||||
height: self.imageHeight,
|
||||
andContainerWidth: UIScreen.main.bounds.size.width)
|
||||
let imageOnScreenHeight = calculatedSize.height
|
||||
|
||||
// Calculate full space for image.
|
||||
|
|
|
@ -47,12 +47,16 @@ struct ImagesCarousel: View {
|
|||
|
||||
// Calculate size of frame (first from cache, then from metadata).
|
||||
if let highestImage, let size = ImageSizeService.shared.get(for: highestImage.url) {
|
||||
self.imageWidth = size.width
|
||||
self.imageHeight = size.height
|
||||
let calculatedSize = ImageSizeService.shared.calculate(width: size.width, height: size.height)
|
||||
|
||||
self.imageWidth = calculatedSize.width
|
||||
self.imageHeight = calculatedSize.height
|
||||
|
||||
self.heightWasPrecalculated = true
|
||||
} else if let highestImage, imgHeight > 0 && imgWidth > 0 {
|
||||
let size = ImageSizeService.shared.calculate(for: highestImage.url, width: imgWidth, height: imgHeight)
|
||||
ImageSizeService.shared.save(for: highestImage.url, width: imgWidth, height: imgHeight)
|
||||
let size = ImageSizeService.shared.calculate(for: highestImage.url)
|
||||
|
||||
self.imageWidth = size.width
|
||||
self.imageHeight = size.height
|
||||
|
||||
|
@ -65,33 +69,39 @@ struct ImagesCarousel: View {
|
|||
}
|
||||
|
||||
var body: some View {
|
||||
TabView(selection: $selected) {
|
||||
ForEach(attachments, id: \.id) { attachment in
|
||||
ImageCarouselPicture(attachment: attachment) { (attachment, imageData) in
|
||||
withAnimation {
|
||||
self.recalculateImageHeight(attachment: attachment, imageData: imageData)
|
||||
}
|
||||
HStack(spacing: 0) {
|
||||
Spacer(minLength: 0)
|
||||
TabView(selection: $selected) {
|
||||
ForEach(attachments, id: \.id) { attachment in
|
||||
ImageCarouselPicture(attachment: attachment) { (attachment, imageData) in
|
||||
withAnimation {
|
||||
self.recalculateImageHeight(attachment: attachment, imageData: imageData)
|
||||
}
|
||||
|
||||
self.asyncAfter(0.4) {
|
||||
attachment.set(data: imageData)
|
||||
}
|
||||
self.asyncAfter(0.4) {
|
||||
attachment.set(data: imageData)
|
||||
}
|
||||
|
||||
}
|
||||
.tag(attachment.id)
|
||||
}
|
||||
.tag(attachment.id)
|
||||
}
|
||||
.padding(0)
|
||||
.frame(height: self.imageHeight)
|
||||
.tabViewStyle(PageTabViewStyle())
|
||||
.onChange(of: selected, perform: { index in
|
||||
if let attachment = attachments.first(where: { item in item.id == index }) {
|
||||
self.selectedAttachment = attachment
|
||||
self.exifCamera = attachment.exifCamera
|
||||
self.exifExposure = attachment.exifExposure
|
||||
self.exifCreatedDate = attachment.exifCreatedDate
|
||||
self.exifLens = attachment.exifLens
|
||||
self.description = attachment.description
|
||||
}
|
||||
})
|
||||
Spacer(minLength: 0)
|
||||
}
|
||||
.frame(height: self.imageHeight)
|
||||
.tabViewStyle(PageTabViewStyle())
|
||||
.onChange(of: selected, perform: { index in
|
||||
if let attachment = attachments.first(where: { item in item.id == index }) {
|
||||
self.selectedAttachment = attachment
|
||||
self.exifCamera = attachment.exifCamera
|
||||
self.exifExposure = attachment.exifExposure
|
||||
self.exifCreatedDate = attachment.exifCreatedDate
|
||||
self.exifLens = attachment.exifLens
|
||||
self.description = attachment.description
|
||||
}
|
||||
})
|
||||
.padding(0)
|
||||
.onAppear {
|
||||
self.selected = self.attachments.first?.id ?? String.empty()
|
||||
}
|
||||
|
@ -102,26 +112,41 @@ struct ImagesCarousel: View {
|
|||
return
|
||||
}
|
||||
|
||||
var imageHeight = 0.0
|
||||
var imageWidth = 0.0
|
||||
var maxImageHeight = 0.0
|
||||
var maxImageWidth = 0.0
|
||||
|
||||
for item in attachments {
|
||||
// Get attachment sizes from cache.
|
||||
if let attachmentSize = ImageSizeService.shared.get(for: item.url) {
|
||||
if attachmentSize.height > maxImageHeight {
|
||||
maxImageHeight = attachmentSize.height
|
||||
maxImageWidth = attachmentSize.width
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// When we don't have in cache read from data and add to cache.
|
||||
if let data = item.data, let image = UIImage(data: data) {
|
||||
if image.size.height > imageHeight {
|
||||
imageHeight = image.size.height
|
||||
imageWidth = image.size.width
|
||||
ImageSizeService.shared.save(for: item.url, width: image.size.width, height: image.size.height)
|
||||
|
||||
if image.size.height > maxImageHeight {
|
||||
maxImageHeight = image.size.height
|
||||
maxImageWidth = image.size.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let image = UIImage(data: imageData) {
|
||||
if image.size.height > imageHeight {
|
||||
imageHeight = image.size.height
|
||||
imageWidth = image.size.width
|
||||
ImageSizeService.shared.save(for: attachment.url, width: image.size.width, height: image.size.height)
|
||||
|
||||
if image.size.height > maxImageHeight {
|
||||
maxImageHeight = image.size.height
|
||||
maxImageWidth = image.size.width
|
||||
}
|
||||
}
|
||||
|
||||
let size = ImageSizeService.shared.calculate(for: attachment.url, width: imageWidth, height: imageHeight)
|
||||
let size = ImageSizeService.shared.calculate(width: maxImageWidth, height: maxImageHeight)
|
||||
self.imageWidth = size.width
|
||||
self.imageHeight = size.height
|
||||
}
|
||||
|
|
|
@ -0,0 +1,137 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the Apache License 2.0.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import WidgetsKit
|
||||
import Semaphore
|
||||
|
||||
struct WaterfallGrid<Data, ID, Content>: View where Data: RandomAccessCollection, Data: Equatable, Content: View,
|
||||
ID: Hashable, Data.Element: Equatable, Data.Element: Identifiable, Data.Element: Hashable, Data.Element: Sizable {
|
||||
@Binding private var columns: Int
|
||||
@Binding private var hideLoadMore: Bool
|
||||
@Binding private var data: Data
|
||||
|
||||
private let content: (Data.Element) -> Content
|
||||
|
||||
@State private var columnsData: [ColumnData<Data.Element>] = []
|
||||
@State private var processedItems: [Data.Element.ID] = []
|
||||
|
||||
private let onLoadMore: () async -> Void
|
||||
private let semaphore = AsyncSemaphore(value: 1)
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .top, spacing: 20) {
|
||||
ForEach(self.columnsData, id: \.id) { columnData in
|
||||
LazyVStack(spacing: 8) {
|
||||
ForEach(columnData.data, id: \.id) { item in
|
||||
self.content(item)
|
||||
}
|
||||
|
||||
if self.hideLoadMore == false {
|
||||
// We can show multiple loading indicators. Each indicator can run loading feature in pararell.
|
||||
// Thus we have to be sure that loading will exeute one by one.
|
||||
LoadingIndicator()
|
||||
.task {
|
||||
Task { @MainActor in
|
||||
await self.loadMoreData()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onFirstAppear {
|
||||
self.recalculateArrays()
|
||||
}
|
||||
.onChange(of: self.data) { _ in
|
||||
self.appendToArrays()
|
||||
}
|
||||
.onChange(of: self.columns) { _ in
|
||||
self.recalculateArrays()
|
||||
}
|
||||
}
|
||||
|
||||
private func loadMoreData() async {
|
||||
await semaphore.wait()
|
||||
defer { semaphore.signal() }
|
||||
|
||||
await self.onLoadMore()
|
||||
}
|
||||
|
||||
private func recalculateArrays() {
|
||||
Task { @MainActor in
|
||||
await semaphore.wait()
|
||||
defer { semaphore.signal() }
|
||||
|
||||
self.columnsData = []
|
||||
self.processedItems = []
|
||||
|
||||
for _ in 0 ..< self.columns {
|
||||
self.columnsData.append(ColumnData())
|
||||
}
|
||||
|
||||
for item in self.data {
|
||||
let index = self.minimumHeightIndex()
|
||||
|
||||
self.columnsData[index].data.append(item)
|
||||
self.columnsData[index].height = self.columnsData[index].height + self.calculateHeight(item: item)
|
||||
self.processedItems.append(item.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func appendToArrays() {
|
||||
Task { @MainActor in
|
||||
await semaphore.wait()
|
||||
defer { semaphore.signal() }
|
||||
|
||||
for item in self.data where self.processedItems.contains(where: { $0 == item.id }) == false {
|
||||
let index = self.minimumHeightIndex()
|
||||
|
||||
self.columnsData[index].data.append(item)
|
||||
self.columnsData[index].height = self.columnsData[index].height + self.calculateHeight(item: item)
|
||||
self.processedItems.append(item.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func calculateHeight(item: Sizable) -> Double {
|
||||
return item.height / item.width
|
||||
}
|
||||
|
||||
private func minimumHeight() -> Double {
|
||||
return self.columnsData.map({ $0.height }).min() ?? .zero
|
||||
}
|
||||
|
||||
private func minimumHeightIndex() -> Int {
|
||||
let minimumHeight = self.minimumHeight()
|
||||
return self.columnsData.lastIndex(where: { $0.height == minimumHeight }) ?? 0
|
||||
}
|
||||
}
|
||||
|
||||
extension WaterfallGrid {
|
||||
init(_ data: Binding<Data>, id: KeyPath<Data.Element, ID>, columns: Binding<Int>,
|
||||
hideLoadMore: Binding<Bool>, content: @escaping (Data.Element) -> Content, onLoadMore: @escaping () async -> Void) {
|
||||
self.content = content
|
||||
self.onLoadMore = onLoadMore
|
||||
|
||||
self._data = data
|
||||
self._columns = columns
|
||||
self._hideLoadMore = hideLoadMore
|
||||
}
|
||||
}
|
||||
|
||||
extension WaterfallGrid where ID == Data.Element.ID, Data.Element: Identifiable {
|
||||
init(_ data: Binding<Data>, columns: Binding<Int>,
|
||||
hideLoadMore: Binding<Bool>, content: @escaping (Data.Element) -> Content, onLoadMore: @escaping () async -> Void) {
|
||||
self.content = content
|
||||
self.onLoadMore = onLoadMore
|
||||
|
||||
self._data = data
|
||||
self._columns = columns
|
||||
self._hideLoadMore = hideLoadMore
|
||||
}
|
||||
}
|
|
@ -1,6 +1,33 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.969",
|
||||
"green" : "0.514",
|
||||
"red" : "0.204"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.973",
|
||||
"green" : "0.655",
|
||||
"red" : "0.290"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "icon.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 45 KiB |
|
@ -1,6 +1,15 @@
|
|||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "display-p3",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.267",
|
||||
"green" : "0.267",
|
||||
"red" : "0.267"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the Apache License 2.0.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
extension View {
|
||||
// func widgetBackground(backgroundView: some View) -> some View {
|
||||
// if #available(iOSApplicationExtension 17.0, *) {
|
||||
// return containerBackground(for: .widget) {
|
||||
// backgroundView
|
||||
// }
|
||||
// } else {
|
||||
// return background(backgroundView)
|
||||
// }
|
||||
// }
|
||||
|
||||
func widgetBackground<V>(@ViewBuilder content: @escaping () -> V) -> some View where V: View {
|
||||
if #available(iOSApplicationExtension 17.0, *) {
|
||||
return containerBackground(for: .widget) {
|
||||
content()
|
||||
}
|
||||
} else {
|
||||
return background(content())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,5 +17,6 @@ struct PhotoWidget: Widget {
|
|||
.configurationDisplayName("Vernissage")
|
||||
.description("widget.title.photoDescription")
|
||||
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
|
||||
.contentMarginsDisabled()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ struct PhotoLargeWidgetView: View {
|
|||
.padding(.leading, 8)
|
||||
.padding(.bottom, 8)
|
||||
}
|
||||
.background {
|
||||
.widgetBackground {
|
||||
uiImage
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
|
|
|
@ -38,7 +38,7 @@ struct PhotoMediumWidgetView: View {
|
|||
.padding(.leading, 8)
|
||||
.padding(.bottom, 8)
|
||||
}
|
||||
.background {
|
||||
.widgetBackground {
|
||||
uiImage
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
|
|
|
@ -33,7 +33,7 @@ struct PhotoSmallWidgetView: View {
|
|||
.padding(.leading, 8)
|
||||
.padding(.bottom, 8)
|
||||
}
|
||||
.background {
|
||||
.widgetBackground {
|
||||
uiImage
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
|
|
|
@ -17,5 +17,6 @@ struct QRCodeWidget: Widget {
|
|||
.configurationDisplayName("Vernissage")
|
||||
.description("widget.title.qrCodeDescription")
|
||||
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
|
||||
.contentMarginsDisabled()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,6 +82,8 @@ struct QRCodeLargeWidgetView: View {
|
|||
.offset(y: -8)
|
||||
}
|
||||
}
|
||||
.widgetBackground {
|
||||
}
|
||||
.padding([.leading, .trailing, .top], 24)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,6 +92,8 @@ struct QRCodeMediumWidgetView: View {
|
|||
.padding(.leading, 3)
|
||||
.offset(y: -4)
|
||||
}
|
||||
.widgetBackground {
|
||||
}
|
||||
.padding([.leading, .trailing, .top], 12)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,6 +61,8 @@ struct QRCodeSmallWidgetView: View {
|
|||
.frame(height: 24)
|
||||
}
|
||||
}
|
||||
.widgetBackground {
|
||||
}
|
||||
.padding(8)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the Apache License 2.0.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
public extension UIDevice {
|
||||
static var isIPad: Bool {
|
||||
UIDevice.current.userInterfaceIdiom == .pad
|
||||
}
|
||||
|
||||
static var isIPhone: Bool {
|
||||
UIDevice.current.userInterfaceIdiom == .phone
|
||||
}
|
||||
}
|
|
@ -14,3 +14,11 @@ public extension View {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension ViewModifier {
|
||||
func asyncAfter(_ time: Double, operation: @escaping () -> Void) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + time) {
|
||||
operation()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the Apache License 2.0.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct DeviceRotationViewModifier: ViewModifier {
|
||||
let action: (UIDeviceOrientation) -> Void
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.onAppear()
|
||||
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
|
||||
action(UIDevice.current.orientation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension View {
|
||||
func onRotate(perform action: @escaping (UIDeviceOrientation) -> Void) -> some View {
|
||||
self.modifier(DeviceRotationViewModifier(action: action))
|
||||
}
|
||||
}
|
||||
|
||||
public struct GalleryProperties {
|
||||
public let imageColumns: Int
|
||||
public let containerWidth: Double
|
||||
public let containerHeight: Double
|
||||
}
|
||||
|
||||
struct DeviceImageGallery: ViewModifier {
|
||||
@Environment(\.horizontalSizeClass) var horizontalSizeClass: UserInterfaceSizeClass?
|
||||
|
||||
let action: (GalleryProperties) -> Void
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
GeometryReader { geometry in
|
||||
content
|
||||
.onRotate { _ in
|
||||
asyncAfter(0.1) {
|
||||
let galleryProperties = self.getGalleryProperties(geometry: geometry, horizontalSize: self.horizontalSizeClass ?? .compact)
|
||||
self.action(galleryProperties)
|
||||
}
|
||||
}
|
||||
.onChange(of: self.horizontalSizeClass) { horizontalSize in
|
||||
asyncAfter(0.1) {
|
||||
let galleryProperties = self.getGalleryProperties(geometry: geometry, horizontalSize: horizontalSize ?? .compact)
|
||||
self.action(galleryProperties)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
asyncAfter(0.1) {
|
||||
let galleryProperties = self.getGalleryProperties(geometry: geometry, horizontalSize: self.horizontalSizeClass ?? .compact)
|
||||
self.action(galleryProperties)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func getGalleryProperties(geometry: GeometryProxy, horizontalSize: UserInterfaceSizeClass) -> GalleryProperties {
|
||||
if horizontalSize == .compact {
|
||||
// View like on iPhone.
|
||||
return GalleryProperties(imageColumns: 1,
|
||||
containerWidth: geometry.size.width,
|
||||
containerHeight: geometry.size.height)
|
||||
} else {
|
||||
// View like on iPad.
|
||||
let imageColumns = geometry.size.width > geometry.size.height ? 3 : 2
|
||||
|
||||
return GalleryProperties(imageColumns: imageColumns,
|
||||
containerWidth: geometry.size.width / Double(imageColumns),
|
||||
containerHeight: geometry.size.height / Double(imageColumns))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension View {
|
||||
func gallery(perform action: @escaping (GalleryProperties) -> Void) -> some View {
|
||||
self.modifier(DeviceImageGallery(action: action))
|
||||
}
|
||||
}
|
|
@ -400,7 +400,7 @@ public struct BaseComposeView: View {
|
|||
Image(systemName: "mappin.and.ellipse")
|
||||
Text("\(name), \(country)")
|
||||
}
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
.padding(.trailing, 8)
|
||||
}
|
||||
}
|
||||
|
@ -425,7 +425,7 @@ public struct BaseComposeView: View {
|
|||
Text(account.displayNameWithoutEmojis)
|
||||
.foregroundColor(.mainTextColor)
|
||||
Text("@\(account.acct)")
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
}
|
||||
.padding(.leading, 8)
|
||||
}
|
||||
|
@ -540,7 +540,7 @@ public struct BaseComposeView: View {
|
|||
Spacer()
|
||||
|
||||
Text("\(self.applicationState.statusMaxCharacters - textModel.text.string.utf16.count)")
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
.font(.system(size: self.keyboardFontTextSize))
|
||||
}
|
||||
.padding(8)
|
||||
|
|
|
@ -79,7 +79,7 @@ public struct PlaceSelectorView: View {
|
|||
.foregroundColor(.mainTextColor)
|
||||
Text(place.country ?? String.empty())
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
|
|
@ -24,7 +24,7 @@ public struct NoDataView: View {
|
|||
Text(self.text, comment: "No data message")
|
||||
.font(.title3)
|
||||
}
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ public struct UsernameRow: View {
|
|||
Text(accountDisplayName ?? accountUsername)
|
||||
.foregroundColor(.mainTextColor)
|
||||
Text("@\(accountUsername)")
|
||||
.foregroundColor(.lightGrayColor)
|
||||
.foregroundColor(.customGrayColor)
|
||||
.font(.footnote)
|
||||
}
|
||||
.padding(.leading, 8)
|
||||
|
|
Loading…
Reference in New Issue