Recent identities menu

This commit is contained in:
Justin Mazzocchi 2020-08-05 04:48:50 -07:00
parent 6f0cf59fd0
commit 038d17674b
No known key found for this signature in database
GPG key ID: E223E6937AAFB01C
7 changed files with 86 additions and 37 deletions

View file

@ -73,6 +73,8 @@
D074577B24D29366004758DB /* URLSessionConfiguration+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D074577924D29366004758DB /* URLSessionConfiguration+Extensions.swift */; }; D074577B24D29366004758DB /* URLSessionConfiguration+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D074577924D29366004758DB /* URLSessionConfiguration+Extensions.swift */; };
D081A40524D0F1A8001B016E /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D081A40424D0F1A8001B016E /* String+Extensions.swift */; }; D081A40524D0F1A8001B016E /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D081A40424D0F1A8001B016E /* String+Extensions.swift */; };
D081A40624D0F1A8001B016E /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D081A40424D0F1A8001B016E /* String+Extensions.swift */; }; D081A40624D0F1A8001B016E /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D081A40424D0F1A8001B016E /* String+Extensions.swift */; };
D0A1CA7424DAC2F1003063E9 /* KingfisherOptionsInfo+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A1CA7324DAC2F1003063E9 /* KingfisherOptionsInfo+Extensions.swift */; };
D0A1CA7524DAC2F1003063E9 /* KingfisherOptionsInfo+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A1CA7324DAC2F1003063E9 /* KingfisherOptionsInfo+Extensions.swift */; };
D0B23F0D24D210E90066F411 /* NSError+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B23F0C24D210E90066F411 /* NSError+Extensions.swift */; }; D0B23F0D24D210E90066F411 /* NSError+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B23F0C24D210E90066F411 /* NSError+Extensions.swift */; };
D0B23F0E24D210E90066F411 /* NSError+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B23F0C24D210E90066F411 /* NSError+Extensions.swift */; }; D0B23F0E24D210E90066F411 /* NSError+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B23F0C24D210E90066F411 /* NSError+Extensions.swift */; };
D0BEC93824C9632800E864C4 /* RootViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEC93724C9632800E864C4 /* RootViewModel.swift */; }; D0BEC93824C9632800E864C4 /* RootViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEC93724C9632800E864C4 /* RootViewModel.swift */; };
@ -187,6 +189,7 @@
D074577624D29006004758DB /* StubbingWebAuthSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StubbingWebAuthSession.swift; sourceTree = "<group>"; }; D074577624D29006004758DB /* StubbingWebAuthSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StubbingWebAuthSession.swift; sourceTree = "<group>"; };
D074577924D29366004758DB /* URLSessionConfiguration+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSessionConfiguration+Extensions.swift"; sourceTree = "<group>"; }; D074577924D29366004758DB /* URLSessionConfiguration+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "URLSessionConfiguration+Extensions.swift"; sourceTree = "<group>"; };
D081A40424D0F1A8001B016E /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; }; D081A40424D0F1A8001B016E /* String+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = "<group>"; };
D0A1CA7324DAC2F1003063E9 /* KingfisherOptionsInfo+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KingfisherOptionsInfo+Extensions.swift"; sourceTree = "<group>"; };
D0B23F0C24D210E90066F411 /* NSError+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSError+Extensions.swift"; sourceTree = "<group>"; }; D0B23F0C24D210E90066F411 /* NSError+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSError+Extensions.swift"; sourceTree = "<group>"; };
D0BEC93724C9632800E864C4 /* RootViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewModel.swift; sourceTree = "<group>"; }; D0BEC93724C9632800E864C4 /* RootViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootViewModel.swift; sourceTree = "<group>"; };
D0BEC93A24C96FD500E864C4 /* RootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = "<group>"; }; D0BEC93A24C96FD500E864C4 /* RootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = "<group>"; };
@ -397,6 +400,7 @@
D0DB6F1624C665B400D965FE /* Extensions */ = { D0DB6F1624C665B400D965FE /* Extensions */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
D0A1CA7324DAC2F1003063E9 /* KingfisherOptionsInfo+Extensions.swift */,
D0B23F0C24D210E90066F411 /* NSError+Extensions.swift */, D0B23F0C24D210E90066F411 /* NSError+Extensions.swift */,
D0C963FD24CC3812003BD330 /* Publisher+Extensions.swift */, D0C963FD24CC3812003BD330 /* Publisher+Extensions.swift */,
D081A40424D0F1A8001B016E /* String+Extensions.swift */, D081A40424D0F1A8001B016E /* String+Extensions.swift */,
@ -695,6 +699,7 @@
D0ED1BB724CE47F400B4899C /* WebAuthSession.swift in Sources */, D0ED1BB724CE47F400B4899C /* WebAuthSession.swift in Sources */,
D0666A7224C6E0D300F3F04B /* Secrets.swift in Sources */, D0666A7224C6E0D300F3F04B /* Secrets.swift in Sources */,
D0BEC95124CA2B7E00E864C4 /* TabNavigation.swift in Sources */, D0BEC95124CA2B7E00E864C4 /* TabNavigation.swift in Sources */,
D0A1CA7424DAC2F1003063E9 /* KingfisherOptionsInfo+Extensions.swift in Sources */,
D0ED1BC424CED54D00B4899C /* HTTPTarget.swift in Sources */, D0ED1BC424CED54D00B4899C /* HTTPTarget.swift in Sources */,
D0C963FE24CC3812003BD330 /* Publisher+Extensions.swift in Sources */, D0C963FE24CC3812003BD330 /* Publisher+Extensions.swift in Sources */,
D04FD73C24D4A83A007D572D /* InstanceEndpoint+Stubbing.swift in Sources */, D04FD73C24D4A83A007D572D /* InstanceEndpoint+Stubbing.swift in Sources */,
@ -760,6 +765,7 @@
D0ED1BB824CE47F400B4899C /* WebAuthSession.swift in Sources */, D0ED1BB824CE47F400B4899C /* WebAuthSession.swift in Sources */,
D0BEC94F24CA2B5300E864C4 /* SidebarNavigation.swift in Sources */, D0BEC94F24CA2B5300E864C4 /* SidebarNavigation.swift in Sources */,
D0666A7324C6E0D300F3F04B /* Secrets.swift in Sources */, D0666A7324C6E0D300F3F04B /* Secrets.swift in Sources */,
D0A1CA7524DAC2F1003063E9 /* KingfisherOptionsInfo+Extensions.swift in Sources */,
D0ED1BC524CED54D00B4899C /* HTTPTarget.swift in Sources */, D0ED1BC524CED54D00B4899C /* HTTPTarget.swift in Sources */,
D0C963FF24CC3812003BD330 /* Publisher+Extensions.swift in Sources */, D0C963FF24CC3812003BD330 /* Publisher+Extensions.swift in Sources */,
D04FD73D24D4A83A007D572D /* InstanceEndpoint+Stubbing.swift in Sources */, D04FD73D24D4A83A007D572D /* InstanceEndpoint+Stubbing.swift in Sources */,

View file

@ -0,0 +1,30 @@
// Copyright © 2020 Metabolist. All rights reserved.
import SwiftUI
import KingfisherSwiftUI
import struct Kingfisher.KingfisherOptionsInfo
import protocol Kingfisher.ImageProcessor
import struct Kingfisher.DownsamplingImageProcessor
import struct Kingfisher.RoundCornerImageProcessor
import struct Kingfisher.FormatIndicatedCacheSerializer
extension KingfisherOptionsInfo {
static func downsampled(size: CGSize, scaleFactor: CGFloat, rounded: Bool = true) -> Self {
var processor: ImageProcessor = DownsamplingImageProcessor(size: size)
if rounded {
processor = processor.append(another: RoundCornerImageProcessor(radius: .widthFraction(0.5)))
}
return [
.processor(processor),
.scaleFactor(scaleFactor),
.cacheOriginalImage,
.cacheSerializer(FormatIndicatedCacheSerializer.png)
]
}
static func downsampled(dimension: CGFloat, scaleFactor: CGFloat, rounded: Bool = true) -> Self {
downsampled(size: CGSize(width: dimension, height: dimension), scaleFactor: scaleFactor, rounded: rounded)
}
}

View file

@ -102,17 +102,27 @@ extension IdentityDatabase {
StoredIdentity StoredIdentity
.including(optional: StoredIdentity.instance) .including(optional: StoredIdentity.instance)
.including(optional: StoredIdentity.account) .including(optional: StoredIdentity.account)
.asRequest(of: IdentityResult.self).fetchAll) .asRequest(of: IdentityResult.self)
.fetchAll)
.removeDuplicates() .removeDuplicates()
.publisher(in: databaseQueue, scheduling: .immediate) .publisher(in: databaseQueue, scheduling: .immediate)
.map { $0.map(Identity.init(result:)) } .map { $0.map(Identity.init(result:)) }
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
func identityCountObservation() -> AnyPublisher<Int, Error> { func recentIdentitiesObservation(excluding: String) -> AnyPublisher<[Identity], Error> {
ValueObservation.tracking(StoredIdentity.fetchCount) ValueObservation.tracking(
StoredIdentity
.filter(Column("id") != excluding)
.order(Column("lastUsedAt").desc)
.limit(10)
.including(optional: StoredIdentity.instance)
.including(optional: StoredIdentity.account)
.asRequest(of: IdentityResult.self)
.fetchAll)
.removeDuplicates() .removeDuplicates()
.publisher(in: databaseQueue, scheduling: .immediate) .publisher(in: databaseQueue, scheduling: .immediate)
.map { $0.map(Identity.init(result:)) }
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }

View file

@ -5,6 +5,7 @@ import Combine
class MainNavigationViewModel: ObservableObject { class MainNavigationViewModel: ObservableObject {
@Published private(set) var identity: Identity @Published private(set) var identity: Identity
@Published private(set) var recentIdentities = [Identity]()
@Published var presentingSettings = false @Published var presentingSettings = false
@Published var alertItem: AlertItem? @Published var alertItem: AlertItem?
var selectedTab: Tab? = .timelines var selectedTab: Tab? = .timelines
@ -37,6 +38,9 @@ class MainNavigationViewModel: ObservableObject {
} }
observation.assignErrorsToAlertItem(to: \.alertItem, on: self).assign(to: &$identity) observation.assignErrorsToAlertItem(to: \.alertItem, on: self).assign(to: &$identity)
environment.identityDatabase.recentIdentitiesObservation(excluding: identityID)
.assignErrorsToAlertItem(to: \.alertItem, on: self)
.assign(to: &$recentIdentities)
environment.identityDatabase.updateLastUsedAt(identityID: identityID) environment.identityDatabase.updateLastUsedAt(identityID: identityID)
.assignErrorsToAlertItem(to: \.alertItem, on: self) .assignErrorsToAlertItem(to: \.alertItem, on: self)

View file

@ -29,5 +29,6 @@ struct IdentitiesView: View {
struct IdentitiesView_Previews: PreviewProvider { struct IdentitiesView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
IdentitiesView(viewModel: .development) IdentitiesView(viewModel: .development)
.environmentObject(RootViewModel.development)
} }
} }

