Show ALT image on timelines
This commit is contained in:
parent
4262dc82db
commit
6111f0d615
|
@ -259,6 +259,7 @@
|
||||||
"status.title.showMediaDescription" = "Show media description";
|
"status.title.showMediaDescription" = "Show media description";
|
||||||
"status.title.mediaDescription" = "Media description";
|
"status.title.mediaDescription" = "Media description";
|
||||||
"status.title.shareImage" = "Share image";
|
"status.title.shareImage" = "Share image";
|
||||||
|
"status.title.altText" = "ALT";
|
||||||
"status.error.loadingStatusFailed" = "Loading status failed.";
|
"status.error.loadingStatusFailed" = "Loading status failed.";
|
||||||
"status.error.notFound" = "Status not existing anymore.";
|
"status.error.notFound" = "Status not existing anymore.";
|
||||||
"status.error.loadingCommentsFailed" = "Comments cannot be downloaded.";
|
"status.error.loadingCommentsFailed" = "Comments cannot be downloaded.";
|
||||||
|
|
|
@ -259,6 +259,7 @@
|
||||||
"status.title.showMediaDescription" = "Show media description";
|
"status.title.showMediaDescription" = "Show media description";
|
||||||
"status.title.mediaDescription" = "Media description";
|
"status.title.mediaDescription" = "Media description";
|
||||||
"status.title.shareImage" = "Share image";
|
"status.title.shareImage" = "Share image";
|
||||||
|
"status.title.altText" = "ALT";
|
||||||
"status.error.loadingStatusFailed" = "Egoera kargatzeak huts egin du.";
|
"status.error.loadingStatusFailed" = "Egoera kargatzeak huts egin du.";
|
||||||
"status.error.notFound" = "Egoera ez da dagoeneko existitzen.";
|
"status.error.notFound" = "Egoera ez da dagoeneko existitzen.";
|
||||||
"status.error.loadingCommentsFailed" = "Ezin dira iruzkinak eskuratu.";
|
"status.error.loadingCommentsFailed" = "Ezin dira iruzkinak eskuratu.";
|
||||||
|
|
|
@ -259,6 +259,7 @@
|
||||||
"status.title.showMediaDescription" = "Pokaż opis zdjęcia";
|
"status.title.showMediaDescription" = "Pokaż opis zdjęcia";
|
||||||
"status.title.mediaDescription" = "Opis zdjęcia";
|
"status.title.mediaDescription" = "Opis zdjęcia";
|
||||||
"status.title.shareImage" = "Udostępnij zdjęcie";
|
"status.title.shareImage" = "Udostępnij zdjęcie";
|
||||||
|
"status.title.altText" = "ALT";
|
||||||
"status.error.loadingStatusFailed" = "Błąd podczas wczytywanie statusu.";
|
"status.error.loadingStatusFailed" = "Błąd podczas wczytywanie statusu.";
|
||||||
"status.error.notFound" = "Status już nie istnieje.";
|
"status.error.notFound" = "Status już nie istnieje.";
|
||||||
"status.error.loadingCommentsFailed" =" Błąd podczas wczytywanie komentarzy.";
|
"status.error.loadingCommentsFailed" =" Błąd podczas wczytywanie komentarzy.";
|
||||||
|
|
|
@ -82,4 +82,19 @@ extension View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func withAlertDestinations(alertDestinations: Binding<AlertDestinations?>) -> some View {
|
||||||
|
self.alert(item: alertDestinations) { destination in
|
||||||
|
switch destination {
|
||||||
|
case .alternativeText(let text):
|
||||||
|
return Alert(title: Text("status.title.mediaDescription", comment: "Media description"),
|
||||||
|
message: Text(text),
|
||||||
|
dismissButton: .default(Text("global.title.ok", comment: "OK")))
|
||||||
|
case .savePhotoSuccess:
|
||||||
|
return Alert(title: Text("global.title.success", comment: "Success"),
|
||||||
|
message: Text("global.title.photoSaved", comment: "Photo has been saved"),
|
||||||
|
dismissButton: .default(Text("global.title.ok", comment: "OK")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,20 @@ enum OverlayDestinations {
|
||||||
case successPayment
|
case successPayment
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum AlertDestinations: Identifiable {
|
||||||
|
case alternativeText(text: String)
|
||||||
|
case savePhotoSuccess
|
||||||
|
|
||||||
|
public var id: String {
|
||||||
|
switch self {
|
||||||
|
case .alternativeText:
|
||||||
|
return "alternativeText"
|
||||||
|
case .savePhotoSuccess:
|
||||||
|
return "savePhotoSuccess"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
class RouterPath: ObservableObject {
|
class RouterPath: ObservableObject {
|
||||||
public var urlHandler: ((URL) -> OpenURLAction.Result)?
|
public var urlHandler: ((URL) -> OpenURLAction.Result)?
|
||||||
|
@ -60,6 +74,7 @@ class RouterPath: ObservableObject {
|
||||||
@Published public var path: [RouteurDestinations] = []
|
@Published public var path: [RouteurDestinations] = []
|
||||||
@Published public var presentedSheet: SheetDestinations?
|
@Published public var presentedSheet: SheetDestinations?
|
||||||
@Published public var presentedOverlay: OverlayDestinations?
|
@Published public var presentedOverlay: OverlayDestinations?
|
||||||
|
@Published public var presentedAlert: AlertDestinations?
|
||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,7 @@ struct VernissageApp: App {
|
||||||
.withAppRouteur()
|
.withAppRouteur()
|
||||||
.withSheetDestinations(sheetDestinations: $routerPath.presentedSheet)
|
.withSheetDestinations(sheetDestinations: $routerPath.presentedSheet)
|
||||||
.withOverlayDestinations(overlayDestinations: $routerPath.presentedOverlay)
|
.withOverlayDestinations(overlayDestinations: $routerPath.presentedOverlay)
|
||||||
|
.withAlertDestinations(alertDestinations: $routerPath.presentedAlert)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.environment(\.managedObjectContext, coreDataHandler.container.viewContext)
|
.environment(\.managedObjectContext, coreDataHandler.container.viewContext)
|
||||||
|
|
|
@ -20,22 +20,9 @@ public extension View {
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct ImageContextMenu: ViewModifier {
|
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
|
@EnvironmentObject var client: Client
|
||||||
@EnvironmentObject var routerPath: RouterPath
|
@EnvironmentObject var routerPath: RouterPath
|
||||||
|
|
||||||
@State private var alertInfo: AlertInfo?
|
|
||||||
|
|
||||||
private let id: String
|
private let id: String
|
||||||
private let url: URL?
|
private let url: URL?
|
||||||
private let altText: String?
|
private let altText: String?
|
||||||
|
@ -92,11 +79,7 @@ private struct ImageContextMenu: ViewModifier {
|
||||||
|
|
||||||
if let altText, altText.count > 0 {
|
if let altText, altText.count > 0 {
|
||||||
Button {
|
Button {
|
||||||
self.alertInfo = AlertInfo(
|
self.routerPath.presentedAlert = .alternativeText(text: altText)
|
||||||
id: .showAlternativeText,
|
|
||||||
title: Text("status.title.mediaDescription", comment: "Media description"),
|
|
||||||
message: Text(altText)
|
|
||||||
)
|
|
||||||
} label: {
|
} label: {
|
||||||
Label("status.title.showMediaDescription", systemImage: "eye.trianglebadge.exclamationmark")
|
Label("status.title.showMediaDescription", systemImage: "eye.trianglebadge.exclamationmark")
|
||||||
}
|
}
|
||||||
|
@ -113,11 +96,7 @@ private struct ImageContextMenu: ViewModifier {
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
let imageSaver = ImageSaver {
|
let imageSaver = ImageSaver {
|
||||||
self.alertInfo = AlertInfo(
|
self.routerPath.presentedAlert = .savePhotoSuccess
|
||||||
id: .photoHasBeenSaved,
|
|
||||||
title: Text("global.title.success", comment: "Success"),
|
|
||||||
message: Text("global.title.photoSaved", comment: "Photo has been saved")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
imageSaver.writeToPhotoAlbum(image: uiImage)
|
imageSaver.writeToPhotoAlbum(image: uiImage)
|
||||||
|
@ -127,11 +106,6 @@ private struct ImageContextMenu: ViewModifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.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 {
|
private func reboost() async {
|
||||||
|
|
|
@ -83,30 +83,6 @@ struct GeneralSectionView: View {
|
||||||
.onChange(of: self.applicationState.menuPosition) { menuPosition in
|
.onChange(of: self.applicationState.menuPosition) { menuPosition in
|
||||||
ApplicationSettingsHandler.shared.set(menuPosition: menuPosition)
|
ApplicationSettingsHandler.shared.set(menuPosition: menuPosition)
|
||||||
}
|
}
|
||||||
|
|
||||||
Toggle(isOn: $applicationState.showAvatarsOnTimeline) {
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
Text("settings.title.showAvatars", comment: "Show avatars")
|
|
||||||
Text("settings.title.showAvatarsOnTimeline", comment: "Show avatars on timeline")
|
|
||||||
.font(.footnote)
|
|
||||||
.foregroundColor(.lightGrayColor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onChange(of: self.applicationState.showAvatarsOnTimeline) { newValue in
|
|
||||||
ApplicationSettingsHandler.shared.set(showAvatarsOnTimeline: newValue)
|
|
||||||
}
|
|
||||||
|
|
||||||
Toggle(isOn: $applicationState.showFavouritesOnTimeline) {
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
Text("settings.title.showFavourite", comment: "Show favourites")
|
|
||||||
Text("settings.title.showFavouriteOnTimeline", comment: "Show favourites on timeline")
|
|
||||||
.font(.footnote)
|
|
||||||
.foregroundColor(.lightGrayColor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onChange(of: self.applicationState.showFavouritesOnTimeline) { newValue in
|
|
||||||
ApplicationSettingsHandler.shared.set(showFavouritesOnTimeline: newValue)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,13 +11,10 @@ struct MediaSettingsView: View {
|
||||||
@EnvironmentObject var applicationState: ApplicationState
|
@EnvironmentObject var applicationState: ApplicationState
|
||||||
@Environment(\.colorScheme) var colorScheme
|
@Environment(\.colorScheme) var colorScheme
|
||||||
|
|
||||||
@State var showSensitive = true
|
|
||||||
@State var showPhotoDescription = true
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Section("settings.title.mediaSettings") {
|
Section("settings.title.mediaSettings") {
|
||||||
|
|
||||||
Toggle(isOn: $showSensitive) {
|
Toggle(isOn: $applicationState.showSensitive) {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Text("settings.title.alwaysShowSensitiveTitle", comment: "Always show NSFW")
|
Text("settings.title.alwaysShowSensitiveTitle", comment: "Always show NSFW")
|
||||||
Text("settings.title.alwaysShowSensitiveDescription", comment: "Force show all NFSW (sensitive) media without warnings")
|
Text("settings.title.alwaysShowSensitiveDescription", comment: "Force show all NFSW (sensitive) media without warnings")
|
||||||
|
@ -25,12 +22,11 @@ struct MediaSettingsView: View {
|
||||||
.foregroundColor(.lightGrayColor)
|
.foregroundColor(.lightGrayColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: showSensitive) { newValue in
|
.onChange(of: self.applicationState.showSensitive) { newValue in
|
||||||
self.applicationState.showSensitive = newValue
|
|
||||||
ApplicationSettingsHandler.shared.set(showSensitive: newValue)
|
ApplicationSettingsHandler.shared.set(showSensitive: newValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
Toggle(isOn: $showPhotoDescription) {
|
Toggle(isOn: $applicationState.showPhotoDescription) {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Text("settings.title.alwaysShowAltTitle", comment: "Show alternative text")
|
Text("settings.title.alwaysShowAltTitle", comment: "Show alternative text")
|
||||||
Text("settings.title.alwaysShowAltDescription", comment: "Show alternative text if present on status details screen")
|
Text("settings.title.alwaysShowAltDescription", comment: "Show alternative text if present on status details screen")
|
||||||
|
@ -38,15 +34,33 @@ struct MediaSettingsView: View {
|
||||||
.foregroundColor(.lightGrayColor)
|
.foregroundColor(.lightGrayColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: showPhotoDescription) { newValue in
|
.onChange(of: self.applicationState.showPhotoDescription) { newValue in
|
||||||
self.applicationState.showPhotoDescription = newValue
|
|
||||||
ApplicationSettingsHandler.shared.set(showPhotoDescription: newValue)
|
ApplicationSettingsHandler.shared.set(showPhotoDescription: newValue)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.onAppear {
|
Toggle(isOn: $applicationState.showAvatarsOnTimeline) {
|
||||||
let defaultSettings = ApplicationSettingsHandler.shared.get()
|
VStack(alignment: .leading) {
|
||||||
self.showSensitive = defaultSettings.showSensitive
|
Text("settings.title.showAvatars", comment: "Show avatars")
|
||||||
self.showPhotoDescription = defaultSettings.showPhotoDescription
|
Text("settings.title.showAvatarsOnTimeline", comment: "Show avatars on timeline")
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundColor(.lightGrayColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: self.applicationState.showAvatarsOnTimeline) { newValue in
|
||||||
|
ApplicationSettingsHandler.shared.set(showAvatarsOnTimeline: newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
Toggle(isOn: $applicationState.showFavouritesOnTimeline) {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text("settings.title.showFavourite", comment: "Show favourites")
|
||||||
|
Text("settings.title.showFavouriteOnTimeline", comment: "Show favourites on timeline")
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundColor(.lightGrayColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: self.applicationState.showFavouritesOnTimeline) { newValue in
|
||||||
|
ApplicationSettingsHandler.shared.set(showFavouritesOnTimeline: newValue)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ServicesKit
|
import ServicesKit
|
||||||
|
import WidgetsKit
|
||||||
|
|
||||||
struct ImageRow: View {
|
struct ImageRow: View {
|
||||||
private let status: StatusData
|
private let status: StatusData
|
||||||
|
@ -84,7 +85,8 @@ struct ImageRow: View {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.frame(width: self.imageWidth, height: self.imageHeight)
|
.frame(width: self.imageWidth, height: self.imageHeight)
|
||||||
.tabViewStyle(PageTabViewStyle())
|
.tabViewStyle(.page(indexDisplayMode: .never))
|
||||||
|
.overlay(CustomPageTabViewStyleView(pages: self.attachmentsData, currentId: $selected))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import SwiftUI
|
||||||
import PixelfedKit
|
import PixelfedKit
|
||||||
import ClientKit
|
import ClientKit
|
||||||
import ServicesKit
|
import ServicesKit
|
||||||
|
import WidgetsKit
|
||||||
|
|
||||||
struct ImageRowAsync: View {
|
struct ImageRowAsync: View {
|
||||||
private let statusViewModel: StatusModel
|
private let statusViewModel: StatusModel
|
||||||
|
@ -86,7 +87,8 @@ struct ImageRowAsync: View {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.frame(width: self.imageWidth, height: self.imageHeight)
|
.frame(width: self.imageWidth, height: self.imageHeight)
|
||||||
.tabViewStyle(PageTabViewStyle())
|
.tabViewStyle(.page(indexDisplayMode: .never))
|
||||||
|
.overlay(CustomPageTabViewStyleView(pages: self.statusViewModel.mediaAttachments, currentId: $selected))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,6 +104,10 @@ struct ImageRowItem: View {
|
||||||
|
|
||||||
ImageAvatar(displayName: self.status.accountDisplayName, avatarUrl: self.status.accountAvatar)
|
ImageAvatar(displayName: self.status.accountDisplayName, avatarUrl: self.status.accountAvatar)
|
||||||
ImageFavourite(isFavourited: $isFavourited)
|
ImageFavourite(isFavourited: $isFavourited)
|
||||||
|
ImageAlternativeText(text: self.attachmentData.text) { text in
|
||||||
|
self.routerPath.presentedAlert = .alternativeText(text: text)
|
||||||
|
}
|
||||||
|
|
||||||
FavouriteTouch(showFavouriteAnimation: $showThumbImage)
|
FavouriteTouch(showFavouriteAnimation: $showThumbImage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,6 +118,10 @@ struct ImageRowItemAsync: View {
|
||||||
avatarUrl: self.statusViewModel.account.avatar)
|
avatarUrl: self.statusViewModel.account.avatar)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImageAlternativeText(text: self.attachment.description) { text in
|
||||||
|
self.routerPath.presentedAlert = .alternativeText(text: text)
|
||||||
|
}
|
||||||
|
|
||||||
ImageFavourite(isFavourited: $isFavourited)
|
ImageFavourite(isFavourited: $isFavourited)
|
||||||
FavouriteTouch(showFavouriteAnimation: $showThumbImage)
|
FavouriteTouch(showFavouriteAnimation: $showThumbImage)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
//
|
||||||
|
// https://mczachurski.dev
|
||||||
|
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||||
|
// Licensed under the Apache License 2.0.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
public struct CustomPageTabViewStyleView<T>: View where T: Identifiable<String> {
|
||||||
|
@Binding var currentId: String
|
||||||
|
|
||||||
|
private let pages: [T]
|
||||||
|
private let circleSize: CGFloat = 8
|
||||||
|
private let circleSpacing: CGFloat = 9
|
||||||
|
|
||||||
|
private let primaryColor = Color.white.opacity(0.7)
|
||||||
|
private let secondaryColor = Color.white.opacity(0.4)
|
||||||
|
|
||||||
|
public init(pages: [T], currentId: Binding<String>) {
|
||||||
|
self.pages = pages
|
||||||
|
self._currentId = currentId
|
||||||
|
}
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
VStack {
|
||||||
|
Spacer()
|
||||||
|
HStack(spacing: circleSpacing) {
|
||||||
|
ForEach(self.pages, id: \.id) { page in
|
||||||
|
Circle()
|
||||||
|
.fill(currentId == page.id ? primaryColor : secondaryColor)
|
||||||
|
.frame(width: circleSize, height: circleSize)
|
||||||
|
.id(page.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
//
|
||||||
|
// https://mczachurski.dev
|
||||||
|
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||||
|
// Licensed under the Apache License 2.0.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import EnvironmentKit
|
||||||
|
|
||||||
|
public struct ImageAlternativeText: View {
|
||||||
|
@EnvironmentObject var applicationState: ApplicationState
|
||||||
|
|
||||||
|
private let text: String?
|
||||||
|
private let open: (String) -> Void
|
||||||
|
|
||||||
|
public init(text: String?, open: @escaping (String) -> Void) {
|
||||||
|
self.text = text
|
||||||
|
self.open = open
|
||||||
|
}
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
if let text = self.text, text.count > 0 && self.applicationState.showPhotoDescription {
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
HStack(alignment: .center) {
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Button {
|
||||||
|
self.open(text)
|
||||||
|
} label: {
|
||||||
|
Text("status.title.altText", comment: "ALT")
|
||||||
|
.font(.system(size: 12))
|
||||||
|
.shadow(color: .black, radius: 4)
|
||||||
|
.foregroundColor(.white.opacity(0.8))
|
||||||
|
.padding(.vertical, 4)
|
||||||
|
.padding(.horizontal, 8)
|
||||||
|
.background(RoundedRectangle(cornerRadius: 8).foregroundColor(.black.opacity(0.8)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.trailing, 12)
|
||||||
|
.padding(.bottom, 12)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue