Add two new menu items: save photo and show alt text
This commit is contained in:
parent
392948d84c
commit
8c2f8f830a
|
@ -3,6 +3,9 @@
|
|||
"global.title.seePost" = "See post";
|
||||
"global.title.refresh" = "Refresh";
|
||||
"global.title.momentsAgo" = "moments ago";
|
||||
"global.title.success" = "Success";
|
||||
"global.title.photoSaved" = "Photo has been saved.";
|
||||
"global.title.ok" = "OK";
|
||||
|
||||
// MARK: Global errors.
|
||||
"global.error.unexpected" = "Unexpected error.";
|
||||
|
@ -252,6 +255,9 @@
|
|||
"status.title.unbookmark" = "Unbookmark";
|
||||
"status.title.comment" = "Comment";
|
||||
"status.title.report" = "Report";
|
||||
"status.title.saveImage" = "Save image";
|
||||
"status.title.showMediaDescription" = "Show media description";
|
||||
"status.title.mediaDescription" = "Media description";
|
||||
"status.error.loadingStatusFailed" = "Loading status failed.";
|
||||
"status.error.notFound" = "Status not existing anymore.";
|
||||
"status.error.loadingCommentsFailed" = "Comments cannot be downloaded.";
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
"global.title.seePost" = "Ikusi bidalketa";
|
||||
"global.title.refresh" = "Freskatu";
|
||||
"global.title.momentsAgo" = "oraintxe bertan";
|
||||
"global.title.success" = "Success";
|
||||
"global.title.photoSaved" = "Photo has been saved.";
|
||||
"global.title.ok" = "OK";
|
||||
|
||||
// MARK: Global errors.
|
||||
"global.error.unexpected" = "Espero ez zen errorea.";
|
||||
|
@ -252,6 +255,9 @@
|
|||
"status.title.unbookmark" = "Kendu laster-marka";
|
||||
"status.title.comment" = "Egin iruzkina";
|
||||
"status.title.report" = "Salatu";
|
||||
"status.title.saveImage" = "Save image";
|
||||
"status.title.showMediaDescription" = "Show media description";
|
||||
"status.title.mediaDescription" = "Media description";
|
||||
"status.error.loadingStatusFailed" = "Egoera kargatzeak huts egin du.";
|
||||
"status.error.notFound" = "Egoera ez da dagoeneko existitzen.";
|
||||
"status.error.loadingCommentsFailed" = "Ezin dira iruzkinak eskuratu.";
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
"global.title.seePost" = "Pokaż zdjęcie";
|
||||
"global.title.refresh" = "Odśwież";
|
||||
"global.title.momentsAgo" = "chwilę temu";
|
||||
"global.title.success" = "Sukces";
|
||||
"global.title.photoSaved" = "Zdjęcie zostało zapisane.";
|
||||
"global.title.ok" = "OK";
|
||||
|
||||
// MARK: Global errors.
|
||||
"global.error.unexpected" = "Wystąpił nieoczekiwany błąd.";
|
||||
|
@ -252,6 +255,9 @@
|
|||
"status.title.unbookmark" = "Usuń z zakładek";
|
||||
"status.title.comment" = "Skomentuj";
|
||||
"status.title.report" = "Zgłoś";
|
||||
"status.title.saveImage" = "Zapisz zdjęcie";
|
||||
"status.title.showMediaDescription" = "Pokaż opis zdjęcia";
|
||||
"status.title.mediaDescription" = "Opis zdjęcia";
|
||||
"status.error.loadingStatusFailed" = "Błąd podczas wczytywanie statusu.";
|
||||
"status.error.notFound" = "Status już nie istnieje.";
|
||||
"status.error.loadingCommentsFailed" =" Błąd podczas wczytywanie komentarzy.";
|
||||
|
|
|
@ -82,6 +82,7 @@
|
|||
F86B7214296BFDCE00EE59EC /* UserProfileHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F86B7213296BFDCE00EE59EC /* UserProfileHeaderView.swift */; };
|
||||
F86B7216296BFFDA00EE59EC /* UserProfileStatusesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F86B7215296BFFDA00EE59EC /* UserProfileStatusesView.swift */; };
|
||||
F86B7221296C49A300EE59EC /* EmptyButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F86B7220296C49A300EE59EC /* EmptyButtonStyle.swift */; };
|
||||
F86BC9E929EBBB67009415EC /* ImageSaver.swift in Sources */ = {isa = PBXBuildFile; fileRef = F86BC9E829EBBB66009415EC /* ImageSaver.swift */; };
|
||||
F8742FC429990AFB00E9642B /* ClientError.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8742FC329990AFB00E9642B /* ClientError.swift */; };
|
||||
F8764187298ABB520057D362 /* ViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8764186298ABB520057D362 /* ViewState.swift */; };
|
||||
F876418D298AE5020057D362 /* PaginableStatusesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F876418C298AE5020057D362 /* PaginableStatusesView.swift */; };
|
||||
|
@ -272,6 +273,7 @@
|
|||
F86B7213296BFDCE00EE59EC /* UserProfileHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileHeaderView.swift; sourceTree = "<group>"; };
|
||||
F86B7215296BFFDA00EE59EC /* UserProfileStatusesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfileStatusesView.swift; sourceTree = "<group>"; };
|
||||
F86B7220296C49A300EE59EC /* EmptyButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyButtonStyle.swift; sourceTree = "<group>"; };
|
||||
F86BC9E829EBBB66009415EC /* ImageSaver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageSaver.swift; sourceTree = "<group>"; };
|
||||
F8742FC329990AFB00E9642B /* ClientError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientError.swift; sourceTree = "<group>"; };
|
||||
F8764186298ABB520057D362 /* ViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewState.swift; sourceTree = "<group>"; };
|
||||
F876418C298AE5020057D362 /* PaginableStatusesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaginableStatusesView.swift; sourceTree = "<group>"; };
|
||||
|
@ -718,6 +720,7 @@
|
|||
F85D4974296407F100751DF7 /* HomeTimelineService.swift */,
|
||||
F88E4D49297EA0490057491A /* RouterPath.swift */,
|
||||
F878842129A4A4E3003CFAD2 /* AppMetadataService.swift */,
|
||||
F86BC9E829EBBB66009415EC /* ImageSaver.swift */,
|
||||
);
|
||||
path = Services;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1117,6 +1120,7 @@
|
|||
F89F57B029D1C11200001EE3 /* RelationshipModel.swift in Sources */,
|
||||
F88AB05829B36B8200345EDE /* AccountsPhotoView.swift in Sources */,
|
||||
F85D4971296402DC00751DF7 /* AuthorizationService.swift in Sources */,
|
||||
F86BC9E929EBBB67009415EC /* ImageSaver.swift in Sources */,
|
||||
F88AB05329B3613900345EDE /* PhotoUrl.swift in Sources */,
|
||||
F88E4D56297EAD6E0057491A /* AppRouteur.swift in Sources */,
|
||||
F88FAD27295F400E009B20C9 /* NotificationsView.swift in Sources */,
|
||||
|
@ -1412,6 +1416,7 @@
|
|||
INFOPLIST_FILE = Vernissage/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Vernissage;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.photography";
|
||||
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Saving photos from Pixelfed";
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||
|
@ -1452,6 +1457,7 @@
|
|||
INFOPLIST_FILE = Vernissage/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Vernissage;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.photography";
|
||||
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "Saving photos from Pixelfed";
|
||||
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait;
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the Apache License 2.0.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
class ImageSaver: NSObject {
|
||||
private let completed: () -> Void
|
||||
|
||||
init(completed: @escaping () -> Void) {
|
||||
self.completed = completed
|
||||
}
|
||||
|
||||
func writeToPhotoAlbum(image: UIImage) {
|
||||
UIImageWriteToSavedPhotosAlbum(image, self, #selector(saveCompleted), nil)
|
||||
}
|
||||
|
||||
@objc func saveCompleted(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
|
||||
self.completed()
|
||||
}
|
||||
}
|
|
@ -10,24 +10,41 @@ import ClientKit
|
|||
import ServicesKit
|
||||
|
||||
public extension View {
|
||||
func imageContextMenu(statusModel: StatusModel) -> some View {
|
||||
modifier(ImageContextMenu(id: statusModel.id, url: statusModel.url))
|
||||
func imageContextMenu(statusModel: StatusModel, attachmentModel: AttachmentModel, uiImage: UIImage?) -> some View {
|
||||
modifier(ImageContextMenu(id: statusModel.id, url: statusModel.url, altText: attachmentModel.description, uiImage: uiImage))
|
||||
}
|
||||
|
||||
func imageContextMenu(statusData: StatusData) -> some View {
|
||||
modifier(ImageContextMenu(id: statusData.id, url: statusData.url))
|
||||
func imageContextMenu(statusData: StatusData, attachmentData: AttachmentData, uiImage: UIImage?) -> some View {
|
||||
modifier(ImageContextMenu(id: statusData.id, url: statusData.url, altText: attachmentData.text, uiImage: uiImage))
|
||||
}
|
||||
}
|
||||
|
||||
private struct ImageContextMenu: ViewModifier {
|
||||
private struct AlertInfo: Identifiable {
|
||||
enum AlertType {
|
||||
case showAlternativeText
|
||||
case photoHasBeenSaved
|
||||
}
|
||||
|
||||
let id: AlertType
|
||||
let title: Text
|
||||
let message: Text
|
||||
}
|
||||
|
||||
@EnvironmentObject var client: Client
|
||||
|
||||
@State private var alertInfo: AlertInfo?
|
||||
|
||||
private let id: String
|
||||
private let url: URL?
|
||||
private let altText: String?
|
||||
private let uiImage: UIImage?
|
||||
|
||||
init(id: String, url: URL?) {
|
||||
init(id: String, url: URL?, altText: String?, uiImage: UIImage?) {
|
||||
self.id = id
|
||||
self.url = url
|
||||
self.altText = altText
|
||||
self.uiImage = uiImage
|
||||
}
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
|
@ -69,8 +86,43 @@ private struct ImageContextMenu: ViewModifier {
|
|||
Label("status.title.shareStatus", systemImage: "square.and.arrow.up")
|
||||
}
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
if let altText, altText.count > 0 {
|
||||
Button {
|
||||
self.alertInfo = AlertInfo(
|
||||
id: .showAlternativeText,
|
||||
title: Text("status.title.mediaDescription", comment: "Media description"),
|
||||
message: Text(altText)
|
||||
)
|
||||
} label: {
|
||||
Label("status.title.showMediaDescription", systemImage: "eye.trianglebadge.exclamationmark")
|
||||
}
|
||||
}
|
||||
|
||||
if let uiImage {
|
||||
Button {
|
||||
let imageSaver = ImageSaver {
|
||||
self.alertInfo = AlertInfo(
|
||||
id: .photoHasBeenSaved,
|
||||
title: Text("global.title.success", comment: "Success"),
|
||||
message: Text("global.title.photoSaved", comment: "Photo has been saved")
|
||||
)
|
||||
}
|
||||
|
||||
imageSaver.writeToPhotoAlbum(image: uiImage)
|
||||
} label: {
|
||||
Label("status.title.saveImage", systemImage: "square.and.arrow.down")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert(item: $alertInfo, content: { info in
|
||||
Alert(title: info.title,
|
||||
message: info.message,
|
||||
dismissButton: .default(Text("global.title.ok", comment: "OK")))
|
||||
})
|
||||
}
|
||||
|
||||
private func reboost() async {
|
||||
|
|
|
@ -73,7 +73,7 @@ private struct NavigationMenu<MenuItems>: ViewModifier where MenuItems: View {
|
|||
}
|
||||
.padding(.horizontal, 8)
|
||||
.background(.ultraThinMaterial)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 22))
|
||||
.clipShape(Capsule())
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
|
@ -81,12 +81,11 @@ private struct NavigationMenu<MenuItems>: ViewModifier where MenuItems: View {
|
|||
Menu {
|
||||
self.menuItems()
|
||||
} label: {
|
||||
Image(systemName: "line.3.horizontal")
|
||||
.resizable()
|
||||
.foregroundColor(.mainTextColor.opacity(0.8))
|
||||
.shadow(radius: 5)
|
||||
.padding(12)
|
||||
.frame(width: 44, height: 44)
|
||||
Image(systemName: "ellipsis")
|
||||
.font(.system(size: 26))
|
||||
.foregroundColor(.mainTextColor.opacity(0.75))
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 8)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,11 +95,10 @@ private struct NavigationMenu<MenuItems>: ViewModifier where MenuItems: View {
|
|||
self.routerPath.presentedSheet = .newStatusEditor
|
||||
} label: {
|
||||
Image(systemName: "plus")
|
||||
.resizable()
|
||||
.foregroundColor(.mainTextColor.opacity(0.8))
|
||||
.shadow(radius: 5)
|
||||
.padding(12)
|
||||
.frame(width: 44, height: 44)
|
||||
.font(.system(size: 26))
|
||||
.foregroundColor(.mainTextColor.opacity(0.75))
|
||||
.padding(.vertical, 10)
|
||||
.padding(.horizontal, 8)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ struct ImageRowItem: View {
|
|||
ZStack {
|
||||
ContentWarning(spoilerText: self.status.spoilerText) {
|
||||
self.imageContainerView(uiImage: uiImage)
|
||||
.imageContextMenu(statusData: self.status)
|
||||
.imageContextMenu(statusData: self.status, attachmentData: self.attachmentData, uiImage: uiImage)
|
||||
} blurred: {
|
||||
ZStack {
|
||||
BlurredImage(blurhash: attachmentData.blurhash)
|
||||
|
@ -62,7 +62,7 @@ struct ImageRowItem: View {
|
|||
}
|
||||
} else {
|
||||
self.imageContainerView(uiImage: uiImage)
|
||||
.imageContextMenu(statusData: self.status)
|
||||
.imageContextMenu(statusData: self.status, attachmentData: self.attachmentData, uiImage: uiImage)
|
||||
.opacity(self.opacity)
|
||||
.onAppear {
|
||||
withAnimation {
|
||||
|
|
|
@ -43,7 +43,9 @@ struct ImageRowItemAsync: View {
|
|||
ZStack {
|
||||
ContentWarning(spoilerText: self.statusViewModel.spoilerText) {
|
||||
self.imageContainerView(image: image)
|
||||
.imageContextMenu(statusModel: self.statusViewModel)
|
||||
.imageContextMenu(statusModel: self.statusViewModel,
|
||||
attachmentModel: self.attachment,
|
||||
uiImage: state.imageResponse?.image)
|
||||
} blurred: {
|
||||
ZStack {
|
||||
BlurredImage(blurhash: attachment.blurhash)
|
||||
|
@ -67,7 +69,9 @@ struct ImageRowItemAsync: View {
|
|||
}
|
||||
} else {
|
||||
self.imageContainerView(image: image)
|
||||
.imageContextMenu(statusModel: self.statusViewModel)
|
||||
.imageContextMenu(statusModel: self.statusViewModel,
|
||||
attachmentModel: self.attachment,
|
||||
uiImage: state.imageResponse?.image)
|
||||
.opacity(self.opacity)
|
||||
.onAppear {
|
||||
if let uiImage = state.imageResponse?.image {
|
||||
|
|
Loading…
Reference in New Issue