From 98035e8530d39550032e7fa8f7bdd930c954bdd4 Mon Sep 17 00:00:00 2001 From: Thomas Ricouard Date: Sat, 16 Sep 2023 15:04:42 +0200 Subject: [PATCH] Better status focused screen transition --- Packages/Env/Sources/Env/CustomEnvValues.swift | 18 ++++++++++++++++++ .../Status/Detail/StatusDetailView.swift | 11 ++++++----- .../Sources/Status/Row/StatusRowView.swift | 17 +++++++++++------ .../Status/Row/StatusRowViewModel.swift | 4 ---- .../Row/Subviews/StatusRowActionsView.swift | 16 ++++++++++++---- .../Row/Subviews/StatusRowContentView.swift | 3 ++- .../Row/Subviews/StatusRowHeaderView.swift | 4 +++- .../Row/Subviews/StatusRowTextView.swift | 7 ++++--- 8 files changed, 56 insertions(+), 24 deletions(-) diff --git a/Packages/Env/Sources/Env/CustomEnvValues.swift b/Packages/Env/Sources/Env/CustomEnvValues.swift index 21e3e2f9..6369cf38 100644 --- a/Packages/Env/Sources/Env/CustomEnvValues.swift +++ b/Packages/Env/Sources/Env/CustomEnvValues.swift @@ -21,6 +21,14 @@ private struct IsSupporter: EnvironmentKey { static let defaultValue: Bool = false } +private struct IsStatusDetailLoaded: EnvironmentKey { + static let defaultValue: Bool = false +} + +private struct IsStatusFocused: EnvironmentKey { + static let defaultValue: Bool = false +} + public extension EnvironmentValues { var isSecondaryColumn: Bool { get { self[SecondaryColumnKey.self] } @@ -46,4 +54,14 @@ public extension EnvironmentValues { get { self[IsSupporter.self] } set { self[IsSupporter.self] = newValue } } + + var isStatusDetailLoaded: Bool { + get { self[IsStatusDetailLoaded.self] } + set { self[IsStatusDetailLoaded.self] = newValue } + } + + var isStatusFocused: Bool { + get { self[IsStatusFocused.self] } + set { self[IsStatusFocused.self] = newValue } + } } diff --git a/Packages/Status/Sources/Status/Detail/StatusDetailView.swift b/Packages/Status/Sources/Status/Detail/StatusDetailView.swift index 5baf2b0b..dc26bd19 100644 --- a/Packages/Status/Sources/Status/Detail/StatusDetailView.swift +++ b/Packages/Status/Sources/Status/Detail/StatusDetailView.swift @@ -22,15 +22,15 @@ public struct StatusDetailView: View { @AccessibilityFocusState private var initialFocusBugWorkaround: Bool public init(statusId: String) { - _viewModel = StateObject(wrappedValue: .init(statusId: statusId)) + _viewModel = StateObject(wrappedValue: { .init(statusId: statusId) }()) } public init(status: Status) { - _viewModel = StateObject(wrappedValue: .init(status: status)) + _viewModel = StateObject(wrappedValue: { .init(status: status) }()) } public init(remoteStatusURL: URL) { - _viewModel = StateObject(wrappedValue: .init(remoteStatusURL: remoteStatusURL)) + _viewModel = StateObject(wrappedValue: { .init(remoteStatusURL: remoteStatusURL) }()) } public var body: some View { @@ -147,8 +147,9 @@ public struct StatusDetailView: View { private func makeCurrentStatusView(status: Status) -> some View { StatusRowView(viewModel: { .init(status: status, client: client, - routerPath: routerPath, - isFocused: true) }) + routerPath: routerPath) }) + .environment(\.isStatusFocused, true) + .environment(\.isStatusDetailLoaded, !viewModel.isLoadingContext) .accessibilityFocused($initialFocusBugWorkaround, equals: true) .overlay { GeometryReader { reader in diff --git a/Packages/Status/Sources/Status/Row/StatusRowView.swift b/Packages/Status/Sources/Status/Row/StatusRowView.swift index 9ebcdad4..d6427e3b 100644 --- a/Packages/Status/Sources/Status/Row/StatusRowView.swift +++ b/Packages/Status/Sources/Status/Row/StatusRowView.swift @@ -12,6 +12,7 @@ public struct StatusRowView: View { @Environment(\.redactionReasons) private var reasons @Environment(\.isCompact) private var isCompact: Bool @Environment(\.accessibilityVoiceOverEnabled) private var accessibilityVoiceOverEnabled + @Environment(\.isStatusFocused) private var isFocused @EnvironmentObject private var quickLook: QuickLook @EnvironmentObject private var theme: Theme @@ -66,20 +67,22 @@ public struct StatusRowView: View { StatusRowContentView(viewModel: viewModel) .contentShape(Rectangle()) .onTapGesture { + guard !isFocused else { return } viewModel.navigateToDetail() } .accessibilityActions { - if viewModel.isFocused, viewModel.showActions { + if isFocused, viewModel.showActions { accessibilityActions } } } - if viewModel.showActions, viewModel.isFocused || theme.statusActionsDisplay != .none, !isInCaptureMode { + if viewModel.showActions, isFocused || theme.statusActionsDisplay != .none, !isInCaptureMode { StatusRowActionsView(viewModel: viewModel) .padding(.top, 8) - .tint(viewModel.isFocused ? theme.tintColor : .gray) + .tint(isFocused ? theme.tintColor : .gray) .contentShape(Rectangle()) .onTapGesture { + guard !isFocused else { return } viewModel.navigateToDetail() } } @@ -122,15 +125,16 @@ public struct StatusRowView: View { leading: .layoutPadding, bottom: 12, trailing: .layoutPadding)) - .accessibilityElement(children: viewModel.isFocused ? .contain : .combine) - .accessibilityLabel(viewModel.isFocused == false && accessibilityVoiceOverEnabled + .accessibilityElement(children: isFocused ? .contain : .combine) + .accessibilityLabel(isFocused == false && accessibilityVoiceOverEnabled ? CombinedAccessibilityLabel(viewModel: viewModel).finalLabel() : Text("")) .accessibilityHidden(viewModel.filter?.filter.filterAction == .hide) .accessibilityAction { + guard !isFocused else { return } viewModel.navigateToDetail() } .accessibilityActions { - if viewModel.isFocused == false, viewModel.showActions { + if isFocused == false, viewModel.showActions { accessibilityActions } } @@ -138,6 +142,7 @@ public struct StatusRowView: View { Color.clear .contentShape(Rectangle()) .onTapGesture { + guard !isFocused else { return } viewModel.navigateToDetail() } } diff --git a/Packages/Status/Sources/Status/Row/StatusRowViewModel.swift b/Packages/Status/Sources/Status/Row/StatusRowViewModel.swift index f8420e63..f22a60fb 100644 --- a/Packages/Status/Sources/Status/Row/StatusRowViewModel.swift +++ b/Packages/Status/Sources/Status/Row/StatusRowViewModel.swift @@ -9,7 +9,6 @@ import SwiftUI @MainActor public class StatusRowViewModel: ObservableObject { let status: Status - let isFocused: Bool // Whether this status is on a remote local timeline (many actions are unavailable if so) let isRemote: Bool let showActions: Bool @@ -102,7 +101,6 @@ public class StatusRowViewModel: ObservableObject { public init(status: Status, client: Client, routerPath: RouterPath, - isFocused: Bool = false, isRemote: Bool = false, showActions: Bool = true, textDisabled: Bool = false) @@ -111,7 +109,6 @@ public class StatusRowViewModel: ObservableObject { finalStatus = status.reblog ?? status self.client = client self.routerPath = routerPath - self.isFocused = isFocused self.isRemote = isRemote self.showActions = showActions self.textDisabled = textDisabled @@ -159,7 +156,6 @@ public class StatusRowViewModel: ObservableObject { } func navigateToDetail() { - guard !isFocused else { return } if isRemote, let url = URL(string: finalStatus.url ?? "") { routerPath.navigate(to: .remoteStatusDetail(url: url)) } else { diff --git a/Packages/Status/Sources/Status/Row/Subviews/StatusRowActionsView.swift b/Packages/Status/Sources/Status/Row/Subviews/StatusRowActionsView.swift index a904ebee..bc950edd 100644 --- a/Packages/Status/Sources/Status/Row/Subviews/StatusRowActionsView.swift +++ b/Packages/Status/Sources/Status/Row/Subviews/StatusRowActionsView.swift @@ -9,7 +9,12 @@ struct StatusRowActionsView: View { @EnvironmentObject private var currentAccount: CurrentAccount @EnvironmentObject private var statusDataController: StatusDataController @EnvironmentObject private var userPreferences: UserPreferences + + @Environment(\.isStatusFocused) private var isFocused + @Environment(\.isStatusDetailLoaded) private var isStatusDetailLoaded + @ObservedObject var viewModel: StatusRowViewModel + func privateBoost() -> Bool { viewModel.status.visibility == .priv && viewModel.status.account.id == currentAccount.account?.id @@ -75,8 +80,8 @@ struct StatusRowActionsView: View { } } - func count(dataController: StatusDataController, viewModel: StatusRowViewModel, theme: Theme) -> Int? { - if theme.statusActionsDisplay == .discret, !viewModel.isFocused { + func count(dataController: StatusDataController, isFocused: Bool, theme: Theme) -> Int? { + if theme.statusActionsDisplay == .discret, isFocused { return nil } switch self { @@ -148,8 +153,11 @@ struct StatusRowActionsView: View { } } } - if viewModel.isFocused { + + if isStatusDetailLoaded { StatusRowDetailView(viewModel: viewModel) + .transition(.move(edge: .bottom)) + .animation(.snappy) } } } @@ -179,7 +187,7 @@ struct StatusRowActionsView: View { .disabled(action == .boost && (viewModel.status.visibility == .direct || viewModel.status.visibility == .priv && viewModel.status.account.id != currentAccount.account?.id)) if let count = action.count(dataController: statusDataController, - viewModel: viewModel, + isFocused: isFocused, theme: theme), !viewModel.isRemote { Text("\(count)") diff --git a/Packages/Status/Sources/Status/Row/Subviews/StatusRowContentView.swift b/Packages/Status/Sources/Status/Row/Subviews/StatusRowContentView.swift index 42df66f3..858ba824 100644 --- a/Packages/Status/Sources/Status/Row/Subviews/StatusRowContentView.swift +++ b/Packages/Status/Sources/Status/Row/Subviews/StatusRowContentView.swift @@ -6,6 +6,7 @@ import SwiftUI struct StatusRowContentView: View { @Environment(\.redactionReasons) private var reasons @Environment(\.isCompact) private var isCompact + @Environment(\.isStatusFocused) private var isFocused @EnvironmentObject private var theme: Theme @@ -44,7 +45,7 @@ struct StatusRowContentView: View { Spacer() } } - .accessibilityHidden(viewModel.isFocused == false) + .accessibilityHidden(isFocused == false) .padding(.vertical, 4) } diff --git a/Packages/Status/Sources/Status/Row/Subviews/StatusRowHeaderView.swift b/Packages/Status/Sources/Status/Row/Subviews/StatusRowHeaderView.swift index bf84c364..e0f92b4b 100644 --- a/Packages/Status/Sources/Status/Row/Subviews/StatusRowHeaderView.swift +++ b/Packages/Status/Sources/Status/Row/Subviews/StatusRowHeaderView.swift @@ -5,6 +5,8 @@ import SwiftUI struct StatusRowHeaderView: View { @Environment(\.isInCaptureMode) private var isInCaptureMode: Bool + @Environment(\.isStatusFocused) private var isFocused + @EnvironmentObject private var theme: Theme let viewModel: StatusRowViewModel @@ -29,7 +31,7 @@ struct StatusRowHeaderView: View { viewModel.navigateToAccountDetail(account: viewModel.finalStatus.account) } .accessibilityActions { - if viewModel.isFocused { + if isFocused { StatusRowContextMenu(viewModel: viewModel) } } diff --git a/Packages/Status/Sources/Status/Row/Subviews/StatusRowTextView.swift b/Packages/Status/Sources/Status/Row/Subviews/StatusRowTextView.swift index b96c4770..5bf804a2 100644 --- a/Packages/Status/Sources/Status/Row/Subviews/StatusRowTextView.swift +++ b/Packages/Status/Sources/Status/Row/Subviews/StatusRowTextView.swift @@ -5,6 +5,7 @@ import SwiftUI struct StatusRowTextView: View { @EnvironmentObject private var theme: Theme + @Environment(\.isStatusFocused) private var isFocused @ObservedObject var viewModel: StatusRowViewModel @@ -15,11 +16,11 @@ struct StatusRowTextView: View { emojis: viewModel.finalStatus.emojis, language: viewModel.finalStatus.language, lineLimit: viewModel.lineLimit) - .font(viewModel.isFocused ? .scaledBodyFocused : .scaledBody) + .font(isFocused ? .scaledBodyFocused : .scaledBody) .lineSpacing(CGFloat(theme.lineSpacing)) .foregroundColor(viewModel.textDisabled ? .gray : theme.labelColor) - .emojiSize(viewModel.isFocused ? Font.scaledBodyFocusedFont.emojiSize : Font.scaledBodyFont.emojiSize) - .emojiBaselineOffset(viewModel.isFocused ? Font.scaledBodyFocusedFont.emojiBaselineOffset : Font.scaledBodyFont.emojiBaselineOffset) + .emojiSize(isFocused ? Font.scaledBodyFocusedFont.emojiSize : Font.scaledBodyFont.emojiSize) + .emojiBaselineOffset(isFocused ? Font.scaledBodyFocusedFont.emojiBaselineOffset : Font.scaledBodyFont.emojiBaselineOffset) .environment(\.openURL, OpenURLAction { url in viewModel.routerPath.handleStatus(status: viewModel.finalStatus, url: url) })