Timeline: restore the selected timeline at the next startup (#694)
* Timeline: restore the selected timeline at the next startup Signed-off-by: Yusuke Arakawa <nekolaboratory@users.noreply.github.com> * Rename: UserDefaultas label name Signed-off-by: Yusuke Arakawa <nekolaboratory@users.noreply.github.com> * Timeline: RawRepresentable of TimelineFilter Signed-off-by: Yusuke Arakawa <nekolaboratory@users.noreply.github.com> * Cleanup code * Supports RemoteTimelineFilter * Cleanup code * Safe saves last viewed status --------- Signed-off-by: Yusuke Arakawa <nekolaboratory@users.noreply.github.com> Co-authored-by: Yusuke Arakawa <nekolaboratory@users.noreply.github.com>
This commit is contained in:
parent
02060cb4cd
commit
dae7b85d3d
|
@ -19,6 +19,8 @@ struct TimelineTab: View {
|
|||
@State private var didAppear: Bool = false
|
||||
@State private var timeline: TimelineFilter
|
||||
@State private var scrollToTopSignal: Int = 0
|
||||
|
||||
@AppStorage("last_timeline_filter") public var lastTimelineFilter: TimelineFilter = TimelineFilter.home
|
||||
|
||||
private let canFilterTimeline: Bool
|
||||
|
||||
|
@ -43,7 +45,11 @@ struct TimelineTab: View {
|
|||
routerPath.client = client
|
||||
if !didAppear && canFilterTimeline {
|
||||
didAppear = true
|
||||
timeline = client.isAuth ? .home : .federated
|
||||
if(client.isAuth) {
|
||||
timeline = lastTimelineFilter
|
||||
} else {
|
||||
timeline = .federated
|
||||
}
|
||||
}
|
||||
Task {
|
||||
await currentAccount.fetchLists()
|
||||
|
@ -53,10 +59,18 @@ struct TimelineTab: View {
|
|||
}
|
||||
}
|
||||
.onChange(of: client.isAuth, perform: { isAuth in
|
||||
timeline = isAuth ? .home : .federated
|
||||
if(client.isAuth) {
|
||||
timeline = lastTimelineFilter
|
||||
} else {
|
||||
timeline = .federated
|
||||
}
|
||||
})
|
||||
.onChange(of: currentAccount.account?.id, perform: { _ in
|
||||
timeline = client.isAuth && canFilterTimeline ? .home : .federated
|
||||
if(client.isAuth && canFilterTimeline) {
|
||||
timeline = lastTimelineFilter
|
||||
} else {
|
||||
timeline = .federated
|
||||
}
|
||||
})
|
||||
.onChange(of: $popToRootTab.wrappedValue) { popToRootTab in
|
||||
if popToRootTab == .timeline {
|
||||
|
@ -70,6 +84,11 @@ struct TimelineTab: View {
|
|||
.onChange(of: currentAccount.account?.id) { _ in
|
||||
routerPath.path = []
|
||||
}
|
||||
.onChange(of: timeline) { timeline in
|
||||
if(timeline == .home || timeline == .federated || timeline == .local) {
|
||||
lastTimelineFilter = timeline
|
||||
}
|
||||
}
|
||||
.withSafariRouter()
|
||||
.environmentObject(routerPath)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Foundation
|
||||
|
||||
public struct List: Decodable, Identifiable, Equatable, Hashable {
|
||||
public struct List: Codable, Identifiable, Equatable, Hashable {
|
||||
public let id: String
|
||||
public let title: String
|
||||
public let repliesPolicy: String
|
||||
|
|
|
@ -136,3 +136,168 @@ public enum TimelineFilter: Hashable, Equatable {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TimelineFilter: Codable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case home
|
||||
case local
|
||||
case federated
|
||||
case trending
|
||||
case hashtag
|
||||
case list
|
||||
case remoteLocal
|
||||
case latest
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let key = container.allKeys.first
|
||||
switch key {
|
||||
case .home:
|
||||
self = .home
|
||||
case .local:
|
||||
self = .local
|
||||
case .federated:
|
||||
self = .federated
|
||||
case .trending:
|
||||
self = .trending
|
||||
case .hashtag:
|
||||
var nestedContainer = try container.nestedUnkeyedContainer(forKey: .hashtag)
|
||||
let tag = try nestedContainer.decode(String.self)
|
||||
let accountId = try nestedContainer.decode(String?.self)
|
||||
self = .hashtag(
|
||||
tag: tag,
|
||||
accountId: accountId
|
||||
)
|
||||
case .list:
|
||||
let list = try container.decode(
|
||||
Models.List.self,
|
||||
forKey: .list
|
||||
)
|
||||
self = .list(list: list)
|
||||
case .remoteLocal:
|
||||
var nestedContainer = try container.nestedUnkeyedContainer(forKey: .remoteLocal)
|
||||
let server = try nestedContainer.decode(String.self)
|
||||
let filter = try nestedContainer.decode(RemoteTimelineFilter.self)
|
||||
self = .remoteLocal(
|
||||
server: server,
|
||||
filter: filter
|
||||
)
|
||||
case .latest:
|
||||
self = .latest
|
||||
default:
|
||||
throw DecodingError.dataCorrupted(
|
||||
DecodingError.Context(
|
||||
codingPath: container.codingPath,
|
||||
debugDescription: "Unabled to decode enum."
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
switch self {
|
||||
case .home:
|
||||
try container.encode(CodingKeys.home.rawValue, forKey: .home)
|
||||
case .local:
|
||||
try container.encode(CodingKeys.local.rawValue, forKey: .local)
|
||||
case .federated:
|
||||
try container.encode(CodingKeys.federated.rawValue, forKey: .federated)
|
||||
case .trending:
|
||||
try container.encode(CodingKeys.trending.rawValue, forKey: .trending)
|
||||
case .hashtag(let tag, let accountId):
|
||||
var nestedContainer = container.nestedUnkeyedContainer(forKey: .hashtag)
|
||||
try nestedContainer.encode(tag)
|
||||
try nestedContainer.encode(accountId)
|
||||
case .list(let list):
|
||||
try container.encode(list, forKey: .list)
|
||||
case .remoteLocal(let server, let filter):
|
||||
var nestedContainer = container.nestedUnkeyedContainer(forKey: .hashtag)
|
||||
try nestedContainer.encode(server)
|
||||
try nestedContainer.encode(filter)
|
||||
case .latest:
|
||||
try container.encode(CodingKeys.latest.rawValue, forKey: .latest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TimelineFilter: RawRepresentable {
|
||||
public init?(rawValue: String) {
|
||||
guard let data = rawValue.data(using: .utf8),
|
||||
let result = try? JSONDecoder().decode(TimelineFilter.self, from: data)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
self = result
|
||||
}
|
||||
|
||||
public var rawValue: String {
|
||||
guard let data = try? JSONEncoder().encode(self),
|
||||
let result = String(data: data, encoding: .utf8)
|
||||
else {
|
||||
return "[]"
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
extension RemoteTimelineFilter: Codable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case local
|
||||
case federated
|
||||
case trending
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let key = container.allKeys.first
|
||||
switch key {
|
||||
case .local:
|
||||
self = .local
|
||||
case .federated:
|
||||
self = .federated
|
||||
case .trending:
|
||||
self = .trending
|
||||
default:
|
||||
throw DecodingError.dataCorrupted(
|
||||
DecodingError.Context(
|
||||
codingPath: container.codingPath,
|
||||
debugDescription: "Unabled to decode enum."
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
switch self {
|
||||
case .local:
|
||||
try container.encode(CodingKeys.local.rawValue, forKey: .local)
|
||||
case .federated:
|
||||
try container.encode(CodingKeys.federated.rawValue, forKey: .federated)
|
||||
case .trending:
|
||||
try container.encode(CodingKeys.trending.rawValue, forKey: .trending)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension RemoteTimelineFilter: RawRepresentable {
|
||||
public init?(rawValue: String) {
|
||||
guard let data = rawValue.data(using: .utf8),
|
||||
let result = try? JSONDecoder().decode(RemoteTimelineFilter.self, from: data)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
self = result
|
||||
}
|
||||
|
||||
public var rawValue: String {
|
||||
guard let data = try? JSONEncoder().encode(self),
|
||||
let result = String(data: data, encoding: .utf8)
|
||||
else {
|
||||
return "[]"
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue