mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2025-06-05 13:28:50 +00:00
Better status focused screen transition
This commit is contained in:
parent
aaafac8e5a
commit
98035e8530
8 changed files with 56 additions and 24 deletions
|
@ -21,6 +21,14 @@ private struct IsSupporter: EnvironmentKey {
|
||||||
static let defaultValue: Bool = false
|
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 {
|
public extension EnvironmentValues {
|
||||||
var isSecondaryColumn: Bool {
|
var isSecondaryColumn: Bool {
|
||||||
get { self[SecondaryColumnKey.self] }
|
get { self[SecondaryColumnKey.self] }
|
||||||
|
@ -46,4 +54,14 @@ public extension EnvironmentValues {
|
||||||
get { self[IsSupporter.self] }
|
get { self[IsSupporter.self] }
|
||||||
set { self[IsSupporter.self] = newValue }
|
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 }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,15 +22,15 @@ public struct StatusDetailView: View {
|
||||||
@AccessibilityFocusState private var initialFocusBugWorkaround: Bool
|
@AccessibilityFocusState private var initialFocusBugWorkaround: Bool
|
||||||
|
|
||||||
public init(statusId: String) {
|
public init(statusId: String) {
|
||||||
_viewModel = StateObject(wrappedValue: .init(statusId: statusId))
|
_viewModel = StateObject(wrappedValue: { .init(statusId: statusId) }())
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(status: Status) {
|
public init(status: Status) {
|
||||||
_viewModel = StateObject(wrappedValue: .init(status: status))
|
_viewModel = StateObject(wrappedValue: { .init(status: status) }())
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(remoteStatusURL: URL) {
|
public init(remoteStatusURL: URL) {
|
||||||
_viewModel = StateObject(wrappedValue: .init(remoteStatusURL: remoteStatusURL))
|
_viewModel = StateObject(wrappedValue: { .init(remoteStatusURL: remoteStatusURL) }())
|
||||||
}
|
}
|
||||||
|
|
||||||
public var body: some View {
|
public var body: some View {
|
||||||
|
@ -147,8 +147,9 @@ public struct StatusDetailView: View {
|
||||||
private func makeCurrentStatusView(status: Status) -> some View {
|
private func makeCurrentStatusView(status: Status) -> some View {
|
||||||
StatusRowView(viewModel: { .init(status: status,
|
StatusRowView(viewModel: { .init(status: status,
|
||||||
client: client,
|
client: client,
|
||||||
routerPath: routerPath,
|
routerPath: routerPath) })
|
||||||
isFocused: true) })
|
.environment(\.isStatusFocused, true)
|
||||||
|
.environment(\.isStatusDetailLoaded, !viewModel.isLoadingContext)
|
||||||
.accessibilityFocused($initialFocusBugWorkaround, equals: true)
|
.accessibilityFocused($initialFocusBugWorkaround, equals: true)
|
||||||
.overlay {
|
.overlay {
|
||||||
GeometryReader { reader in
|
GeometryReader { reader in
|
||||||
|
|
|
@ -12,6 +12,7 @@ public struct StatusRowView: View {
|
||||||
@Environment(\.redactionReasons) private var reasons
|
@Environment(\.redactionReasons) private var reasons
|
||||||
@Environment(\.isCompact) private var isCompact: Bool
|
@Environment(\.isCompact) private var isCompact: Bool
|
||||||
@Environment(\.accessibilityVoiceOverEnabled) private var accessibilityVoiceOverEnabled
|
@Environment(\.accessibilityVoiceOverEnabled) private var accessibilityVoiceOverEnabled
|
||||||
|
@Environment(\.isStatusFocused) private var isFocused
|
||||||
|
|
||||||
@EnvironmentObject private var quickLook: QuickLook
|
@EnvironmentObject private var quickLook: QuickLook
|
||||||
@EnvironmentObject private var theme: Theme
|
@EnvironmentObject private var theme: Theme
|
||||||
|
@ -66,20 +67,22 @@ public struct StatusRowView: View {
|
||||||
StatusRowContentView(viewModel: viewModel)
|
StatusRowContentView(viewModel: viewModel)
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
|
guard !isFocused else { return }
|
||||||
viewModel.navigateToDetail()
|
viewModel.navigateToDetail()
|
||||||
}
|
}
|
||||||
.accessibilityActions {
|
.accessibilityActions {
|
||||||
if viewModel.isFocused, viewModel.showActions {
|
if isFocused, viewModel.showActions {
|
||||||
accessibilityActions
|
accessibilityActions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if viewModel.showActions, viewModel.isFocused || theme.statusActionsDisplay != .none, !isInCaptureMode {
|
if viewModel.showActions, isFocused || theme.statusActionsDisplay != .none, !isInCaptureMode {
|
||||||
StatusRowActionsView(viewModel: viewModel)
|
StatusRowActionsView(viewModel: viewModel)
|
||||||
.padding(.top, 8)
|
.padding(.top, 8)
|
||||||
.tint(viewModel.isFocused ? theme.tintColor : .gray)
|
.tint(isFocused ? theme.tintColor : .gray)
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
|
guard !isFocused else { return }
|
||||||
viewModel.navigateToDetail()
|
viewModel.navigateToDetail()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,15 +125,16 @@ public struct StatusRowView: View {
|
||||||
leading: .layoutPadding,
|
leading: .layoutPadding,
|
||||||
bottom: 12,
|
bottom: 12,
|
||||||
trailing: .layoutPadding))
|
trailing: .layoutPadding))
|
||||||
.accessibilityElement(children: viewModel.isFocused ? .contain : .combine)
|
.accessibilityElement(children: isFocused ? .contain : .combine)
|
||||||
.accessibilityLabel(viewModel.isFocused == false && accessibilityVoiceOverEnabled
|
.accessibilityLabel(isFocused == false && accessibilityVoiceOverEnabled
|
||||||
? CombinedAccessibilityLabel(viewModel: viewModel).finalLabel() : Text(""))
|
? CombinedAccessibilityLabel(viewModel: viewModel).finalLabel() : Text(""))
|
||||||
.accessibilityHidden(viewModel.filter?.filter.filterAction == .hide)
|
.accessibilityHidden(viewModel.filter?.filter.filterAction == .hide)
|
||||||
.accessibilityAction {
|
.accessibilityAction {
|
||||||
|
guard !isFocused else { return }
|
||||||
viewModel.navigateToDetail()
|
viewModel.navigateToDetail()
|
||||||
}
|
}
|
||||||
.accessibilityActions {
|
.accessibilityActions {
|
||||||
if viewModel.isFocused == false, viewModel.showActions {
|
if isFocused == false, viewModel.showActions {
|
||||||
accessibilityActions
|
accessibilityActions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -138,6 +142,7 @@ public struct StatusRowView: View {
|
||||||
Color.clear
|
Color.clear
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
|
guard !isFocused else { return }
|
||||||
viewModel.navigateToDetail()
|
viewModel.navigateToDetail()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ import SwiftUI
|
||||||
@MainActor
|
@MainActor
|
||||||
public class StatusRowViewModel: ObservableObject {
|
public class StatusRowViewModel: ObservableObject {
|
||||||
let status: Status
|
let status: Status
|
||||||
let isFocused: Bool
|
|
||||||
// Whether this status is on a remote local timeline (many actions are unavailable if so)
|
// Whether this status is on a remote local timeline (many actions are unavailable if so)
|
||||||
let isRemote: Bool
|
let isRemote: Bool
|
||||||
let showActions: Bool
|
let showActions: Bool
|
||||||
|
@ -102,7 +101,6 @@ public class StatusRowViewModel: ObservableObject {
|
||||||
public init(status: Status,
|
public init(status: Status,
|
||||||
client: Client,
|
client: Client,
|
||||||
routerPath: RouterPath,
|
routerPath: RouterPath,
|
||||||
isFocused: Bool = false,
|
|
||||||
isRemote: Bool = false,
|
isRemote: Bool = false,
|
||||||
showActions: Bool = true,
|
showActions: Bool = true,
|
||||||
textDisabled: Bool = false)
|
textDisabled: Bool = false)
|
||||||
|
@ -111,7 +109,6 @@ public class StatusRowViewModel: ObservableObject {
|
||||||
finalStatus = status.reblog ?? status
|
finalStatus = status.reblog ?? status
|
||||||
self.client = client
|
self.client = client
|
||||||
self.routerPath = routerPath
|
self.routerPath = routerPath
|
||||||
self.isFocused = isFocused
|
|
||||||
self.isRemote = isRemote
|
self.isRemote = isRemote
|
||||||
self.showActions = showActions
|
self.showActions = showActions
|
||||||
self.textDisabled = textDisabled
|
self.textDisabled = textDisabled
|
||||||
|
@ -159,7 +156,6 @@ public class StatusRowViewModel: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
func navigateToDetail() {
|
func navigateToDetail() {
|
||||||
guard !isFocused else { return }
|
|
||||||
if isRemote, let url = URL(string: finalStatus.url ?? "") {
|
if isRemote, let url = URL(string: finalStatus.url ?? "") {
|
||||||
routerPath.navigate(to: .remoteStatusDetail(url: url))
|
routerPath.navigate(to: .remoteStatusDetail(url: url))
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -9,7 +9,12 @@ struct StatusRowActionsView: View {
|
||||||
@EnvironmentObject private var currentAccount: CurrentAccount
|
@EnvironmentObject private var currentAccount: CurrentAccount
|
||||||
@EnvironmentObject private var statusDataController: StatusDataController
|
@EnvironmentObject private var statusDataController: StatusDataController
|
||||||
@EnvironmentObject private var userPreferences: UserPreferences
|
@EnvironmentObject private var userPreferences: UserPreferences
|
||||||
|
|
||||||
|
@Environment(\.isStatusFocused) private var isFocused
|
||||||
|
@Environment(\.isStatusDetailLoaded) private var isStatusDetailLoaded
|
||||||
|
|
||||||
@ObservedObject var viewModel: StatusRowViewModel
|
@ObservedObject var viewModel: StatusRowViewModel
|
||||||
|
|
||||||
|
|
||||||
func privateBoost() -> Bool {
|
func privateBoost() -> Bool {
|
||||||
viewModel.status.visibility == .priv && viewModel.status.account.id == currentAccount.account?.id
|
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? {
|
func count(dataController: StatusDataController, isFocused: Bool, theme: Theme) -> Int? {
|
||||||
if theme.statusActionsDisplay == .discret, !viewModel.isFocused {
|
if theme.statusActionsDisplay == .discret, isFocused {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
switch self {
|
switch self {
|
||||||
|
@ -148,8 +153,11 @@ struct StatusRowActionsView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if viewModel.isFocused {
|
|
||||||
|
if isStatusDetailLoaded {
|
||||||
StatusRowDetailView(viewModel: viewModel)
|
StatusRowDetailView(viewModel: viewModel)
|
||||||
|
.transition(.move(edge: .bottom))
|
||||||
|
.animation(.snappy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -179,7 +187,7 @@ struct StatusRowActionsView: View {
|
||||||
.disabled(action == .boost &&
|
.disabled(action == .boost &&
|
||||||
(viewModel.status.visibility == .direct || viewModel.status.visibility == .priv && viewModel.status.account.id != currentAccount.account?.id))
|
(viewModel.status.visibility == .direct || viewModel.status.visibility == .priv && viewModel.status.account.id != currentAccount.account?.id))
|
||||||
if let count = action.count(dataController: statusDataController,
|
if let count = action.count(dataController: statusDataController,
|
||||||
viewModel: viewModel,
|
isFocused: isFocused,
|
||||||
theme: theme), !viewModel.isRemote
|
theme: theme), !viewModel.isRemote
|
||||||
{
|
{
|
||||||
Text("\(count)")
|
Text("\(count)")
|
||||||
|
|
|
@ -6,6 +6,7 @@ import SwiftUI
|
||||||
struct StatusRowContentView: View {
|
struct StatusRowContentView: View {
|
||||||
@Environment(\.redactionReasons) private var reasons
|
@Environment(\.redactionReasons) private var reasons
|
||||||
@Environment(\.isCompact) private var isCompact
|
@Environment(\.isCompact) private var isCompact
|
||||||
|
@Environment(\.isStatusFocused) private var isFocused
|
||||||
|
|
||||||
@EnvironmentObject private var theme: Theme
|
@EnvironmentObject private var theme: Theme
|
||||||
|
|
||||||
|
@ -44,7 +45,7 @@ struct StatusRowContentView: View {
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.accessibilityHidden(viewModel.isFocused == false)
|
.accessibilityHidden(isFocused == false)
|
||||||
.padding(.vertical, 4)
|
.padding(.vertical, 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,8 @@ import SwiftUI
|
||||||
|
|
||||||
struct StatusRowHeaderView: View {
|
struct StatusRowHeaderView: View {
|
||||||
@Environment(\.isInCaptureMode) private var isInCaptureMode: Bool
|
@Environment(\.isInCaptureMode) private var isInCaptureMode: Bool
|
||||||
|
@Environment(\.isStatusFocused) private var isFocused
|
||||||
|
|
||||||
@EnvironmentObject private var theme: Theme
|
@EnvironmentObject private var theme: Theme
|
||||||
|
|
||||||
let viewModel: StatusRowViewModel
|
let viewModel: StatusRowViewModel
|
||||||
|
@ -29,7 +31,7 @@ struct StatusRowHeaderView: View {
|
||||||
viewModel.navigateToAccountDetail(account: viewModel.finalStatus.account)
|
viewModel.navigateToAccountDetail(account: viewModel.finalStatus.account)
|
||||||
}
|
}
|
||||||
.accessibilityActions {
|
.accessibilityActions {
|
||||||
if viewModel.isFocused {
|
if isFocused {
|
||||||
StatusRowContextMenu(viewModel: viewModel)
|
StatusRowContextMenu(viewModel: viewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import SwiftUI
|
||||||
|
|
||||||
struct StatusRowTextView: View {
|
struct StatusRowTextView: View {
|
||||||
@EnvironmentObject private var theme: Theme
|
@EnvironmentObject private var theme: Theme
|
||||||
|
@Environment(\.isStatusFocused) private var isFocused
|
||||||
|
|
||||||
@ObservedObject var viewModel: StatusRowViewModel
|
@ObservedObject var viewModel: StatusRowViewModel
|
||||||
|
|
||||||
|
@ -15,11 +16,11 @@ struct StatusRowTextView: View {
|
||||||
emojis: viewModel.finalStatus.emojis,
|
emojis: viewModel.finalStatus.emojis,
|
||||||
language: viewModel.finalStatus.language,
|
language: viewModel.finalStatus.language,
|
||||||
lineLimit: viewModel.lineLimit)
|
lineLimit: viewModel.lineLimit)
|
||||||
.font(viewModel.isFocused ? .scaledBodyFocused : .scaledBody)
|
.font(isFocused ? .scaledBodyFocused : .scaledBody)
|
||||||
.lineSpacing(CGFloat(theme.lineSpacing))
|
.lineSpacing(CGFloat(theme.lineSpacing))
|
||||||
.foregroundColor(viewModel.textDisabled ? .gray : theme.labelColor)
|
.foregroundColor(viewModel.textDisabled ? .gray : theme.labelColor)
|
||||||
.emojiSize(viewModel.isFocused ? Font.scaledBodyFocusedFont.emojiSize : Font.scaledBodyFont.emojiSize)
|
.emojiSize(isFocused ? Font.scaledBodyFocusedFont.emojiSize : Font.scaledBodyFont.emojiSize)
|
||||||
.emojiBaselineOffset(viewModel.isFocused ? Font.scaledBodyFocusedFont.emojiBaselineOffset : Font.scaledBodyFont.emojiBaselineOffset)
|
.emojiBaselineOffset(isFocused ? Font.scaledBodyFocusedFont.emojiBaselineOffset : Font.scaledBodyFont.emojiBaselineOffset)
|
||||||
.environment(\.openURL, OpenURLAction { url in
|
.environment(\.openURL, OpenURLAction { url in
|
||||||
viewModel.routerPath.handleStatus(status: viewModel.finalStatus, url: url)
|
viewModel.routerPath.handleStatus(status: viewModel.finalStatus, url: url)
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue