forked from shadowfacts/Tusker
Scaffolding for push subscription alert types
This commit is contained in:
parent
94c1eb2c81
commit
bdd4a4d755
|
@ -57,7 +57,7 @@ public struct PushSubscription {
|
|||
case all, followed, followers
|
||||
}
|
||||
|
||||
public struct Alerts: OptionSet {
|
||||
public struct Alerts: OptionSet, Hashable {
|
||||
public static let mention = Alerts(rawValue: 1 << 0)
|
||||
public static let status = Alerts(rawValue: 1 << 1)
|
||||
public static let reblog = Alerts(rawValue: 1 << 2)
|
||||
|
|
|
@ -32,13 +32,7 @@ struct NotificationsPrefsView: View {
|
|||
|
||||
private var enableSection: some View {
|
||||
Section {
|
||||
HStack {
|
||||
Text("Push Notifications")
|
||||
|
||||
Spacer()
|
||||
|
||||
TriStateToggle(titleKey: "Push Notifications Enabled", mode: $isSetup, onChange: isSetupChanged(newValue:))
|
||||
}
|
||||
TriStateToggle("Push Notifications", mode: $isSetup, onChange: isSetupChanged(newValue:))
|
||||
}
|
||||
.appGroupedListRowBackground()
|
||||
.alertWithData("An Error Occurred", data: $error) { error in
|
||||
|
|
|
@ -32,9 +32,10 @@ struct PushInstanceSettingsView: View {
|
|||
HStack {
|
||||
PrefsAccountView(account: account)
|
||||
Spacer()
|
||||
TriStateToggle(titleKey: "\(account.instanceURL.host!) notifications enabled", mode: $mode, onChange: updateNotificationsEnabled(enabled:))
|
||||
TriStateToggle("\(account.instanceURL.host!) notifications enabled", labelHidden: true, mode: $mode, onChange: updateNotificationsEnabled(enabled:))
|
||||
.labelsHidden()
|
||||
}
|
||||
PushSubscriptionView(account: account, subscription: subscription)
|
||||
PushSubscriptionView(account: account, subscription: subscription, updateSubscription: updateSubscription)
|
||||
}
|
||||
.alertWithData("An Error Occurred", data: $error) { data in
|
||||
Button("OK") {}
|
||||
|
@ -90,11 +91,27 @@ struct PushInstanceSettingsView: View {
|
|||
subscription = nil
|
||||
PushManager.logger.debug("Push subscription removed on \(account.instanceURL)")
|
||||
}
|
||||
|
||||
private func updateSubscription(alerts: PushNotifications.PushSubscription.Alerts, policy: PushNotifications.PushSubscription.Policy) async {
|
||||
try! await Task.sleep(nanoseconds: NSEC_PER_SEC)
|
||||
let req = Pachyderm.PushSubscription.update(alerts: .init(alerts), policy: .init(policy))
|
||||
let mastodonController = await MastodonController.getForAccount(account)
|
||||
do {
|
||||
let (result, _) = try await mastodonController.run(req)
|
||||
PushManager.logger.debug("Push subscription \(result.id) updated on \(account.instanceURL)")
|
||||
subscription?.alerts = alerts
|
||||
subscription?.policy = policy
|
||||
} catch {
|
||||
PushManager.logger.error("Error updating subscription: \(String(describing: error))")
|
||||
self.error = .updating(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum Error: LocalizedError {
|
||||
case enabling(any Swift.Error)
|
||||
case disabling(any Swift.Error)
|
||||
case updating(any Swift.Error)
|
||||
|
||||
var errorDescription: String? {
|
||||
switch self {
|
||||
|
@ -102,6 +119,8 @@ private enum Error: LocalizedError {
|
|||
"Enabling push: \(error.localizedDescription)"
|
||||
case .disabling(let error):
|
||||
"Disabling push: \(error.localizedDescription)"
|
||||
case .updating(let error):
|
||||
"Updating settings: \(error.localizedDescription)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,10 +13,11 @@ import PushNotifications
|
|||
struct PushSubscriptionView: View {
|
||||
let account: UserAccountInfo
|
||||
let subscription: PushSubscription?
|
||||
let updateSubscription: (PushSubscription.Alerts, PushSubscription.Policy) async -> Void
|
||||
|
||||
var body: some View {
|
||||
if let subscription {
|
||||
Text("wee")
|
||||
PushSubscriptionSettingsView(account: account, subscription: subscription, updateSubscription: updateSubscription)
|
||||
} else {
|
||||
Text("No notifications")
|
||||
.foregroundStyle(.secondary)
|
||||
|
@ -24,6 +25,51 @@ struct PushSubscriptionView: View {
|
|||
}
|
||||
}
|
||||
|
||||
private struct PushSubscriptionSettingsView: View {
|
||||
let account: UserAccountInfo
|
||||
let subscription: PushSubscription
|
||||
let updateSubscription: (PushSubscription.Alerts, PushSubscription.Policy) async -> Void
|
||||
@State private var isLoading: [PushSubscription.Alerts: Bool] = [:]
|
||||
|
||||
init(account: UserAccountInfo, subscription: PushSubscription, updateSubscription: @escaping (PushSubscription.Alerts, PushSubscription.Policy) async -> Void) {
|
||||
self.account = account
|
||||
self.subscription = subscription
|
||||
self.updateSubscription = updateSubscription
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .prefsAvatar) {
|
||||
TriStateToggle("Mentions", mode: alertsBinding(for: .mention)) {
|
||||
await onChange(alert: .mention, value: $0)
|
||||
}
|
||||
}
|
||||
// this is the default value of the alignment guide, but this modifier is loading bearing
|
||||
.alignmentGuide(.prefsAvatar, computeValue: { dimension in
|
||||
dimension[.leading]
|
||||
})
|
||||
// otherwise the flexible view makes the containing stack extend under the edge of the list row
|
||||
.padding(.leading, 38)
|
||||
}
|
||||
|
||||
private func alertsBinding(for alert: PushSubscription.Alerts) -> Binding<TriStateToggle.Mode> {
|
||||
return Binding {
|
||||
isLoading[alert] == true ? .loading : subscription.alerts.contains(alert) ? .on : .off
|
||||
} set: { newValue in
|
||||
isLoading[alert] = newValue == .loading
|
||||
}
|
||||
}
|
||||
|
||||
private func onChange(alert: PushSubscription.Alerts, value: Bool) async {
|
||||
var newAlerts = subscription.alerts
|
||||
if value {
|
||||
newAlerts.insert(alert)
|
||||
} else {
|
||||
newAlerts.remove(alert)
|
||||
}
|
||||
await updateSubscription(newAlerts, subscription.policy)
|
||||
}
|
||||
}
|
||||
|
||||
//#Preview {
|
||||
// PushSubscriptionView()
|
||||
//}
|
||||
|
|
|
@ -10,40 +10,45 @@ import SwiftUI
|
|||
|
||||
struct TriStateToggle: View {
|
||||
let titleKey: LocalizedStringKey
|
||||
@available(iOS, obsoleted: 16.0, message: "Switch to LabeledContent")
|
||||
let labelHidden: Bool
|
||||
@Binding var mode: Mode
|
||||
let onChange: (Bool) async -> Void
|
||||
@State private var isOn: Bool
|
||||
|
||||
init(titleKey: LocalizedStringKey, mode: Binding<Mode>, onChange: @escaping (Bool) async -> Void) {
|
||||
init(_ titleKey: LocalizedStringKey, labelHidden: Bool = false, mode: Binding<Mode>, onChange: @escaping (Bool) async -> Void) {
|
||||
self.titleKey = titleKey
|
||||
self.labelHidden = labelHidden
|
||||
self._mode = mode
|
||||
self.onChange = onChange
|
||||
self.isOn = mode.wrappedValue == .on
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
toggleOrSpinner
|
||||
.onChange(of: mode) { newValue in
|
||||
switch newValue {
|
||||
case .off:
|
||||
isOn = false
|
||||
case .loading:
|
||||
break
|
||||
case .on:
|
||||
isOn = true
|
||||
}
|
||||
content
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var content: some View {
|
||||
if #available(iOS 16.0, *) {
|
||||
LabeledContent(titleKey) {
|
||||
toggleOrSpinner
|
||||
}
|
||||
} else if labelHidden {
|
||||
toggleOrSpinner
|
||||
} else {
|
||||
HStack {
|
||||
Text(titleKey)
|
||||
Spacer()
|
||||
toggleOrSpinner
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var toggleOrSpinner: some View {
|
||||
if mode == .loading {
|
||||
ProgressView()
|
||||
} else {
|
||||
ZStack {
|
||||
Toggle(titleKey, isOn: Binding {
|
||||
isOn
|
||||
mode == .on
|
||||
} set: { newValue in
|
||||
isOn = newValue
|
||||
mode = .loading
|
||||
Task {
|
||||
await onChange(newValue)
|
||||
|
@ -51,6 +56,11 @@ struct TriStateToggle: View {
|
|||
}
|
||||
})
|
||||
.labelsHidden()
|
||||
.opacity(mode == .loading ? 0 : 1)
|
||||
|
||||
if mode == .loading {
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,10 +71,9 @@ struct TriStateToggle: View {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#Preview {
|
||||
@State var mode = TriStateToggle.Mode.on
|
||||
return TriStateToggle(titleKey: "", mode: $mode) { _ in
|
||||
return TriStateToggle("", mode: $mode) { _ in
|
||||
try! await Task.sleep(nanoseconds: NSEC_PER_SEC)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ struct PrefsAccountView: View {
|
|||
let account: UserAccountInfo
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
HStack(spacing: 8) {
|
||||
LocalAccountAvatarView(localAccountInfo: account)
|
||||
VStack(alignment: .prefsAvatar) {
|
||||
Text(verbatim: account.username)
|
||||
|
@ -37,7 +37,7 @@ struct PrefsAccountView: View {
|
|||
|
||||
private struct AvatarAlignment: AlignmentID {
|
||||
static func defaultValue(in context: ViewDimensions) -> CGFloat {
|
||||
0
|
||||
context[.leading]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue