diff --git a/Vernissage.xcodeproj/project.pbxproj b/Vernissage.xcodeproj/project.pbxproj index 7116298..bd80fca 100644 --- a/Vernissage.xcodeproj/project.pbxproj +++ b/Vernissage.xcodeproj/project.pbxproj @@ -158,7 +158,6 @@ 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 */; }; - F8A4A88329E3FD1C00267E36 /* ImageAvatar.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8A4A88229E3FD1C00267E36 /* ImageAvatar.swift */; }; 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 */; }; @@ -176,7 +175,6 @@ F8F6E44D29BCC1F90004795E /* MediumWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F6E44829BCC0F00004795E /* MediumWidgetView.swift */; }; F8F6E44E29BCC1FB0004795E /* LargeWidgetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F6E44A29BCC0FF0004795E /* LargeWidgetView.swift */; }; F8F6E45129BCE9190004795E /* UIImage+Resize.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8F6E45029BCE9190004795E /* UIImage+Resize.swift */; }; - F8FFBD4829E9901E0047EE80 /* ImageFavourite.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8FFBD4729E9901E0047EE80 /* ImageFavourite.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -330,7 +328,6 @@ F89D6C49297196FF001DA3D4 /* ImageViewer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewer.swift; sourceTree = ""; }; F89F0605299139F6003DC875 /* Vernissage-002.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-002.xcdatamodel"; sourceTree = ""; }; F89F57AF29D1C11200001EE3 /* RelationshipModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelationshipModel.swift; sourceTree = ""; }; - F8A4A88229E3FD1C00267E36 /* ImageAvatar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageAvatar.swift; sourceTree = ""; }; F8A4A88429E4099900267E36 /* Vernissage-008.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-008.xcdatamodel"; sourceTree = ""; }; F8A93D7D2965FD89001D8331 /* UserProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileView.swift; sourceTree = ""; }; F8AFF7C029B259150087D083 /* HashtagsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagsView.swift; sourceTree = ""; }; @@ -358,7 +355,6 @@ F8F6E44829BCC0F00004795E /* MediumWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediumWidgetView.swift; sourceTree = ""; }; F8F6E44A29BCC0FF0004795E /* LargeWidgetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeWidgetView.swift; sourceTree = ""; }; F8F6E45029BCE9190004795E /* UIImage+Resize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Resize.swift"; sourceTree = ""; }; - F8FFBD4729E9901E0047EE80 /* ImageFavourite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageFavourite.swift; sourceTree = ""; }; F8FFBD4929E99BEE0047EE80 /* Vernissage-009.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-009.xcdatamodel"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -638,8 +634,6 @@ isa = PBXGroup; children = ( F88BC53A29E06A5100CE6141 /* ImageContextMenu.swift */, - F8A4A88229E3FD1C00267E36 /* ImageAvatar.swift */, - F8FFBD4729E9901E0047EE80 /* ImageFavourite.swift */, ); path = ViewModifiers; sourceTree = ""; @@ -1080,9 +1074,7 @@ F86B7221296C49A300EE59EC /* EmptyButtonStyle.swift in Sources */, F80048042961850500E6868A /* AttachmentData+CoreDataProperties.swift in Sources */, F88E4D4A297EA0490057491A /* RouterPath.swift in Sources */, - F8FFBD4829E9901E0047EE80 /* ImageFavourite.swift in Sources */, F88E4D48297E90CD0057491A /* TrendStatusesView.swift in Sources */, - F8A4A88329E3FD1C00267E36 /* ImageAvatar.swift in Sources */, F800480A2961EA1900E6868A /* AttachmentDataHandler.swift in Sources */, F80048032961850500E6868A /* AttachmentData+CoreDataClass.swift in Sources */, F891E7D029C368750022C449 /* ImageRowItemAsync.swift in Sources */, diff --git a/Vernissage/ViewModifiers/ImageAvatar.swift b/Vernissage/ViewModifiers/ImageAvatar.swift deleted file mode 100644 index 8412fb4..0000000 --- a/Vernissage/ViewModifiers/ImageAvatar.swift +++ /dev/null @@ -1,83 +0,0 @@ -// -// https://mczachurski.dev -// Copyright © 2023 Marcin Czachurski and the repository contributors. -// Licensed under the Apache License 2.0. -// - -import Foundation -import SwiftUI -import NukeUI -import ClientKit -import ServicesKit -import EnvironmentKit - -public extension View { - func imageAvatar(displayName: String?, avatarUrl: URL?) -> some View { - modifier(ImageAvatar(displayName: displayName, avatarUrl: avatarUrl)) - } -} - -private struct ImageAvatar: ViewModifier { - @EnvironmentObject var applicationState: ApplicationState - - private let displayName: String? - private let avatarUrl: URL? - - init(displayName: String?, avatarUrl: URL?) { - self.displayName = displayName - self.avatarUrl = avatarUrl - } - - func body(content: Content) -> some View { - if self.applicationState.showAvatarsOnTimeline { - ZStack { - // Image. - content - - // Avatar. - VStack(alignment: .leading) { - - HStack(alignment: .center) { - LazyImage(url: avatarUrl) { state in - if let image = state.image { - self.buildAvatar(image: image) - } else if state.isLoading { - self.buildAvatar() - } else { - self.buildAvatar() - } - } - - Text(displayName ?? "") - .font(.system(size: 15)) - .foregroundColor(.white.opacity(0.8)) - .fontWeight(.semibold) - .shadow(color: .black, radius: 2) - Spacer() - } - - Spacer() - } - .padding(.leading, 8) - .padding(.top, 8) - } - } else { - content - } - } - - @ViewBuilder - private func buildAvatar(image: Image? = nil) -> some View { - (image ?? Image("Avatar")) - .resizable() - .clipShape(applicationState.avatarShape.shape()) - .aspectRatio(contentMode: .fit) - .frame(width: 24, height: 24) - .overlay( - applicationState.avatarShape.shape() - .stroke(Color.white.opacity(0.6), lineWidth: 1) - .frame(width: 24, height: 24) - ) - .shadow(color: .black, radius: 2) - } -} diff --git a/Vernissage/ViewModifiers/ImageFavourite.swift b/Vernissage/ViewModifiers/ImageFavourite.swift deleted file mode 100644 index d9a8d9f..0000000 --- a/Vernissage/ViewModifiers/ImageFavourite.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// https://mczachurski.dev -// Copyright © 2023 Marcin Czachurski and the repository contributors. -// Licensed under the Apache License 2.0. -// - -import Foundation -import SwiftUI -import NukeUI -import ClientKit -import ServicesKit -import EnvironmentKit - -public extension View { - func imageFavourite(isFavourited: Binding) -> some View { - modifier(ImageFavourite(isFavourited: isFavourited)) - } -} - -private struct ImageFavourite: ViewModifier { - @EnvironmentObject var applicationState: ApplicationState - @Binding private var isFavourited: Bool - - init(isFavourited: Binding) { - self._isFavourited = isFavourited - } - - func body(content: Content) -> some View { - if self.applicationState.showFavouritesOnTimeline && self.isFavourited { - ZStack { - // Image. - content - - // Avatar. - VStack(alignment: .leading) { - Spacer() - - HStack(alignment: .center) { - Image(systemName: "star.fill") - .font(.system(size: 12)) - .shadow(color: .black, radius: 4) - .foregroundColor(.white.opacity(0.8)) - Spacer() - } - } - .padding(.leading, 12) - .padding(.bottom, 14) - } - } else { - content - } - } -} diff --git a/Vernissage/Widgets/ImageRowItem.swift b/Vernissage/Widgets/ImageRowItem.swift index 5533122..8381293 100644 --- a/Vernissage/Widgets/ImageRowItem.swift +++ b/Vernissage/Widgets/ImageRowItem.swift @@ -42,20 +42,15 @@ struct ImageRowItem: View { if self.status.sensitive && !self.applicationState.showSensitive { ZStack { ContentWarning(spoilerText: self.status.spoilerText) { - self.imageView(uiImage: uiImage) - - if showThumbImage { - FavouriteTouch { - self.showThumbImage = false - } - } + self.imageContainerView(uiImage: uiImage) } blurred: { - BlurredImage(blurhash: attachmentData.blurhash) - .imageAvatar(displayName: self.status.accountDisplayName, - avatarUrl: self.status.accountAvatar) - .onTapGesture { - self.navigateToStatus() - } + ZStack { + BlurredImage(blurhash: attachmentData.blurhash) + ImageAvatar(displayName: self.status.accountDisplayName, avatarUrl: self.status.accountAvatar) + } + .onTapGesture { + self.navigateToStatus() + } } } .opacity(self.opacity) @@ -66,13 +61,7 @@ struct ImageRowItem: View { } } else { ZStack { - self.imageView(uiImage: uiImage) - - if showThumbImage { - FavouriteTouch { - self.showThumbImage = false - } - } + self.imageContainerView(uiImage: uiImage) } .opacity(self.opacity) .onAppear { @@ -108,6 +97,15 @@ struct ImageRowItem: View { } } + @ViewBuilder + private func imageContainerView(uiImage: UIImage) -> some View { + self.imageView(uiImage: uiImage) + + ImageAvatar(displayName: self.status.accountDisplayName, avatarUrl: self.status.accountAvatar) + ImageFavourite(isFavourited: $isFavourited) + FavouriteTouch(showFavouriteAnimation: $showThumbImage) + } + @ViewBuilder private func imageView(uiImage: UIImage) -> some View { Image(uiImage: uiImage) @@ -129,14 +127,13 @@ struct ImageRowItem: View { HapticService.shared.fireHaptic(of: .buttonPress) // Mark favourite booleans used to show star in the timeline view. - self.isFavourited = true + withAnimation(.default.delay(2.0)) { + self.isFavourited = true + } } .onTapGesture { self.navigateToStatus() } - .imageAvatar(displayName: self.status.accountDisplayName, - avatarUrl: self.status.accountAvatar) - .imageFavourite(isFavourited: $isFavourited) .imageContextMenu(statusData: self.status) .onAppear { self.isFavourited = self.status.favourited diff --git a/Vernissage/Widgets/ImageRowItemAsync.swift b/Vernissage/Widgets/ImageRowItemAsync.swift index c3619a2..e4cdfd4 100644 --- a/Vernissage/Widgets/ImageRowItemAsync.swift +++ b/Vernissage/Widgets/ImageRowItemAsync.swift @@ -42,21 +42,15 @@ struct ImageRowItemAsync: View { if self.statusViewModel.sensitive && !self.applicationState.showSensitive { ZStack { ContentWarning(spoilerText: self.statusViewModel.spoilerText) { - self.imageView(image: image) + self.imageContainerView(image: image) } blurred: { - BlurredImage(blurhash: attachment.blurhash) - .if(self.showAvatar) { - $0.imageAvatar(displayName: self.statusViewModel.account.displayNameWithoutEmojis, - avatarUrl: self.statusViewModel.account.avatar) - } - .onTapGesture { - self.navigateToStatus() - } - } - - if showThumbImage { - FavouriteTouch { - self.showThumbImage = false + ZStack { + BlurredImage(blurhash: attachment.blurhash) + ImageAvatar(displayName: self.statusViewModel.account.displayNameWithoutEmojis, + avatarUrl: self.statusViewModel.account.avatar) + } + .onTapGesture { + self.navigateToStatus() } } } @@ -72,13 +66,7 @@ struct ImageRowItemAsync: View { } } else { ZStack { - self.imageView(image: image) - - if showThumbImage { - FavouriteTouch { - self.showThumbImage = false - } - } + self.imageContainerView(image: image) } .opacity(self.opacity) .onAppear { @@ -116,6 +104,19 @@ struct ImageRowItemAsync: View { .priority(.high) } + @ViewBuilder + private func imageContainerView(image: Image) -> some View { + self.imageView(image: image) + + if self.showAvatar { + ImageAvatar(displayName: self.statusViewModel.account.displayNameWithoutEmojis, + avatarUrl: self.statusViewModel.account.avatar) + } + + ImageFavourite(isFavourited: $isFavourited) + FavouriteTouch(showFavouriteAnimation: $showThumbImage) + } + @ViewBuilder private func imageView(image: Image) -> some View { image @@ -133,16 +134,13 @@ struct ImageRowItemAsync: View { // Mark favourite booleans used to show star in the timeline view. self.statusViewModel.favourited = true - self.isFavourited = true + withAnimation(.default.delay(2.0)) { + self.isFavourited = true + } } .onTapGesture { self.navigateToStatus() } - .if(self.showAvatar) { - $0.imageAvatar(displayName: self.statusViewModel.account.displayNameWithoutEmojis, - avatarUrl: self.statusViewModel.account.avatar) - } - .imageFavourite(isFavourited: $isFavourited) .imageContextMenu(statusModel: self.statusViewModel) .onAppear { self.isFavourited = self.statusViewModel.favourited diff --git a/WidgetsKit/Sources/WidgetsKit/Widgets/FavouriteTouch.swift b/WidgetsKit/Sources/WidgetsKit/Widgets/FavouriteTouch.swift index ea13553..30e221f 100644 --- a/WidgetsKit/Sources/WidgetsKit/Widgets/FavouriteTouch.swift +++ b/WidgetsKit/Sources/WidgetsKit/Widgets/FavouriteTouch.swift @@ -11,41 +11,43 @@ public struct FavouriteTouch: View { @State private var showCircle = 0 @State private var opacity = 1.0 - private let finished: () -> Void + @Binding private var showFavouriteAnimation: Bool - public init(finished: @escaping () -> Void) { - self.finished = finished + public init(showFavouriteAnimation: Binding) { + self._showFavouriteAnimation = showFavouriteAnimation } public var body: some View { - ZStack { - Circle() - .frame(width: 55, height: 55, alignment: .center) - .foregroundColor(.white.opacity(0.75)) - .scaleEffect(CGFloat(showCircle)) + if self.showFavouriteAnimation { + ZStack { + Circle() + .frame(width: 55, height: 55, alignment: .center) + .foregroundColor(.white.opacity(0.75)) + .scaleEffect(CGFloat(showCircle)) - Image(systemName: "star.fill") - .font(.system(size: 26)) - .foregroundColor(.black.opacity(0.4)) - .clipShape(Rectangle().offset(y: CGFloat(showThumb))) - } - .opacity(opacity) - .onAppear { - withAnimation(Animation.interpolatingSpring(stiffness: 170, damping: 15)) { - showCircle = 1 + Image(systemName: "star.fill") + .font(.system(size: 26)) + .foregroundColor(.black.opacity(0.4)) + .clipShape(Rectangle().offset(y: CGFloat(showThumb))) } + .opacity(opacity) + .onAppear { + withAnimation(Animation.interpolatingSpring(stiffness: 170, damping: 15)) { + showCircle = 1 + } - withAnimation(Animation.easeInOut(duration: 0.5).delay(0.25)) { - showThumb = 0 - } + withAnimation(Animation.easeInOut(duration: 0.5).delay(0.25)) { + showThumb = 0 + } - withAnimation(Animation.easeInOut(duration: 0.5).delay(1.75)) { - opacity = 0 + withAnimation(Animation.easeInOut(duration: 0.5).delay(1.75)) { + opacity = 0 + } + } + .task { + try? await Task.sleep(nanoseconds: 2_500_000_000) + self.showFavouriteAnimation = false } - } - .task { - try? await Task.sleep(nanoseconds: 2_500_000_000) - self.finished() } } } diff --git a/WidgetsKit/Sources/WidgetsKit/Widgets/ImageAvatar.swift b/WidgetsKit/Sources/WidgetsKit/Widgets/ImageAvatar.swift new file mode 100644 index 0000000..805f956 --- /dev/null +++ b/WidgetsKit/Sources/WidgetsKit/Widgets/ImageAvatar.swift @@ -0,0 +1,66 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the Apache License 2.0. +// + +import Foundation +import SwiftUI +import NukeUI +import EnvironmentKit + +public struct ImageAvatar: View { + @EnvironmentObject var applicationState: ApplicationState + + private let displayName: String? + private let avatarUrl: URL? + + public init(displayName: String?, avatarUrl: URL?) { + self.displayName = displayName + self.avatarUrl = avatarUrl + } + + public var body: some View { + if self.applicationState.showAvatarsOnTimeline { + VStack(alignment: .leading) { + HStack(alignment: .center) { + LazyImage(url: avatarUrl) { state in + if let image = state.image { + self.buildAvatar(image: image) + } else if state.isLoading { + self.buildAvatar() + } else { + self.buildAvatar() + } + } + + Text(displayName ?? "") + .font(.system(size: 15)) + .foregroundColor(.white.opacity(0.8)) + .fontWeight(.semibold) + .shadow(color: .black, radius: 2) + Spacer() + } + + Spacer() + } + .padding(.leading, 8) + .padding(.top, 8) + } + } + + @ViewBuilder + private func buildAvatar(image: Image? = nil) -> some View { + (image ?? Image("Avatar")) + .resizable() + .clipShape(applicationState.avatarShape.shape()) + .aspectRatio(contentMode: .fit) + .frame(width: 24, height: 24) + .overlay( + applicationState.avatarShape.shape() + .stroke(Color.white.opacity(0.6), lineWidth: 1) + .frame(width: 24, height: 24) + ) + .shadow(color: .black, radius: 2) + } +} diff --git a/WidgetsKit/Sources/WidgetsKit/Widgets/ImageFavourite.swift b/WidgetsKit/Sources/WidgetsKit/Widgets/ImageFavourite.swift new file mode 100644 index 0000000..7158d14 --- /dev/null +++ b/WidgetsKit/Sources/WidgetsKit/Widgets/ImageFavourite.swift @@ -0,0 +1,36 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the Apache License 2.0. +// + +import Foundation +import SwiftUI +import EnvironmentKit + +public struct ImageFavourite: View { + @EnvironmentObject var applicationState: ApplicationState + @Binding private var isFavourited: Bool + + public init(isFavourited: Binding) { + self._isFavourited = isFavourited + } + + public var body: some View { + if self.applicationState.showFavouritesOnTimeline && self.isFavourited { + VStack(alignment: .leading) { + Spacer() + + HStack(alignment: .center) { + Image(systemName: "star.fill") + .font(.system(size: 12)) + .shadow(color: .black, radius: 4) + .foregroundColor(.white.opacity(0.8)) + Spacer() + } + } + .padding(.leading, 12) + .padding(.bottom, 14) + } + } +}