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.mediaDescription" = "Media description";
|
||||
"status.title.shareImage" = "Share image";
|
||||
"status.title.altText" = "ALT";
|
||||
"status.error.loadingStatusFailed" = "Loading status failed.";
|
||||
"status.error.notFound" = "Status not existing anymore.";
|
||||
"status.error.loadingCommentsFailed" = "Comments cannot be downloaded.";
|
||||
|
|
|
@ -259,6 +259,7 @@
|
|||
"status.title.showMediaDescription" = "Show media description";
|
||||
"status.title.mediaDescription" = "Media description";
|
||||
"status.title.shareImage" = "Share image";
|
||||
"status.title.altText" = "ALT";
|
||||
"status.error.loadingStatusFailed" = "Egoera kargatzeak huts egin du.";
|
||||
"status.error.notFound" = "Egoera ez da dagoeneko existitzen.";
|
||||
"status.error.loadingCommentsFailed" = "Ezin dira iruzkinak eskuratu.";
|
||||
|
|
|
@ -259,6 +259,7 @@
|
|||
"status.title.showMediaDescription" = "Pokaż opis zdjęcia";
|
||||
"status.title.mediaDescription" = "Opis zdjęcia";
|
||||
"status.title.shareImage" = "Udostępnij zdjęcie";
|
||||
"status.title.altText" = "ALT";
|
||||
"status.error.loadingStatusFailed" = "Błąd podczas wczytywanie statusu.";
|
||||
"status.error.notFound" = "Status już nie istnieje.";
|
||||
"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
|
||||
}
|
||||
|
||||
enum AlertDestinations: Identifiable {
|
||||
case alternativeText(text: String)
|
||||
case savePhotoSuccess
|
||||
|
||||
public var id: String {
|
||||
switch self {
|
||||
case .alternativeText:
|
||||
return "alternativeText"
|
||||
case .savePhotoSuccess:
|
||||
return "savePhotoSuccess"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
class RouterPath: ObservableObject {
|
||||
public var urlHandler: ((URL) -> OpenURLAction.Result)?
|
||||
|
@ -60,6 +74,7 @@ class RouterPath: ObservableObject {
|
|||
@Published public var path: [RouteurDestinations] = []
|
||||
@Published public var presentedSheet: SheetDestinations?
|
||||
@Published public var presentedOverlay: OverlayDestinations?
|
||||
@Published public var presentedAlert: AlertDestinations?
|
||||
|
||||
public init() {}
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@ struct VernissageApp: App {
|
|||
.withAppRouteur()
|
||||
.withSheetDestinations(sheetDestinations: $routerPath.presentedSheet)
|
||||
.withOverlayDestinations(overlayDestinations: $routerPath.presentedOverlay)
|
||||
.withAlertDestinations(alertDestinations: $routerPath.presentedAlert)
|
||||
}
|
||||
}
|
||||
.environment(\.managedObjectContext, coreDataHandler.container.viewContext)
|
||||
|
|
|
@ -20,22 +20,9 @@ public extension View {
|
|||
}
|
||||
|
||||
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 routerPath: RouterPath
|
||||
|
||||
@State private var alertInfo: AlertInfo?
|
||||
|
||||
private let id: String
|
||||
private let url: URL?
|
||||
private let altText: String?
|
||||
|
@ -92,11 +79,7 @@ private struct ImageContextMenu: ViewModifier {
|
|||
|
||||
if let altText, altText.count > 0 {
|
||||
Button {
|
||||
self.alertInfo = AlertInfo(
|
||||
id: .showAlternativeText,
|
||||
title: Text("status.title.mediaDescription", comment: "Media description"),
|
||||
message: Text(altText)
|
||||
)
|
||||
self.routerPath.presentedAlert = .alternativeText(text: altText)
|
||||
} label: {
|
||||
Label("status.title.showMediaDescription", systemImage: "eye.trianglebadge.exclamationmark")
|
||||
}
|
||||
|
@ -113,11 +96,7 @@ private struct ImageContextMenu: ViewModifier {
|
|||
|
||||
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")
|
||||
)
|
||||
self.routerPath.presentedAlert = .savePhotoSuccess
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
@ -83,30 +83,6 @@ struct GeneralSectionView: View {
|
|||
.onChange(of: self.applicationState.menuPosition) { menuPosition in
|
||||
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
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
|
||||
@State var showSensitive = true
|
||||
@State var showPhotoDescription = true
|
||||
|
||||
var body: some View {
|
||||
Section("settings.title.mediaSettings") {
|
||||
|
||||
Toggle(isOn: $showSensitive) {
|
||||
Toggle(isOn: $applicationState.showSensitive) {
|
||||
VStack(alignment: .leading) {
|
||||
Text("settings.title.alwaysShowSensitiveTitle", comment: "Always show NSFW")
|
||||
Text("settings.title.alwaysShowSensitiveDescription", comment: "Force show all NFSW (sensitive) media without warnings")
|
||||
|
@ -25,12 +22,11 @@ struct MediaSettingsView: View {
|
|||
.foregroundColor(.lightGrayColor)
|
||||
}
|
||||
}
|
||||
.onChange(of: showSensitive) { newValue in
|
||||
self.applicationState.showSensitive = newValue
|
||||
.onChange(of: self.applicationState.showSensitive) { newValue in
|
||||
ApplicationSettingsHandler.shared.set(showSensitive: newValue)
|
||||
}
|
||||
|
||||
Toggle(isOn: $showPhotoDescription) {
|
||||
Toggle(isOn: $applicationState.showPhotoDescription) {
|
||||
VStack(alignment: .leading) {
|
||||
Text("settings.title.alwaysShowAltTitle", comment: "Show alternative text")
|
||||
Text("settings.title.alwaysShowAltDescription", comment: "Show alternative text if present on status details screen")
|
||||
|
@ -38,15 +34,33 @@ struct MediaSettingsView: View {
|
|||
.foregroundColor(.lightGrayColor)
|
||||
}
|
||||
}
|
||||
.onChange(of: showPhotoDescription) { newValue in
|
||||
self.applicationState.showPhotoDescription = newValue
|
||||
.onChange(of: self.applicationState.showPhotoDescription) { newValue in
|
||||
ApplicationSettingsHandler.shared.set(showPhotoDescription: newValue)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
let defaultSettings = ApplicationSettingsHandler.shared.get()
|
||||
self.showSensitive = defaultSettings.showSensitive
|
||||
self.showPhotoDescription = defaultSettings.showPhotoDescription
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import SwiftUI
|
||||
import ServicesKit
|
||||
import WidgetsKit
|
||||
|
||||
struct ImageRow: View {
|
||||
private let status: StatusData
|
||||
|
@ -84,7 +85,8 @@ struct ImageRow: View {
|
|||
}
|
||||
})
|
||||
.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 ClientKit
|
||||
import ServicesKit
|
||||
import WidgetsKit
|
||||
|
||||
struct ImageRowAsync: View {
|
||||
private let statusViewModel: StatusModel
|
||||
|
@ -86,7 +87,8 @@ struct ImageRowAsync: View {
|
|||
}
|
||||
})
|
||||
.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)
|
||||
ImageFavourite(isFavourited: $isFavourited)
|
||||
ImageAlternativeText(text: self.attachmentData.text) { text in
|
||||
self.routerPath.presentedAlert = .alternativeText(text: text)
|
||||
}
|
||||
|
||||
FavouriteTouch(showFavouriteAnimation: $showThumbImage)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,6 +118,10 @@ struct ImageRowItemAsync: View {
|
|||
avatarUrl: self.statusViewModel.account.avatar)
|
||||
}
|
||||
|
||||
ImageAlternativeText(text: self.attachment.description) { text in
|
||||
self.routerPath.presentedAlert = .alternativeText(text: text)
|
||||
}
|
||||
|
||||
ImageFavourite(isFavourited: $isFavourited)
|
||||
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