feat(App): Implement Haptic Feedback for Tab switches and Timeline reload
This commit is contained in:
parent
6362eea3b9
commit
9a3ba02683
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
])
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue