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:
Yusuke Arakawa 2023-02-09 20:27:59 +09:00 committed by GitHub
parent 02060cb4cd
commit dae7b85d3d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 188 additions and 4 deletions

View file

@ -20,6 +20,8 @@ struct TimelineTab: View {
@State private var timeline: TimelineFilter @State private var timeline: TimelineFilter
@State private var scrollToTopSignal: Int = 0 @State private var scrollToTopSignal: Int = 0
@AppStorage("last_timeline_filter") public var lastTimelineFilter: TimelineFilter = TimelineFilter.home
private let canFilterTimeline: Bool private let canFilterTimeline: Bool
init(popToRootTab: Binding<Tab>, timeline: TimelineFilter? = nil) { init(popToRootTab: Binding<Tab>, timeline: TimelineFilter? = nil) {
@ -43,7 +45,11 @@ struct TimelineTab: View {
routerPath.client = client routerPath.client = client
if !didAppear && canFilterTimeline { if !didAppear && canFilterTimeline {
didAppear = true didAppear = true
timeline = client.isAuth ? .home : .federated if(client.isAuth) {
timeline = lastTimelineFilter
} else {
timeline = .federated
}
} }
Task { Task {
await currentAccount.fetchLists() await currentAccount.fetchLists()
@ -53,10 +59,18 @@ struct TimelineTab: View {
} }
} }
.onChange(of: client.isAuth, perform: { isAuth in .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 .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 .onChange(of: $popToRootTab.wrappedValue) { popToRootTab in
if popToRootTab == .timeline { if popToRootTab == .timeline {
@ -70,6 +84,11 @@ struct TimelineTab: View {
.onChange(of: currentAccount.account?.id) { _ in .onChange(of: currentAccount.account?.id) { _ in
routerPath.path = [] routerPath.path = []
} }
.onChange(of: timeline) { timeline in
if(timeline == .home || timeline == .federated || timeline == .local) {
lastTimelineFilter = timeline
}
}
.withSafariRouter() .withSafariRouter()
.environmentObject(routerPath) .environmentObject(routerPath)
} }

View file

@ -1,6 +1,6 @@
import Foundation import Foundation
public struct List: Decodable, Identifiable, Equatable, Hashable { public struct List: Codable, Identifiable, Equatable, Hashable {
public let id: String public let id: String
public let title: String public let title: String
public let repliesPolicy: String public let repliesPolicy: String

View file

@ -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
}
}