Move all UI*FeedbackGenerators to FeedbackGenerator and disable them for now (IOS-247) (#1267)

* Move all UI*FeedbackGenerators to FeedbackGenerator and disable them for now (IOS-247)

* Fix copyright header

* Remove empty private constructor
This commit is contained in:
Marcus Kida 2024-04-09 16:41:47 +02:00 committed by GitHub
parent eace1ea815
commit 4ea600403b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 81 additions and 51 deletions

View File

@ -15,8 +15,7 @@ extension DataSourceFacade {
dependency: NeedsDependency & AuthContextProvider,
account: Mastodon.Entity.Account
) async throws -> Mastodon.Entity.Relationship {
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
await selectionFeedbackGenerator.selectionChanged()
FeedbackGenerator.shared.generate(.selectionChanged)
let apiService = dependency.context.apiService
let authBox = dependency.authContext.mastodonAuthenticationBox
@ -39,8 +38,7 @@ extension DataSourceFacade {
dependency: NeedsDependency & AuthContextProvider,
account: Mastodon.Entity.Account
) async throws -> Mastodon.Entity.Empty {
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
await selectionFeedbackGenerator.selectionChanged()
FeedbackGenerator.shared.generate(.selectionChanged)
let apiService = dependency.context.apiService
let authBox = dependency.authContext.mastodonAuthenticationBox

View File

@ -17,8 +17,7 @@ extension DataSourceFacade {
provider: NeedsDependency & AuthContextProvider & DataSourceProvider,
status: MastodonStatus
) async throws {
let selectionFeedbackGenerator = UISelectionFeedbackGenerator()
selectionFeedbackGenerator.selectionChanged()
FeedbackGenerator.shared.generate(.selectionChanged)
let updatedStatus = try await provider.context.apiService.bookmark(
record: status,

View File

@ -16,9 +16,8 @@ extension DataSourceFacade {
provider: DataSourceProvider & AuthContextProvider,
status: MastodonStatus
) async throws {
let selectionFeedbackGenerator = UISelectionFeedbackGenerator()
selectionFeedbackGenerator.selectionChanged()
FeedbackGenerator.shared.generate(.selectionChanged)
let updatedStatus = try await provider.context.apiService.favorite(
status: status,
authenticationBox: provider.authContext.mastodonAuthenticationBox

View File

@ -25,8 +25,7 @@ extension DataSourceFacade {
return try await withCheckedThrowingContinuation { continuation in
Task { @MainActor in
let performAction = {
let selectionFeedbackGenerator = UISelectionFeedbackGenerator()
selectionFeedbackGenerator.selectionChanged()
FeedbackGenerator.shared.generate(.selectionChanged)
let response = try await dependency.context.apiService.toggleFollow(
account: account,
@ -84,8 +83,7 @@ extension DataSourceFacade {
notificationView: NotificationView,
query: Mastodon.API.Account.FollowRequestQuery
) async throws {
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
await selectionFeedbackGenerator.selectionChanged()
FeedbackGenerator.shared.generate(.selectionChanged)
let userID = notification.account.id
let state: MastodonFollowRequestState = notification.followRequestState

View File

@ -14,9 +14,8 @@ extension DataSourceFacade {
dependency: NeedsDependency & AuthContextProvider,
account: Mastodon.Entity.Account
) async throws -> Mastodon.Entity.Relationship {
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
await selectionFeedbackGenerator.selectionChanged()
FeedbackGenerator.shared.generate(.selectionChanged)
let response = try await dependency.context.apiService.toggleMute(
authenticationBox: dependency.authContext.mastodonAuthenticationBox,
account: account

View File

@ -47,9 +47,8 @@ private extension DataSourceFacade {
provider: DataSourceProvider & AuthContextProvider,
status: MastodonStatus
) async throws {
let selectionFeedbackGenerator = UISelectionFeedbackGenerator()
selectionFeedbackGenerator.selectionChanged()
FeedbackGenerator.shared.generate(.selectionChanged)
let updatedStatus = try await provider.context.apiService.reblog(
status: status,
authenticationBox: provider.authContext.mastodonAuthenticationBox

View File

@ -98,9 +98,8 @@ extension DataSourceFacade {
switch action {
case .reply:
let selectionFeedbackGenerator = UISelectionFeedbackGenerator()
selectionFeedbackGenerator.selectionChanged()
FeedbackGenerator.shared.generate(.selectionChanged)
let composeViewModel = ComposeViewModel(
context: provider.context,
authContext: provider.authContext,

View File

@ -22,8 +22,7 @@ extension DataSourceFacade {
provider: Provider,
status: MastodonStatus
) async throws -> Mastodon.Entity.Translation? {
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
await selectionFeedbackGenerator.selectionChanged()
FeedbackGenerator.shared.generate(.selectionChanged)
do {
let value = try await provider.context

View File

@ -85,9 +85,8 @@ extension StatusTableViewControllerNavigateableCore where Self: DataSourceProvid
private func replyStatus() async {
guard let status = await statusRecord() else { return }
let selectionFeedbackGenerator = UISelectionFeedbackGenerator()
selectionFeedbackGenerator.selectionChanged()
FeedbackGenerator.shared.generate(.selectionChanged)
let composeViewModel = ComposeViewModel(
context: self.context,
authContext: authContext,

View File

@ -161,8 +161,7 @@ extension HomeTimelineViewModel.LoadLatestState {
viewModel.timelineIsEmpty.value = latestStatusIDs.isEmpty && statuses.isEmpty
if !isUserInitiated {
await UIImpactFeedbackGenerator(style: .light)
.impactOccurred()
FeedbackGenerator.shared.generate(.impact(.light))
}
} catch {

View File

@ -8,6 +8,7 @@
import UIKit
import MastodonAsset
import MastodonLocalization
import MastodonCore
enum CategoryPickerSection: Equatable, Hashable {
case main
@ -36,13 +37,13 @@ extension CategoryPickerSection {
let allLanguagesAction = UIAction(title: L10n.Scene.ServerPicker.Language.all) { _ in
viewModel.selectedLanguage.value = nil
UISelectionFeedbackGenerator().selectionChanged()
FeedbackGenerator.shared.generate(.selectionChanged)
cell.titleLabel.text = L10n.Scene.ServerPicker.Button.language
}
let languageActions = viewModel.allLanguages.value.compactMap { language in
UIAction(title: language.language ?? language.locale) { action in
UISelectionFeedbackGenerator().selectionChanged()
FeedbackGenerator.shared.generate(.selectionChanged)
viewModel.selectedLanguage.value = language.locale
cell.titleLabel.text = language.language
}
@ -64,19 +65,19 @@ extension CategoryPickerSection {
let doesntMatterAction = UIAction(title: L10n.Scene.ServerPicker.SignupSpeed.all) { _ in
viewModel.manualApprovalRequired.value = nil
cell.titleLabel.text = L10n.Scene.ServerPicker.Button.signupSpeed
UISelectionFeedbackGenerator().selectionChanged()
FeedbackGenerator.shared.generate(.selectionChanged)
}
let manualApprovalAction = UIAction(title: L10n.Scene.ServerPicker.SignupSpeed.manuallyReviewed) { action in
viewModel.manualApprovalRequired.value = true
cell.titleLabel.text = action.title
UISelectionFeedbackGenerator().selectionChanged()
FeedbackGenerator.shared.generate(.selectionChanged)
}
let instantSignupAction = UIAction(title: L10n.Scene.ServerPicker.SignupSpeed.instant) { action in
viewModel.manualApprovalRequired.value = false
cell.titleLabel.text = action.title
UISelectionFeedbackGenerator().selectionChanged()
FeedbackGenerator.shared.generate(.selectionChanged)
}
let signupSpeedMenu = UIMenu(title: L10n.Scene.ServerPicker.Button.signupSpeed,

View File

@ -10,6 +10,7 @@ import Tabman
import MastodonAsset
import MastodonUI
import MastodonLocalization
import MastodonCore
protocol PickServerServerSectionTableHeaderViewDelegate: AnyObject {
func pickServerServerSectionTableHeaderView(_ headerView: PickServerServerSectionTableHeaderView, collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
@ -97,7 +98,7 @@ extension PickServerServerSectionTableHeaderView {
extension PickServerServerSectionTableHeaderView: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
UISelectionFeedbackGenerator().selectionChanged()
FeedbackGenerator.shared.generate(.selectionChanged)
collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .centeredHorizontally)
delegate?.pickServerServerSectionTableHeaderView(self, collectionView: collectionView, didSelectItemAt: indexPath)

View File

@ -47,7 +47,7 @@ class MainTabBarController: UITabBarController {
@Published var avatarURL: URL?
// haptic feedback
private let selectionFeedbackGenerator = UIImpactFeedbackGenerator(style: .medium)
private let feedbackGenerator = FeedbackGenerator.shared
init(
context: AppContext,
@ -249,7 +249,7 @@ extension MainTabBarController {
@objc private func composeButtonDidPressed(_ sender: Any) {
selectionFeedbackGenerator.impactOccurred()
feedbackGenerator.generate(.impact(.medium))
guard let authContext = self.authContext else { return }
let composeViewModel = ComposeViewModel(
context: context,
@ -382,7 +382,7 @@ extension MainTabBarController: UITabBarControllerDelegate {
// Different tab has been selected, send haptic feedback
if viewController.tabBarItem.tag != tabBarController.selectedIndex {
selectionFeedbackGenerator.impactOccurred()
feedbackGenerator.generate(.impact(.medium))
}
// Assert index is as same as the tab rawValue. This check needs to be done `shouldSelect`

View File

@ -22,10 +22,14 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var coordinator: SceneCoordinator?
var savedShortCutItem: UIApplicationShortcutItem?
let feedbackGenerator = FeedbackGenerator.shared
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = scene as? UIWindowScene else { return }
feedbackGenerator.isEnabled = false // Disable Haptic Feedback for now
#if DEBUG
let window = TouchesVisibleWindow(windowScene: windowScene)
self.window = window

View File

@ -0,0 +1,40 @@
// Copyright © 2024 Mastodon gGmbH. All rights reserved.
import UIKit
public class FeedbackGenerator {
private let lightImpactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light)
private let mediumImpactFeedbackGenerator = UIImpactFeedbackGenerator(style: .medium)
private let notificationFeedbackGenerator = UINotificationFeedbackGenerator()
private let selectionFeedbackGenerator = UISelectionFeedbackGenerator()
public enum Impact {
case light, medium
}
public enum Feedback {
case impact(Impact)
case notification(UINotificationFeedbackGenerator.FeedbackType)
case selectionChanged
}
public static let shared = FeedbackGenerator()
public var isEnabled = true
public func generate(_ feedback: Feedback) {
guard isEnabled else { return }
DispatchQueue.main.async { [self] in
switch feedback {
case .impact(.light):
lightImpactFeedbackGenerator.impactOccurred()
case .impact(.medium):
mediumImpactFeedbackGenerator.impactOccurred()
case let .notification(type):
notificationFeedbackGenerator.notificationOccurred(type)
case .selectionChanged:
selectionFeedbackGenerator.selectionChanged()
}
}
}
}

View File

@ -32,9 +32,7 @@ extension PhotoLibraryService {
extension PhotoLibraryService {
public func save(imageSource source: ImageSource) -> AnyPublisher<Void, Error> {
let impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light)
let notificationFeedbackGenerator = UINotificationFeedbackGenerator()
let feedbackGenerator = FeedbackGenerator.shared
let imageDataPublisher: AnyPublisher<Data, Error> = {
switch source {
@ -50,13 +48,13 @@ extension PhotoLibraryService {
PhotoLibraryService.save(imageData: data)
}
.handleEvents(receiveSubscription: { _ in
impactFeedbackGenerator.impactOccurred()
feedbackGenerator.generate(.impact(.light))
}, receiveCompletion: { completion in
switch completion {
case .failure:
notificationFeedbackGenerator.notificationOccurred(.error)
feedbackGenerator.generate(.notification(.error))
case .finished:
notificationFeedbackGenerator.notificationOccurred(.success)
feedbackGenerator.generate(.notification(.success))
}
})
.eraseToAnyPublisher()
@ -67,10 +65,8 @@ extension PhotoLibraryService {
extension PhotoLibraryService {
public func copy(imageSource source: ImageSource) -> AnyPublisher<Void, Error> {
let impactFeedbackGenerator = UIImpactFeedbackGenerator(style: .light)
let notificationFeedbackGenerator = UINotificationFeedbackGenerator()
let feedbackGenerator = FeedbackGenerator.shared
let imageDataPublisher: AnyPublisher<Data, Error> = {
switch source {
case .url(let url):
@ -85,13 +81,13 @@ extension PhotoLibraryService {
PhotoLibraryService.copy(imageData: data)
}
.handleEvents(receiveSubscription: { _ in
impactFeedbackGenerator.impactOccurred()
feedbackGenerator.generate(.impact(.light))
}, receiveCompletion: { completion in
switch completion {
case .failure:
notificationFeedbackGenerator.notificationOccurred(.error)
feedbackGenerator.generate(.notification(.error))
case .finished:
notificationFeedbackGenerator.notificationOccurred(.success)
feedbackGenerator.generate(.notification(.success))
}
})
.eraseToAnyPublisher()