Merge branch 'develop' into patch-1
This commit is contained in:
commit
c5b8715f99
|
@ -31,6 +31,7 @@ extension ApplicationSettings {
|
|||
@NSManaged public var menuPosition: Int32
|
||||
@NSManaged public var showAvatarsOnTimeline: Bool
|
||||
@NSManaged public var showFavouritesOnTimeline: Bool
|
||||
@NSManaged public var showAltIconOnTimeline: Bool
|
||||
}
|
||||
|
||||
extension ApplicationSettings: Identifiable {
|
||||
|
|
|
@ -56,6 +56,7 @@ class ApplicationSettingsHandler {
|
|||
applicationState.showPhotoDescription = defaultSettings.showPhotoDescription
|
||||
applicationState.showAvatarsOnTimeline = defaultSettings.showAvatarsOnTimeline
|
||||
applicationState.showFavouritesOnTimeline = defaultSettings.showFavouritesOnTimeline
|
||||
applicationState.showAltIconOnTimeline = defaultSettings.showAltIconOnTimeline
|
||||
|
||||
if let menuPosition = MenuPosition(rawValue: Int(defaultSettings.menuPosition)) {
|
||||
applicationState.menuPosition = menuPosition
|
||||
|
@ -158,6 +159,12 @@ class ApplicationSettingsHandler {
|
|||
CoreDataHandler.shared.save()
|
||||
}
|
||||
|
||||
func set(showAltIconOnTimeline: Bool) {
|
||||
let defaultSettings = self.get()
|
||||
defaultSettings.showAltIconOnTimeline = showAltIconOnTimeline
|
||||
CoreDataHandler.shared.save()
|
||||
}
|
||||
|
||||
private func createApplicationSettingsEntity(viewContext: NSManagedObjectContext? = nil) -> ApplicationSettings {
|
||||
let context = viewContext ?? CoreDataHandler.shared.container.viewContext
|
||||
return ApplicationSettings(context: context)
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>_XCCurrentVersionName</key>
|
||||
<string>Vernissage-009.xcdatamodel</string>
|
||||
<string>Vernissage-010.xcdatamodel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21754" systemVersion="22E261" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="AccountData" representedClassName="AccountData" syncable="YES">
|
||||
<attribute name="accessToken" optional="YES" attributeType="String"/>
|
||||
<attribute name="acct" attributeType="String"/>
|
||||
<attribute name="avatar" optional="YES" attributeType="URI"/>
|
||||
<attribute name="avatarData" optional="YES" attributeType="Binary"/>
|
||||
<attribute name="clientId" attributeType="String"/>
|
||||
<attribute name="clientSecret" attributeType="String"/>
|
||||
<attribute name="clientVapidKey" attributeType="String"/>
|
||||
<attribute name="createdAt" attributeType="String"/>
|
||||
<attribute name="displayName" optional="YES" attributeType="String"/>
|
||||
<attribute name="followersCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="followingCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="header" optional="YES" attributeType="URI"/>
|
||||
<attribute name="id" attributeType="String"/>
|
||||
<attribute name="lastSeenStatusId" optional="YES" attributeType="String"/>
|
||||
<attribute name="locked" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="note" optional="YES" attributeType="String"/>
|
||||
<attribute name="refreshToken" optional="YES" attributeType="String"/>
|
||||
<attribute name="serverUrl" attributeType="URI"/>
|
||||
<attribute name="statusesCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="url" optional="YES" attributeType="URI"/>
|
||||
<attribute name="username" attributeType="String"/>
|
||||
<relationship name="statuses" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="StatusData" inverseName="pixelfedAccount" inverseEntity="StatusData"/>
|
||||
</entity>
|
||||
<entity name="ApplicationSettings" representedClassName="ApplicationSettings" syncable="YES">
|
||||
<attribute name="activeIcon" attributeType="String" defaultValueString="Default"/>
|
||||
<attribute name="avatarShape" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="YES"/>
|
||||
<attribute name="currentAccount" optional="YES" attributeType="String"/>
|
||||
<attribute name="hapticAnimationEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="hapticButtonPressEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="hapticNotificationEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="hapticRefreshEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="hapticTabSelectionEnabled" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
|
||||
<attribute name="lastRefreshTokens" attributeType="Date" defaultDateTimeInterval="694256400" usesScalarValueType="NO"/>
|
||||
<attribute name="menuPosition" attributeType="Integer 32" defaultValueString="1" usesScalarValueType="YES"/>
|
||||
<attribute name="showAltIconOnTimeline" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="showAvatarsOnTimeline" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="showFavouritesOnTimeline" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="showPhotoDescription" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="showSensitive" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="theme" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="tintColor" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="YES"/>
|
||||
</entity>
|
||||
<entity name="AttachmentData" representedClassName="AttachmentData" syncable="YES">
|
||||
<attribute name="blurhash" optional="YES" attributeType="String"/>
|
||||
<attribute name="data" optional="YES" attributeType="Binary" allowsExternalBinaryDataStorage="YES"/>
|
||||
<attribute name="exifCamera" optional="YES" attributeType="String"/>
|
||||
<attribute name="exifCreatedDate" optional="YES" attributeType="String"/>
|
||||
<attribute name="exifExposure" optional="YES" attributeType="String"/>
|
||||
<attribute name="exifLens" optional="YES" attributeType="String"/>
|
||||
<attribute name="id" attributeType="String"/>
|
||||
<attribute name="metaImageHeight" optional="YES" attributeType="Integer 32" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="metaImageWidth" optional="YES" attributeType="Integer 32" defaultValueString="0.0" usesScalarValueType="YES"/>
|
||||
<attribute name="previewUrl" optional="YES" attributeType="URI"/>
|
||||
<attribute name="remoteUrl" optional="YES" attributeType="URI"/>
|
||||
<attribute name="statusId" attributeType="String"/>
|
||||
<attribute name="text" optional="YES" attributeType="String"/>
|
||||
<attribute name="type" attributeType="String"/>
|
||||
<attribute name="url" attributeType="URI"/>
|
||||
<relationship name="statusRelation" maxCount="1" deletionRule="Nullify" destinationEntity="StatusData" inverseName="attachmentsRelation" inverseEntity="StatusData"/>
|
||||
</entity>
|
||||
<entity name="StatusData" representedClassName="StatusData" syncable="YES">
|
||||
<attribute name="accountAvatar" optional="YES" attributeType="URI"/>
|
||||
<attribute name="accountDisplayName" optional="YES" attributeType="String"/>
|
||||
<attribute name="accountId" attributeType="String"/>
|
||||
<attribute name="accountUsername" optional="YES" attributeType="String"/>
|
||||
<attribute name="applicationName" optional="YES" attributeType="String"/>
|
||||
<attribute name="applicationWebsite" optional="YES" attributeType="URI"/>
|
||||
<attribute name="bookmarked" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="content" attributeType="String"/>
|
||||
<attribute name="createdAt" attributeType="String"/>
|
||||
<attribute name="favourited" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="favouritesCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="id" attributeType="String"/>
|
||||
<attribute name="inReplyToAccount" optional="YES" attributeType="String"/>
|
||||
<attribute name="inReplyToId" optional="YES" attributeType="String"/>
|
||||
<attribute name="muted" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="pinned" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="reblogged" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="rebloggedAccountAvatar" optional="YES" attributeType="URI"/>
|
||||
<attribute name="rebloggedAccountDisplayName" optional="YES" attributeType="String"/>
|
||||
<attribute name="rebloggedAccountId" optional="YES" attributeType="String"/>
|
||||
<attribute name="rebloggedAccountUsername" optional="YES" attributeType="String"/>
|
||||
<attribute name="rebloggedStatusId" optional="YES" attributeType="String"/>
|
||||
<attribute name="reblogsCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="repliesCount" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
<attribute name="sensitive" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
|
||||
<attribute name="spoilerText" optional="YES" attributeType="String"/>
|
||||
<attribute name="uri" attributeType="String"/>
|
||||
<attribute name="url" optional="YES" attributeType="URI"/>
|
||||
<attribute name="visibility" attributeType="String"/>
|
||||
<relationship name="attachmentsRelation" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="AttachmentData" inverseName="statusRelation" inverseEntity="AttachmentData"/>
|
||||
<relationship name="pixelfedAccount" maxCount="1" deletionRule="Nullify" destinationEntity="AccountData" inverseName="statuses" inverseEntity="AccountData"/>
|
||||
</entity>
|
||||
</model>
|
|
@ -95,6 +95,9 @@ public class ApplicationState: ObservableObject {
|
|||
/// Should favourites be visible on timelines.
|
||||
@Published public var showFavouritesOnTimeline = false
|
||||
|
||||
/// Should ALT icon be visible on timelines.
|
||||
@Published public var showAltIconOnTimeline = false
|
||||
|
||||
public func changeApplicationState(accountModel: AccountModel, instance: Instance?, lastSeenStatusId: String?) {
|
||||
self.account = accountModel
|
||||
self.lastSeenStatusId = lastSeenStatusId
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
"global.title.success" = "Success";
|
||||
"global.title.photoSaved" = "Photo has been saved.";
|
||||
"global.title.ok" = "OK";
|
||||
"global.title.showMore" = "Show more";
|
||||
"global.title.showLess" = "Show less";
|
||||
|
||||
// MARK: Global errors.
|
||||
"global.error.unexpected" = "Unexpected error.";
|
||||
|
@ -218,6 +220,8 @@
|
|||
"settings.title.showAvatarsOnTimeline" = "Avatars will be displayed on timelines";
|
||||
"settings.title.showFavourite" = "Show favourites";
|
||||
"settings.title.showFavouriteOnTimeline" = "Favourites will be displayed on timelines";
|
||||
"settings.title.showAltText" = "Show ALT icon";
|
||||
"settings.title.showAltTextOnTimeline" = "ALT icon will be displayed on timelines";
|
||||
|
||||
// Mark: Signin view.
|
||||
"signin.navigationBar.title" = "Sign in to Pixelfed";
|
||||
|
@ -259,6 +263,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.";
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
"global.title.success" = "Primeran";
|
||||
"global.title.photoSaved" = "Argazkia gorde da.";
|
||||
"global.title.ok" = "Ados";
|
||||
"global.title.showMore" = "Show more";
|
||||
"global.title.showLess" = "Show less";
|
||||
|
||||
// MARK: Global errors.
|
||||
"global.error.unexpected" = "Espero ez zen errorea.";
|
||||
|
@ -218,6 +220,8 @@
|
|||
"settings.title.showAvatarsOnTimeline" = "Abatarrak denbora-lerroan erakutsiko dira";
|
||||
"settings.title.showFavourite" = "Erakutsi gogokoak";
|
||||
"settings.title.showFavouriteOnTimeline" = "Gogokoak denbora-lerroan erakutsiko dira";
|
||||
"settings.title.showAltText" = "Show ALT icon";
|
||||
"settings.title.showAltTextOnTimeline" = "ALT icon will be displayed on timelines";
|
||||
|
||||
// Mark: Signin view.
|
||||
"signin.navigationBar.title" = "Hasi saioa Pixelfed-en";
|
||||
|
@ -259,6 +263,7 @@
|
|||
"status.title.showMediaDescription" = "Erakutsi multimediaren deskribapena";
|
||||
"status.title.mediaDescription" = "Multimediaren deskribapena";
|
||||
"status.title.shareImage" = "Partekatu irudia";
|
||||
"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.";
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
"global.title.success" = "Sukces";
|
||||
"global.title.photoSaved" = "Zdjęcie zostało zapisane.";
|
||||
"global.title.ok" = "OK";
|
||||
"global.title.showMore" = "Pokaż więcej";
|
||||
"global.title.showLess" = "Pokaż mniej";
|
||||
|
||||
// MARK: Global errors.
|
||||
"global.error.unexpected" = "Wystąpił nieoczekiwany błąd.";
|
||||
|
@ -218,6 +220,8 @@
|
|||
"settings.title.showAvatarsOnTimeline" = "Awatary będą widoczne na osiach zdjęć";
|
||||
"settings.title.showFavourite" = "Wyświetlaj polubienia";
|
||||
"settings.title.showFavouriteOnTimeline" = "Polubienia będą widoczne na osiach zdjęć";
|
||||
"settings.title.showAltText" = "Wyświetlaj ikonę ALT";
|
||||
"settings.title.showAltTextOnTimeline" = "Ikony ALT będą widonczne na osiach zdjęć";
|
||||
|
||||
// Mark: Signin view.
|
||||
"signin.navigationBar.title" = "Zaloguj się do Pixelfed";
|
||||
|
@ -259,6 +263,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.";
|
||||
|
|
|
@ -234,6 +234,7 @@
|
|||
F83CBEFA298298A1002972C8 /* ImageCarouselPicture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCarouselPicture.swift; sourceTree = "<group>"; };
|
||||
F844F42429D2DC39000DD896 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
|
||||
F858906A29E1CC7A00D4BDED /* UIApplication+Window.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIApplication+Window.swift"; sourceTree = "<group>"; };
|
||||
F85B586C29ED169B00A16D12 /* Vernissage-010.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-010.xcdatamodel"; sourceTree = "<group>"; };
|
||||
F85D4970296402DC00751DF7 /* AuthorizationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationService.swift; sourceTree = "<group>"; };
|
||||
F85D4974296407F100751DF7 /* HomeTimelineService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTimelineService.swift; sourceTree = "<group>"; };
|
||||
F85D497629640A5200751DF7 /* ImageRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRow.swift; sourceTree = "<group>"; };
|
||||
|
@ -1181,7 +1182,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = VernissageWidget/VernissageWidgetExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 118;
|
||||
CURRENT_PROJECT_VERSION = 119;
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = VernissageWidget/Info.plist;
|
||||
|
@ -1209,7 +1210,7 @@
|
|||
ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;
|
||||
CODE_SIGN_ENTITLEMENTS = VernissageWidget/VernissageWidgetExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 118;
|
||||
CURRENT_PROJECT_VERSION = 119;
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = VernissageWidget/Info.plist;
|
||||
|
@ -1236,7 +1237,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = VernissageShare/VernissageShareExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 118;
|
||||
CURRENT_PROJECT_VERSION = 119;
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = VernissageShare/Info.plist;
|
||||
|
@ -1263,7 +1264,7 @@
|
|||
buildSettings = {
|
||||
CODE_SIGN_ENTITLEMENTS = VernissageShare/VernissageShareExtension.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 118;
|
||||
CURRENT_PROJECT_VERSION = 119;
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = VernissageShare/Info.plist;
|
||||
|
@ -1412,7 +1413,7 @@
|
|||
CODE_SIGN_ENTITLEMENTS = Vernissage/Vernissage.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 118;
|
||||
CURRENT_PROJECT_VERSION = 119;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
@ -1453,7 +1454,7 @@
|
|||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||
CODE_SIGN_ENTITLEMENTS = Vernissage/Vernissage.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 118;
|
||||
CURRENT_PROJECT_VERSION = 119;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = B2U9FEKYP8;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
@ -1623,6 +1624,7 @@
|
|||
F88C2476295C37BB0006098B /* Vernissage.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
F85B586C29ED169B00A16D12 /* Vernissage-010.xcdatamodel */,
|
||||
F8FFBD4929E99BEE0047EE80 /* Vernissage-009.xcdatamodel */,
|
||||
F8A4A88429E4099900267E36 /* Vernissage-008.xcdatamodel */,
|
||||
F8911A1829DE9E5500770F44 /* Vernissage-007.xcdatamodel */,
|
||||
|
@ -1634,7 +1636,7 @@
|
|||
F8C937A929882CA90004D782 /* Vernissage-001.xcdatamodel */,
|
||||
F88C2477295C37BB0006098B /* Vernissage.xcdatamodel */,
|
||||
);
|
||||
currentVersion = F8FFBD4929E99BEE0047EE80 /* Vernissage-009.xcdatamodel */;
|
||||
currentVersion = F85B586C29ED169B00A16D12 /* Vernissage-010.xcdatamodel */;
|
||||
path = Vernissage.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
versionGroupType = wrapper.xcdatamodel;
|
||||
|
|
|
@ -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,45 @@ 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)
|
||||
}
|
||||
|
||||
Toggle(isOn: $applicationState.showAltIconOnTimeline) {
|
||||
VStack(alignment: .leading) {
|
||||
Text("settings.title.showAltText", comment: "Show ALT icon")
|
||||
Text("settings.title.showAltTextOnTimeline", comment: "ALT icon will be displayed on timelines")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.lightGrayColor)
|
||||
}
|
||||
}
|
||||
.onChange(of: self.applicationState.showAltIconOnTimeline) { newValue in
|
||||
ApplicationSettingsHandler.shared.set(showAltIconOnTimeline: newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,7 +130,7 @@ struct StatusView: View {
|
|||
LabelIcon(iconName: "calendar", value: self.exifCreatedDate?.toDate(.isoDateTimeSec)?.formatted())
|
||||
|
||||
if self.applicationState.showPhotoDescription {
|
||||
LabelIcon(iconName: "eye.trianglebadge.exclamationmark", value: self.description)
|
||||
LabelIcon(iconName: "eye.trianglebadge.exclamationmark", value: self.description, isExpandable: true)
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 2)
|
||||
|
|
|
@ -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,68 @@
|
|||
//
|
||||
// https://mczachurski.dev
|
||||
// Copyright © 2023 Marcin Czachurski and the repository contributors.
|
||||
// Licensed under the MIT License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct ExpandableText: View {
|
||||
let text: String
|
||||
let lineLimit: Int
|
||||
|
||||
@State private var isExpanded = false
|
||||
@State private var isTruncated: Bool?
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Text(text)
|
||||
.lineLimit(isExpanded ? nil : lineLimit)
|
||||
.background(calculateTruncation(text: text))
|
||||
|
||||
if isTruncated == true {
|
||||
button
|
||||
}
|
||||
}
|
||||
// Re-calculate isTruncated for the new text
|
||||
.onChange(of: text, perform: { _ in isTruncated = nil })
|
||||
}
|
||||
|
||||
func calculateTruncation(text: String) -> some View {
|
||||
// Select the view that fits in the background of the line-limited text.
|
||||
ViewThatFits(in: .vertical) {
|
||||
Text(text)
|
||||
.hidden()
|
||||
.onAppear {
|
||||
// If the whole text fits, then isTruncated is set to false and no button is shown.
|
||||
guard isTruncated == nil else { return }
|
||||
isTruncated = false
|
||||
}
|
||||
Color.clear
|
||||
.hidden()
|
||||
.onAppear {
|
||||
// If the whole text does not fit, Color.clear is selected,
|
||||
// isTruncated is set to true and button is shown.
|
||||
guard isTruncated == nil else { return }
|
||||
isTruncated = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var button: some View {
|
||||
HStack {
|
||||
Spacer()
|
||||
Button {
|
||||
withAnimation {
|
||||
isExpanded.toggle()
|
||||
}
|
||||
} label: {
|
||||
Text(isExpanded ? "global.title.showLess" : "global.title.showMore", comment: "Show less/more")
|
||||
.foregroundColor(.accentColor)
|
||||
.textCase(.uppercase)
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
}
|
|
@ -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.showAltIconOnTimeline {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,10 +9,12 @@ import SwiftUI
|
|||
public struct LabelIcon: View {
|
||||
let iconName: String
|
||||
let value: String?
|
||||
let isExpandable: Bool
|
||||
|
||||
public init(iconName: String, value: String?) {
|
||||
public init(iconName: String, value: String?, isExpandable: Bool = false) {
|
||||
self.iconName = iconName
|
||||
self.value = value
|
||||
self.isExpandable = isExpandable
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
|
@ -20,8 +22,14 @@ public struct LabelIcon: View {
|
|||
HStack(alignment: .center) {
|
||||
Image(systemName: iconName)
|
||||
.frame(width: 24, alignment: .center)
|
||||
Text(value)
|
||||
.font(.footnote)
|
||||
|
||||
if self.isExpandable {
|
||||
ExpandableText(text: value, lineLimit: 3)
|
||||
.font(.footnote)
|
||||
} else {
|
||||
Text(value)
|
||||
.font(.footnote)
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 2)
|
||||
} else {
|
||||
|
|
Loading…
Reference in New Issue