Fix and simplify startup position
This commit is contained in:
parent
728fdddca1
commit
3149fd8edf
|
@ -16,7 +16,6 @@ public struct ContentDatabase {
|
||||||
|
|
||||||
public init(id: Identity.Id,
|
public init(id: Identity.Id,
|
||||||
useHomeTimelineLastReadId: Bool,
|
useHomeTimelineLastReadId: Bool,
|
||||||
useNotificationsLastReadId: Bool,
|
|
||||||
inMemory: Bool,
|
inMemory: Bool,
|
||||||
appGroup: String,
|
appGroup: String,
|
||||||
keychain: Keychain.Type) throws {
|
keychain: Keychain.Type) throws {
|
||||||
|
@ -35,8 +34,7 @@ public struct ContentDatabase {
|
||||||
|
|
||||||
try Self.clean(
|
try Self.clean(
|
||||||
databaseWriter,
|
databaseWriter,
|
||||||
useHomeTimelineLastReadId: useHomeTimelineLastReadId,
|
useHomeTimelineLastReadId: useHomeTimelineLastReadId)
|
||||||
useNotificationsLastReadId: useNotificationsLastReadId)
|
|
||||||
|
|
||||||
activeFiltersPublisher = ValueObservation.tracking {
|
activeFiltersPublisher = ValueObservation.tracking {
|
||||||
try Filter.filter(Filter.Columns.expiresAt == nil || Filter.Columns.expiresAt > Date()).fetchAll($0)
|
try Filter.filter(Filter.Columns.expiresAt == nil || Filter.Columns.expiresAt > Date()).fetchAll($0)
|
||||||
|
@ -708,48 +706,14 @@ private extension ContentDatabase {
|
||||||
|
|
||||||
// swiftlint:disable:next function_body_length
|
// swiftlint:disable:next function_body_length
|
||||||
static func clean(_ databaseWriter: DatabaseWriter,
|
static func clean(_ databaseWriter: DatabaseWriter,
|
||||||
useHomeTimelineLastReadId: Bool,
|
useHomeTimelineLastReadId: Bool) throws {
|
||||||
useNotificationsLastReadId: Bool) throws {
|
|
||||||
try databaseWriter.write {
|
try databaseWriter.write {
|
||||||
let notificationAccountIds: [Account.Id]
|
try NotificationRecord.deleteAll($0)
|
||||||
let notificationStatusIds: [Status.Id]
|
|
||||||
|
|
||||||
try ConversationRecord.deleteAll($0)
|
try ConversationRecord.deleteAll($0)
|
||||||
try StatusAncestorJoin.deleteAll($0)
|
try StatusAncestorJoin.deleteAll($0)
|
||||||
try StatusDescendantJoin.deleteAll($0)
|
try StatusDescendantJoin.deleteAll($0)
|
||||||
try AccountList.deleteAll($0)
|
try AccountList.deleteAll($0)
|
||||||
|
|
||||||
if useNotificationsLastReadId {
|
|
||||||
var notificationIds = try MastodonNotification.Id.fetchAll(
|
|
||||||
$0,
|
|
||||||
NotificationRecord.select(NotificationRecord.Columns.id)
|
|
||||||
.order(NotificationRecord.Columns.id.desc))
|
|
||||||
|
|
||||||
if let lastReadId = try MastodonNotification.Id.fetchOne(
|
|
||||||
$0,
|
|
||||||
LastReadIdRecord.filter(
|
|
||||||
LastReadIdRecord.Columns.markerTimeline == Marker.Timeline.notifications.rawValue)
|
|
||||||
.select(LastReadIdRecord.Columns.id))
|
|
||||||
?? notificationIds.first,
|
|
||||||
let index = notificationIds.firstIndex(of: lastReadId) {
|
|
||||||
notificationIds = Array(notificationIds.prefix(index + Self.cleanAfterLastReadIdCount))
|
|
||||||
}
|
|
||||||
|
|
||||||
try NotificationRecord.filter(!notificationIds.contains(NotificationRecord.Columns.id)).deleteAll($0)
|
|
||||||
notificationAccountIds = try Account.Id.fetchAll(
|
|
||||||
$0,
|
|
||||||
NotificationRecord.select(NotificationRecord.Columns.accountId))
|
|
||||||
notificationStatusIds = try Status.Id.fetchAll(
|
|
||||||
$0,
|
|
||||||
NotificationRecord.filter(
|
|
||||||
NotificationRecord.Columns.statusId != nil)
|
|
||||||
.select(NotificationRecord.Columns.statusId))
|
|
||||||
} else {
|
|
||||||
try NotificationRecord.deleteAll($0)
|
|
||||||
notificationAccountIds = []
|
|
||||||
notificationStatusIds = []
|
|
||||||
}
|
|
||||||
|
|
||||||
if useHomeTimelineLastReadId {
|
if useHomeTimelineLastReadId {
|
||||||
try TimelineRecord.filter(TimelineRecord.Columns.id != Timeline.home.id).deleteAll($0)
|
try TimelineRecord.filter(TimelineRecord.Columns.id != Timeline.home.id).deleteAll($0)
|
||||||
var statusIds = try Status.Id.fetchAll(
|
var statusIds = try Status.Id.fetchAll(
|
||||||
|
@ -766,7 +730,6 @@ private extension ContentDatabase {
|
||||||
statusIds = Array(statusIds.prefix(index + Self.cleanAfterLastReadIdCount))
|
statusIds = Array(statusIds.prefix(index + Self.cleanAfterLastReadIdCount))
|
||||||
}
|
}
|
||||||
|
|
||||||
statusIds += notificationStatusIds
|
|
||||||
statusIds += try Status.Id.fetchAll(
|
statusIds += try Status.Id.fetchAll(
|
||||||
$0,
|
$0,
|
||||||
StatusRecord.filter(statusIds.contains(StatusRecord.Columns.id)
|
StatusRecord.filter(statusIds.contains(StatusRecord.Columns.id)
|
||||||
|
@ -774,7 +737,6 @@ private extension ContentDatabase {
|
||||||
.select(StatusRecord.Columns.reblogId))
|
.select(StatusRecord.Columns.reblogId))
|
||||||
try StatusRecord.filter(!statusIds.contains(StatusRecord.Columns.id)).deleteAll($0)
|
try StatusRecord.filter(!statusIds.contains(StatusRecord.Columns.id)).deleteAll($0)
|
||||||
var accountIds = try Account.Id.fetchAll($0, StatusRecord.select(StatusRecord.Columns.accountId))
|
var accountIds = try Account.Id.fetchAll($0, StatusRecord.select(StatusRecord.Columns.accountId))
|
||||||
accountIds += notificationAccountIds
|
|
||||||
accountIds += try Account.Id.fetchAll(
|
accountIds += try Account.Id.fetchAll(
|
||||||
$0,
|
$0,
|
||||||
AccountRecord.filter(accountIds.contains(AccountRecord.Columns.id)
|
AccountRecord.filter(accountIds.contains(AccountRecord.Columns.id)
|
||||||
|
@ -783,8 +745,8 @@ private extension ContentDatabase {
|
||||||
try AccountRecord.filter(!accountIds.contains(AccountRecord.Columns.id)).deleteAll($0)
|
try AccountRecord.filter(!accountIds.contains(AccountRecord.Columns.id)).deleteAll($0)
|
||||||
} else {
|
} else {
|
||||||
try TimelineRecord.deleteAll($0)
|
try TimelineRecord.deleteAll($0)
|
||||||
try StatusRecord.filter(!notificationStatusIds.contains(StatusRecord.Columns.id)).deleteAll($0)
|
try StatusRecord.deleteAll($0)
|
||||||
try AccountRecord.filter(!notificationAccountIds.contains(AccountRecord.Columns.id)).deleteAll($0)
|
try AccountRecord.deleteAll($0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -198,7 +198,6 @@
|
||||||
"preferences.home-timeline-position-on-startup" = "Home timeline position on startup";
|
"preferences.home-timeline-position-on-startup" = "Home timeline position on startup";
|
||||||
"preferences.notifications-position-on-startup" = "Notifications position on startup";
|
"preferences.notifications-position-on-startup" = "Notifications position on startup";
|
||||||
"preferences.position.remember-position" = "Remember position";
|
"preferences.position.remember-position" = "Remember position";
|
||||||
"preferences.position.sync-position" = "Sync position with web and other devices";
|
|
||||||
"preferences.position.newest" = "Load newest";
|
"preferences.position.newest" = "Load newest";
|
||||||
"preferences.require-double-tap-to-reblog" = "Require double tap to reblog";
|
"preferences.require-double-tap-to-reblog" = "Require double tap to reblog";
|
||||||
"preferences.require-double-tap-to-favorite" = "Require double tap to favorite";
|
"preferences.require-double-tap-to-favorite" = "Require double tap to favorite";
|
||||||
|
|
|
@ -34,7 +34,6 @@ public struct IdentityService {
|
||||||
contentDatabase = try ContentDatabase(
|
contentDatabase = try ContentDatabase(
|
||||||
id: id,
|
id: id,
|
||||||
useHomeTimelineLastReadId: appPreferences.homeTimelineBehavior == .rememberPosition,
|
useHomeTimelineLastReadId: appPreferences.homeTimelineBehavior == .rememberPosition,
|
||||||
useNotificationsLastReadId: appPreferences.notificationsTabBehavior == .rememberPosition,
|
|
||||||
inMemory: environment.inMemoryContent,
|
inMemory: environment.inMemoryContent,
|
||||||
appGroup: AppEnvironment.appGroup,
|
appGroup: AppEnvironment.appGroup,
|
||||||
keychain: environment.keychain)
|
keychain: environment.keychain)
|
||||||
|
@ -134,10 +133,6 @@ public extension IdentityService {
|
||||||
switch AppPreferences(environment: environment).positionBehavior(markerTimeline: markerTimeline) {
|
switch AppPreferences(environment: environment).positionBehavior(markerTimeline: markerTimeline) {
|
||||||
case .rememberPosition:
|
case .rememberPosition:
|
||||||
return contentDatabase.setLastReadId(id, markerTimeline: markerTimeline)
|
return contentDatabase.setLastReadId(id, markerTimeline: markerTimeline)
|
||||||
case .syncPosition:
|
|
||||||
return mastodonAPIClient.request(MarkersEndpoint.post([markerTimeline: id]))
|
|
||||||
.ignoreOutput()
|
|
||||||
.eraseToAnyPublisher()
|
|
||||||
case .newest:
|
case .newest:
|
||||||
return Empty().eraseToAnyPublisher()
|
return Empty().eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,6 @@ public extension AppPreferences {
|
||||||
|
|
||||||
enum PositionBehavior: String, CaseIterable, Identifiable {
|
enum PositionBehavior: String, CaseIterable, Identifiable {
|
||||||
case rememberPosition
|
case rememberPosition
|
||||||
case syncPosition
|
|
||||||
case newest
|
case newest
|
||||||
|
|
||||||
public var id: String { rawValue }
|
public var id: String { rawValue }
|
||||||
|
@ -116,18 +115,6 @@ public extension AppPreferences {
|
||||||
set { self[.homeTimelineBehavior] = newValue.rawValue }
|
set { self[.homeTimelineBehavior] = newValue.rawValue }
|
||||||
}
|
}
|
||||||
|
|
||||||
var notificationsTabBehavior: PositionBehavior {
|
|
||||||
get {
|
|
||||||
if let rawValue = self[.notificationsTabBehavior] as String?,
|
|
||||||
let value = PositionBehavior(rawValue: rawValue) {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
return .newest
|
|
||||||
}
|
|
||||||
set { self[.notificationsTabBehavior] = newValue.rawValue }
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaultEmojiSkinTone: SystemEmoji.SkinTone? {
|
var defaultEmojiSkinTone: SystemEmoji.SkinTone? {
|
||||||
get {
|
get {
|
||||||
if let rawValue = self[.defaultEmojiSkinTone] as Int?,
|
if let rawValue = self[.defaultEmojiSkinTone] as Int?,
|
||||||
|
@ -158,7 +145,7 @@ public extension AppPreferences {
|
||||||
case .home:
|
case .home:
|
||||||
return homeTimelineBehavior
|
return homeTimelineBehavior
|
||||||
case .notifications:
|
case .notifications:
|
||||||
return notificationsTabBehavior
|
return .newest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,6 @@ extension ContentDatabase {
|
||||||
static let preview = try! ContentDatabase(
|
static let preview = try! ContentDatabase(
|
||||||
id: identityId,
|
id: identityId,
|
||||||
useHomeTimelineLastReadId: false,
|
useHomeTimelineLastReadId: false,
|
||||||
useNotificationsLastReadId: false,
|
|
||||||
inMemory: true,
|
inMemory: true,
|
||||||
appGroup: "group.metabolist.metatext",
|
appGroup: "group.metabolist.metatext",
|
||||||
keychain: MockKeychain.self)
|
keychain: MockKeychain.self)
|
||||||
|
|
|
@ -21,7 +21,7 @@ public class CollectionItemsViewModel: ObservableObject {
|
||||||
private let lastReadId = CurrentValueSubject<String?, Never>(nil)
|
private let lastReadId = CurrentValueSubject<String?, Never>(nil)
|
||||||
private var lastSelectedLoadMore: LoadMore?
|
private var lastSelectedLoadMore: LoadMore?
|
||||||
private var hasRequestedUsingMarker = false
|
private var hasRequestedUsingMarker = false
|
||||||
private var shouldRestorePositionOfLocalLastReadId = false
|
private var markerScrollPositionItemId: CollectionItem.Id?
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
public init(collectionService: CollectionService, identityContext: IdentityContext) {
|
public init(collectionService: CollectionService, identityContext: IdentityContext) {
|
||||||
|
@ -50,8 +50,10 @@ public class CollectionItemsViewModel: ObservableObject {
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
if let markerTimeline = collectionService.markerTimeline {
|
if let markerTimeline = collectionService.markerTimeline {
|
||||||
shouldRestorePositionOfLocalLastReadId =
|
if identityContext.appPreferences.positionBehavior(markerTimeline: markerTimeline) == .rememberPosition {
|
||||||
identityContext.appPreferences.positionBehavior(markerTimeline: markerTimeline) == .rememberPosition
|
markerScrollPositionItemId = identityContext.service.getLocalLastReadId(markerTimeline)
|
||||||
|
}
|
||||||
|
|
||||||
lastReadId.compactMap { $0 }
|
lastReadId.compactMap { $0 }
|
||||||
.removeDuplicates()
|
.removeDuplicates()
|
||||||
.debounce(for: .seconds(Self.lastReadIdDebounceInterval), scheduler: DispatchQueue.global())
|
.debounce(for: .seconds(Self.lastReadIdDebounceInterval), scheduler: DispatchQueue.global())
|
||||||
|
@ -102,32 +104,7 @@ extension CollectionItemsViewModel: CollectionViewModel {
|
||||||
public var canRefresh: Bool { collectionService.canRefresh }
|
public var canRefresh: Bool { collectionService.canRefresh }
|
||||||
|
|
||||||
public func request(maxId: String? = nil, minId: String? = nil, search: Search?) {
|
public func request(maxId: String? = nil, minId: String? = nil, search: Search?) {
|
||||||
let publisher: AnyPublisher<Never, Error>
|
collectionService.request(maxId: realMaxId(maxId: maxId), minId: minId, search: search)
|
||||||
|
|
||||||
if let markerTimeline = collectionService.markerTimeline,
|
|
||||||
identityContext.appPreferences.positionBehavior(markerTimeline: markerTimeline) == .syncPosition,
|
|
||||||
!hasRequestedUsingMarker {
|
|
||||||
publisher = identityContext.service.getMarker(markerTimeline)
|
|
||||||
.flatMap { [weak self] in
|
|
||||||
self?.collectionService.request(maxId: $0.lastReadId, minId: nil, search: nil)
|
|
||||||
?? Empty().eraseToAnyPublisher()
|
|
||||||
}
|
|
||||||
.catch { [weak self] _ in
|
|
||||||
self?.collectionService.request(maxId: nil, minId: nil, search: nil)
|
|
||||||
?? Empty().eraseToAnyPublisher()
|
|
||||||
}
|
|
||||||
.collect()
|
|
||||||
.flatMap { [weak self] _ in
|
|
||||||
self?.collectionService.request(maxId: nil, minId: nil, search: nil)
|
|
||||||
?? Empty().eraseToAnyPublisher()
|
|
||||||
}
|
|
||||||
.eraseToAnyPublisher()
|
|
||||||
self.hasRequestedUsingMarker = true
|
|
||||||
} else {
|
|
||||||
publisher = collectionService.request(maxId: realMaxId(maxId: maxId), minId: minId, search: search)
|
|
||||||
}
|
|
||||||
|
|
||||||
publisher
|
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
.assignErrorsToAlertItem(to: \.alertItem, on: self)
|
||||||
.handleEvents(
|
.handleEvents(
|
||||||
|
@ -180,8 +157,7 @@ extension CollectionItemsViewModel: CollectionViewModel {
|
||||||
public func viewedAtTop(indexPath: IndexPath) {
|
public func viewedAtTop(indexPath: IndexPath) {
|
||||||
topVisibleIndexPath = indexPath
|
topVisibleIndexPath = indexPath
|
||||||
|
|
||||||
if !shouldRestorePositionOfLocalLastReadId,
|
if lastUpdate.sections.count > indexPath.section,
|
||||||
lastUpdate.sections.count > indexPath.section,
|
|
||||||
lastUpdate.sections[indexPath.section].items.count > indexPath.item {
|
lastUpdate.sections[indexPath.section].items.count > indexPath.item {
|
||||||
lastReadId.send(lastUpdate.sections[indexPath.section].items[indexPath.item].itemId)
|
lastReadId.send(lastUpdate.sections[indexPath.section].items[indexPath.item].itemId)
|
||||||
}
|
}
|
||||||
|
@ -387,13 +363,11 @@ private extension CollectionItemsViewModel {
|
||||||
let items = lastUpdate.sections.map(\.items).reduce([], +)
|
let items = lastUpdate.sections.map(\.items).reduce([], +)
|
||||||
let newItems = newSections.map(\.items).reduce([], +)
|
let newItems = newSections.map(\.items).reduce([], +)
|
||||||
|
|
||||||
if shouldRestorePositionOfLocalLastReadId,
|
if let itemId = markerScrollPositionItemId,
|
||||||
let markerTimeline = collectionService.markerTimeline,
|
newItems.contains(where: { $0.itemId == itemId }) {
|
||||||
let localLastReadId = identityContext.service.getLocalLastReadId(markerTimeline),
|
markerScrollPositionItemId = nil
|
||||||
newItems.contains(where: { $0.itemId == localLastReadId }) {
|
|
||||||
shouldRestorePositionOfLocalLastReadId = false
|
|
||||||
|
|
||||||
return localLastReadId
|
return itemId
|
||||||
}
|
}
|
||||||
|
|
||||||
if collectionService is ContextService,
|
if collectionService is ContextService,
|
||||||
|
|
|
@ -121,12 +121,6 @@ struct PreferencesView: View {
|
||||||
Text(option.localizedStringKey).tag(option)
|
Text(option.localizedStringKey).tag(option)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Picker("preferences.notifications-position-on-startup",
|
|
||||||
selection: $identityContext.appPreferences.notificationsTabBehavior) {
|
|
||||||
ForEach(AppPreferences.PositionBehavior.allCases) { option in
|
|
||||||
Text(option.localizedStringKey).tag(option)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,8 +177,6 @@ extension AppPreferences.PositionBehavior {
|
||||||
switch self {
|
switch self {
|
||||||
case .rememberPosition:
|
case .rememberPosition:
|
||||||
return "preferences.position.remember-position"
|
return "preferences.position.remember-position"
|
||||||
case .syncPosition:
|
|
||||||
return "preferences.position.sync-position"
|
|
||||||
case .newest:
|
case .newest:
|
||||||
return "preferences.position.newest"
|
return "preferences.position.newest"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue