Improve per-instance push settings

This commit is contained in:
Shadowfacts 2024-04-10 19:13:47 -04:00
parent ff8a83ca2d
commit ee992bc0bf
4 changed files with 131 additions and 12 deletions

View File

@ -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 {

View File

@ -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)
}
}
}

View File

@ -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)

View File

@ -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()
//} //}