damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

commit 2e34230119226e7b44882224372ee762f6aeed8f
parent cad89525b725c34f46f2096c44cb8c67fe8bab11
Author: William Casarin <jb55@jb55.com>
Date:   Thu, 23 Mar 2023 08:54:25 -0600

Clean up image views

Diffstat:
Mdamus.xcodeproj/project.pbxproj | 28++++++++++++++++++++++++----
Mdamus/Components/ImageCarousel.swift | 151+------------------------------------------------------------------------------
Adamus/Views/Images/ImageContainerView.swift | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Adamus/Views/Images/ImageContextMenuModifier.swift | 43+++++++++++++++++++++++++++++++++++++++++++
Adamus/Views/Images/ImageView.swift | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rdamus/Views/ProfileName.swift -> damus/Views/Profile/ProfileName.swift | 0
Rdamus/Views/ProfilePicView.swift -> damus/Views/Profile/ProfilePicView.swift | 0
Rdamus/Views/ProfilePictureSelector.swift -> damus/Views/Profile/ProfilePictureSelector.swift | 0
Rdamus/Views/ProfileView.swift -> damus/Views/Profile/ProfileView.swift | 0
Mdamus/Views/ProfileZoomView.swift | 4++--
10 files changed, 223 insertions(+), 156 deletions(-)

diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj @@ -222,6 +222,9 @@ 4CF0ABEE29844B5500D66079 /* AnyEncodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABED29844B5500D66079 /* AnyEncodable.swift */; }; 4CF0ABF029857E9200D66079 /* Bech32Object.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABEF29857E9200D66079 /* Bech32Object.swift */; }; 4CF0ABF62985CD5500D66079 /* UserSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CF0ABF52985CD5500D66079 /* UserSearch.swift */; }; + 4CFF8F6329CC9AD7008DB934 /* ImageContextMenuModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6229CC9AD7008DB934 /* ImageContextMenuModifier.swift */; }; + 4CFF8F6729CC9E3A008DB934 /* ImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6629CC9E3A008DB934 /* ImageView.swift */; }; + 4CFF8F6929CC9ED1008DB934 /* ImageContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CFF8F6829CC9ED1008DB934 /* ImageContainerView.swift */; }; 4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FE60CDC295E1C5E00105A1F /* Wallet.swift */; }; 50A50A8D29A09E1C00C01BE7 /* RequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */; }; 5C513FBA297F72980072348F /* CustomPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FB9297F72980072348F /* CustomPicker.swift */; }; @@ -595,6 +598,9 @@ 4CF0ABED29844B5500D66079 /* AnyEncodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyEncodable.swift; sourceTree = "<group>"; }; 4CF0ABEF29857E9200D66079 /* Bech32Object.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32Object.swift; sourceTree = "<group>"; }; 4CF0ABF52985CD5500D66079 /* UserSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSearch.swift; sourceTree = "<group>"; }; + 4CFF8F6229CC9AD7008DB934 /* ImageContextMenuModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageContextMenuModifier.swift; sourceTree = "<group>"; }; + 4CFF8F6629CC9E3A008DB934 /* ImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageView.swift; sourceTree = "<group>"; }; + 4CFF8F6829CC9ED1008DB934 /* ImageContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageContainerView.swift; sourceTree = "<group>"; }; 4FE60CDC295E1C5E00105A1F /* Wallet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Wallet.swift; sourceTree = "<group>"; }; 50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestTests.swift; sourceTree = "<group>"; }; 5C513FB9297F72980072348F /* CustomPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPicker.swift; sourceTree = "<group>"; }; @@ -797,6 +803,7 @@ 4C75EFA227FA576C0006080F /* Views */ = { isa = PBXGroup; children = ( + 4CFF8F6129CC9A80008DB934 /* Images */, 4CCEB7AC29B53D180078AA28 /* Search */, 4C30AC7029A5676F00E2BD5A /* Notifications */, 4CE0E2B029A3DF4700DB4CA2 /* Timeline */, @@ -834,10 +841,6 @@ 9CA876E129A00CE90003B9A3 /* AttachMediaUtility.swift */, 9C83F89229A937B900136C08 /* TextViewWrapper.swift */, 4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */, - 4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */, - 4C285C892838B985008A31F1 /* ProfilePictureSelector.swift */, - 4CEE2AF2280B25C500AB5EEF /* ProfilePicView.swift */, - 4C8682862814DE470026224F /* ProfileView.swift */, 4C3AC7A42836987600E1F516 /* MainTabView.swift */, 4C363A8B28236B92006E126D /* PubkeyView.swift */, 4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */, @@ -959,6 +962,10 @@ 4CB9D4A52992D01900A9A7E4 /* Profile */ = { isa = PBXGroup; children = ( + 4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */, + 4C285C892838B985008A31F1 /* ProfilePictureSelector.swift */, + 4CEE2AF2280B25C500AB5EEF /* ProfilePicView.swift */, + 4C8682862814DE470026224F /* ProfileView.swift */, 4CB9D4A62992D02B00A9A7E4 /* ProfileNameView.swift */, 4CB9D4A82992D2F400A9A7E4 /* FollowsYou.swift */, 4C9F18E329ABDE6D008C55EC /* MaybeAnonPfpView.swift */, @@ -1177,6 +1184,16 @@ path = Posting; sourceTree = "<group>"; }; + 4CFF8F6129CC9A80008DB934 /* Images */ = { + isa = PBXGroup; + children = ( + 4CFF8F6229CC9AD7008DB934 /* ImageContextMenuModifier.swift */, + 4CFF8F6629CC9E3A008DB934 /* ImageView.swift */, + 4CFF8F6829CC9ED1008DB934 /* ImageContainerView.swift */, + ); + path = Images; + sourceTree = "<group>"; + }; 7C0F392D29B57C8F0039859C /* Extensions */ = { isa = PBXGroup; children = ( @@ -1434,6 +1451,7 @@ 4CE4F9E328528C5200C00DD9 /* AddRelayView.swift in Sources */, 4C363A9A28283854006E126D /* Reply.swift in Sources */, BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */, + 4CFF8F6729CC9E3A008DB934 /* ImageView.swift in Sources */, 4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */, 4CB8838B296F6E1E00DC99E7 /* NIP05Badge.swift in Sources */, 4C3EA66828FF5F9900C48A62 /* hex.c in Sources */, @@ -1488,6 +1506,7 @@ 4C363A94282704FA006E126D /* Post.swift in Sources */, 4C216F32286E388800040376 /* DMChatView.swift in Sources */, 4CAAD8AD298851D000060CEA /* AccountDeletion.swift in Sources */, + 4CFF8F6329CC9AD7008DB934 /* ImageContextMenuModifier.swift in Sources */, 4C54AA0A29A55429003E4487 /* EventGroup.swift in Sources */, 4C3EA67928FF7ABF00C48A62 /* list.c in Sources */, 4C64987E286D082C00EAE2B3 /* DirectMessagesModel.swift in Sources */, @@ -1547,6 +1566,7 @@ 4C3EA63D28FF52D600C48A62 /* bolt11.c in Sources */, 4C9BB83129C0ED4F00FC4E37 /* DisplayName.swift in Sources */, 7CFF6317299FEFE5005D382A /* SelectableText.swift in Sources */, + 4CFF8F6929CC9ED1008DB934 /* ImageContainerView.swift in Sources */, 4C54AA0729A540BA003E4487 /* NotificationsModel.swift in Sources */, 4CB55EF3295E5D59007FD187 /* RecommendedRelayView.swift in Sources */, 4CF0ABEC29844B4700D66079 /* AnyDecodable.swift in Sources */, diff --git a/damus/Components/ImageCarousel.swift b/damus/Components/ImageCarousel.swift @@ -31,158 +31,9 @@ struct ShareSheet: UIViewControllerRepresentable { } } -struct ImageContextMenuModifier: ViewModifier { - let url: URL? - let image: UIImage? - @Binding var showShareSheet: Bool - - func body(content: Content) -> some View { - return content.contextMenu { - Button { - UIPasteboard.general.url = url - } label: { - Label(NSLocalizedString("Copy Image URL", comment: "Context menu option to copy the URL of an image into clipboard."), systemImage: "doc.on.doc") - } - if let someImage = image { - Button { - UIPasteboard.general.image = someImage - } label: { - Label(NSLocalizedString("Copy Image", comment: "Context menu option to copy an image into clipboard."), systemImage: "photo.on.rectangle") - } - Button { - UIImageWriteToSavedPhotosAlbum(someImage, nil, nil, nil) - } label: { - Label(NSLocalizedString("Save Image", comment: "Context menu option to save an image."), systemImage: "square.and.arrow.down") - } - } - Button { - showShareSheet = true - } label: { - Label(NSLocalizedString("Share", comment: "Button to share an image."), systemImage: "square.and.arrow.up") - } - } - } -} -private struct ImageContainerView: View { - - let url: URL? - - @State private var image: UIImage? - @State private var showShareSheet = false - - private struct ImageHandler: ImageModifier { - @Binding var handler: UIImage? - - func modify(_ image: UIImage) -> UIImage { - handler = image - return image - } - } - - var body: some View { - - KFAnimatedImage(url) - .imageContext(.note) - .configure { view in - view.framePreloadCount = 3 - } - .imageModifier(ImageHandler(handler: $image)) - .clipped() - .modifier(ImageContextMenuModifier(url: url, image: image, showShareSheet: $showShareSheet)) - .sheet(isPresented: $showShareSheet) { - ShareSheet(activityItems: [url]) - } - } -} -struct ImageView: View { - - let urls: [URL?] - - @Environment(\.presentationMode) var presentationMode - - @State private var selectedIndex = 0 - @State var showMenu = true - - var navBarView: some View { - VStack { - HStack { - /* - Text(urls[selectedIndex]?.lastPathComponent ?? "") - .bold() - */ - - Spacer() - - Button(action: { - presentationMode.wrappedValue.dismiss() - }, label: { - Image(systemName: "xmark") - }) - } - .padding() - } - } - - var tabViewIndicator: some View { - HStack(spacing: 10) { - ForEach(urls.indices, id: \.self) { index in - Capsule() - .fill(index == selectedIndex ? Color(UIColor.label) : Color.secondary) - .frame(width: 7, height: 7) - } - } - .padding() - .background(.regularMaterial) - .clipShape(Capsule()) - } - - var body: some View { - ZStack { - Color(.systemBackground) - .ignoresSafeArea() - - TabView(selection: $selectedIndex) { - ForEach(urls.indices, id: \.self) { index in - ZoomableScrollView { - ImageContainerView(url: urls[index]) - .aspectRatio(contentMode: .fit) - .padding(.top, Theme.safeAreaInsets?.top) - .padding(.bottom, Theme.safeAreaInsets?.bottom) - } - .modifier(SwipeToDismissModifier(minDistance: 50, onDismiss: { - presentationMode.wrappedValue.dismiss() - })) - .ignoresSafeArea() - .tag(index) - } - } - .ignoresSafeArea() - .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) - .gesture(TapGesture(count: 2).onEnded { - // Prevents menu from hiding on double tap - }) - .gesture(TapGesture(count: 1).onEnded { - showMenu.toggle() - }) - .overlay( - VStack { - if showMenu { - navBarView - Spacer() - - if (urls.count > 1) { - tabViewIndicator - } - } - } - .animation(.easeInOut, value: showMenu) - .padding(.bottom, Theme.safeAreaInsets?.bottom) - ) - } - } -} + struct ImageCarousel: View { var urls: [URL] diff --git a/damus/Views/Images/ImageContainerView.swift b/damus/Views/Images/ImageContainerView.swift @@ -0,0 +1,51 @@ +// +// CarouselImageContainerView.swift +// damus +// +// Created by William Casarin on 2023-03-23. +// + +import SwiftUI +import Kingfisher + + +// lots of overlap between this and ImageContainerView +struct ImageContainerView: View { + + let url: URL? + + @State private var image: UIImage? + @State private var showShareSheet = false + + private struct ImageHandler: ImageModifier { + @Binding var handler: UIImage? + + func modify(_ image: UIImage) -> UIImage { + handler = image + return image + } + } + + var body: some View { + + KFAnimatedImage(url) + .imageContext(.note) + .configure { view in + view.framePreloadCount = 3 + } + .imageModifier(ImageHandler(handler: $image)) + .clipped() + .modifier(ImageContextMenuModifier(url: url, image: image, showShareSheet: $showShareSheet)) + .sheet(isPresented: $showShareSheet) { + ShareSheet(activityItems: [url]) + } + } +} + +let test_image_url = URL(string: "https://jb55.com/red-me.jpg")! + +struct ImageContainerView_Previews: PreviewProvider { + static var previews: some View { + ImageContainerView(url: test_image_url) + } +} diff --git a/damus/Views/Images/ImageContextMenuModifier.swift b/damus/Views/Images/ImageContextMenuModifier.swift @@ -0,0 +1,43 @@ +// +// ImageContextMenuModifier.swift +// damus +// +// Created by William Casarin on 2023-03-23. +// + +import Foundation +import SwiftUI +import UIKit + +struct ImageContextMenuModifier: ViewModifier { + let url: URL? + let image: UIImage? + @Binding var showShareSheet: Bool + + func body(content: Content) -> some View { + return content.contextMenu { + Button { + UIPasteboard.general.url = url + } label: { + Label(NSLocalizedString("Copy Image URL", comment: "Context menu option to copy the URL of an image into clipboard."), systemImage: "doc.on.doc") + } + if let someImage = image { + Button { + UIPasteboard.general.image = someImage + } label: { + Label(NSLocalizedString("Copy Image", comment: "Context menu option to copy an image into clipboard."), systemImage: "photo.on.rectangle") + } + Button { + UIImageWriteToSavedPhotosAlbum(someImage, nil, nil, nil) + } label: { + Label(NSLocalizedString("Save Image", comment: "Context menu option to save an image."), systemImage: "square.and.arrow.down") + } + } + Button { + showShareSheet = true + } label: { + Label(NSLocalizedString("Share", comment: "Button to share an image."), systemImage: "square.and.arrow.up") + } + } + } +} diff --git a/damus/Views/Images/ImageView.swift b/damus/Views/Images/ImageView.swift @@ -0,0 +1,102 @@ +// +// ImageView.swift +// damus +// +// Created by William Casarin on 2023-03-23. +// + +import SwiftUI + +struct ImageView: View { + + let urls: [URL?] + + @Environment(\.presentationMode) var presentationMode + + @State private var selectedIndex = 0 + @State var showMenu = true + + var navBarView: some View { + VStack { + HStack { + /* + Text(urls[selectedIndex]?.lastPathComponent ?? "") + .bold() + */ + + Spacer() + + Button(action: { + presentationMode.wrappedValue.dismiss() + }, label: { + Image(systemName: "xmark") + }) + } + .padding() + } + } + + var tabViewIndicator: some View { + HStack(spacing: 10) { + ForEach(urls.indices, id: \.self) { index in + Capsule() + .fill(index == selectedIndex ? Color(UIColor.label) : Color.secondary) + .frame(width: 7, height: 7) + } + } + .padding() + .background(.regularMaterial) + .clipShape(Capsule()) + } + + var body: some View { + ZStack { + Color(.systemBackground) + .ignoresSafeArea() + + TabView(selection: $selectedIndex) { + ForEach(urls.indices, id: \.self) { index in + ZoomableScrollView { + ImageContainerView(url: urls[index]) + .aspectRatio(contentMode: .fit) + .padding(.top, Theme.safeAreaInsets?.top) + .padding(.bottom, Theme.safeAreaInsets?.bottom) + } + .modifier(SwipeToDismissModifier(minDistance: 50, onDismiss: { + presentationMode.wrappedValue.dismiss() + })) + .ignoresSafeArea() + .tag(index) + } + } + .ignoresSafeArea() + .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) + .gesture(TapGesture(count: 2).onEnded { + // Prevents menu from hiding on double tap + }) + .gesture(TapGesture(count: 1).onEnded { + showMenu.toggle() + }) + .overlay( + VStack { + if showMenu { + navBarView + Spacer() + + if (urls.count > 1) { + tabViewIndicator + } + } + } + .animation(.easeInOut, value: showMenu) + .padding(.bottom, Theme.safeAreaInsets?.bottom) + ) + } + } +} + +struct ImageView_Previews: PreviewProvider { + static var previews: some View { + ImageView(urls: [URL(string: "https://jb55.com/red-me.jpg")]) + } +} diff --git a/damus/Views/ProfileName.swift b/damus/Views/Profile/ProfileName.swift diff --git a/damus/Views/ProfilePicView.swift b/damus/Views/Profile/ProfilePicView.swift diff --git a/damus/Views/ProfilePictureSelector.swift b/damus/Views/Profile/ProfilePictureSelector.swift diff --git a/damus/Views/ProfileView.swift b/damus/Views/Profile/ProfileView.swift diff --git a/damus/Views/ProfileZoomView.swift b/damus/Views/ProfileZoomView.swift @@ -7,7 +7,7 @@ import SwiftUI import Kingfisher -private struct ImageContainerView: View { +struct ProfileImageContainerView: View { let url: URL? @@ -68,7 +68,7 @@ struct ProfileZoomView: View { .ignoresSafeArea() ZoomableScrollView { - ImageContainerView(url: get_profile_url(picture: nil, pubkey: pubkey, profiles: profiles)) + ProfileImageContainerView(url: get_profile_url(picture: nil, pubkey: pubkey, profiles: profiles)) .aspectRatio(contentMode: .fit) .padding(.top, Theme.safeAreaInsets?.top) .padding(.bottom, Theme.safeAreaInsets?.bottom)