mirror of
https://github.com/metabolist/metatext.git
synced 2024-09-29 06:41:55 +00:00
Recent identities menu
This commit is contained in:
parent
6f0cf59fd0
commit
038d17674b
7 changed files with 86 additions and 37 deletions
|
@ -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 */,
|
||||||
|
|
30
Shared/Extensions/KingfisherOptionsInfo+Extensions.swift
Normal file
30
Shared/Extensions/KingfisherOptionsInfo+Extensions.swift
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,8 +36,8 @@ 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()
|
||||||
|
@ -48,27 +47,35 @@ private extension TabNavigation {
|
||||||
viewModel.presentingSettings.toggle()
|
viewModel.presentingSettings.toggle()
|
||||||
} label: {
|
} label: {
|
||||||
KFImage(viewModel.identity.image,
|
KFImage(viewModel.identity.image,
|
||||||
options: [
|
options: .downsampled(dimension: 28, scaleFactor: displayScale))
|
||||||
.processor(
|
|
||||||
DownsamplingImageProcessor(size: CGSize(width: 28, height: 28))
|
|
||||||
),
|
|
||||||
.scaleFactor(displayScale),
|
|
||||||
.cacheOriginalImage
|
|
||||||
])
|
|
||||||
.placeholder { Image(systemName: "gear") }
|
.placeholder { Image(systemName: "gear") }
|
||||||
.renderingMode(.original)
|
.renderingMode(.original)
|
||||||
.clipShape(Circle())
|
.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)
|
default: Text(tab.title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
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
|
||||||
|
|
Loading…
Reference in a new issue