mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-11-25 17:51:01 +00:00
Improve SoundEffectManager & HapticManager (#1662)
* Remove unnecessary vars and switches * Improve SoundEffectManager call-site API * Improve HapticManager call-site API
This commit is contained in:
parent
6e1e83cace
commit
4266ac4b42
20 changed files with 75 additions and 105 deletions
|
@ -14,8 +14,8 @@ extension IceCubesApp {
|
|||
}
|
||||
}
|
||||
|
||||
HapticManager.shared.fireHaptic(of: .tabSelection)
|
||||
SoundEffectManager.shared.playSound(of: .tabSelection)
|
||||
HapticManager.shared.fireHaptic(.tabSelection)
|
||||
SoundEffectManager.shared.playSound(.tabSelection)
|
||||
|
||||
selectedTab = newTab
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ struct SideBarView<Content: View>: View {
|
|||
Button {
|
||||
if account.id == appAccounts.currentAccount.id {
|
||||
selectedTab = .profile
|
||||
SoundEffectManager.shared.playSound(of: .tabSelection)
|
||||
SoundEffectManager.shared.playSound(.tabSelection)
|
||||
} else {
|
||||
var transation = Transaction()
|
||||
transation.disablesAnimations = true
|
||||
|
@ -114,7 +114,7 @@ struct SideBarView<Content: View>: View {
|
|||
}
|
||||
}
|
||||
selectedTab = tab
|
||||
SoundEffectManager.shared.playSound(of: .tabSelection)
|
||||
SoundEffectManager.shared.playSound(.tabSelection)
|
||||
if tab == .notifications {
|
||||
if let token = appAccounts.currentAccount.oauthToken {
|
||||
userPreferences.notificationsCount[token] = 0
|
||||
|
|
|
@ -118,12 +118,12 @@ public struct AccountDetailView: View {
|
|||
}
|
||||
.refreshable {
|
||||
Task {
|
||||
SoundEffectManager.shared.playSound(of: .pull)
|
||||
HapticManager.shared.fireHaptic(of: .dataRefresh(intensity: 0.3))
|
||||
SoundEffectManager.shared.playSound(.pull)
|
||||
HapticManager.shared.fireHaptic(.dataRefresh(intensity: 0.3))
|
||||
await viewModel.fetchAccount()
|
||||
await viewModel.fetchNewestStatuses()
|
||||
HapticManager.shared.fireHaptic(of: .dataRefresh(intensity: 0.7))
|
||||
SoundEffectManager.shared.playSound(of: .refresh)
|
||||
HapticManager.shared.fireHaptic(.dataRefresh(intensity: 0.7))
|
||||
SoundEffectManager.shared.playSound(.refresh)
|
||||
}
|
||||
}
|
||||
.onChange(of: watcher.latestEvent?.id) {
|
||||
|
|
|
@ -48,13 +48,13 @@ public struct AppAccountView: View {
|
|||
let account = viewModel.account
|
||||
{
|
||||
routerPath.navigate(to: .accountSettingsWithAccount(account: account, appAccount: viewModel.appAccount))
|
||||
HapticManager.shared.fireHaptic(of: .buttonPress)
|
||||
HapticManager.shared.fireHaptic(.buttonPress)
|
||||
} else {
|
||||
var transation = Transaction()
|
||||
transation.disablesAnimations = true
|
||||
withTransaction(transation) {
|
||||
appAccounts.currentAccount = viewModel.appAccount
|
||||
HapticManager.shared.fireHaptic(of: .notification(.success))
|
||||
HapticManager.shared.fireHaptic(.notification(.success))
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
|
|
|
@ -43,7 +43,7 @@ public struct AppAccountsSelectorView: View {
|
|||
public var body: some View {
|
||||
Button {
|
||||
isPresented.toggle()
|
||||
HapticManager.shared.fireHaptic(of: .buttonPress)
|
||||
HapticManager.shared.fireHaptic(.buttonPress)
|
||||
} label: {
|
||||
labelView
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ public struct AppAccountsSelectorView: View {
|
|||
Section {
|
||||
Button {
|
||||
isPresented = false
|
||||
HapticManager.shared.fireHaptic(of: .buttonPress)
|
||||
HapticManager.shared.fireHaptic(.buttonPress)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
||||
routerPath.presentedSheet = .addAccount
|
||||
}
|
||||
|
@ -142,7 +142,7 @@ public struct AppAccountsSelectorView: View {
|
|||
private var settingsButton: some View {
|
||||
Button {
|
||||
isPresented = false
|
||||
HapticManager.shared.fireHaptic(of: .buttonPress)
|
||||
HapticManager.shared.fireHaptic(.buttonPress)
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
|
||||
routerPath.presentedSheet = .settings
|
||||
}
|
||||
|
|
|
@ -81,7 +81,7 @@ struct ConversationsListRow: View {
|
|||
}
|
||||
.accessibilityAction(.magicTap) {
|
||||
if let lastStatus = conversation.lastStatus {
|
||||
HapticManager.shared.fireHaptic(of: .notification(.success))
|
||||
HapticManager.shared.fireHaptic(.notification(.success))
|
||||
routerPath.presentedSheet = .replyToStatusEditor(status: lastStatus)
|
||||
}
|
||||
}
|
||||
|
@ -182,7 +182,7 @@ struct ConversationsListRow: View {
|
|||
var replyAction: some View {
|
||||
if let lastStatus = conversation.lastStatus {
|
||||
Button("status.action.reply") {
|
||||
HapticManager.shared.fireHaptic(of: .notification(.success))
|
||||
HapticManager.shared.fireHaptic(.notification(.success))
|
||||
routerPath.presentedSheet = .replyToStatusEditor(status: lastStatus)
|
||||
}
|
||||
} else {
|
||||
|
@ -195,7 +195,7 @@ struct ConversationsListRow: View {
|
|||
if let lastStatus = conversation.lastStatus {
|
||||
if lastStatus.account.id != currentAccount.account?.id {
|
||||
Button("@\(lastStatus.account.username)") {
|
||||
HapticManager.shared.fireHaptic(of: .notification(.success))
|
||||
HapticManager.shared.fireHaptic(.notification(.success))
|
||||
routerPath.navigate(to: .accountDetail(id: lastStatus.account.id))
|
||||
}
|
||||
}
|
||||
|
@ -205,18 +205,18 @@ struct ConversationsListRow: View {
|
|||
case .url:
|
||||
if UIApplication.shared.canOpenURL(link.url) {
|
||||
Button("accessibility.tabs.timeline.content-link-\(link.title)") {
|
||||
HapticManager.shared.fireHaptic(of: .notification(.success))
|
||||
HapticManager.shared.fireHaptic(.notification(.success))
|
||||
_ = routerPath.handle(url: link.url)
|
||||
}
|
||||
}
|
||||
case .hashtag:
|
||||
Button("accessibility.tabs.timeline.content-hashtag-\(link.title)") {
|
||||
HapticManager.shared.fireHaptic(of: .notification(.success))
|
||||
HapticManager.shared.fireHaptic(.notification(.success))
|
||||
_ = routerPath.handle(url: link.url)
|
||||
}
|
||||
case .mention:
|
||||
Button("\(link.title)") {
|
||||
HapticManager.shared.fireHaptic(of: .notification(.success))
|
||||
HapticManager.shared.fireHaptic(.notification(.success))
|
||||
_ = routerPath.handle(url: link.url)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -105,11 +105,11 @@ public struct ConversationsListView: View {
|
|||
// note: this Task wrapper should not be necessary, but it reportedly crashes without it
|
||||
// when refreshing on an empty list
|
||||
Task {
|
||||
SoundEffectManager.shared.playSound(of: .pull)
|
||||
HapticManager.shared.fireHaptic(of: .dataRefresh(intensity: 0.3))
|
||||
SoundEffectManager.shared.playSound(.pull)
|
||||
HapticManager.shared.fireHaptic(.dataRefresh(intensity: 0.3))
|
||||
await viewModel.fetchConversations()
|
||||
HapticManager.shared.fireHaptic(of: .dataRefresh(intensity: 0.7))
|
||||
SoundEffectManager.shared.playSound(of: .refresh)
|
||||
HapticManager.shared.fireHaptic(.dataRefresh(intensity: 0.7))
|
||||
SoundEffectManager.shared.playSound(.refresh)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
|
|
|
@ -30,7 +30,7 @@ public struct StatusEditorToolbarItem: ToolbarContent {
|
|||
openWindow(value: WindowDestination.newStatusEditor(visibility: visibility))
|
||||
} else {
|
||||
routerPath.presentedSheet = .newStatusEditor(visibility: visibility)
|
||||
HapticManager.shared.fireHaptic(of: .buttonPress)
|
||||
HapticManager.shared.fireHaptic(.buttonPress)
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
|
|
|
@ -25,7 +25,7 @@ public class HapticManager {
|
|||
}
|
||||
|
||||
@MainActor
|
||||
public func fireHaptic(of type: HapticType) {
|
||||
public func fireHaptic(_ type: HapticType) {
|
||||
guard supportsHaptics else { return }
|
||||
|
||||
switch type {
|
||||
|
|
|
@ -11,14 +11,7 @@ public class SoundEffectManager {
|
|||
case pull, refresh, tootSent, tabSelection, bookmark, boost, favorite, share
|
||||
}
|
||||
|
||||
var pullId: SystemSoundID = 0
|
||||
var refreshId: SystemSoundID = 1
|
||||
var tootSentId: SystemSoundID = 2
|
||||
var tabSelectionId: SystemSoundID = 3
|
||||
var bookmarkId: SystemSoundID = 4
|
||||
var boostId: SystemSoundID = 5
|
||||
var favoriteId: SystemSoundID = 6
|
||||
var shareId: SystemSoundID = 7
|
||||
private var systemSoundIDs: [SoundEffect: SystemSoundID] = [:]
|
||||
|
||||
private let userPreferences = UserPreferences.shared
|
||||
|
||||
|
@ -27,49 +20,26 @@ public class SoundEffectManager {
|
|||
}
|
||||
|
||||
private func registerSounds() {
|
||||
for effect in SoundEffect.allCases {
|
||||
if let url = Bundle.main.url(forResource: effect.rawValue, withExtension: "wav") {
|
||||
switch effect {
|
||||
case .pull:
|
||||
AudioServicesCreateSystemSoundID(url as CFURL, &pullId)
|
||||
case .refresh:
|
||||
AudioServicesCreateSystemSoundID(url as CFURL, &refreshId)
|
||||
case .tootSent:
|
||||
AudioServicesCreateSystemSoundID(url as CFURL, &tootSentId)
|
||||
case .tabSelection:
|
||||
AudioServicesCreateSystemSoundID(url as CFURL, &tabSelectionId)
|
||||
case .bookmark:
|
||||
AudioServicesCreateSystemSoundID(url as CFURL, &bookmarkId)
|
||||
case .boost:
|
||||
AudioServicesCreateSystemSoundID(url as CFURL, &boostId)
|
||||
case .favorite:
|
||||
AudioServicesCreateSystemSoundID(url as CFURL, &favoriteId)
|
||||
case .share:
|
||||
AudioServicesCreateSystemSoundID(url as CFURL, &shareId)
|
||||
}
|
||||
}
|
||||
SoundEffect.allCases.forEach { effect in
|
||||
guard let url = Bundle.main.url(forResource: effect.rawValue, withExtension: "wav") else { return }
|
||||
register(url: url, for: effect)
|
||||
}
|
||||
}
|
||||
|
||||
public func playSound(of type: SoundEffect) {
|
||||
guard userPreferences.soundEffectEnabled else { return }
|
||||
switch type {
|
||||
case .pull:
|
||||
AudioServicesPlaySystemSound(pullId)
|
||||
case .refresh:
|
||||
AudioServicesPlaySystemSound(refreshId)
|
||||
case .tootSent:
|
||||
AudioServicesPlaySystemSound(tootSentId)
|
||||
case .tabSelection:
|
||||
AudioServicesPlaySystemSound(tabSelectionId)
|
||||
case .bookmark:
|
||||
AudioServicesPlaySystemSound(bookmarkId)
|
||||
case .boost:
|
||||
AudioServicesPlaySystemSound(boostId)
|
||||
case .favorite:
|
||||
AudioServicesPlaySystemSound(favoriteId)
|
||||
case .share:
|
||||
AudioServicesPlaySystemSound(shareId)
|
||||
private func register(url: URL, for effect: SoundEffect) {
|
||||
var soundId: SystemSoundID = .init()
|
||||
AudioServicesCreateSystemSoundID(url as CFURL, &soundId)
|
||||
systemSoundIDs[effect] = soundId
|
||||
}
|
||||
|
||||
public func playSound(_ effect: SoundEffect) {
|
||||
guard
|
||||
userPreferences.soundEffectEnabled,
|
||||
let soundId = systemSoundIDs[effect]
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
AudioServicesPlaySystemSound(soundId)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,11 +82,11 @@ public struct ExploreView: View {
|
|||
}
|
||||
.refreshable {
|
||||
Task {
|
||||
SoundEffectManager.shared.playSound(of: .pull)
|
||||
HapticManager.shared.fireHaptic(of: .dataRefresh(intensity: 0.3))
|
||||
SoundEffectManager.shared.playSound(.pull)
|
||||
HapticManager.shared.fireHaptic(.dataRefresh(intensity: 0.3))
|
||||
await viewModel.fetchTrending()
|
||||
HapticManager.shared.fireHaptic(of: .dataRefresh(intensity: 0.7))
|
||||
SoundEffectManager.shared.playSound(of: .refresh)
|
||||
HapticManager.shared.fireHaptic(.dataRefresh(intensity: 0.7))
|
||||
SoundEffectManager.shared.playSound(.refresh)
|
||||
}
|
||||
}
|
||||
.listStyle(.grouped)
|
||||
|
|
|
@ -203,7 +203,7 @@ struct NotificationRowView: View {
|
|||
private var accessibilityUserActions: some View {
|
||||
ForEach(notification.accounts) { account in
|
||||
Button("@\(account.username)") {
|
||||
HapticManager.shared.fireHaptic(of: .notification(.success))
|
||||
HapticManager.shared.fireHaptic(.notification(.success))
|
||||
routerPath.navigate(to: .accountDetail(id: account.id))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,11 +93,11 @@ public struct NotificationsListView: View {
|
|||
await viewModel.fetchNotifications()
|
||||
}
|
||||
.refreshable {
|
||||
SoundEffectManager.shared.playSound(of: .pull)
|
||||
HapticManager.shared.fireHaptic(of: .dataRefresh(intensity: 0.3))
|
||||
SoundEffectManager.shared.playSound(.pull)
|
||||
HapticManager.shared.fireHaptic(.dataRefresh(intensity: 0.3))
|
||||
await viewModel.fetchNotifications()
|
||||
HapticManager.shared.fireHaptic(of: .dataRefresh(intensity: 0.7))
|
||||
SoundEffectManager.shared.playSound(of: .refresh)
|
||||
HapticManager.shared.fireHaptic(.dataRefresh(intensity: 0.7))
|
||||
SoundEffectManager.shared.playSound(.refresh)
|
||||
}
|
||||
.onChange(of: watcher.latestEvent?.id) {
|
||||
if let latestEvent = watcher.latestEvent {
|
||||
|
|
|
@ -213,7 +213,7 @@ public struct StatusEditorView: View {
|
|||
let status = await viewModel.postStatus()
|
||||
if status != nil {
|
||||
close()
|
||||
SoundEffectManager.shared.playSound(of: .tootSent)
|
||||
SoundEffectManager.shared.playSound(.tootSent)
|
||||
NotificationCenter.default.post(name: .shareSheetClose,
|
||||
object: nil)
|
||||
if !viewModel.mode.isInShareExtension, !preferences.requestedReview, !ProcessInfo.processInfo.isMacCatalystApp {
|
||||
|
|
|
@ -192,7 +192,7 @@ import SwiftUI
|
|||
case let .edit(status):
|
||||
postStatus = try await client.put(endpoint: Statuses.editStatus(id: status.id, json: data))
|
||||
}
|
||||
HapticManager.shared.fireHaptic(of: .notification(.success))
|
||||
HapticManager.shared.fireHaptic(.notification(.success))
|
||||
if hasExplicitlySelectedLanguage, let selectedLanguage {
|
||||
preferences?.markLanguageAsSelected(isoCode: selectedLanguage)
|
||||
}
|
||||
|
@ -204,7 +204,7 @@ import SwiftUI
|
|||
showPostingErrorAlert = true
|
||||
}
|
||||
isPosting = false
|
||||
HapticManager.shared.fireHaptic(of: .notification(.error))
|
||||
HapticManager.shared.fireHaptic(.notification(.error))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -195,19 +195,19 @@ public struct StatusRowView: View {
|
|||
private var accessibilityActions: some View {
|
||||
// Add reply and quote, which are lost when the swipe actions are removed
|
||||
Button("status.action.reply") {
|
||||
HapticManager.shared.fireHaptic(of: .notification(.success))
|
||||
HapticManager.shared.fireHaptic(.notification(.success))
|
||||
viewModel.routerPath.presentedSheet = .replyToStatusEditor(status: viewModel.status)
|
||||
}
|
||||
|
||||
Button("settings.swipeactions.status.action.quote") {
|
||||
HapticManager.shared.fireHaptic(of: .notification(.success))
|
||||
HapticManager.shared.fireHaptic(.notification(.success))
|
||||
viewModel.routerPath.presentedSheet = .quoteStatusEditor(status: viewModel.status)
|
||||
}
|
||||
.disabled(viewModel.status.visibility == .direct || viewModel.status.visibility == .priv)
|
||||
|
||||
if viewModel.finalStatus.mediaAttachments.isEmpty == false {
|
||||
Button("accessibility.status.media-viewer-action.label") {
|
||||
HapticManager.shared.fireHaptic(of: .notification(.success))
|
||||
HapticManager.shared.fireHaptic(.notification(.success))
|
||||
let attachments = viewModel.finalStatus.mediaAttachments
|
||||
if ProcessInfo.processInfo.isMacCatalystApp {
|
||||
openWindow(value: WindowDestination.mediaViewer(attachments: attachments,
|
||||
|
@ -225,14 +225,14 @@ public struct StatusRowView: View {
|
|||
}
|
||||
|
||||
Button("@\(viewModel.status.account.username)") {
|
||||
HapticManager.shared.fireHaptic(of: .notification(.success))
|
||||
HapticManager.shared.fireHaptic(.notification(.success))
|
||||
viewModel.routerPath.navigate(to: .accountDetail(id: viewModel.status.account.id))
|
||||
}
|
||||
|
||||
// Add a reference to the post creator
|
||||
if viewModel.status.account != viewModel.finalStatus.account {
|
||||
Button("@\(viewModel.finalStatus.account.username)") {
|
||||
HapticManager.shared.fireHaptic(of: .notification(.success))
|
||||
HapticManager.shared.fireHaptic(.notification(.success))
|
||||
viewModel.routerPath.navigate(to: .accountDetail(id: viewModel.finalStatus.account.id))
|
||||
}
|
||||
}
|
||||
|
@ -243,18 +243,18 @@ public struct StatusRowView: View {
|
|||
case .url:
|
||||
if UIApplication.shared.canOpenURL(link.url) {
|
||||
Button("accessibility.tabs.timeline.content-link-\(link.title)") {
|
||||
HapticManager.shared.fireHaptic(of: .notification(.success))
|
||||
HapticManager.shared.fireHaptic(.notification(.success))
|
||||
_ = viewModel.routerPath.handle(url: link.url)
|
||||
}
|
||||
}
|
||||
case .hashtag:
|
||||
Button("accessibility.tabs.timeline.content-hashtag-\(link.title)") {
|
||||
HapticManager.shared.fireHaptic(of: .notification(.success))
|
||||
HapticManager.shared.fireHaptic(.notification(.success))
|
||||
_ = viewModel.routerPath.handle(url: link.url)
|
||||
}
|
||||
case .mention:
|
||||
Button("\(link.title)") {
|
||||
HapticManager.shared.fireHaptic(of: .notification(.success))
|
||||
HapticManager.shared.fireHaptic(.notification(.success))
|
||||
_ = viewModel.routerPath.handle(url: link.url)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -201,23 +201,23 @@ struct StatusRowActionsView: View {
|
|||
return
|
||||
}
|
||||
}
|
||||
HapticManager.shared.fireHaptic(of: .notification(.success))
|
||||
HapticManager.shared.fireHaptic(.notification(.success))
|
||||
switch action {
|
||||
case .respond:
|
||||
SoundEffectManager.shared.playSound(of: .share)
|
||||
SoundEffectManager.shared.playSound(.share)
|
||||
if ProcessInfo.processInfo.isMacCatalystApp {
|
||||
openWindow(value: WindowDestination.replyToStatusEditor(status: viewModel.localStatus ?? viewModel.status))
|
||||
} else {
|
||||
viewModel.routerPath.presentedSheet = .replyToStatusEditor(status: viewModel.localStatus ?? viewModel.status)
|
||||
}
|
||||
case .favorite:
|
||||
SoundEffectManager.shared.playSound(of: .favorite)
|
||||
SoundEffectManager.shared.playSound(.favorite)
|
||||
await statusDataController.toggleFavorite(remoteStatus: viewModel.localStatusId)
|
||||
case .bookmark:
|
||||
SoundEffectManager.shared.playSound(of: .bookmark)
|
||||
SoundEffectManager.shared.playSound(.bookmark)
|
||||
await statusDataController.toggleBookmark(remoteStatus: viewModel.localStatusId)
|
||||
case .boost:
|
||||
SoundEffectManager.shared.playSound(of: .boost)
|
||||
SoundEffectManager.shared.playSound(.boost)
|
||||
await statusDataController.toggleReblog(remoteStatus: viewModel.localStatusId)
|
||||
default:
|
||||
break
|
||||
|
|
|
@ -83,7 +83,7 @@ struct StatusRowSwipeView: View {
|
|||
@ViewBuilder
|
||||
private func makeSwipeButtonForRouterPath(action: StatusAction, destination: SheetDestination) -> some View {
|
||||
Button {
|
||||
HapticManager.shared.fireHaptic(of: .notification(.success))
|
||||
HapticManager.shared.fireHaptic(.notification(.success))
|
||||
viewModel.routerPath.presentedSheet = destination
|
||||
} label: {
|
||||
makeSwipeLabel(action: action, style: preferences.swipeActionsIconStyle)
|
||||
|
@ -94,7 +94,7 @@ struct StatusRowSwipeView: View {
|
|||
private func makeSwipeButtonForTask(action: StatusAction, privateBoost: Bool = false, task: @escaping () async -> Void) -> some View {
|
||||
Button {
|
||||
Task {
|
||||
HapticManager.shared.fireHaptic(of: .notification(.success))
|
||||
HapticManager.shared.fireHaptic(.notification(.success))
|
||||
await task()
|
||||
}
|
||||
} label: {
|
||||
|
|
|
@ -20,7 +20,7 @@ import SwiftUI
|
|||
func removeStatus(status: Status) {
|
||||
if !disableUpdate, let index = pendingStatuses.firstIndex(of: status.id) {
|
||||
pendingStatuses.removeSubrange(index ... (pendingStatuses.count - 1))
|
||||
HapticManager.shared.fireHaptic(of: .timeline)
|
||||
HapticManager.shared.fireHaptic(.timeline)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -109,11 +109,11 @@ public struct TimelineView: View {
|
|||
viewModel.isTimelineVisible = false
|
||||
}
|
||||
.refreshable {
|
||||
SoundEffectManager.shared.playSound(of: .pull)
|
||||
HapticManager.shared.fireHaptic(of: .dataRefresh(intensity: 0.3))
|
||||
SoundEffectManager.shared.playSound(.pull)
|
||||
HapticManager.shared.fireHaptic(.dataRefresh(intensity: 0.3))
|
||||
await viewModel.pullToRefresh()
|
||||
HapticManager.shared.fireHaptic(of: .dataRefresh(intensity: 0.7))
|
||||
SoundEffectManager.shared.playSound(of: .refresh)
|
||||
HapticManager.shared.fireHaptic(.dataRefresh(intensity: 0.7))
|
||||
SoundEffectManager.shared.playSound(.refresh)
|
||||
}
|
||||
.onChange(of: watcher.latestEvent?.id) {
|
||||
if let latestEvent = watcher.latestEvent {
|
||||
|
|
Loading…
Reference in a new issue