Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show transition on account switch #1086

Merged
merged 10 commits into from
Jun 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Mlem.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
036CC3AF2B8145C30098B6A1 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036CC3AE2B8145C30098B6A1 /* AppState.swift */; };
037386472BDAFE81007492B5 /* LemmyMarkdownUI in Frameworks */ = {isa = PBXBuildFile; productRef = 037386462BDAFE81007492B5 /* LemmyMarkdownUI */; };
037658DF2BE7D9EF00F4DD4D /* Community1Providing+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 037658DE2BE7D9EF00F4DD4D /* Community1Providing+Extensions.swift */; };
0380965F2C10AA80003ED1D8 /* AppState+Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0380965E2C10AA80003ED1D8 /* AppState+Transition.swift */; };
038096612C10AAD8003ED1D8 /* TransitionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 038096602C10AAD8003ED1D8 /* TransitionView.swift */; };
0382A7F02C09F0F800C79DDA /* PersonView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0382A7EF2C09F0F800C79DDA /* PersonView.swift */; };
0382A7F22C0A758E00C79DDA /* ProfileDateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0382A7F12C0A758E00C79DDA /* ProfileDateView.swift */; };
0382A7F42C0A76A900C79DDA /* Date+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0382A7F32C0A76A900C79DDA /* Date+Extensions.swift */; };
Expand Down Expand Up @@ -203,6 +205,8 @@
0369B35A2BFB86E3001EFEDF /* Account.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Account.swift; sourceTree = "<group>"; };
036CC3AE2B8145C30098B6A1 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; };
037658DE2BE7D9EF00F4DD4D /* Community1Providing+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Community1Providing+Extensions.swift"; sourceTree = "<group>"; };
0380965E2C10AA80003ED1D8 /* AppState+Transition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppState+Transition.swift"; sourceTree = "<group>"; };
038096602C10AAD8003ED1D8 /* TransitionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransitionView.swift; sourceTree = "<group>"; };
0382A7EF2C09F0F800C79DDA /* PersonView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersonView.swift; sourceTree = "<group>"; };
0382A7F12C0A758E00C79DDA /* ProfileDateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileDateView.swift; sourceTree = "<group>"; };
0382A7F32C0A76A900C79DDA /* Date+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extensions.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -633,6 +637,7 @@
6363D5C627EE196700E34822 /* ContentView.swift */,
6363D5C427EE196700E34822 /* MlemApp.swift */,
B157E0C32A507B8000B02C8B /* FlowRoot.swift */,
038096602C10AAD8003ED1D8 /* TransitionView.swift */,
CDA1E81A2B8FC39A007953EF /* Onboarding */,
6363D5F427EE1BAE00E34822 /* Tabs */,
);
Expand Down Expand Up @@ -781,6 +786,7 @@
children = (
CD4D58B22B86BFD400B82964 /* AccountsTracker.swift */,
036CC3AE2B8145C30098B6A1 /* AppState.swift */,
0380965E2C10AA80003ED1D8 /* AppState+Transition.swift */,
CD317D4B2BE97FED008F63E2 /* Palette.swift */,
CD4D58A42B86BD1B00B82964 /* PersistenceRepository.swift */,
CD4ED8462BF110FA00EFA0A2 /* TabReselectTracker.swift */,
Expand Down Expand Up @@ -1079,6 +1085,7 @@
CD1446212A5B328E00610EF1 /* Privacy Policy.swift in Sources */,
03FE140C2BF953B000A8377F /* HandleError.swift in Sources */,
CD1446272A5B36DA00610EF1 /* EULA.swift in Sources */,
038096612C10AAD8003ED1D8 /* TransitionView.swift in Sources */,
0382A7F42C0A76A900C79DDA /* Date+Extensions.swift in Sources */,
03D2A63B2C010B7500ED4FF2 /* GuestAccount.swift in Sources */,
CD4ED84A2BF1113800EFA0A2 /* View+TabReselectConsumer.swift in Sources */,
Expand Down Expand Up @@ -1163,6 +1170,7 @@
CD4D58F82B87B0D100B82964 /* InternetSpeed.swift in Sources */,
0382A7F02C09F0F800C79DDA /* PersonView.swift in Sources */,
CDBFCB6C2C054AA7008CD468 /* TilePostView.swift in Sources */,
0380965F2C10AA80003ED1D8 /* AppState+Transition.swift in Sources */,
CD4D58932B86BA5C00B82964 /* StandardPalette.swift in Sources */,
03D2A63F2C010DBF00ED4FF2 /* GuestSession.swift in Sources */,
030FF6812BC859FD00F6BFAC /* CustomTabViewHostingController.swift in Sources */,
Expand Down
2 changes: 2 additions & 0 deletions Mlem/App/Constants/Icons.swift
Original file line number Diff line number Diff line change
Expand Up @@ -228,4 +228,6 @@ enum Icons {
static let noFile: String = "questionmark.folder"
static let forward: String = "chevron.right"
static let imageDetails: String = "doc.badge.ellipsis"
static let accountSwitchReload: String = "arrow.2.circlepath"
static let accountSwitchKeepPlace: String = "checkmark.diamond"
}
38 changes: 38 additions & 0 deletions Mlem/App/Globals/Definitions/AppState+transition.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// AppState+Transition.swift
// Mlem
//
// Created by Sjmarf on 05/06/2024.
//

import SwiftUI

extension AppState {
func transition(_ account: any Account) {
Task { @MainActor in
let transition = TransitionView(account: account)
guard let transitionView = UIHostingController(rootView: transition).view,
let window = UIApplication.shared.firstKeyWindow else {
return
}

transitionView.alpha = 0
window.addSubview(transitionView)
UIView.animate(withDuration: 0.15) {
transitionView.alpha = 1
}

transitionView.translatesAutoresizingMaskIntoConstraints = false
transitionView.heightAnchor.constraint(equalTo: window.heightAnchor).isActive = true
transitionView.widthAnchor.constraint(equalTo: window.widthAnchor).isActive = true

DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
UIView.animate(withDuration: 0.3) {
transitionView.alpha = 0
} completion: { _ in
transitionView.removeFromSuperview()
}
}
}
}
}
59 changes: 45 additions & 14 deletions Mlem/App/Globals/Definitions/AppState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,61 @@
import Dependencies
import Foundation
import MlemMiddleware
import Observation
import SwiftUI

