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 */; };
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 */; };
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 */; };
D0B23F0E24D210E90066F411 /* NSError+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B23F0C24D210E90066F411 /* NSError+Extensions.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>"; };
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>"; };
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>"; };
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>"; };
@ -397,6 +400,7 @@
D0DB6F1624C665B400D965FE /* Extensions */ = {
isa = PBXGroup;
children = (
D0A1CA7324DAC2F1003063E9 /* KingfisherOptionsInfo+Extensions.swift */,
D0B23F0C24D210E90066F411 /* NSError+Extensions.swift */,
D0C963FD24CC3812003BD330 /* Publisher+Extensions.swift */,
D081A40424D0F1A8001B016E /* String+Extensions.swift */,
@ -695,6 +699,7 @@
D0ED1BB724CE47F400B4899C /* WebAuthSession.swift in Sources */,
D0666A7224C6E0D300F3F04B /* Secrets.swift in Sources */,
D0BEC95124CA2B7E00E864C4 /* TabNavigation.swift in Sources */,
D0A1CA7424DAC2F1003063E9 /* KingfisherOptionsInfo+Extensions.swift in Sources */,
D0ED1BC424CED54D00B4899C /* HTTPTarget.swift in Sources */,
D0C963FE24CC3812003BD330 /* Publisher+Extensions.swift in Sources */,
D04FD73C24D4A83A007D572D /* InstanceEndpoint+Stubbing.swift in Sources */,
@ -760,6 +765,7 @@
D0ED1BB824CE47F400B4899C /* WebAuthSession.swift in Sources */,
D0BEC94F24CA2B5300E864C4 /* SidebarNavigation.swift in Sources */,
D0666A7324C6E0D300F3F04B /* Secrets.swift in Sources */,
D0A1CA7524DAC2F1003063E9 /* KingfisherOptionsInfo+Extensions.swift in Sources */,
D0ED1BC524CED54D00B4899C /* HTTPTarget.swift in Sources */,
D0C963FF24CC3812003BD330 /* Publisher+Extensions.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
.including(optional: StoredIdentity.instance)
.including(optional: StoredIdentity.account)
.asRequest(of: IdentityResult.self).fetchAll)
.asRequest(of: IdentityResult.self)
.fetchAll)
.removeDuplicates()
.publisher(in: databaseQueue, scheduling: .immediate)
.map { $0.map(Identity.init(result:)) }
.eraseToAnyPublisher()
}
func identityCountObservation() -> AnyPublisher<Int, Error> {
ValueObservation.tracking(StoredIdentity.fetchCount)
func recentIdentitiesObservation(excluding: String) -> AnyPublisher<[Identity], Error> {
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()
.publisher(in: databaseQueue, scheduling: .immediate)
.map { $0.map(Identity.init(result:)) }
.eraseToAnyPublisher()
}

View file

@ -5,6 +5,7 @@ import Combine
class MainNavigationViewModel: ObservableObject {
@Published private(set) var identity: Identity
@Published private(set) var recentIdentities = [Identity]()
@Published var presentingSettings = false
@Published var alertItem: AlertItem?
var selectedTab: Tab? = .timelines
@ -37,6 +38,9 @@ class MainNavigationViewModel: ObservableObject {
}
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)
.assignErrorsToAlertItem(to: \.alertItem, on: self)

View file

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

View file

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

View file

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