Improve per-instance push settings
This commit is contained in:
parent
ff8a83ca2d
commit
ee992bc0bf
|
@ -53,8 +53,12 @@ public struct PushSubscription {
|
||||||
self.policy = policy
|
self.policy = policy
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum Policy: String {
|
public enum Policy: String, CaseIterable, Identifiable {
|
||||||
case all, followed, followers
|
case all, followed, followers
|
||||||
|
|
||||||
|
public var id: some Hashable {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Alerts: OptionSet, Hashable {
|
public struct Alerts: OptionSet, Hashable {
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
//
|
||||||
|
// AsyncPicker.swift
|
||||||
|
// TuskerComponents
|
||||||
|
//
|
||||||
|
// Created by Shadowfacts on 4/9/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
public struct AsyncPicker<V: Hashable, Content: View>: View {
|
||||||
|
let titleKey: LocalizedStringKey
|
||||||
|
@available(iOS, obsoleted: 16.0, message: "Switch to LabeledContent")
|
||||||
|
let labelHidden: Bool
|
||||||
|
let alignment: Alignment
|
||||||
|
@Binding var value: V
|
||||||
|
let onChange: (V) async -> Bool
|
||||||
|
let content: Content
|
||||||
|
@State private var isLoading = false
|
||||||
|
|
||||||
|
public init(_ titleKey: LocalizedStringKey, labelHidden: Bool = false, alignment: Alignment = .center, value: Binding<V>, onChange: @escaping (V) async -> Bool, @ViewBuilder content: () -> Content) {
|
||||||
|
self.titleKey = titleKey
|
||||||
|
self.labelHidden = labelHidden
|
||||||
|
self.alignment = alignment
|
||||||
|
self._value = value
|
||||||
|
self.onChange = onChange
|
||||||
|
self.content = content()
|
||||||
|
}
|
||||||
|
|
||||||
|
public var body: some View {
|
||||||
|
if #available(iOS 16.0, *) {
|
||||||
|
LabeledContent(titleKey) {
|
||||||
|
picker
|
||||||
|
}
|
||||||
|
} else if labelHidden {
|
||||||
|
picker
|
||||||
|
} else {
|
||||||
|
HStack {
|
||||||
|
Text(titleKey)
|
||||||
|
Spacer()
|
||||||
|
picker
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var picker: some View {
|
||||||
|
ZStack(alignment: alignment) {
|
||||||
|
Picker(titleKey, selection: Binding(get: {
|
||||||
|
value
|
||||||
|
}, set: { newValue in
|
||||||
|
let oldValue = value
|
||||||
|
value = newValue
|
||||||
|
isLoading = true
|
||||||
|
Task {
|
||||||
|
let operationCompleted = await onChange(newValue)
|
||||||
|
if !operationCompleted {
|
||||||
|
value = oldValue
|
||||||
|
}
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
|
})) {
|
||||||
|
content
|
||||||
|
}
|
||||||
|
.labelsHidden()
|
||||||
|
.opacity(isLoading ? 0 : 1)
|
||||||
|
|
||||||
|
if isLoading {
|
||||||
|
ProgressView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#Preview {
|
||||||
|
@State var value = 0
|
||||||
|
return AsyncPicker("", value: $value) { _ in
|
||||||
|
try! await Task.sleep(nanoseconds: NSEC_PER_SEC)
|
||||||
|
return true
|
||||||
|
} content: {
|
||||||
|
ForEach(0..<10) {
|
||||||
|
Text("\($0)").tag($0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -70,6 +70,7 @@ struct NotificationsPrefsView: View {
|
||||||
private func startRegistration() async -> Bool {
|
private func startRegistration() async -> Bool {
|
||||||
let authorized: Bool
|
let authorized: Bool
|
||||||
do {
|
do {
|
||||||
|
// TODO: support .providesAppNotificationSettings
|
||||||
authorized = try await UNUserNotificationCenter.current().requestAuthorization(options: [.alert])
|
authorized = try await UNUserNotificationCenter.current().requestAuthorization(options: [.alert])
|
||||||
} catch {
|
} catch {
|
||||||
self.error = .requestingAuthorization(error)
|
self.error = .requestingAuthorization(error)
|
||||||
|
|
|
@ -21,6 +21,7 @@ struct PushSubscriptionView: View {
|
||||||
PushSubscriptionSettingsView(account: account, subscription: subscription, updateSubscription: updateSubscription)
|
PushSubscriptionSettingsView(account: account, subscription: subscription, updateSubscription: updateSubscription)
|
||||||
} else {
|
} else {
|
||||||
Text("No notifications")
|
Text("No notifications")
|
||||||
|
.font(.callout)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,15 +40,17 @@ private struct PushSubscriptionSettingsView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .prefsAvatar) {
|
VStack {
|
||||||
toggle("All", alert: [.mention, .favorite, .reblog, .follow, .followRequest, .poll, .update])
|
alertsToggles
|
||||||
toggle("Mentions", alert: .mention)
|
|
||||||
toggle("Favorites", alert: .favorite)
|
AsyncPicker("From", alignment: .trailing, value: .constant(subscription.policy)) { newPolicy in
|
||||||
toggle("Reblogs", alert: .reblog)
|
await updateSubscription(subscription.alerts, newPolicy)
|
||||||
toggle("Follows", alert: [.follow, .followRequest])
|
} content: {
|
||||||
toggle("Polls", alert: .poll)
|
ForEach(PushSubscription.Policy.allCases) {
|
||||||
toggle("Edits", alert: .update)
|
Text($0.displayName).tag($0)
|
||||||
// status notifications not supported until we can enable/disable them in the app
|
}
|
||||||
|
}
|
||||||
|
.pickerStyle(.menu)
|
||||||
}
|
}
|
||||||
// this is the default value of the alignment guide, but this modifier is loading bearing
|
// this is the default value of the alignment guide, but this modifier is loading bearing
|
||||||
.alignmentGuide(.prefsAvatar, computeValue: { dimension in
|
.alignmentGuide(.prefsAvatar, computeValue: { dimension in
|
||||||
|
@ -57,6 +60,21 @@ private struct PushSubscriptionSettingsView: View {
|
||||||
.padding(.leading, 38)
|
.padding(.leading, 38)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var alertsToggles: some View {
|
||||||
|
GroupBox("Get notifications for") {
|
||||||
|
VStack {
|
||||||
|
toggle("All", alert: [.mention, .favorite, .reblog, .follow, .followRequest, .poll, .update])
|
||||||
|
toggle("Mentions", alert: .mention)
|
||||||
|
toggle("Favorites", alert: .favorite)
|
||||||
|
toggle("Reblogs", alert: .reblog)
|
||||||
|
toggle("Follows", alert: [.follow, .followRequest])
|
||||||
|
toggle("Polls finishing", alert: .poll)
|
||||||
|
toggle("Edits", alert: .update)
|
||||||
|
// status notifications not supported until we can enable/disable them in the app
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func toggle(_ titleKey: LocalizedStringKey, alert: PushSubscription.Alerts) -> some View {
|
private func toggle(_ titleKey: LocalizedStringKey, alert: PushSubscription.Alerts) -> some View {
|
||||||
let binding: Binding<AsyncToggle.Mode> = Binding {
|
let binding: Binding<AsyncToggle.Mode> = Binding {
|
||||||
isLoading[alert] == true ? .loading : subscription.alerts.contains(alert) ? .on : .off
|
isLoading[alert] == true ? .loading : subscription.alerts.contains(alert) ? .on : .off
|
||||||
|
@ -64,11 +82,11 @@ private struct PushSubscriptionSettingsView: View {
|
||||||
isLoading[alert] = newValue == .loading
|
isLoading[alert] = newValue == .loading
|
||||||
}
|
}
|
||||||
return AsyncToggle(titleKey, mode: binding) {
|
return AsyncToggle(titleKey, mode: binding) {
|
||||||
return await updateSubscription(alert: alert, isOn: $0)
|
return await updateAlert(alert, isOn: $0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateSubscription(alert: PushSubscription.Alerts, isOn: Bool) async -> Bool {
|
private func updateAlert(_ alert: PushSubscription.Alerts, isOn: Bool) async -> Bool {
|
||||||
var newAlerts = subscription.alerts
|
var newAlerts = subscription.alerts
|
||||||
if isOn {
|
if isOn {
|
||||||
newAlerts.insert(alert)
|
newAlerts.insert(alert)
|
||||||
|
@ -79,6 +97,19 @@ private struct PushSubscriptionSettingsView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private extension PushSubscription.Policy {
|
||||||
|
var displayName: String {
|
||||||
|
switch self {
|
||||||
|
case .all:
|
||||||
|
"Anyone"
|
||||||
|
case .followed:
|
||||||
|
"Accounts you follow"
|
||||||
|
case .followers:
|
||||||
|
"Your followers"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//#Preview {
|
//#Preview {
|
||||||
// PushSubscriptionView()
|
// PushSubscriptionView()
|
||||||
//}
|
//}
|
||||||
|
|
Loading…
Reference in New Issue