@Observable
class AppState {
private(set) var guestSession: GuestSession!
private(set) var activeSessions: [UserSession] = []
private(set) var guestSession: GuestSession! {
didSet {
if oldValue != guestSession {
oldValue?.deactivate()
}
}
}

private(set) var activeSessions: [UserSession] = [] {
didSet {
if oldValue != activeSessions {
for session in Set(oldValue).subtracting(activeSessions) {
session.deactivate()
}
}
}
}

/// ``ContentView`` watches this for changes. When it is toggled, the app is refreshed.
var appRefreshToggle: Bool = true

private init() {
self.guestSession = .init(account: AccountsTracker.main.defaultGuestAccount)
changeAccount(to: AccountsTracker.main.defaultAccount, deactivateOldGuest: false)
setAccount(to: AccountsTracker.main.defaultAccount)
}

func changeAccount(to account: any Account) {
changeAccount(to: account, deactivateOldGuest: true)
/// If `keepPlace` is `nil`, use the value from `UserDefaults`.
func changeAccount(to account: any Account, keepPlace: Bool? = nil) {
@AppStorage("accounts.keepPlace") var keepPlaceSetting = false
let keepPlace = keepPlace ?? keepPlaceSetting
if keepPlace {
ToastModel.main.add(.account(account))
setAccount(to: account)
} else {
transition(account)
// The delays between these events are necessary to stop SwiftUIIntrospect from causing a lag spike.
// That library seems to not like us adding subviews to the window directly. For some reason adding
// these delays fixes that.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.appRefreshToggle = false
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) {
self.setAccount(to: account)
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.7) {
self.appRefreshToggle = true
}
}
}

private func changeAccount(to account: any Account, deactivateOldGuest: Bool) {
ToastModel.main.add(.account(account))

activeSessions.forEach { $0.deactivate() }
if deactivateOldGuest {
guestSession?.deactivate()
}

private func setAccount(to account: any Account) {
// Save because we updated `lastUsed` in the above `deactivate()` calls
AccountsTracker.main.saveAccounts(ofType: .all)

Expand Down
41 changes: 25 additions & 16 deletions Mlem/App/Logic/HandleError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,18 @@ func handleError(
#if DEBUG
print("☠️ ERROR ☠️")
print("📝 -> \(error.localizedDescription)")
if let error = error as? ApiClientError {
print(" \(error.description)")
}
print("📂 -> \(file) | \(function) | line: \(line)")
#endif

switch error {
// TODO: Modify MlemMiddleware to attach the ApiClient throwing the error to ApiClientError.invalidSession, so that we can access the relevant UserStub in a multi-account context
case ApiClientError.invalidSession:
if let user = AppState.main.firstSession.account as? UserAccount {
for layer in NavigationModel.main.layers {
switch layer.path.first {
case let .login(page):
switch page {
case .reauth:
return
default:
break
}
default:
break
}
}
NavigationModel.main.openSheet(.login(.reauth(user)))
}
showReauthSheet()
case ApiClientError.cancelled:
print("Cancellation error")
default:
if let errorDetails {
errorDetails.wrappedValue = .init(error: error)
Expand All @@ -48,3 +38,22 @@ func handleError(
}
}
}

private func showReauthSheet() {
if let user = AppState.main.firstSession.account as? UserAccount {
for layer in NavigationModel.main.layers {
switch layer.path.first {
case let .login(page):
switch page {
case .reauth:
return
default:
break
}
default:
break
}
}
NavigationModel.main.openSheet(.login(.reauth(user)))
}
}
8 changes: 8 additions & 0 deletions Mlem/App/Models/ErrorDetails.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import Combine
import MlemMiddleware
import SwiftUI
import UniformTypeIdentifiers

Expand All @@ -31,4 +32,11 @@ struct ErrorDetails: Hashable {
static func == (lhs: ErrorDetails, rhs: ErrorDetails) -> Bool {
lhs.hashValue == rhs.hashValue
}

var errorText: String {
if let error = error as? ApiClientError {
return error.description
}
return error?.localizedDescription ?? ""
}
}
42 changes: 22 additions & 20 deletions Mlem/App/Views/Root/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,28 @@ struct ContentView: View {
var navigationModel: NavigationModel { .main }

var body: some View {
content
.onReceive(timer) { _ in
appState.cleanCaches()
}
.sheet(isPresented: Binding(
get: { !(navigationModel.layers.first?.isFullScreenCover ?? true) },
set: { if !$0 { navigationModel.layers.removeAll() } }
)) {
NavigationLayerView(layer: navigationModel.layers[0], hasSheetModifiers: true)
}
.fullScreenCover(isPresented: Binding(
get: { navigationModel.layers.first?.isFullScreenCover ?? false },
set: { if !$0 { navigationModel.layers.removeAll() } }
)) {
NavigationLayerView(layer: navigationModel.layers[0], hasSheetModifiers: true)
}
.tint(palette.accent)
.environment(palette)
.environment(tabReselectTracker)
.environment(appState)
if appState.appRefreshToggle {
content
.onReceive(timer) { _ in
appState.cleanCaches()
}
.sheet(isPresented: Binding(
get: { !(navigationModel.layers.first?.isFullScreenCover ?? true) },
set: { if !$0 { navigationModel.layers.removeAll() } }
)) {
NavigationLayerView(layer: navigationModel.layers[0], hasSheetModifiers: true)
}
.fullScreenCover(isPresented: Binding(
get: { navigationModel.layers.first?.isFullScreenCover ?? false },
set: { if !$0 { navigationModel.layers.removeAll() } }
)) {
NavigationLayerView(layer: navigationModel.layers[0], hasSheetModifiers: true)
}
.tint(palette.accent)
.environment(palette)
.environment(tabReselectTracker)
.environment(appState)
}
}

var shouldDisplayToasts: Bool {
Expand Down
4 changes: 3 additions & 1 deletion Mlem/App/Views/Root/Onboarding/LandingPage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@ struct LandingPage: View {
//
// let newAccount = try await authenticatedApiClient.loadUser()
//
// // MARK: Save the account's credentials into the keychain

// MARK: Save the account's credentials into the keychain

//
// AppConstants.keychain["\(newAccount.id)_accessToken"] = response.jwt
// AccountsTracker.main.addAccount(account: newAccount)
Expand Down
7 changes: 0 additions & 7 deletions Mlem/App/Views/Root/Tabs/Feeds/FeedsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,6 @@ struct MinimalPostFeedView: View {
}
}
}
.task(id: appState.firstApi) {
do {
try await postTracker.loadMoreItems()
} catch {
handleError(error)
}
}
.task(id: appState.firstApi) {
do {
try await postTracker.changeFeedType(to: .aggregateFeed(appState.firstApi, type: .subscribed))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,20 @@ import SwiftUI
struct AccountListSettingsView: View {
@Environment(AppState.self) var appState

@AppStorage("accounts.keepPlace") var keepPlace: Bool = false

var accounts: [UserAccount] { AccountsTracker.main.userAccounts }

var body: some View {
Form {
headerView
AccountListView()
Section {
Toggle(
"Reload on Switch",
isOn: Binding(get: { !keepPlace }, set: { keepPlace = !$0 })
)
}
}
}

Expand Down
30 changes: 30 additions & 0 deletions Mlem/App/Views/Root/TransitionView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// AccountTransitionView.swift
// Mlem
//
// Created by Eric Andrews on 2023-10-02.
//

import Foundation
import SwiftUI

struct TransitionView: View {
let account: any Account
@State var accountNameOpacity: CGFloat = .zero

var body: some View {
VStack(spacing: 24) {
Text(account is UserAccount ? "Welcome" : "Welcome to")
.onAppear {
withAnimation(.easeIn(duration: 0.5)) {
accountNameOpacity = 1.0
}
}
Text(account.nickname)
.opacity(accountNameOpacity)
}
.font(.largeTitle)
.bold()
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
2 changes: 1 addition & 1 deletion Mlem/App/Views/Shared/Avatar/AvatarView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ struct AvatarView: View {
.aspectRatio(1, contentMode: .fill)
.clipShape(Circle())
.background {
if url != nil, loading {
if url != nil, loading, showLoadingPlaceholder {
ProgressView()
} else if url == nil {
DefaultAvatarView(avatarType: type)
Expand Down
Loading