Better status focused screen transition

This commit is contained in:
Thomas Ricouard 2023-09-16 15:04:42 +02:00
parent aaafac8e5a
commit 98035e8530
8 changed files with 56 additions and 24 deletions

View file

@ -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 }
}
}

View file

@ -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

View file

@ -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()
}
}

View file

@ -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 {

View file

@ -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)")

View file

@ -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)
}

View file

@ -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)
}
}

View file

@ -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)
})