View file

@ -2,7 +2,6 @@
import SwiftUI import SwiftUI
import KingfisherSwiftUI import KingfisherSwiftUI
import struct Kingfisher.DownsamplingImageProcessor
struct SettingsView: View { struct SettingsView: View {
@StateObject var viewModel: SettingsViewModel @StateObject var viewModel: SettingsViewModel
@ -16,14 +15,7 @@ struct SettingsView: View {
Form { Form {
HStack { HStack {
KFImage(viewModel.identity.image, KFImage(viewModel.identity.image,
options: [ options: .downsampled(dimension: 50, scaleFactor: displayScale))
.processor(
DownsamplingImageProcessor(size: CGSize(width: 50, height: 50))
),
.scaleFactor(displayScale),
.cacheOriginalImage
])
.clipShape(Circle())
Text(viewModel.identity.handle) Text(viewModel.identity.handle)
.font(.subheadline) .font(.subheadline)
} }
@ -89,7 +81,6 @@ private extension View {
struct SettingsView_Previews: PreviewProvider { struct SettingsView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
SettingsView(viewModel: .development) SettingsView(viewModel: .development)
.environmentObject(MainNavigationViewModel.development)
.environmentObject(RootViewModel.development) .environmentObject(RootViewModel.development)
} }
} }

View file

@ -2,7 +2,6 @@
import SwiftUI import SwiftUI
import KingfisherSwiftUI import KingfisherSwiftUI
import struct Kingfisher.DownsamplingImageProcessor
struct TabNavigation: View { struct TabNavigation: View {
@ObservedObject var viewModel: MainNavigationViewModel @ObservedObject var viewModel: MainNavigationViewModel
@ -37,30 +36,37 @@ struct TabNavigation: View {
} }
private extension TabNavigation { private extension TabNavigation {
@ViewBuilder
func view(tab: MainNavigationViewModel.Tab) -> some View { func view(tab: MainNavigationViewModel.Tab) -> some View {
Group { switch tab {
switch tab { case .timelines:
case .timelines: TimelineView()
TimelineView() .navigationBarTitle(viewModel.identity.handle, displayMode: .inline)
.navigationBarTitle(viewModel.identity.handle, displayMode: .inline) .navigationBarItems(
.navigationBarItems( leading: Button {
leading: Button { viewModel.presentingSettings.toggle()
viewModel.presentingSettings.toggle() } label: {
} label: { KFImage(viewModel.identity.image,
KFImage(viewModel.identity.image, options: .downsampled(dimension: 28, scaleFactor: displayScale))
options: [ .placeholder { Image(systemName: "gear") }
.processor( .renderingMode(.original)
DownsamplingImageProcessor(size: CGSize(width: 28, height: 28)) .contextMenu(ContextMenu {
), ForEach(viewModel.recentIdentities) { recentIdentity in
.scaleFactor(displayScale), Button {
.cacheOriginalImage rootViewModel.newIdentitySelected(id: recentIdentity.id)
]) } label: {
.placeholder { Image(systemName: "gear") } Label(
.renderingMode(.original) title: { Text(recentIdentity.handle) },
.clipShape(Circle()) icon: {
}) KFImage(recentIdentity.image,
default: Text(tab.title) options: .downsampled(dimension: 28, scaleFactor: displayScale))
} .renderingMode(.original)
})
}
}
})
})
default: Text(tab.title)
} }
} }
} }
@ -69,6 +75,7 @@ private extension TabNavigation {
struct TabNavigation_Previews: PreviewProvider { struct TabNavigation_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
TabNavigation(viewModel: .development) TabNavigation(viewModel: .development)
.environmentObject(RootViewModel.development)
} }
} }
#endif #endif