mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-25 09:41:00 +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 */; };
|
||||
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 */,
|
||||
|
|
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
|
||||
.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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -29,5 +29,6 @@ struct IdentitiesView: View {
|
|||
struct IdentitiesView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
IdentitiesView(viewModel: .development)
|
||||
.environmentObject(RootViewModel.development)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue