From 446fbd9b9ed04ee09c53bd8c25391e9bc70ade9c Mon Sep 17 00:00:00 2001 From: Marcin Czachurski Date: Thu, 11 May 2023 20:00:39 +0200 Subject: [PATCH] Initial PoC of iPad grid --- Vernissage.xcodeproj/project.pbxproj | 29 ++++++++--- Vernissage/Views/StatusesView.swift | 50 +++++++++++-------- .../Subviews/UserProfileStatusesView.swift | 16 +++++- Vernissage/Widgets/ImageRowAsync.swift | 8 +-- Vernissage/Widgets/ImageRowItemAsync.swift | 9 ++-- .../WidgetsKit/Views/BaseComposeView.swift | 2 +- 6 files changed, 78 insertions(+), 36 deletions(-) diff --git a/Vernissage.xcodeproj/project.pbxproj b/Vernissage.xcodeproj/project.pbxproj index d84c597..d014013 100644 --- a/Vernissage.xcodeproj/project.pbxproj +++ b/Vernissage.xcodeproj/project.pbxproj @@ -27,6 +27,7 @@ F8210DEA2966E4F9001D9973 /* AnimatePlaceholderModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8210DE92966E4F9001D9973 /* AnimatePlaceholderModifier.swift */; }; F825F0C929F7A562008BD204 /* UserProfilePrivateAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F825F0C829F7A562008BD204 /* UserProfilePrivateAccountView.swift */; }; F825F0CB29F7CFC4008BD204 /* FollowRequestsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F825F0CA29F7CFC4008BD204 /* FollowRequestsView.swift */; }; + F830C3CD2A07A4020005FEF8 /* WaterfallGrid in Frameworks */ = {isa = PBXBuildFile; productRef = F830C3CC2A07A4020005FEF8 /* WaterfallGrid */; }; F835082329BEF9C400DE3247 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F835082629BEF9C400DE3247 /* Localizable.strings */; }; F835082429BEF9C400DE3247 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = F835082629BEF9C400DE3247 /* Localizable.strings */; }; F83CBEFB298298A1002972C8 /* ImageCarouselPicture.swift in Sources */ = {isa = PBXBuildFile; fileRef = F83CBEFA298298A1002972C8 /* ImageCarouselPicture.swift */; }; @@ -449,6 +450,7 @@ F8210DD92966BB7E001D9973 /* NukeUI in Frameworks */, F89B5CC029D019B600549F2F /* HTMLString in Frameworks */, F88BC52A29E046D700CE6141 /* WidgetsKit in Frameworks */, + F830C3CD2A07A4020005FEF8 /* WaterfallGrid in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -988,6 +990,7 @@ F88BC52629E0431D00CE6141 /* ServicesKit */, F88BC52929E046D700CE6141 /* WidgetsKit */, F88BC52C29E04BB600CE6141 /* EnvironmentKit */, + F830C3CC2A07A4020005FEF8 /* WaterfallGrid */, ); productName = Vernissage; productReference = F88C2468295C37B80006098B /* Vernissage.app */; @@ -1031,6 +1034,7 @@ F88E4D4B297EA4290057491A /* XCRemoteSwiftPackageReference "EmojiText" */, F89B5CBE29D019B600549F2F /* XCRemoteSwiftPackageReference "HTMLString" */, F84625F929FE393B002D3AF4 /* XCRemoteSwiftPackageReference "QRCode" */, + F830C3CB2A07A4020005FEF8 /* XCRemoteSwiftPackageReference "WaterfallGrid" */, ); productRefGroup = F88C2469295C37B80006098B /* Products */; projectDirPath = ""; @@ -1338,7 +1342,7 @@ SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 1; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; @@ -1366,7 +1370,7 @@ SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 1; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; @@ -1394,7 +1398,7 @@ SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 1; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; @@ -1421,7 +1425,7 @@ SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = 1; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; @@ -1580,7 +1584,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; }; @@ -1621,7 +1625,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; }; @@ -1675,6 +1679,14 @@ minimumVersion = 12.0.0; }; }; + F830C3CB2A07A4020005FEF8 /* XCRemoteSwiftPackageReference "WaterfallGrid" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/paololeonardi/WaterfallGrid.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.0.0; + }; + }; F84625F929FE393B002D3AF4 /* XCRemoteSwiftPackageReference "QRCode" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/dmrschmidt/QRCode"; @@ -1717,6 +1729,11 @@ package = F8210DD32966BB7E001D9973 /* XCRemoteSwiftPackageReference "Nuke" */; productName = NukeUI; }; + F830C3CC2A07A4020005FEF8 /* WaterfallGrid */ = { + isa = XCSwiftPackageProductDependency; + package = F830C3CB2A07A4020005FEF8 /* XCRemoteSwiftPackageReference "WaterfallGrid" */; + productName = WaterfallGrid; + }; F84625FA29FE393B002D3AF4 /* QRCode */ = { isa = XCSwiftPackageProductDependency; package = F84625F929FE393B002D3AF4 /* XCRemoteSwiftPackageReference "QRCode" */; diff --git a/Vernissage/Views/StatusesView.swift b/Vernissage/Views/StatusesView.swift index 12093c7..2768020 100644 --- a/Vernissage/Views/StatusesView.swift +++ b/Vernissage/Views/StatusesView.swift @@ -11,6 +11,7 @@ import ClientKit import ServicesKit import EnvironmentKit import WidgetsKit +import WaterfallGrid struct StatusesView: View { public enum ListType: Hashable { @@ -50,7 +51,7 @@ struct StatusesView: View { @State private var state: ViewState = .loading @State private var lastStatusId: String? - private let defaultLimit = 20 + private let defaultLimit = 40 private let imagePrefetcher = ImagePrefetcher(destination: .diskCache) var body: some View { @@ -88,26 +89,35 @@ struct StatusesView: View { @ViewBuilder private func list() -> some View { ScrollView { - LazyVStack(alignment: .center) { - ForEach(self.statusViewModels, id: \.id) { item in - ImageRowAsync(statusViewModel: item) - } - - 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() - } - } + WaterfallGrid(self.statusViewModels, id: \.id) { item in + ImageRowAsync(statusViewModel: item, + withAvatar: true, + imageScale: self.applicationState.showGridOnUserProfile ? .squareHalfWidth : .orginalFullWidth) + .padding(.top, -2) } + .gridStyle(columns: 3, spacing: 4) + // .padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + +// LazyVStack(alignment: .center) { +// ForEach(self.statusViewModels, id: \.id) { item in +// ImageRowAsync(statusViewModel: item) +// } +// +// 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() +// } +// } +// } } .refreshable { do { diff --git a/Vernissage/Views/UserProfileView/Subviews/UserProfileStatusesView.swift b/Vernissage/Views/UserProfileView/Subviews/UserProfileStatusesView.swift index 47e654d..4f6e8e5 100644 --- a/Vernissage/Views/UserProfileView/Subviews/UserProfileStatusesView.swift +++ b/Vernissage/Views/UserProfileView/Subviews/UserProfileStatusesView.swift @@ -11,6 +11,7 @@ import ClientKit import ServicesKit import EnvironmentKit import WidgetsKit +import WaterfallGrid struct UserProfileStatusesView: View { @EnvironmentObject private var applicationState: ApplicationState @@ -22,7 +23,7 @@ struct UserProfileStatusesView: View { @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)] @@ -54,7 +55,19 @@ struct UserProfileStatusesView: View { .padding(.bottom, 8) } } + + WaterfallGrid(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) +// } + } + .gridStyle(columns: 3, spacing: 2) + .padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + /* LazyVGrid(columns: self.applicationState.showGridOnUserProfile ? dubleGrid : singleGrids, spacing: 5) { ForEach(self.statusViewModels, id: \.id) { item in ImageRowAsync(statusViewModel: item, @@ -80,6 +93,7 @@ struct UserProfileStatusesView: View { } } } + */ } else { LoadingIndicator() .onFirstAppear { diff --git a/Vernissage/Widgets/ImageRowAsync.swift b/Vernissage/Widgets/ImageRowAsync.swift index d84afc6..5b8acc8 100644 --- a/Vernissage/Widgets/ImageRowAsync.swift +++ b/Vernissage/Widgets/ImageRowAsync.swift @@ -59,8 +59,8 @@ struct ImageRowAsync: View { } } } - .if(self.imageScale == .orginalFullWidth) { - $0.frame(width: self.imageWidth, height: self.imageHeight) + .if(self.imageScale == .squareHalfWidth) { + $0.frame(width: self.imageWidth / 3, height: self.imageHeight / 3) } } else { TabView(selection: $selected) { @@ -98,8 +98,8 @@ struct ImageRowAsync: View { } } }) - .if(self.imageScale == .orginalFullWidth) { - $0.frame(width: self.imageWidth, height: self.imageHeight) + .if(self.imageScale == .squareHalfWidth) { + $0.frame(width: self.imageWidth / 3, height: self.imageHeight / 3) } .tabViewStyle(.page(indexDisplayMode: .never)) .overlay(CustomPageTabViewStyleView(pages: self.statusViewModel.mediaAttachments, currentId: $selected)) diff --git a/Vernissage/Widgets/ImageRowItemAsync.swift b/Vernissage/Widgets/ImageRowItemAsync.swift index f660505..1dc6011 100644 --- a/Vernissage/Widgets/ImageRowItemAsync.swift +++ b/Vernissage/Widgets/ImageRowItemAsync.swift @@ -155,10 +155,11 @@ 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: .fill) + .aspectRatio(contentMode: .fit) +// .if(self.imageScale == .squareHalfWidth) { +// $0.frame(width: UIScreen.main.bounds.width / 4, height: UIScreen.main.bounds.width / 4).clipped() +// } .onTapGesture(count: 2) { Task { // Update favourite in Pixelfed server. diff --git a/WidgetsKit/Sources/WidgetsKit/Views/BaseComposeView.swift b/WidgetsKit/Sources/WidgetsKit/Views/BaseComposeView.swift index 5830312..4ef011d 100644 --- a/WidgetsKit/Sources/WidgetsKit/Views/BaseComposeView.swift +++ b/WidgetsKit/Sources/WidgetsKit/Views/BaseComposeView.swift @@ -159,7 +159,7 @@ public struct BaseComposeView: View { } .photosPicker(isPresented: $photosPickerVisible, selection: $selectedItems, - maxSelectionCount: 4, + maxSelectionCount: self.applicationState.statusMaxMediaAttachments, matching: .images) .fileImporter(isPresented: $isFileImporterPresented, allowedContentTypes: [.image],