Various UI enhancements
This commit is contained in:
parent
03a5dd9f54
commit
dd5a6a8b45
|
@ -17,6 +17,9 @@ struct AccountTab: View {
|
|||
AccountDetailView(account: account)
|
||||
.withAppRouteur()
|
||||
.withSheetDestinations(sheetDestinations: $routeurPath.presentedSheet)
|
||||
.toolbar {
|
||||
statusEditorToolbarItem(routeurPath: routeurPath)
|
||||
}
|
||||
} else {
|
||||
AccountDetailView(account: .placeholder())
|
||||
.redacted(reason: .placeholder)
|
||||
|
|
|
@ -16,6 +16,9 @@ struct ExploreTab: View {
|
|||
ExploreView()
|
||||
.withAppRouteur()
|
||||
.withSheetDestinations(sheetDestinations: $routeurPath.presentedSheet)
|
||||
.toolbar {
|
||||
statusEditorToolbarItem(routeurPath: routeurPath)
|
||||
}
|
||||
}
|
||||
.environmentObject(routeurPath)
|
||||
.onChange(of: $popToRootTab.wrappedValue) { popToRootTab in
|
||||
|
|
|
@ -15,6 +15,9 @@ struct NotificationsTab: View {
|
|||
NotificationsListView()
|
||||
.withAppRouteur()
|
||||
.withSheetDestinations(sheetDestinations: $routeurPath.presentedSheet)
|
||||
.toolbar {
|
||||
statusEditorToolbarItem(routeurPath: routeurPath)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
routeurPath.client = client
|
||||
|
|
|
@ -6,7 +6,7 @@ import Account
|
|||
import Models
|
||||
import DesignSystem
|
||||
|
||||
struct SettingsTabs: View {
|
||||
struct SettingsTabs: View {
|
||||
@EnvironmentObject private var client: Client
|
||||
@EnvironmentObject private var currentAccount: CurrentAccount
|
||||
@EnvironmentObject private var currentInstance: CurrentInstance
|
||||
|
|
|
@ -17,14 +17,8 @@ struct TimelineTab: View {
|
|||
.withSheetDestinations(sheetDestinations: $routeurPath.presentedSheet)
|
||||
.toolbar {
|
||||
if client.isAuth {
|
||||
statusEditorToolbarItem(routeurPath: routeurPath)
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button {
|
||||
routeurPath.presentedSheet = .newStatusEditor
|
||||
} label: {
|
||||
Image(systemName: "square.and.pencil")
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
timelineFilterButton
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import SwiftUI
|
||||
import Env
|
||||
|
||||
extension View {
|
||||
public func statusEditorToolbarItem(routeurPath: RouterPath) -> some ToolbarContent {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button {
|
||||
routeurPath.presentedSheet = .newStatusEditor
|
||||
} label: {
|
||||
Image(systemName: "square.and.pencil")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -58,7 +58,7 @@ public struct ExploreView: View {
|
|||
|
||||
private var loadingView: some View {
|
||||
ForEach(Status.placeholders()) { status in
|
||||
StatusRowView(viewModel: .init(status: status, isEmbed: false))
|
||||
StatusRowView(viewModel: .init(status: status, isCompact: false))
|
||||
.padding(.vertical, 8)
|
||||
.redacted(reason: .placeholder)
|
||||
.shimmering()
|
||||
|
@ -162,7 +162,7 @@ public struct ExploreView: View {
|
|||
Section("Trending Posts") {
|
||||
ForEach(viewModel.trendingStatuses
|
||||
.prefix(upTo: viewModel.trendingStatuses.count > 3 ? 3 : viewModel.trendingStatuses.count)) { status in
|
||||
StatusRowView(viewModel: .init(status: status, isEmbed: false))
|
||||
StatusRowView(viewModel: .init(status: status, isCompact: false))
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
|
@ -170,7 +170,7 @@ public struct ExploreView: View {
|
|||
NavigationLink {
|
||||
List {
|
||||
ForEach(viewModel.trendingStatuses) { status in
|
||||
StatusRowView(viewModel: .init(status: status, isEmbed: false))
|
||||
StatusRowView(viewModel: .init(status: status, isCompact: false))
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
|
|
|
@ -74,14 +74,10 @@ struct NotificationRowView: View {
|
|||
@ViewBuilder
|
||||
private func makeContent(type: Models.Notification.NotificationType) -> some View {
|
||||
if let status = notification.status {
|
||||
StatusRowView(viewModel: .init(status: status, isEmbed: true))
|
||||
.padding(8)
|
||||
.background(theme.secondaryBackgroundColor)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 4)
|
||||
.stroke(.gray.opacity(0.35), lineWidth: 1)
|
||||
)
|
||||
.padding(.top, 8)
|
||||
HStack {
|
||||
StatusRowView(viewModel: .init(status: status, isCompact: true))
|
||||
Spacer()
|
||||
}
|
||||
} else {
|
||||
Group {
|
||||
Text("@\(notification.account.acct)")
|
||||
|
|
|
@ -50,7 +50,7 @@ class NotificationsViewModel: ObservableObject {
|
|||
try await client.get(endpoint: Notifications.notifications(sinceId: first.id,
|
||||
maxId: nil,
|
||||
types: queryTypes))
|
||||
nextPageState = newNotifications.count < 15 ? .none : .hasNextPage
|
||||
nextPageState = notifications.count < 15 ? .none : .hasNextPage
|
||||
notifications.insert(contentsOf: newNotifications, at: 0)
|
||||
}
|
||||
state = .display(notifications: notifications,
|
||||
|
|
|
@ -25,27 +25,27 @@ public struct StatusDetailView: View {
|
|||
switch viewModel.state {
|
||||
case .loading:
|
||||
ForEach(Status.placeholders()) { status in
|
||||
StatusRowView(viewModel: .init(status: status, isEmbed: false))
|
||||
StatusRowView(viewModel: .init(status: status, isCompact: false))
|
||||
.redacted(reason: .placeholder)
|
||||
.shimmering()
|
||||
}
|
||||
case let.display(status, context):
|
||||
if !context.ancestors.isEmpty {
|
||||
ForEach(context.ancestors) { ancestor in
|
||||
StatusRowView(viewModel: .init(status: ancestor, isEmbed: false))
|
||||
StatusRowView(viewModel: .init(status: ancestor, isCompact: false))
|
||||
Divider()
|
||||
.padding(.vertical, DS.Constants.dividerPadding)
|
||||
}
|
||||
}
|
||||
StatusRowView(viewModel: .init(status: status,
|
||||
isEmbed: false,
|
||||
isCompact: false,
|
||||
isFocused: true))
|
||||
.id(status.id)
|
||||
Divider()
|
||||
.padding(.bottom, DS.Constants.dividerPadding * 2)
|
||||
if !context.descendants.isEmpty {
|
||||
ForEach(context.descendants) { descendant in
|
||||
StatusRowView(viewModel: .init(status: descendant, isEmbed: false))
|
||||
StatusRowView(viewModel: .init(status: descendant, isCompact: false))
|
||||
Divider()
|
||||
.padding(.vertical, DS.Constants.dividerPadding)
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ public struct StatusEmbededView: View {
|
|||
HStack {
|
||||
VStack(alignment: .leading) {
|
||||
makeAccountView(account: status.reblog?.account ?? status.account)
|
||||
StatusRowView(viewModel: .init(status: status, isEmbed: true))
|
||||
StatusRowView(viewModel: .init(status: status, isCompact: true))
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
|
|||
switch fetcher.statusesState {
|
||||
case .loading:
|
||||
ForEach(Status.placeholders()) { status in
|
||||
StatusRowView(viewModel: .init(status: status, isEmbed: false))
|
||||
StatusRowView(viewModel: .init(status: status, isCompact: false))
|
||||
.redacted(reason: .placeholder)
|
||||
.shimmering()
|
||||
Divider()
|
||||
|
@ -25,7 +25,7 @@ public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
|
|||
Text(error.localizedDescription)
|
||||
case let .display(statuses, nextPageState):
|
||||
ForEach(statuses, id: \.viewId) { status in
|
||||
StatusRowView(viewModel: .init(status: status, isEmbed: false))
|
||||
StatusRowView(viewModel: .init(status: status, isCompact: false))
|
||||
Divider()
|
||||
.padding(.vertical, DS.Constants.dividerPadding)
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ public struct StatusPollView: View {
|
|||
|
||||
private func percentForOption(option: Poll.Option) -> Int {
|
||||
let ratio = (Float(option.votesCount) / Float(viewModel.poll.votesCount)) * 100
|
||||
return Int(ceil(ratio))
|
||||
return Int(round(ratio))
|
||||
}
|
||||
|
||||
private func isSelected(option: Poll.Option) -> Bool {
|
||||
|
|
|
@ -9,11 +9,15 @@ public struct StatusMediaPreviewView: View {
|
|||
@EnvironmentObject private var quickLook: QuickLook
|
||||
|
||||
public let attachements: [MediaAttachement]
|
||||
public let isCompact: Bool
|
||||
|
||||
@State private var isQuickLookLoading: Bool = false
|
||||
@State private var width: CGFloat = 0
|
||||
|
||||
private var imageMaxHeight: CGFloat {
|
||||
if isCompact {
|
||||
return 50
|
||||
}
|
||||
if attachements.count == 1 {
|
||||
return 300
|
||||
}
|
||||
|
@ -21,6 +25,9 @@ public struct StatusMediaPreviewView: View {
|
|||
}
|
||||
|
||||
private func size(for media: MediaAttachement) -> CGSize? {
|
||||
if isCompact {
|
||||
return .init(width: 50, height: 50)
|
||||
}
|
||||
if let width = media.meta?.original.width,
|
||||
let height = media.meta?.original.height {
|
||||
return .init(width: CGFloat(width), height: CGFloat(height))
|
||||
|
@ -29,6 +36,9 @@ public struct StatusMediaPreviewView: View {
|
|||
}
|
||||
|
||||
private func imageSize(from: CGSize, newWidth: CGFloat) -> CGSize {
|
||||
if isCompact {
|
||||
return .init(width: 50, height: 50)
|
||||
}
|
||||
let ratio = newWidth / from.width
|
||||
let newHeight = from.height * ratio
|
||||
return .init(width: newWidth, height: newHeight)
|
||||
|
@ -44,21 +54,22 @@ public struct StatusMediaPreviewView: View {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
VStack {
|
||||
if isCompact {
|
||||
HStack {
|
||||
if let firstAttachement = attachements.first {
|
||||
makePreview(attachement: firstAttachement)
|
||||
}
|
||||
if attachements.count > 1, let secondAttachement = attachements[1] {
|
||||
makePreview(attachement: secondAttachement)
|
||||
}
|
||||
makeAttachementView(for: 0)
|
||||
makeAttachementView(for: 1)
|
||||
makeAttachementView(for: 2)
|
||||
makeAttachementView(for: 3)
|
||||
}
|
||||
HStack {
|
||||
if attachements.count > 2, let secondAttachement = attachements[2] {
|
||||
makePreview(attachement: secondAttachement)
|
||||
} else {
|
||||
VStack {
|
||||
HStack {
|
||||
makeAttachementView(for: 0)
|
||||
makeAttachementView(for: 1)
|
||||
}
|
||||
if attachements.count > 3, let secondAttachement = attachements[3] {
|
||||
makePreview(attachement: secondAttachement)
|
||||
HStack {
|
||||
makeAttachementView(for: 2)
|
||||
makeAttachementView(for: 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -72,6 +83,13 @@ public struct StatusMediaPreviewView: View {
|
|||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func makeAttachementView(for index: Int) -> some View {
|
||||
if attachements.count > index {
|
||||
makePreview(attachement: attachements[index])
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func makeFeaturedImagePreview(attachement: MediaAttachement) -> some View {
|
||||
switch attachement.supportedType {
|
||||
|
@ -134,20 +152,21 @@ public struct StatusMediaPreviewView: View {
|
|||
RoundedRectangle(cornerRadius: 4)
|
||||
.fill(Color.gray)
|
||||
.frame(maxHeight: imageMaxHeight)
|
||||
.frame(width: proxy.frame(in: .local).width)
|
||||
.frame(width: isCompact ? imageMaxHeight : proxy.frame(in: .local).width)
|
||||
.shimmering()
|
||||
}
|
||||
}
|
||||
.frame(width: proxy.frame(in: .local).width)
|
||||
.frame(width: isCompact ? imageMaxHeight : proxy.frame(in: .local).width)
|
||||
.frame(height: imageMaxHeight)
|
||||
case .gifv:
|
||||
if let url = attachement.url {
|
||||
VideoPlayerView(viewModel: .init(url: url))
|
||||
.frame(width: proxy.frame(in: .local).width)
|
||||
.frame(width: isCompact ? imageMaxHeight : proxy.frame(in: .local).width)
|
||||
.frame(height: imageMaxHeight)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(width: isCompact ? imageMaxHeight : nil)
|
||||
.frame(height: imageMaxHeight)
|
||||
}
|
||||
.onTapGesture {
|
||||
|
|
|
@ -18,12 +18,12 @@ public struct StatusRowView: View {
|
|||
|
||||
public var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
if !viewModel.isEmbed {
|
||||
if !viewModel.isCompact {
|
||||
reblogView
|
||||
replyView
|
||||
}
|
||||
statusView
|
||||
if !viewModel.isEmbed {
|
||||
if !viewModel.isCompact {
|
||||
StatusActionsView(viewModel: viewModel)
|
||||
.padding(.vertical, 8)
|
||||
.tint(viewModel.isFocused ? theme.tintColor : .gray)
|
||||
|
@ -35,7 +35,7 @@ public struct StatusRowView: View {
|
|||
}
|
||||
.onAppear {
|
||||
viewModel.client = client
|
||||
if !viewModel.isEmbed {
|
||||
if !viewModel.isCompact {
|
||||
Task {
|
||||
await viewModel.loadEmbededStatus()
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ public struct StatusRowView: View {
|
|||
private var statusView: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
if let status: AnyStatus = viewModel.status.reblog ?? viewModel.status {
|
||||
if !viewModel.isEmbed {
|
||||
if !viewModel.isCompact {
|
||||
HStack(alignment: .top) {
|
||||
Button {
|
||||
routeurPath.navigate(to: .accountDetailWithAccount(account: status.account))
|
||||
|
@ -122,7 +122,7 @@ public struct StatusRowView: View {
|
|||
routeurPath.handleStatus(status: status, url: url)
|
||||
})
|
||||
|
||||
if !viewModel.isEmbed, let embed = viewModel.embededStatus {
|
||||
if !viewModel.isCompact, let embed = viewModel.embededStatus {
|
||||
StatusEmbededView(status: embed)
|
||||
}
|
||||
|
||||
|
@ -131,14 +131,10 @@ public struct StatusRowView: View {
|
|||
}
|
||||
|
||||
if !status.mediaAttachments.isEmpty {
|
||||
if viewModel.isEmbed {
|
||||
Image(systemName: "paperclip")
|
||||
} else {
|
||||
StatusMediaPreviewView(attachements: status.mediaAttachments)
|
||||
.padding(.vertical, 4)
|
||||
}
|
||||
StatusMediaPreviewView(attachements: status.mediaAttachments, isCompact: viewModel.isCompact)
|
||||
.padding(.vertical, 4)
|
||||
}
|
||||
if let card = status.card, !viewModel.isEmbed {
|
||||
if let card = status.card, !viewModel.isCompact {
|
||||
StatusCardView(card: card)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import Network
|
|||
@MainActor
|
||||
public class StatusRowViewModel: ObservableObject {
|
||||
let status: Status
|
||||
let isEmbed: Bool
|
||||
let isCompact: Bool
|
||||
let isFocused: Bool
|
||||
|
||||
@Published var favouritesCount: Int
|
||||
|
@ -19,10 +19,10 @@ public class StatusRowViewModel: ObservableObject {
|
|||
var client: Client?
|
||||
|
||||
public init(status: Status,
|
||||
isEmbed: Bool = false,
|
||||
isCompact: Bool = false,
|
||||
isFocused: Bool = false) {
|
||||
self.status = status
|
||||
self.isEmbed = isEmbed
|
||||
self.isCompact = isCompact
|
||||
self.isFocused = isFocused
|
||||
if let reblog = status.reblog {
|
||||
self.isFavourited = reblog.favourited == true
|
||||
|
|
Loading…
Reference in New Issue