feat(App): Implement Haptic Feedback for Tab switches and Timeline reload

This commit is contained in:
Marcus Kida 2023-02-07 11:35:25 +01:00
parent 6362eea3b9
commit 9a3ba02683
No known key found for this signature in database
GPG Key ID: 19FF64E08013CA40
4 changed files with 79 additions and 50 deletions

View File

@ -394,7 +394,7 @@ extension HomeTimelineViewController {
}
@objc private func refreshControlValueChanged(_ sender: RefreshControl) {
guard viewModel.loadLatestStateMachine.enter(HomeTimelineViewModel.LoadLatestState.Loading.self) else {
guard viewModel.loadLatestStateMachine.enter(HomeTimelineViewModel.LoadLatestState.LoadingManually.self) else {
sender.endRefreshing()
return
}

View File

@ -61,7 +61,35 @@ extension HomeTimelineViewModel.LoadLatestState {
}
override func didEnter(from previousState: GKState?) {
didEnter(from: previousState, viewModel: viewModel, isUserInitiated: false)
}
}
class LoadingManually: HomeTimelineViewModel.LoadLatestState {
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass == Fail.self || stateClass == Idle.self
}
override func didEnter(from previousState: GKState?) {
didEnter(from: previousState, viewModel: viewModel, isUserInitiated: true)
}
}
class Fail: HomeTimelineViewModel.LoadLatestState {
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass == Loading.self || stateClass == Idle.self
}
}
class Idle: HomeTimelineViewModel.LoadLatestState {
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass == Loading.self
}
}
private func didEnter(from previousState: GKState?, viewModel: HomeTimelineViewModel?, isUserInitiated: Bool) {
super.didEnter(from: previousState)
guard let viewModel else { return }
let latestFeedRecords = viewModel.fetchedResultsController.records.prefix(APIService.onceRequestStatusMaxCount)
@ -102,6 +130,11 @@ extension HomeTimelineViewModel.LoadLatestState {
}
viewModel.timelineIsEmpty.value = latestStatusIDs.isEmpty && statuses.isEmpty
if !isUserInitiated {
await UIImpactFeedbackGenerator(style: .light)
.impactOccurred()
}
} catch {
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public): fetch statuses failed: \(error.localizedDescription)")
await enter(state: Idle.self)
@ -111,17 +144,3 @@ extension HomeTimelineViewModel.LoadLatestState {
} // end Task
}
}
class Fail: HomeTimelineViewModel.LoadLatestState {
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass == Loading.self || stateClass == Idle.self
}
}
class Idle: HomeTimelineViewModel.LoadLatestState {
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
return stateClass == Loading.self
}
}
}

View File

@ -52,6 +52,7 @@ final class HomeTimelineViewModel: NSObject {
let stateMachine = GKStateMachine(states: [
LoadLatestState.Initial(viewModel: self),
LoadLatestState.Loading(viewModel: self),
LoadLatestState.LoadingManually(viewModel: self),
LoadLatestState.Fail(viewModel: self),
LoadLatestState.Idle(viewModel: self),
])

View File

@ -155,6 +155,9 @@ class MainTabBarController: UITabBarController {
var avatarURLObserver: AnyCancellable?
@Published var avatarURL: URL?
// haptic feedback
private let selectionFeedbackGenerator = UIImpactFeedbackGenerator(style: .medium)
init(
context: AppContext,
coordinator: SceneCoordinator,
@ -378,6 +381,7 @@ extension MainTabBarController {
@objc private func composeButtonDidPressed(_ sender: Any) {
logger.log(level: .debug, "\((#file as NSString).lastPathComponent, privacy: .public)[\(#line, privacy: .public)], \(#function, privacy: .public)")
selectionFeedbackGenerator.impactOccurred()
guard let authContext = self.authContext else { return }
let composeViewModel = ComposeViewModel(
context: context,
@ -573,6 +577,11 @@ extension MainTabBarController: UITabBarControllerDelegate {
return false
}
// Different tab has been selected, send haptic feedback
if viewController.tabBarItem.tag != tabBarController.selectedIndex {
selectionFeedbackGenerator.impactOccurred()
}
// Assert index is as same as the tab rawValue. This check needs to be done `shouldSelect`
// because the nav controller has already popped in `didSelect`.
if currentTab.rawValue == tabBarController.selectedIndex,