Centralize haptic feedbacks

This commit is contained in:
Thomas Ricouard 2023-02-02 16:56:42 +01:00
parent 262f7288ad
commit bb6910cd83
10 changed files with 47 additions and 27 deletions

View file

@ -29,8 +29,6 @@ struct IceCubesApp: App {
@State private var popToRootTab: Tab = .other
@State private var sideBarLoadedTabs: Set<Tab> = Set()
private let feedbackGenerator = UISelectionFeedbackGenerator()
private var availableTabs: [Tab] {
appAccountsManager.currentClient.isAuth ? Tab.loggedInTabs() : Tab.loggedOutTab()
}
@ -142,7 +140,7 @@ struct IceCubesApp: App {
}
}
selectedTab = newTab
feedbackGenerator.selectionChanged()
HapticManager.shared.selectionChanged()
})) {
ForEach(availableTabs) { tab in
tab.makeContentView(popToRootTab: $popToRootTab)

View file

@ -10,8 +10,6 @@ public struct AppAccountsSelectorView: View {
@State private var accountsViewModel: [AppAccountViewModel] = []
let feedbackGenerator = UIImpactFeedbackGenerator()
private let accountCreationEnabled: Bool
private let avatarSize: AvatarView.Size
@ -22,8 +20,6 @@ public struct AppAccountsSelectorView: View {
self.routerPath = routerPath
self.accountCreationEnabled = accountCreationEnabled
self.avatarSize = avatarSize
feedbackGenerator.prepare()
}
public var body: some View {
@ -42,7 +38,7 @@ public struct AppAccountsSelectorView: View {
}
}
.onTapGesture {
feedbackGenerator.impactOccurred(intensity: 0.3)
HapticManager.shared.impact()
}
.onAppear {
refreshAccounts()
@ -82,7 +78,7 @@ public struct AppAccountsSelectorView: View {
appAccounts.currentAccount = viewModel.appAccount
}
feedbackGenerator.impactOccurred(intensity: 0.7)
HapticManager.shared.impact()
} label: {
HStack {
if viewModel.account?.id == currentAccount.account?.id {
@ -96,6 +92,7 @@ public struct AppAccountsSelectorView: View {
if accountCreationEnabled {
Divider()
Button {
HapticManager.shared.impact()
routerPath.presentedSheet = .addAccount
} label: {
Label("app-account.button.add", systemImage: "person.badge.plus")
@ -105,6 +102,7 @@ public struct AppAccountsSelectorView: View {
if UIDevice.current.userInterfaceIdiom == .phone {
Divider()
Button {
HapticManager.shared.impact()
routerPath.presentedSheet = .settings
} label: {
Label("tab.settings", systemImage: "gear")

View file

@ -7,9 +7,8 @@ public extension View {
func statusEditorToolbarItem(routerPath: RouterPath, visibility: Models.Visibility) -> some ToolbarContent {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
let feedback = UISelectionFeedbackGenerator()
routerPath.presentedSheet = .newStatusEditor(visibility: visibility)
feedback.selectionChanged()
HapticManager.shared.impact()
} label: {
Image(systemName: "square.and.pencil")
}
@ -21,7 +20,6 @@ public struct StatusEditorToolbarItem: ToolbarContent {
@EnvironmentObject private var routerPath: RouterPath
let visibility: Models.Visibility
let feedbackGenerator = UISelectionFeedbackGenerator()
public init(visibility: Models.Visibility) {
self.visibility = visibility
@ -31,7 +29,7 @@ public struct StatusEditorToolbarItem: ToolbarContent {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
routerPath.presentedSheet = .newStatusEditor(visibility: visibility)
feedbackGenerator.selectionChanged()
HapticManager.shared.impact()
} label: {
Image(systemName: "square.and.pencil")
}

View file

@ -0,0 +1,30 @@
import UIKit
public class HapticManager {
public static let shared: HapticManager = .init()
private let selectionGenerator = UISelectionFeedbackGenerator()
private let impactGenerator = UIImpactFeedbackGenerator(style: .heavy)
private let notificationGenerator = UINotificationFeedbackGenerator()
private init() {
selectionGenerator.prepare()
impactGenerator.prepare()
}
public func selectionChanged(){
selectionGenerator.selectionChanged()
}
public func impact() {
impactGenerator.impactOccurred()
}
public func impact(intensity: CGFloat) {
impactGenerator.impactOccurred(intensity: intensity)
}
public func notification(type: UINotificationFeedbackGenerator.FeedbackType){
notificationGenerator.notificationOccurred(type)
}
}

View file

@ -8,7 +8,6 @@ import SwiftUI
@MainActor
public class StatusEditorViewModel: ObservableObject {
var mode: Mode
let generator = UINotificationFeedbackGenerator()
var client: Client?
var currentAccount: Account?
@ -140,7 +139,7 @@ public class StatusEditorViewModel: ObservableObject {
case let .edit(status):
postStatus = try await client.put(endpoint: Statuses.editStatus(id: status.id, json: data))
}
generator.notificationOccurred(.success)
HapticManager.shared.notification(type: .success)
if hasExplicitlySelectedLanguage, let selectedLanguage {
preferences?.markLanguageAsSelected(isoCode: selectedLanguage)
}
@ -152,7 +151,7 @@ public class StatusEditorViewModel: ObservableObject {
showPostingErrorAlert = true
}
isPosting = false
generator.notificationOccurred(.error)
HapticManager.shared.notification(type: .error)
return nil
}
}

View file

@ -10,8 +10,6 @@ struct StatusActionsView: View {
@EnvironmentObject private var routerPath: RouterPath
@ObservedObject var viewModel: StatusRowViewModel
let generator = UINotificationFeedbackGenerator()
@MainActor
enum Actions: CaseIterable {
case respond, boost, favorite, bookmark, share
@ -158,7 +156,7 @@ struct StatusActionsView: View {
private func handleAction(action: Actions) {
Task {
generator.notificationOccurred(.success)
HapticManager.shared.notification(type: .success)
switch action {
case .respond:
routerPath.presentedSheet = .replyToStatusEditor(status: viewModel.status)

View file

@ -422,6 +422,7 @@ public struct StatusRowView: View {
private var trailinSwipeActions: some View {
Button {
Task {
HapticManager.shared.notification(type: .success)
if viewModel.isFavorited {
await viewModel.unFavorite()
} else {
@ -434,6 +435,7 @@ public struct StatusRowView: View {
.tint(.yellow)
Button {
Task {
HapticManager.shared.notification(type: .success)
if viewModel.isReblogged {
await viewModel.unReblog()
} else {
@ -449,6 +451,7 @@ public struct StatusRowView: View {
@ViewBuilder
private var leadingSwipeActions: some View {
Button {
HapticManager.shared.notification(type: .success)
routerPath.presentedSheet = .replyToStatusEditor(status: viewModel.status)
} label: {
Image(systemName: "arrowshape.turn.up.left")

View file

@ -1,11 +1,10 @@
import Foundation
import Models
import SwiftUI
import Env
@MainActor
class PendingStatusesObserver: ObservableObject {
let feedbackGenerator = UIImpactFeedbackGenerator(style: .light)
@Published var pendingStatusesCount: Int = 0
var disableUpdate: Bool = false
@ -19,7 +18,7 @@ class PendingStatusesObserver: ObservableObject {
func removeStatus(status: Status) {
if !disableUpdate, let index = pendingStatuses.firstIndex(of: status.id) {
pendingStatuses.removeSubrange(index ... (pendingStatuses.count - 1))
feedbackGenerator.impactOccurred()
HapticManager.shared.selectionChanged()
}
}

View file

@ -25,8 +25,6 @@ public struct TimelineView: View {
@Binding var timeline: TimelineFilter
@Binding var scrollToTopSignal: Int
private let feedbackGenerator = UIImpactFeedbackGenerator()
public init(timeline: Binding<TimelineFilter>, scrollToTopSignal: Binding<Int>) {
_timeline = timeline
_scrollToTopSignal = scrollToTopSignal
@ -82,9 +80,9 @@ public struct TimelineView: View {
}
}
.refreshable {
feedbackGenerator.impactOccurred(intensity: 0.3)
HapticManager.shared.impact(intensity: 0.3)
await viewModel.fetchStatuses()
feedbackGenerator.impactOccurred(intensity: 0.7)
HapticManager.shared.impact(intensity: 0.7)
}
.onChange(of: watcher.latestEvent?.id) { _ in
if let latestEvent = watcher.latestEvent {

View file

@ -212,7 +212,6 @@ extension TimelineViewModel: StatusesFetcher {
} else {
// Append new statuses in the timeline indicator.
pendingStatusesObserver.pendingStatuses.insert(contentsOf: newStatuses.map { $0.id }, at: 0)
pendingStatusesObserver.feedbackGenerator.impactOccurred()
// High chance the user is scrolled to the top.
// We need to update the statuses state, and then scroll to the previous top most status.