Diffable fade animations (#1343)

* animate only 2 seconds after the appearence

* scrolling is stable

* code improvement

* comment and removing code that is not necessary anymore

* feature flag

* comment

* better code

* the day divider ID is the timestamp itself as ISO

* Apply suggestions from code review

Co-authored-by: Alfonso Grillo <alfogrillo@gmail.com>

* format fix

---------

Co-authored-by: Alfonso Grillo <alfogrillo@gmail.com>
This commit is contained in:
Mauro 2023-07-18 16:18:11 +02:00 committed by GitHub
parent 48dbbfb4d0
commit f46abbaf9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 34 additions and 12 deletions

View File

@ -36,6 +36,7 @@ final class AppSettings {
case shareLocationEnabled
case hasShownWelcomeScreen
case notificationSettingsEnabled
case timelineDiffableAnimationsEnabled
}
private static var suiteName: String = InfoPlistReader.main.appGroupIdentifier
@ -226,4 +227,7 @@ final class AppSettings {
@UserPreference(key: UserDefaultsKeys.notificationSettingsEnabled, defaultValue: false, storageType: .userDefaults(store))
var notificationSettingsEnabled
@UserPreference(key: UserDefaultsKeys.timelineDiffableAnimationsEnabled, defaultValue: false, storageType: .userDefaults(store))
var timelineDiffableAnimationsEnabled
}

View File

@ -97,6 +97,8 @@ class TimelineTableViewController: UIViewController {
private var previousLayout: LayoutDescriptor?
/// Whether or not the view has been shown on screen yet.
private var hasAppearedOnce = false
/// Whether the scroll and the animations should happen
private var shouldAnimate = false
init(coordinator: TimelineView.Coordinator,
timelineStyle: TimelineStyle,
@ -168,6 +170,11 @@ class TimelineTableViewController: UIViewController {
guard !hasAppearedOnce else { return }
scrollToBottom(animated: false)
hasAppearedOnce = true
// This is to prevent the SS proxy issue that forces a full refresh of all the timeline items
// the first time the timeline is opened
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
self.shouldAnimate = ServiceLocator.shared.settings.timelineDiffableAnimationsEnabled
}
}
override func didMove(toParent parent: UIViewController?) {
@ -231,8 +238,7 @@ class TimelineTableViewController: UIViewController {
return cell
}
// dataSource?.defaultRowAnimation = .automatic
dataSource?.defaultRowAnimation = .fade
tableView.delegate = self
}
@ -249,13 +255,14 @@ class TimelineTableViewController: UIViewController {
snapshot.appendSections([.main])
snapshot.appendItems(timelineItemsIDs)
MXLog.verbose("DIFF: \(snapshot.itemIdentifiers.difference(from: dataSource.snapshot().itemIdentifiers))")
dataSource.apply(snapshot, animatingDifferences: false)
// Probably redundant now we observe content size changes
// Leaving in place for the release and will reassess after.
updateTopPadding()
let currentSnapshot = dataSource.snapshot()
MXLog.verbose("DIFF: \(snapshot.itemIdentifiers.difference(from: currentSnapshot.itemIdentifiers))")
// We only animate if the last item has changed
// We don't care to animate backpagination since we want to keep the scrolling position when that happens
let animated = shouldAnimate && snapshot.itemIdentifiers.last != currentSnapshot.itemIdentifiers.last
dataSource.apply(snapshot, animatingDifferences: animated)
if previousLayout.isBottomVisible {
scrollToBottom(animated: false)
} else if let pinnedItem = previousLayout.pinnedItem {

View File

@ -32,6 +32,7 @@ struct DeveloperOptionsScreenViewStateBindings {
var locationEventsEnabled: Bool
var shareLocationEnabled: Bool
var notificationSettingsEnabled: Bool
var timelineDiffableAnimationsEnabled: Bool
}
enum DeveloperOptionsScreenViewAction {
@ -42,5 +43,6 @@ enum DeveloperOptionsScreenViewAction {
case changedLocationEventsEnabled
case changedShareLocationEnabled
case changedNotificationSettingsEnabled
case changedTimelineDiffableAnimationsEnabled
case clearCache
}

View File

@ -32,7 +32,8 @@ class DeveloperOptionsScreenViewModel: DeveloperOptionsScreenViewModelType, Deve
isEncryptionSyncEnabled: appSettings.isEncryptionSyncEnabled,
locationEventsEnabled: appSettings.locationEventsEnabled,
shareLocationEnabled: appSettings.shareLocationEnabled,
notificationSettingsEnabled: appSettings.notificationSettingsEnabled)
notificationSettingsEnabled: appSettings.notificationSettingsEnabled,
timelineDiffableAnimationsEnabled: appSettings.timelineDiffableAnimationsEnabled)
let state = DeveloperOptionsScreenViewState(bindings: bindings)
super.init(initialViewState: state)
@ -58,6 +59,8 @@ class DeveloperOptionsScreenViewModel: DeveloperOptionsScreenViewModelType, Deve
appSettings.shareLocationEnabled = state.bindings.shareLocationEnabled
case .changedNotificationSettingsEnabled:
appSettings.notificationSettingsEnabled = state.bindings.notificationSettingsEnabled
case .changedTimelineDiffableAnimationsEnabled:
appSettings.timelineDiffableAnimationsEnabled = state.bindings.timelineDiffableAnimationsEnabled
case .clearCache:
callback?(.clearCache)
}

View File

@ -79,6 +79,13 @@ struct DeveloperOptionsScreen: View {
.onChange(of: context.userSuggestionsEnabled) { _ in
context.send(viewAction: .changedUserSuggestionsEnabled)
}
Toggle(isOn: $context.timelineDiffableAnimationsEnabled) {
Text("Enable diffable animations in the timeline")
}
.onChange(of: context.timelineDiffableAnimationsEnabled) { _ in
context.send(viewAction: .changedTimelineDiffableAnimationsEnabled)
}
}
Section {

View File

@ -355,8 +355,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
let date = Date(timeIntervalSince1970: TimeInterval(timestamp / 1000))
let dateString = date.formatted(date: .complete, time: .omitted)
// Separators without stable identifiers cause UI glitches
let identifier = "\(chunkIndex)-\(dateString)"
let identifier = date.formatted(.iso8601.dateSeparator(.dash))
return SeparatorRoomTimelineItem(id: .init(timelineID: identifier), text: dateString)
case .readMarker:
return ReadMarkerRoomTimelineItem()