mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-21 15:50:59 +00:00
Notification preferences
This commit is contained in:
parent
a566babe3a
commit
98a80e5704
11 changed files with 188 additions and 42 deletions
|
@ -228,6 +228,16 @@ public extension IdentityDatabase {
|
||||||
|
|
||||||
return Identity(info: info)
|
return Identity(info: info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only for use in notification extension
|
||||||
|
func identity(id: Identity.Id) throws -> Identity? {
|
||||||
|
guard let info = try databaseWriter.read(
|
||||||
|
IdentityInfo.request(IdentityRecord.filter(IdentityRecord.Columns.id == id))
|
||||||
|
.fetchOne)
|
||||||
|
else { return nil }
|
||||||
|
|
||||||
|
return Identity(info: info)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension IdentityDatabase {
|
private extension IdentityDatabase {
|
||||||
|
|
|
@ -186,6 +186,10 @@
|
||||||
"preferences.notification-types.mention" = "Mention";
|
"preferences.notification-types.mention" = "Mention";
|
||||||
"preferences.notification-types.poll" = "Poll";
|
"preferences.notification-types.poll" = "Poll";
|
||||||
"preferences.notification-types.status" = "Status";
|
"preferences.notification-types.status" = "Status";
|
||||||
|
"preferences.notifications" = "Notifications";
|
||||||
|
"preferences.notifications.include-account-name" = "Include account name";
|
||||||
|
"preferences.notifications.include-pictures" = "Include pictures";
|
||||||
|
"preferences.notifications.sounds" = "Sounds";
|
||||||
"preferences.muted-users" = "Muted Users";
|
"preferences.muted-users" = "Muted Users";
|
||||||
"preferences.home-timeline-position-on-startup" = "Home timeline position on startup";
|
"preferences.home-timeline-position-on-startup" = "Home timeline position on startup";
|
||||||
"preferences.notifications-position-on-startup" = "Notifications position on startup";
|
"preferences.notifications-position-on-startup" = "Notifications position on startup";
|
||||||
|
|
|
@ -38,3 +38,7 @@ public extension MastodonNotification {
|
||||||
public static var unknownCase: Self { .unknown }
|
public static var unknownCase: Self { .unknown }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension MastodonNotification.NotificationType: Identifiable {
|
||||||
|
public var id: Self { self }
|
||||||
|
}
|
||||||
|
|
|
@ -3,21 +3,11 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public struct PushNotification: Codable {
|
public struct PushNotification: Codable {
|
||||||
public enum NotificationType: String, Codable, Unknowable {
|
|
||||||
case mention
|
|
||||||
case reblog
|
|
||||||
case favourite
|
|
||||||
case follow
|
|
||||||
case unknown
|
|
||||||
|
|
||||||
public static var unknownCase: Self { .unknown }
|
|
||||||
}
|
|
||||||
|
|
||||||
public let accessToken: String
|
public let accessToken: String
|
||||||
public let body: String
|
public let body: String
|
||||||
public let title: String
|
public let title: String
|
||||||
public let icon: URL
|
public let icon: URL
|
||||||
public let notificationId: Int
|
public let notificationId: Int
|
||||||
public let notificationType: NotificationType
|
public let notificationType: MastodonNotification.NotificationType
|
||||||
public let preferredLocale: String
|
public let preferredLocale: String
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,6 +106,7 @@
|
||||||
D08B8D72254246E200B1EBEF /* PollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D71254246E200B1EBEF /* PollView.swift */; };
|
D08B8D72254246E200B1EBEF /* PollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D71254246E200B1EBEF /* PollView.swift */; };
|
||||||
D08B8D822544D80000B1EBEF /* PollOptionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D812544D80000B1EBEF /* PollOptionButton.swift */; };
|
D08B8D822544D80000B1EBEF /* PollOptionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D812544D80000B1EBEF /* PollOptionButton.swift */; };
|
||||||
D08B8D8D2544E6EC00B1EBEF /* PollResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D8C2544E6EC00B1EBEF /* PollResultView.swift */; };
|
D08B8D8D2544E6EC00B1EBEF /* PollResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B8D8C2544E6EC00B1EBEF /* PollResultView.swift */; };
|
||||||
|
D08B9F1025CB8E060062D040 /* NotificationPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B9F0F25CB8E060062D040 /* NotificationPreferencesView.swift */; };
|
||||||
D08E512125786A6600FA2C5F /* UIButton+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E512025786A6600FA2C5F /* UIButton+Extensions.swift */; };
|
D08E512125786A6600FA2C5F /* UIButton+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E512025786A6600FA2C5F /* UIButton+Extensions.swift */; };
|
||||||
D08E52612579D2E100FA2C5F /* DomainBlocksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E52602579D2E100FA2C5F /* DomainBlocksView.swift */; };
|
D08E52612579D2E100FA2C5F /* DomainBlocksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E52602579D2E100FA2C5F /* DomainBlocksView.swift */; };
|
||||||
D08E5276257C36CA00FA2C5F /* Share Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = D08E526C257C36CA00FA2C5F /* Share Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
D08E5276257C36CA00FA2C5F /* Share Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = D08E526C257C36CA00FA2C5F /* Share Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
|
@ -297,6 +298,7 @@
|
||||||
D08B8D71254246E200B1EBEF /* PollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollView.swift; sourceTree = "<group>"; };
|
D08B8D71254246E200B1EBEF /* PollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollView.swift; sourceTree = "<group>"; };
|
||||||
D08B8D812544D80000B1EBEF /* PollOptionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionButton.swift; sourceTree = "<group>"; };
|
D08B8D812544D80000B1EBEF /* PollOptionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollOptionButton.swift; sourceTree = "<group>"; };
|
||||||
D08B8D8C2544E6EC00B1EBEF /* PollResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollResultView.swift; sourceTree = "<group>"; };
|
D08B8D8C2544E6EC00B1EBEF /* PollResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PollResultView.swift; sourceTree = "<group>"; };
|
||||||
|
D08B9F0F25CB8E060062D040 /* NotificationPreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationPreferencesView.swift; sourceTree = "<group>"; };
|
||||||
D08E512025786A6600FA2C5F /* UIButton+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+Extensions.swift"; sourceTree = "<group>"; };
|
D08E512025786A6600FA2C5F /* UIButton+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+Extensions.swift"; sourceTree = "<group>"; };
|
||||||
D08E52602579D2E100FA2C5F /* DomainBlocksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainBlocksView.swift; sourceTree = "<group>"; };
|
D08E52602579D2E100FA2C5F /* DomainBlocksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DomainBlocksView.swift; sourceTree = "<group>"; };
|
||||||
D08E526C257C36CA00FA2C5F /* Share Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Share Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
D08E526C257C36CA00FA2C5F /* Share Extension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Share Extension.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
@ -469,6 +471,7 @@
|
||||||
D0BEB21024FA2A90001B0F04 /* EditFilterView.swift */,
|
D0BEB21024FA2A90001B0F04 /* EditFilterView.swift */,
|
||||||
D0BEB20424FA1107001B0F04 /* FiltersView.swift */,
|
D0BEB20424FA1107001B0F04 /* FiltersView.swift */,
|
||||||
D0BEB1FE24F9E5BB001B0F04 /* ListsView.swift */,
|
D0BEB1FE24F9E5BB001B0F04 /* ListsView.swift */,
|
||||||
|
D08B9F0F25CB8E060062D040 /* NotificationPreferencesView.swift */,
|
||||||
D0C7D42D24F76169001EBDBB /* NotificationTypesPreferencesView.swift */,
|
D0C7D42D24F76169001EBDBB /* NotificationTypesPreferencesView.swift */,
|
||||||
D0C7D42624F76169001EBDBB /* PreferencesView.swift */,
|
D0C7D42624F76169001EBDBB /* PreferencesView.swift */,
|
||||||
D0B32F4F250B373600311912 /* RegistrationView.swift */,
|
D0B32F4F250B373600311912 /* RegistrationView.swift */,
|
||||||
|
@ -1003,6 +1006,7 @@
|
||||||
D08B8D42253F92B600B1EBEF /* ImagePageViewController.swift in Sources */,
|
D08B8D42253F92B600B1EBEF /* ImagePageViewController.swift in Sources */,
|
||||||
D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */,
|
D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */,
|
||||||
D0C7D4D624F7616A001EBDBB /* NSMutableAttributedString+Extensions.swift in Sources */,
|
D0C7D4D624F7616A001EBDBB /* NSMutableAttributedString+Extensions.swift in Sources */,
|
||||||
|
D08B9F1025CB8E060062D040 /* NotificationPreferencesView.swift in Sources */,
|
||||||
D0E9F9AA258450B300EF503D /* CompositionInputAccessoryView.swift in Sources */,
|
D0E9F9AA258450B300EF503D /* CompositionInputAccessoryView.swift in Sources */,
|
||||||
D021A60A25C36B32008A0C0D /* IdentityTableViewCell.swift in Sources */,
|
D021A60A25C36B32008A0C0D /* IdentityTableViewCell.swift in Sources */,
|
||||||
D0849C7F25903C4900A5EBCC /* Status+Extensions.swift in Sources */,
|
D0849C7F25903C4900A5EBCC /* Status+Extensions.swift in Sources */,
|
||||||
|
|
|
@ -4,6 +4,10 @@
|
||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.security.app-sandbox</key>
|
<key>com.apple.security.app-sandbox</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>com.apple.security.application-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>group.metabolist.metatext</string>
|
||||||
|
</array>
|
||||||
<key>com.apple.security.network.client</key>
|
<key>com.apple.security.network.client</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>keychain-access-groups</key>
|
<key>keychain-access-groups</key>
|
||||||
|
|
|
@ -29,9 +29,11 @@ final class NotificationService: UNNotificationServiceExtension {
|
||||||
guard let bestAttemptContent = bestAttemptContent else { return }
|
guard let bestAttemptContent = bestAttemptContent else { return }
|
||||||
|
|
||||||
let pushNotification: PushNotification
|
let pushNotification: PushNotification
|
||||||
|
let decryptedJSON: Data
|
||||||
|
let identityId: Identity.Id
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let decryptedJSON = try Self.extractAndDecrypt(userInfo: request.content.userInfo)
|
(decryptedJSON, identityId) = try Self.extractAndDecrypt(userInfo: request.content.userInfo)
|
||||||
|
|
||||||
pushNotification = try MastodonDecoder().decode(PushNotification.self, from: decryptedJSON)
|
pushNotification = try MastodonDecoder().decode(PushNotification.self, from: decryptedJSON)
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -43,35 +45,23 @@ final class NotificationService: UNNotificationServiceExtension {
|
||||||
bestAttemptContent.title = pushNotification.title
|
bestAttemptContent.title = pushNotification.title
|
||||||
bestAttemptContent.body = XMLUnescaper(string: pushNotification.body).unescape()
|
bestAttemptContent.body = XMLUnescaper(string: pushNotification.body).unescape()
|
||||||
|
|
||||||
let fileName = pushNotification.icon.lastPathComponent
|
let appPreferences = AppPreferences(environment: environment)
|
||||||
let fileURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent(fileName)
|
|
||||||
|
|
||||||
KingfisherManager.shared.retrieveImage(with: pushNotification.icon) {
|
if appPreferences.notificationSounds.contains(pushNotification.notificationType) {
|
||||||
switch $0 {
|
bestAttemptContent.sound = .default
|
||||||
case let .success(result):
|
}
|
||||||
let format: ImageFormat
|
|
||||||
|
|
||||||
switch fileURL.pathExtension.lowercased() {
|
if appPreferences.notificationAccountName,
|
||||||
case "jpg", "jpeg":
|
let accountName = try? AllIdentitiesService(environment: environment).identity(id: identityId)?.handle {
|
||||||
format = .JPEG
|
bestAttemptContent.subtitle = accountName
|
||||||
case "gif":
|
}
|
||||||
format = .GIF
|
|
||||||
case "png":
|
|
||||||
format = .PNG
|
|
||||||
default:
|
|
||||||
format = .unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
if appPreferences.notificationPictures {
|
||||||
try result.image.kf.data(format: format)?.write(to: fileURL)
|
Self.addImage(pushNotification: pushNotification,
|
||||||
bestAttemptContent.attachments = [try UNNotificationAttachment(identifier: fileName, url: fileURL)]
|
bestAttemptContent: bestAttemptContent,
|
||||||
contentHandler(bestAttemptContent)
|
contentHandler: contentHandler)
|
||||||
} catch {
|
} else {
|
||||||
contentHandler(bestAttemptContent)
|
contentHandler(bestAttemptContent)
|
||||||
}
|
|
||||||
case .failure:
|
|
||||||
contentHandler(bestAttemptContent)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,10 +96,10 @@ private extension NotificationService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func extractAndDecrypt(userInfo: [AnyHashable: Any]) throws -> Data {
|
static func extractAndDecrypt(userInfo: [AnyHashable: Any]) throws -> (Data, Identity.Id) {
|
||||||
guard
|
guard
|
||||||
let identityIdString = userInfo[identityIdUserInfoKey] as? String,
|
let identityIdString = userInfo[identityIdUserInfoKey] as? String,
|
||||||
let identityId = UUID(uuidString: identityIdString),
|
let identityId = Identity.Id(uuidString: identityIdString),
|
||||||
let encryptedMessageBase64 = (userInfo[encryptedMessageUserInfoKey] as? String)?.URLSafeBase64ToBase64(),
|
let encryptedMessageBase64 = (userInfo[encryptedMessageUserInfoKey] as? String)?.URLSafeBase64ToBase64(),
|
||||||
let encryptedMessage = Data(base64Encoded: encryptedMessageBase64),
|
let encryptedMessage = Data(base64Encoded: encryptedMessageBase64),
|
||||||
let saltBase64 = (userInfo[saltUserInfoKey] as? String)?.URLSafeBase64ToBase64(),
|
let saltBase64 = (userInfo[saltUserInfoKey] as? String)?.URLSafeBase64ToBase64(),
|
||||||
|
@ -125,11 +115,12 @@ private extension NotificationService {
|
||||||
let pushKey = try secretsService.getPushKey()
|
let pushKey = try secretsService.getPushKey()
|
||||||
else { throw NotificationServiceError.keychainDataAbsent }
|
else { throw NotificationServiceError.keychainDataAbsent }
|
||||||
|
|
||||||
return try decrypt(encryptedMessage: encryptedMessage,
|
return (try decrypt(encryptedMessage: encryptedMessage,
|
||||||
privateKeyData: pushKey,
|
privateKeyData: pushKey,
|
||||||
serverPublicKeyData: serverPublicKeyData,
|
serverPublicKeyData: serverPublicKeyData,
|
||||||
auth: auth,
|
auth: auth,
|
||||||
salt: salt)
|
salt: salt),
|
||||||
|
identityId)
|
||||||
}
|
}
|
||||||
|
|
||||||
static func decrypt(encryptedMessage: Data,
|
static func decrypt(encryptedMessage: Data,
|
||||||
|
@ -179,6 +170,43 @@ private extension NotificationService {
|
||||||
|
|
||||||
return Data(unpadded)
|
return Data(unpadded)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func addImage(pushNotification: PushNotification,
|
||||||
|
bestAttemptContent: UNMutableNotificationContent,
|
||||||
|
contentHandler: @escaping (UNNotificationContent) -> Void) {
|
||||||
|
let fileName = pushNotification.icon.lastPathComponent
|
||||||
|
let fileURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
|
||||||
|
.appendingPathComponent(fileName)
|
||||||
|
|
||||||
|
KingfisherManager.shared.retrieveImage(with: pushNotification.icon) {
|
||||||
|
switch $0 {
|
||||||
|
case let .success(result):
|
||||||
|
let format: ImageFormat
|
||||||
|
|
||||||
|
switch fileURL.pathExtension.lowercased() {
|
||||||
|
case "jpg", "jpeg":
|
||||||
|
format = .JPEG
|
||||||
|
case "gif":
|
||||||
|
format = .GIF
|
||||||
|
case "png":
|
||||||
|
format = .PNG
|
||||||
|
default:
|
||||||
|
format = .unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
try result.image.kf.data(format: format)?.write(to: fileURL)
|
||||||
|
bestAttemptContent.attachments =
|
||||||
|
[try UNNotificationAttachment(identifier: fileName, url: fileURL)]
|
||||||
|
contentHandler(bestAttemptContent)
|
||||||
|
} catch {
|
||||||
|
contentHandler(bestAttemptContent)
|
||||||
|
}
|
||||||
|
case .failure:
|
||||||
|
contentHandler(bestAttemptContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension String {
|
extension String {
|
||||||
|
|
|
@ -136,6 +136,11 @@ public extension AllIdentitiesService {
|
||||||
.ignoreOutput()
|
.ignoreOutput()
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only for use in notification extension
|
||||||
|
func identity(id: Identity.Id) throws -> Identity? {
|
||||||
|
try database.identity(id: id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension AllIdentitiesService.IdentityCreation {
|
private extension AllIdentitiesService.IdentityCreation {
|
||||||
|
|
|
@ -140,6 +140,15 @@ public extension AppPreferences {
|
||||||
set { self[.defaultEmojiSkinTone] = newValue?.rawValue }
|
set { self[.defaultEmojiSkinTone] = newValue?.rawValue }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var notificationSounds: Set<MastodonNotification.NotificationType> {
|
||||||
|
get {
|
||||||
|
Set((self[.notificationSounds] as [String]?)?.compactMap {
|
||||||
|
MastodonNotification.NotificationType(rawValue: $0)
|
||||||
|
} ?? MastodonNotification.NotificationType.allCasesExceptUnknown)
|
||||||
|
}
|
||||||
|
set { self[.notificationSounds] = newValue.map { $0.rawValue } }
|
||||||
|
}
|
||||||
|
|
||||||
var shouldReduceMotion: Bool {
|
var shouldReduceMotion: Bool {
|
||||||
systemReduceMotion() && useSystemReduceMotionForMedia
|
systemReduceMotion() && useSystemReduceMotionForMedia
|
||||||
}
|
}
|
||||||
|
@ -167,6 +176,16 @@ public extension AppPreferences {
|
||||||
get { self[.requireDoubleTapToFavorite] ?? false }
|
get { self[.requireDoubleTapToFavorite] ?? false }
|
||||||
set { self[.requireDoubleTapToFavorite] = newValue }
|
set { self[.requireDoubleTapToFavorite] = newValue }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var notificationPictures: Bool {
|
||||||
|
get { self[.notificationPictures] ?? true }
|
||||||
|
set { self[.notificationPictures] = newValue }
|
||||||
|
}
|
||||||
|
|
||||||
|
var notificationAccountName: Bool {
|
||||||
|
get { self[.notificationAccountName] ?? false }
|
||||||
|
set { self[.notificationAccountName] = newValue }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension AppPreferences {
|
private extension AppPreferences {
|
||||||
|
@ -183,6 +202,9 @@ private extension AppPreferences {
|
||||||
case notificationsTabBehavior
|
case notificationsTabBehavior
|
||||||
case defaultEmojiSkinTone
|
case defaultEmojiSkinTone
|
||||||
case showReblogAndFavoriteCounts
|
case showReblogAndFavoriteCounts
|
||||||
|
case notificationPictures
|
||||||
|
case notificationAccountName
|
||||||
|
case notificationSounds
|
||||||
}
|
}
|
||||||
|
|
||||||
subscript<T>(index: Item) -> T? {
|
subscript<T>(index: Item) -> T? {
|
||||||
|
|
73
Views/SwiftUI/NotificationPreferencesView.swift
Normal file
73
Views/SwiftUI/NotificationPreferencesView.swift
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
// Copyright © 2021 Metabolist. All rights reserved.
|
||||||
|
|
||||||
|
import Mastodon
|
||||||
|
import SwiftUI
|
||||||
|
import ViewModels
|
||||||
|
|
||||||
|
struct NotificationPreferencesView: View {
|
||||||
|
@StateObject var viewModel: PreferencesViewModel
|
||||||
|
@StateObject var identityContext: IdentityContext
|
||||||
|
|
||||||
|
init(viewModel: PreferencesViewModel) {
|
||||||
|
_viewModel = StateObject(wrappedValue: viewModel)
|
||||||
|
_identityContext = StateObject(wrappedValue: viewModel.identityContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Form {
|
||||||
|
Section {
|
||||||
|
Toggle("preferences.notifications.include-pictures",
|
||||||
|
isOn: $identityContext.appPreferences.notificationPictures)
|
||||||
|
Toggle("preferences.notifications.include-account-name",
|
||||||
|
isOn: $identityContext.appPreferences.notificationAccountName)
|
||||||
|
}
|
||||||
|
Section(header: Text("preferences.notifications.sounds")) {
|
||||||
|
ForEach(MastodonNotification.NotificationType.allCasesExceptUnknown) { type in
|
||||||
|
Toggle(type.localizedStringKey, isOn: .init {
|
||||||
|
viewModel.identityContext.appPreferences.notificationSounds.contains(type)
|
||||||
|
} set: {
|
||||||
|
if $0 {
|
||||||
|
viewModel.identityContext.appPreferences.notificationSounds.insert(type)
|
||||||
|
} else {
|
||||||
|
viewModel.identityContext.appPreferences.notificationSounds.remove(type)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationTitle("preferences.notifications")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension MastodonNotification.NotificationType {
|
||||||
|
var localizedStringKey: LocalizedStringKey {
|
||||||
|
switch self {
|
||||||
|
case .follow:
|
||||||
|
return "preferences.notification-types.follow"
|
||||||
|
case .mention:
|
||||||
|
return "preferences.notification-types.mention"
|
||||||
|
case .reblog:
|
||||||
|
return "preferences.notification-types.reblog"
|
||||||
|
case .favourite:
|
||||||
|
return "preferences.notification-types.favourite"
|
||||||
|
case .poll:
|
||||||
|
return "preferences.notification-types.poll"
|
||||||
|
case .followRequest:
|
||||||
|
return "preferences.notification-types.follow-request"
|
||||||
|
case .status:
|
||||||
|
return "preferences.notification-types.status"
|
||||||
|
case .unknown:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
import PreviewViewModels
|
||||||
|
|
||||||
|
struct NotificationPreferencesView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
NotificationPreferencesView(viewModel: .init(identityContext: .preview))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -63,6 +63,8 @@ struct PreferencesView: View {
|
||||||
&& viewModel.identityContext.identity.authenticated)
|
&& viewModel.identityContext.identity.authenticated)
|
||||||
}
|
}
|
||||||
Section(header: Text("preferences.app")) {
|
Section(header: Text("preferences.app")) {
|
||||||
|
NavigationLink("preferences.notifications",
|
||||||
|
destination: NotificationPreferencesView(viewModel: viewModel))
|
||||||
Picker("preferences.status-word",
|
Picker("preferences.status-word",
|
||||||
selection: $identityContext.appPreferences.statusWord) {
|
selection: $identityContext.appPreferences.statusWord) {
|
||||||
ForEach(AppPreferences.StatusWord.allCases) { option in
|
ForEach(AppPreferences.StatusWord.allCases) { option in
|
||||||
|
|
Loading…
Reference in a new issue