Clean up HapticManager (#678)

* Check for haptic capabilities

* Make manager do most of work

* ABC enum

* Fix spelling 😊

* Small tweak
This commit is contained in:
Sean Goldin 2023-02-06 10:53:27 -06:00 committed by GitHub
parent 2f5e170983
commit 90ec3d419c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 60 additions and 63 deletions

View file

@ -140,9 +140,7 @@ struct IceCubesApp: App {
} }
} }
selectedTab = newTab selectedTab = newTab
if userPreferences.hapticTabSelectionEnabled { HapticManager.shared.fireHaptic(of: .tabSelection)
HapticManager.shared.selectionChanged()
}
})) { })) {
ForEach(availableTabs) { tab in ForEach(availableTabs) { tab in
tab.makeContentView(popToRootTab: $popToRootTab) tab.makeContentView(popToRootTab: $popToRootTab)

View file

@ -102,9 +102,11 @@ struct SettingsTabs: View {
NavigationLink(destination: DisplaySettingsView()) { NavigationLink(destination: DisplaySettingsView()) {
Label("settings.general.display", systemImage: "paintpalette") Label("settings.general.display", systemImage: "paintpalette")
} }
NavigationLink(destination: HapticSettingsView()) { if HapticManager.shared.supportsHaptics {
Label("settings.general.haptic", systemImage: "waveform.path") NavigationLink(destination: HapticSettingsView()) {
} Label("settings.general.haptic", systemImage: "waveform.path")
}
}
NavigationLink(destination: remoteLocalTimelinesView) { NavigationLink(destination: remoteLocalTimelinesView) {
Label("settings.general.remote-timelines", systemImage: "dot.radiowaves.right") Label("settings.general.remote-timelines", systemImage: "dot.radiowaves.right")
} }

View file

@ -38,9 +38,7 @@ public struct AppAccountsSelectorView: View {
} }
} }
.onTapGesture { .onTapGesture {
if UserPreferences.shared.hapticButtonPressEnabled { HapticManager.shared.fireHaptic(of: .buttonPress)
HapticManager.shared.impact()
}
} }
.onAppear { .onAppear {
refreshAccounts() refreshAccounts()
@ -80,9 +78,7 @@ public struct AppAccountsSelectorView: View {
appAccounts.currentAccount = viewModel.appAccount appAccounts.currentAccount = viewModel.appAccount
} }
if UserPreferences.shared.hapticButtonPressEnabled { HapticManager.shared.fireHaptic(of: .buttonPress)
HapticManager.shared.impact()
}
} label: { } label: {
HStack { HStack {
if viewModel.account?.id == currentAccount.account?.id { if viewModel.account?.id == currentAccount.account?.id {
@ -96,9 +92,7 @@ public struct AppAccountsSelectorView: View {
if accountCreationEnabled { if accountCreationEnabled {
Divider() Divider()
Button { Button {
if UserPreferences.shared.hapticButtonPressEnabled { HapticManager.shared.fireHaptic(of: .buttonPress)
HapticManager.shared.impact()
}
routerPath.presentedSheet = .addAccount routerPath.presentedSheet = .addAccount
} label: { } label: {
Label("app-account.button.add", systemImage: "person.badge.plus") Label("app-account.button.add", systemImage: "person.badge.plus")
@ -108,9 +102,7 @@ public struct AppAccountsSelectorView: View {
if UIDevice.current.userInterfaceIdiom == .phone { if UIDevice.current.userInterfaceIdiom == .phone {
Divider() Divider()
Button { Button {
if UserPreferences.shared.hapticButtonPressEnabled { HapticManager.shared.fireHaptic(of: .buttonPress)
HapticManager.shared.impact()
}
routerPath.presentedSheet = .settings routerPath.presentedSheet = .settings
} label: { } label: {
Label("tab.settings", systemImage: "gear") Label("tab.settings", systemImage: "gear")

View file

@ -8,9 +8,7 @@ public extension View {
ToolbarItem(placement: .navigationBarTrailing) { ToolbarItem(placement: .navigationBarTrailing) {
Button { Button {
routerPath.presentedSheet = .newStatusEditor(visibility: visibility) routerPath.presentedSheet = .newStatusEditor(visibility: visibility)
if UserPreferences.shared.hapticButtonPressEnabled { HapticManager.shared.fireHaptic(of: .buttonPress)
HapticManager.shared.impact()
}
} label: { } label: {
Image(systemName: "square.and.pencil") Image(systemName: "square.and.pencil")
} }
@ -31,9 +29,7 @@ public struct StatusEditorToolbarItem: ToolbarContent {
ToolbarItem(placement: .navigationBarTrailing) { ToolbarItem(placement: .navigationBarTrailing) {
Button { Button {
routerPath.presentedSheet = .newStatusEditor(visibility: visibility) routerPath.presentedSheet = .newStatusEditor(visibility: visibility)
if UserPreferences.shared.hapticButtonPressEnabled { HapticManager.shared.fireHaptic(of: .buttonPress)
HapticManager.shared.impact()
}
} label: { } label: {
Image(systemName: "square.and.pencil") Image(systemName: "square.and.pencil")
} }

View file

@ -1,30 +1,57 @@
import CoreHaptics
import UIKit import UIKit
public class HapticManager { public class HapticManager {
public static let shared: HapticManager = .init() public static let shared: HapticManager = .init()
public enum HapticType {
case buttonPress
case dataRefresh(intensity: CGFloat)
case notification(_ type: UINotificationFeedbackGenerator.FeedbackType)
case tabSelection
case timeline
}
private let selectionGenerator = UISelectionFeedbackGenerator() private let selectionGenerator = UISelectionFeedbackGenerator()
private let impactGenerator = UIImpactFeedbackGenerator(style: .heavy) private let impactGenerator = UIImpactFeedbackGenerator(style: .heavy)
private let notificationGenerator = UINotificationFeedbackGenerator() private let notificationGenerator = UINotificationFeedbackGenerator()
private let userPreferences = UserPreferences.shared
private init() { private init() {
selectionGenerator.prepare() selectionGenerator.prepare()
impactGenerator.prepare() impactGenerator.prepare()
} }
public func selectionChanged() { @MainActor
selectionGenerator.selectionChanged() public func fireHaptic(of type: HapticType) {
guard supportsHaptics else { return }
switch type {
case .buttonPress:
if userPreferences.hapticButtonPressEnabled {
impactGenerator.impactOccurred()
}
case let .dataRefresh(intensity):
if userPreferences.hapticTimelineEnabled {
impactGenerator.impactOccurred(intensity: intensity)
}
case let .notification(type):
if userPreferences.hapticButtonPressEnabled {
notificationGenerator.notificationOccurred(type)
}
case .tabSelection:
if userPreferences.hapticTabSelectionEnabled {
selectionGenerator.selectionChanged()
}
case .timeline:
if userPreferences.hapticTimelineEnabled {
selectionGenerator.selectionChanged()
}
}
} }
public func impact() { public var supportsHaptics: Bool {
impactGenerator.impactOccurred() CHHapticEngine.capabilitiesForHardware().supportsHaptics
}
public func impact(intensity: CGFloat) {
impactGenerator.impactOccurred(intensity: intensity)
}
public func notification(type: UINotificationFeedbackGenerator.FeedbackType) {
notificationGenerator.notificationOccurred(type)
} }
} }

View file

@ -174,9 +174,7 @@ public class StatusEditorViewModel: ObservableObject {
case let .edit(status): case let .edit(status):
postStatus = try await client.put(endpoint: Statuses.editStatus(id: status.id, json: data)) postStatus = try await client.put(endpoint: Statuses.editStatus(id: status.id, json: data))
} }
if UserPreferences.shared.hapticButtonPressEnabled { HapticManager.shared.fireHaptic(of: .notification(.success))
HapticManager.shared.notification(type: .success)
}
if hasExplicitlySelectedLanguage, let selectedLanguage { if hasExplicitlySelectedLanguage, let selectedLanguage {
preferences?.markLanguageAsSelected(isoCode: selectedLanguage) preferences?.markLanguageAsSelected(isoCode: selectedLanguage)
} }
@ -188,9 +186,7 @@ public class StatusEditorViewModel: ObservableObject {
showPostingErrorAlert = true showPostingErrorAlert = true
} }
isPosting = false isPosting = false
if UserPreferences.shared.hapticButtonPressEnabled { HapticManager.shared.fireHaptic(of: .notification(.error))
HapticManager.shared.notification(type: .error)
}
return nil return nil
} }
} }

View file

@ -179,9 +179,7 @@ struct StatusActionsView: View {
private func handleAction(action: Actions) { private func handleAction(action: Actions) {
Task { Task {
if UserPreferences.shared.hapticButtonPressEnabled { HapticManager.shared.fireHaptic(of: .notification(.success))
HapticManager.shared.notification(type: .success)
}
switch action { switch action {
case .respond: case .respond:
routerPath.presentedSheet = .replyToStatusEditor(status: viewModel.status) routerPath.presentedSheet = .replyToStatusEditor(status: viewModel.status)

View file

@ -425,9 +425,7 @@ public struct StatusRowView: View {
private var trailinSwipeActions: some View { private var trailinSwipeActions: some View {
Button { Button {
Task { Task {
if UserPreferences.shared.hapticButtonPressEnabled { HapticManager.shared.fireHaptic(of: .notification(.success))
HapticManager.shared.notification(type: .success)
}
if viewModel.isFavorited { if viewModel.isFavorited {
await viewModel.unFavorite() await viewModel.unFavorite()
} else { } else {
@ -440,9 +438,7 @@ public struct StatusRowView: View {
.tint(.yellow) .tint(.yellow)
Button { Button {
Task { Task {
if UserPreferences.shared.hapticButtonPressEnabled { HapticManager.shared.fireHaptic(of: .notification(.success))
HapticManager.shared.notification(type: .success)
}
if viewModel.isReblogged { if viewModel.isReblogged {
await viewModel.unReblog() await viewModel.unReblog()
} else { } else {
@ -458,9 +454,7 @@ public struct StatusRowView: View {
@ViewBuilder @ViewBuilder
private var leadingSwipeActions: some View { private var leadingSwipeActions: some View {
Button { Button {
if UserPreferences.shared.hapticButtonPressEnabled { HapticManager.shared.fireHaptic(of: .notification(.success))
HapticManager.shared.notification(type: .success)
}
routerPath.presentedSheet = .replyToStatusEditor(status: viewModel.status) routerPath.presentedSheet = .replyToStatusEditor(status: viewModel.status)
} label: { } label: {
Image(systemName: "arrowshape.turn.up.left") Image(systemName: "arrowshape.turn.up.left")

View file

@ -19,9 +19,7 @@ class PendingStatusesObserver: ObservableObject {
func removeStatus(status: Status) { func removeStatus(status: Status) {
if !disableUpdate, let index = pendingStatuses.firstIndex(of: status.id) { if !disableUpdate, let index = pendingStatuses.firstIndex(of: status.id) {
pendingStatuses.removeSubrange(index ... (pendingStatuses.count - 1)) pendingStatuses.removeSubrange(index ... (pendingStatuses.count - 1))
if UserPreferences.shared.hapticTimelineEnabled { HapticManager.shared.fireHaptic(of: .timeline)
HapticManager.shared.selectionChanged()
}
} }
} }

View file

@ -112,13 +112,9 @@ public struct TimelineView: View {
viewModel.isTimelineVisible = false viewModel.isTimelineVisible = false
} }
.refreshable { .refreshable {
if UserPreferences.shared.hapticTimelineEnabled { HapticManager.shared.fireHaptic(of: .dataRefresh(intensity: 0.3))
HapticManager.shared.impact(intensity: 0.3)
}
await viewModel.fetchStatuses() await viewModel.fetchStatuses()
if UserPreferences.shared.hapticTimelineEnabled { HapticManager.shared.fireHaptic(of: .dataRefresh(intensity: 0.7))
HapticManager.shared.impact(intensity: 0.7)
}
} }
.onChange(of: watcher.latestEvent?.id) { _ in .onChange(of: watcher.latestEvent?.id) { _ in
if let latestEvent = watcher.latestEvent { if let latestEvent = watcher.latestEvent {