diff --git a/Vernissage.xcodeproj/project.pbxproj b/Vernissage.xcodeproj/project.pbxproj index 82a534b..74b0701 100644 --- a/Vernissage.xcodeproj/project.pbxproj +++ b/Vernissage.xcodeproj/project.pbxproj @@ -133,6 +133,8 @@ F8AD061329A565620042F111 /* String+Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8AD061229A565620042F111 /* String+Random.swift */; }; F8AFF7C129B259150087D083 /* TrendingTagsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8AFF7C029B259150087D083 /* TrendingTagsView.swift */; }; F8AFF7C429B25EF40087D083 /* TagImagesGridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8AFF7C329B25EF40087D083 /* TagImagesGridView.swift */; }; + F8B05ACB29B489B100857221 /* HapticsSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B05ACA29B489B100857221 /* HapticsSectionView.swift */; }; + F8B05ACE29B48E2F00857221 /* MediaSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B05ACD29B48E2F00857221 /* MediaSettingsView.swift */; }; F8B0885E29942E31002AB40A /* ThirdPartyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B0885D29942E31002AB40A /* ThirdPartyView.swift */; }; F8B0886029943498002AB40A /* OtherSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B0885F29943498002AB40A /* OtherSectionView.swift */; }; F8B08862299435C9002AB40A /* SupportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B08861299435C9002AB40A /* SupportView.swift */; }; @@ -287,6 +289,10 @@ F8AD061229A565620042F111 /* String+Random.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Random.swift"; sourceTree = ""; }; F8AFF7C029B259150087D083 /* TrendingTagsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingTagsView.swift; sourceTree = ""; }; F8AFF7C329B25EF40087D083 /* TagImagesGridView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagImagesGridView.swift; sourceTree = ""; }; + F8B05AC929B488C600857221 /* Vernissage-003.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-003.xcdatamodel"; sourceTree = ""; }; + F8B05ACA29B489B100857221 /* HapticsSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HapticsSectionView.swift; sourceTree = ""; }; + F8B05ACC29B48DD000857221 /* Vernissage-004.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Vernissage-004.xcdatamodel"; sourceTree = ""; }; + F8B05ACD29B48E2F00857221 /* MediaSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaSettingsView.swift; sourceTree = ""; }; F8B0885D29942E31002AB40A /* ThirdPartyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdPartyView.swift; sourceTree = ""; }; F8B0885F29943498002AB40A /* OtherSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OtherSectionView.swift; sourceTree = ""; }; F8B08861299435C9002AB40A /* SupportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportView.swift; sourceTree = ""; }; @@ -509,6 +515,8 @@ F8B0885F29943498002AB40A /* OtherSectionView.swift */, F8B08861299435C9002AB40A /* SupportView.swift */, F86A4306299AA5E900DF7645 /* ThanksView.swift */, + F8B05ACA29B489B100857221 /* HapticsSectionView.swift */, + F8B05ACD29B48E2F00857221 /* MediaSettingsView.swift */, ); path = Subviews; sourceTree = ""; @@ -864,6 +872,7 @@ F897978A2968314A00B22335 /* LoadingIndicator.swift in Sources */, F8B9B351298D4B34009CC69C /* Client+Account.swift in Sources */, F8210DE52966E160001D9973 /* Color+SystemColors.swift in Sources */, + F8B05ACB29B489B100857221 /* HapticsSectionView.swift in Sources */, F88FAD2B295F43B8009B20C9 /* AccountData+CoreDataProperties.swift in Sources */, F85D4975296407F100751DF7 /* HomeTimelineService.swift in Sources */, F80048062961850500E6868A /* StatusData+CoreDataProperties.swift in Sources */, @@ -887,6 +896,7 @@ F8B9B353298D4B5D009CC69C /* Client+Search.swift in Sources */, F85D497B29640C8200751DF7 /* UsernameRow.swift in Sources */, F86A4305299AA12800DF7645 /* PurchaseError.swift in Sources */, + F8B05ACE29B48E2F00857221 /* MediaSettingsView.swift in Sources */, F89D6C4429718092001DA3D4 /* AccentsSectionView.swift in Sources */, F88E4D42297E69FD0057491A /* StatusesView.swift in Sources */, F86FB555298BF83F000131F0 /* FavouriteTouch.swift in Sources */, @@ -1115,7 +1125,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 48; + CURRENT_PROJECT_VERSION = 49; DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\""; DEVELOPMENT_TEAM = B2U9FEKYP8; ENABLE_PREVIEWS = YES; @@ -1152,7 +1162,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 48; + CURRENT_PROJECT_VERSION = 49; DEVELOPMENT_ASSET_PATHS = "\"Vernissage/Preview Content\""; DEVELOPMENT_TEAM = B2U9FEKYP8; ENABLE_PREVIEWS = YES; @@ -1281,11 +1291,13 @@ F88C2476295C37BB0006098B /* Vernissage.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + F8B05ACC29B48DD000857221 /* Vernissage-004.xcdatamodel */, + F8B05AC929B488C600857221 /* Vernissage-003.xcdatamodel */, F89F0605299139F6003DC875 /* Vernissage-002.xcdatamodel */, F8C937A929882CA90004D782 /* Vernissage-001.xcdatamodel */, F88C2477295C37BB0006098B /* Vernissage.xcdatamodel */, ); - currentVersion = F89F0605299139F6003DC875 /* Vernissage-002.xcdatamodel */; + currentVersion = F8B05ACC29B48DD000857221 /* Vernissage-004.xcdatamodel */; path = Vernissage.xcdatamodeld; sourceTree = ""; versionGroupType = wrapper.xcdatamodel; diff --git a/Vernissage/CoreData/ApplicationSettings+CoreDataProperties.swift b/Vernissage/CoreData/ApplicationSettings+CoreDataProperties.swift index 5efe575..19b5862 100644 --- a/Vernissage/CoreData/ApplicationSettings+CoreDataProperties.swift +++ b/Vernissage/CoreData/ApplicationSettings+CoreDataProperties.swift @@ -18,6 +18,14 @@ extension ApplicationSettings { @NSManaged public var tintColor: Int32 @NSManaged public var avatarShape: Int32 @NSManaged public var lastRefreshTokens: Date + + @NSManaged public var hapticTabSelectionEnabled: Bool + @NSManaged public var hapticRefreshEnabled: Bool + @NSManaged public var hapticButtonPressEnabled: Bool + @NSManaged public var hapticAnimationEnabled: Bool + @NSManaged public var hapticNotificationEnabled: Bool + + @NSManaged public var showSensitive: Bool } extension ApplicationSettings : Identifiable { diff --git a/Vernissage/CoreData/ApplicationSettingsHandler.swift b/Vernissage/CoreData/ApplicationSettingsHandler.swift index 24414ae..60cb5f7 100644 --- a/Vernissage/CoreData/ApplicationSettingsHandler.swift +++ b/Vernissage/CoreData/ApplicationSettingsHandler.swift @@ -62,4 +62,40 @@ class ApplicationSettingsHandler { let context = CoreDataHandler.shared.container.viewContext return ApplicationSettings(context: context) } + + func setHapticTabSelectionEnabled(value: Bool) { + let defaultSettings = self.getDefaultSettings() + defaultSettings.hapticTabSelectionEnabled = value + CoreDataHandler.shared.save() + } + + func setHapticRefreshEnabled(value: Bool) { + let defaultSettings = self.getDefaultSettings() + defaultSettings.hapticRefreshEnabled = value + CoreDataHandler.shared.save() + } + + func setHapticAnimationEnabled(value: Bool) { + let defaultSettings = self.getDefaultSettings() + defaultSettings.hapticAnimationEnabled = value + CoreDataHandler.shared.save() + } + + func setHapticNotificationEnabled(value: Bool) { + let defaultSettings = self.getDefaultSettings() + defaultSettings.hapticNotificationEnabled = value + CoreDataHandler.shared.save() + } + + func setHapticButtonPressEnabled(value: Bool) { + let defaultSettings = self.getDefaultSettings() + defaultSettings.hapticButtonPressEnabled = value + CoreDataHandler.shared.save() + } + + func setShowSensitive(value: Bool) { + let defaultSettings = self.getDefaultSettings() + defaultSettings.showSensitive = value + CoreDataHandler.shared.save() + } } diff --git a/Vernissage/EnvironmentObjects/ApplicationState.swift b/Vernissage/EnvironmentObjects/ApplicationState.swift index dfb6b1f..de68703 100644 --- a/Vernissage/EnvironmentObjects/ApplicationState.swift +++ b/Vernissage/EnvironmentObjects/ApplicationState.swift @@ -55,6 +55,24 @@ public class ApplicationState: ObservableObject { /// Status id for showed interaction row. @Published var showInteractionStatusId = String.empty() + /// Should we fire haptic when user change tabs. + @Published var hapticTabSelectionEnabled = true + + /// Should we fire haptic when user refresh list. + @Published var hapticRefreshEnabled = true + + /// Should we fire haptic when user tap button. + @Published var hapticButtonPressEnabled = true + + /// Should we fire haptic when animation is finished. + @Published var hapticAnimationEnabled = true + + /// Should we fire haptic when notification occures. + @Published var hapticNotificationEnabled = true + + /// Should sensitive photos without mask. + @Published var showSensitive = false + public func changeApplicationState(accountModel: AccountModel, instance: Instance?, lastSeenStatusId: String?) { self.account = accountModel self.lastSeenStatusId = lastSeenStatusId diff --git a/Vernissage/Haptics/HapticService.swift b/Vernissage/Haptics/HapticService.swift index d705353..d73da0e 100644 --- a/Vernissage/Haptics/HapticService.swift +++ b/Vernissage/Haptics/HapticService.swift @@ -15,7 +15,6 @@ public class HapticService { case dataRefresh(intensity: CGFloat) case notification(_ type: UINotificationFeedbackGenerator.FeedbackType) case tabSelection - case timeline case animation } @@ -34,17 +33,25 @@ public class HapticService { switch type { case .buttonPress: - impactGenerator.impactOccurred() + if ApplicationState.shared.hapticButtonPressEnabled { + impactGenerator.impactOccurred() + } case let .dataRefresh(intensity): - impactGenerator.impactOccurred(intensity: intensity) + if ApplicationState.shared.hapticRefreshEnabled { + impactGenerator.impactOccurred(intensity: intensity) + } case let .notification(type): - notificationGenerator.notificationOccurred(type) + if ApplicationState.shared.hapticNotificationEnabled { + notificationGenerator.notificationOccurred(type) + } case .tabSelection: - selectionGenerator.selectionChanged() - case .timeline: - selectionGenerator.selectionChanged() + if ApplicationState.shared.hapticTabSelectionEnabled { + selectionGenerator.selectionChanged() + } case .animation: - selectionGenerator.selectionChanged() + if ApplicationState.shared.hapticAnimationEnabled { + selectionGenerator.selectionChanged() + } } } diff --git a/Vernissage/Vernissage.xcdatamodeld/.xccurrentversion b/Vernissage/Vernissage.xcdatamodeld/.xccurrentversion index 17d88c3..6ab7daf 100644 --- a/Vernissage/Vernissage.xcdatamodeld/.xccurrentversion +++ b/Vernissage/Vernissage.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - Vernissage-002.xcdatamodel + Vernissage-004.xcdatamodel diff --git a/Vernissage/Vernissage.xcdatamodeld/Vernissage-003.xcdatamodel/contents b/Vernissage/Vernissage.xcdatamodeld/Vernissage-003.xcdatamodel/contents new file mode 100644 index 0000000..b6c5c9b --- /dev/null +++ b/Vernissage/Vernissage.xcdatamodeld/Vernissage-003.xcdatamodel/contents @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Vernissage/Vernissage.xcdatamodeld/Vernissage-004.xcdatamodel/contents b/Vernissage/Vernissage.xcdatamodeld/Vernissage-004.xcdatamodel/contents new file mode 100644 index 0000000..9d7814d --- /dev/null +++ b/Vernissage/Vernissage.xcdatamodeld/Vernissage-004.xcdatamodel/contents @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Vernissage/Views/SettingsView/SettingsView.swift b/Vernissage/Views/SettingsView/SettingsView.swift index 6e5529d..6019b2b 100644 --- a/Vernissage/Views/SettingsView/SettingsView.swift +++ b/Vernissage/Views/SettingsView/SettingsView.swift @@ -34,6 +34,12 @@ struct SettingsView: View { // Avatar shapes. AvatarShapesSectionView() + // Media settings view. + MediaSettingsView() + + // Haptics. + HapticsSectionView() + // Support. SupportView() diff --git a/Vernissage/Views/SettingsView/Subviews/HapticsSectionView.swift b/Vernissage/Views/SettingsView/Subviews/HapticsSectionView.swift new file mode 100644 index 0000000..a7770a3 --- /dev/null +++ b/Vernissage/Views/SettingsView/Subviews/HapticsSectionView.swift @@ -0,0 +1,61 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import SwiftUI + +struct HapticsSectionView: View { + @EnvironmentObject var applicationState: ApplicationState + @Environment(\.colorScheme) var colorScheme + + @State var hapticTabSelectionEnabled = true + @State var hapticButtonPressEnabled = true + @State var hapticRefreshEnabled = true + @State var hapticAnimationEnabled = true + @State var hapticNotificationEnabled = true + + var body: some View { + Section("Haptics") { + + Toggle("Tab selection", isOn: $hapticTabSelectionEnabled) + .onChange(of: hapticTabSelectionEnabled) { newValue in + self.applicationState.hapticTabSelectionEnabled = newValue + ApplicationSettingsHandler.shared.setHapticTabSelectionEnabled(value: newValue) + } + + Toggle("Button press", isOn: $hapticButtonPressEnabled) + .onChange(of: hapticButtonPressEnabled) { newValue in + self.applicationState.hapticButtonPressEnabled = newValue + ApplicationSettingsHandler.shared.setHapticButtonPressEnabled(value: newValue) + } + + Toggle("List refresh", isOn: $hapticRefreshEnabled) + .onChange(of: hapticRefreshEnabled) { newValue in + self.applicationState.hapticRefreshEnabled = newValue + ApplicationSettingsHandler.shared.setHapticRefreshEnabled(value: newValue) + } + + Toggle("Animation finished", isOn: $hapticAnimationEnabled) + .onChange(of: hapticAnimationEnabled) { newValue in + self.applicationState.hapticAnimationEnabled = newValue + ApplicationSettingsHandler.shared.setHapticAnimationEnabled(value: newValue) + } + +// Toggle("Notification", isOn: $hapticNotificationEnabled) +// .onChange(of: hapticNotificationEnabled) { newValue in +// self.applicationState.hapticNotificationEnabled = newValue +// ApplicationSettingsHandler.shared.setHapticNotificationEnabled(value: newValue) +// } + } + .onAppear { + let defaultSettings = ApplicationSettingsHandler.shared.getDefaultSettings() + self.hapticTabSelectionEnabled = defaultSettings.hapticTabSelectionEnabled + self.hapticButtonPressEnabled = defaultSettings.hapticButtonPressEnabled + self.hapticRefreshEnabled = defaultSettings.hapticRefreshEnabled + self.hapticAnimationEnabled = defaultSettings.hapticAnimationEnabled + self.hapticNotificationEnabled = defaultSettings.hapticNotificationEnabled + } + } +} diff --git a/Vernissage/Views/SettingsView/Subviews/MediaSettingsView.swift b/Vernissage/Views/SettingsView/Subviews/MediaSettingsView.swift new file mode 100644 index 0000000..93f00d7 --- /dev/null +++ b/Vernissage/Views/SettingsView/Subviews/MediaSettingsView.swift @@ -0,0 +1,29 @@ +// +// https://mczachurski.dev +// Copyright © 2023 Marcin Czachurski and the repository contributors. +// Licensed under the MIT License. +// + +import SwiftUI + +struct MediaSettingsView: View { + @EnvironmentObject var applicationState: ApplicationState + @Environment(\.colorScheme) var colorScheme + + @State var showSensitive = true + + var body: some View { + Section("Media settings") { + + Toggle("Always show NSFW (sensitive)", isOn: $showSensitive) + .onChange(of: showSensitive) { newValue in + self.applicationState.showSensitive = newValue + ApplicationSettingsHandler.shared.setShowSensitive(value: newValue) + } + } + .onAppear { + let defaultSettings = ApplicationSettingsHandler.shared.getDefaultSettings() + self.showSensitive = defaultSettings.showSensitive + } + } +} diff --git a/Vernissage/Widgets/ImageRow.swift b/Vernissage/Widgets/ImageRow.swift index da37ee9..d8e2834 100644 --- a/Vernissage/Widgets/ImageRow.swift +++ b/Vernissage/Widgets/ImageRow.swift @@ -46,7 +46,7 @@ struct ImageRow: View { if let attachmentData { if let uiImage { ZStack { - if self.status.sensitive { + if self.status.sensitive && !self.applicationState.showSensitive { ContentWarning(blurhash: attachmentData.blurhash, spoilerText: self.status.spoilerText) { Image(uiImage: uiImage) .resizable() diff --git a/Vernissage/Widgets/ImageRowAsync.swift b/Vernissage/Widgets/ImageRowAsync.swift index ec11278..39fd4db 100644 --- a/Vernissage/Widgets/ImageRowAsync.swift +++ b/Vernissage/Widgets/ImageRowAsync.swift @@ -51,7 +51,7 @@ struct ImageRowAsync: View { ZStack { LazyImage(url: attachment.url) { state in if let image = state.image { - if self.statusViewModel.sensitive { + if self.statusViewModel.sensitive && !self.applicationState.showSensitive { ZStack { ContentWarning(blurhash: attachment.blurhash, spoilerText: self.statusViewModel.spoilerText) { self.imageView(image: image)