Various UI enhancements

This commit is contained in:
Thomas Ricouard 2022-12-29 17:22:07 +01:00
parent 03a5dd9f54
commit dd5a6a8b45
16 changed files with 86 additions and 58 deletions

View File

@ -17,6 +17,9 @@ struct AccountTab: View {
AccountDetailView(account: account) AccountDetailView(account: account)
.withAppRouteur() .withAppRouteur()
.withSheetDestinations(sheetDestinations: $routeurPath.presentedSheet) .withSheetDestinations(sheetDestinations: $routeurPath.presentedSheet)
.toolbar {
statusEditorToolbarItem(routeurPath: routeurPath)
}
} else { } else {
AccountDetailView(account: .placeholder()) AccountDetailView(account: .placeholder())
.redacted(reason: .placeholder) .redacted(reason: .placeholder)

View File

@ -16,6 +16,9 @@ struct ExploreTab: View {
ExploreView() ExploreView()
.withAppRouteur() .withAppRouteur()
.withSheetDestinations(sheetDestinations: $routeurPath.presentedSheet) .withSheetDestinations(sheetDestinations: $routeurPath.presentedSheet)
.toolbar {
statusEditorToolbarItem(routeurPath: routeurPath)
}
} }
.environmentObject(routeurPath) .environmentObject(routeurPath)
.onChange(of: $popToRootTab.wrappedValue) { popToRootTab in .onChange(of: $popToRootTab.wrappedValue) { popToRootTab in

View File

@ -15,6 +15,9 @@ struct NotificationsTab: View {
NotificationsListView() NotificationsListView()
.withAppRouteur() .withAppRouteur()
.withSheetDestinations(sheetDestinations: $routeurPath.presentedSheet) .withSheetDestinations(sheetDestinations: $routeurPath.presentedSheet)
.toolbar {
statusEditorToolbarItem(routeurPath: routeurPath)
}
} }
.onAppear { .onAppear {
routeurPath.client = client routeurPath.client = client

View File

@ -6,7 +6,7 @@ import Account
import Models import Models
import DesignSystem import DesignSystem
struct SettingsTabs: View { struct SettingsTabs: View {
@EnvironmentObject private var client: Client @EnvironmentObject private var client: Client
@EnvironmentObject private var currentAccount: CurrentAccount @EnvironmentObject private var currentAccount: CurrentAccount
@EnvironmentObject private var currentInstance: CurrentInstance @EnvironmentObject private var currentInstance: CurrentInstance

View File

@ -17,14 +17,8 @@ struct TimelineTab: View {
.withSheetDestinations(sheetDestinations: $routeurPath.presentedSheet) .withSheetDestinations(sheetDestinations: $routeurPath.presentedSheet)
.toolbar { .toolbar {
if client.isAuth { if client.isAuth {
statusEditorToolbarItem(routeurPath: routeurPath)
ToolbarItem(placement: .navigationBarLeading) { ToolbarItem(placement: .navigationBarLeading) {
Button {
routeurPath.presentedSheet = .newStatusEditor
} label: {
Image(systemName: "square.and.pencil")
}
}
ToolbarItem(placement: .navigationBarTrailing) {
timelineFilterButton timelineFilterButton
} }
} }

View File

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

View File

@ -58,7 +58,7 @@ public struct ExploreView: View {
private var loadingView: some View { private var loadingView: some View {
ForEach(Status.placeholders()) { status in ForEach(Status.placeholders()) { status in
StatusRowView(viewModel: .init(status: status, isEmbed: false)) StatusRowView(viewModel: .init(status: status, isCompact: false))
.padding(.vertical, 8) .padding(.vertical, 8)
.redacted(reason: .placeholder) .redacted(reason: .placeholder)
.shimmering() .shimmering()
@ -162,7 +162,7 @@ public struct ExploreView: View {
Section("Trending Posts") { Section("Trending Posts") {
ForEach(viewModel.trendingStatuses ForEach(viewModel.trendingStatuses
.prefix(upTo: viewModel.trendingStatuses.count > 3 ? 3 : viewModel.trendingStatuses.count)) { status in .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) .listRowBackground(theme.primaryBackgroundColor)
.padding(.vertical, 8) .padding(.vertical, 8)
} }
@ -170,7 +170,7 @@ public struct ExploreView: View {
NavigationLink { NavigationLink {
List { List {
ForEach(viewModel.trendingStatuses) { status in ForEach(viewModel.trendingStatuses) { status in
StatusRowView(viewModel: .init(status: status, isEmbed: false)) StatusRowView(viewModel: .init(status: status, isCompact: false))
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
.padding(.vertical, 8) .padding(.vertical, 8)
} }

View File

@ -74,14 +74,10 @@ struct NotificationRowView: View {
@ViewBuilder @ViewBuilder
private func makeContent(type: Models.Notification.NotificationType) -> some View { private func makeContent(type: Models.Notification.NotificationType) -> some View {
if let status = notification.status { if let status = notification.status {
StatusRowView(viewModel: .init(status: status, isEmbed: true)) HStack {
.padding(8) StatusRowView(viewModel: .init(status: status, isCompact: true))
.background(theme.secondaryBackgroundColor) Spacer()
.overlay( }
RoundedRectangle(cornerRadius: 4)
.stroke(.gray.opacity(0.35), lineWidth: 1)
)
.padding(.top, 8)
} else { } else {
Group { Group {
Text("@\(notification.account.acct)") Text("@\(notification.account.acct)")

View File

@ -50,7 +50,7 @@ class NotificationsViewModel: ObservableObject {
try await client.get(endpoint: Notifications.notifications(sinceId: first.id, try await client.get(endpoint: Notifications.notifications(sinceId: first.id,
maxId: nil, maxId: nil,
types: queryTypes)) types: queryTypes))
nextPageState = newNotifications.count < 15 ? .none : .hasNextPage nextPageState = notifications.count < 15 ? .none : .hasNextPage
notifications.insert(contentsOf: newNotifications, at: 0) notifications.insert(contentsOf: newNotifications, at: 0)
} }
state = .display(notifications: notifications, state = .display(notifications: notifications,

View File

@ -25,27 +25,27 @@ public struct StatusDetailView: View {
switch viewModel.state { switch viewModel.state {
case .loading: case .loading:
ForEach(Status.placeholders()) { status in ForEach(Status.placeholders()) { status in
StatusRowView(viewModel: .init(status: status, isEmbed: false)) StatusRowView(viewModel: .init(status: status, isCompact: false))
.redacted(reason: .placeholder) .redacted(reason: .placeholder)
.shimmering() .shimmering()
} }
case let.display(status, context): case let.display(status, context):
if !context.ancestors.isEmpty { if !context.ancestors.isEmpty {
ForEach(context.ancestors) { ancestor in ForEach(context.ancestors) { ancestor in
StatusRowView(viewModel: .init(status: ancestor, isEmbed: false)) StatusRowView(viewModel: .init(status: ancestor, isCompact: false))
Divider() Divider()
.padding(.vertical, DS.Constants.dividerPadding) .padding(.vertical, DS.Constants.dividerPadding)
} }
} }
StatusRowView(viewModel: .init(status: status, StatusRowView(viewModel: .init(status: status,
isEmbed: false, isCompact: false,
isFocused: true)) isFocused: true))
.id(status.id) .id(status.id)
Divider() Divider()
.padding(.bottom, DS.Constants.dividerPadding * 2) .padding(.bottom, DS.Constants.dividerPadding * 2)
if !context.descendants.isEmpty { if !context.descendants.isEmpty {
ForEach(context.descendants) { descendant in ForEach(context.descendants) { descendant in
StatusRowView(viewModel: .init(status: descendant, isEmbed: false)) StatusRowView(viewModel: .init(status: descendant, isCompact: false))
Divider() Divider()
.padding(.vertical, DS.Constants.dividerPadding) .padding(.vertical, DS.Constants.dividerPadding)
} }

View File

@ -16,7 +16,7 @@ public struct StatusEmbededView: View {
HStack { HStack {
VStack(alignment: .leading) { VStack(alignment: .leading) {
makeAccountView(account: status.reblog?.account ?? status.account) makeAccountView(account: status.reblog?.account ?? status.account)
StatusRowView(viewModel: .init(status: status, isEmbed: true)) StatusRowView(viewModel: .init(status: status, isCompact: true))
} }
Spacer() Spacer()
} }

View File

@ -15,7 +15,7 @@ public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
switch fetcher.statusesState { switch fetcher.statusesState {
case .loading: case .loading:
ForEach(Status.placeholders()) { status in ForEach(Status.placeholders()) { status in
StatusRowView(viewModel: .init(status: status, isEmbed: false)) StatusRowView(viewModel: .init(status: status, isCompact: false))
.redacted(reason: .placeholder) .redacted(reason: .placeholder)
.shimmering() .shimmering()
Divider() Divider()
@ -25,7 +25,7 @@ public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
Text(error.localizedDescription) Text(error.localizedDescription)
case let .display(statuses, nextPageState): case let .display(statuses, nextPageState):
ForEach(statuses, id: \.viewId) { status in ForEach(statuses, id: \.viewId) { status in
StatusRowView(viewModel: .init(status: status, isEmbed: false)) StatusRowView(viewModel: .init(status: status, isCompact: false))
Divider() Divider()
.padding(.vertical, DS.Constants.dividerPadding) .padding(.vertical, DS.Constants.dividerPadding)
} }

View File

@ -26,7 +26,7 @@ public struct StatusPollView: View {
private func percentForOption(option: Poll.Option) -> Int { private func percentForOption(option: Poll.Option) -> Int {
let ratio = (Float(option.votesCount) / Float(viewModel.poll.votesCount)) * 100 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 { private func isSelected(option: Poll.Option) -> Bool {

View File

@ -9,11 +9,15 @@ public struct StatusMediaPreviewView: View {
@EnvironmentObject private var quickLook: QuickLook @EnvironmentObject private var quickLook: QuickLook
public let attachements: [MediaAttachement] public let attachements: [MediaAttachement]
public let isCompact: Bool
@State private var isQuickLookLoading: Bool = false @State private var isQuickLookLoading: Bool = false
@State private var width: CGFloat = 0 @State private var width: CGFloat = 0
private var imageMaxHeight: CGFloat { private var imageMaxHeight: CGFloat {
if isCompact {
return 50
}
if attachements.count == 1 { if attachements.count == 1 {
return 300 return 300
} }
@ -21,6 +25,9 @@ public struct StatusMediaPreviewView: View {
} }
private func size(for media: MediaAttachement) -> CGSize? { private func size(for media: MediaAttachement) -> CGSize? {
if isCompact {
return .init(width: 50, height: 50)
}
if let width = media.meta?.original.width, if let width = media.meta?.original.width,
let height = media.meta?.original.height { let height = media.meta?.original.height {
return .init(width: CGFloat(width), height: CGFloat(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 { private func imageSize(from: CGSize, newWidth: CGFloat) -> CGSize {
if isCompact {
return .init(width: 50, height: 50)
}
let ratio = newWidth / from.width let ratio = newWidth / from.width
let newHeight = from.height * ratio let newHeight = from.height * ratio
return .init(width: newWidth, height: newHeight) return .init(width: newWidth, height: newHeight)
@ -44,21 +54,22 @@ public struct StatusMediaPreviewView: View {
} }
} }
} else { } else {
VStack { if isCompact {
HStack { HStack {
if let firstAttachement = attachements.first { makeAttachementView(for: 0)
makePreview(attachement: firstAttachement) makeAttachementView(for: 1)
} makeAttachementView(for: 2)
if attachements.count > 1, let secondAttachement = attachements[1] { makeAttachementView(for: 3)
makePreview(attachement: secondAttachement)
}
} }
HStack { } else {
if attachements.count > 2, let secondAttachement = attachements[2] { VStack {
makePreview(attachement: secondAttachement) HStack {
makeAttachementView(for: 0)
makeAttachementView(for: 1)
} }
if attachements.count > 3, let secondAttachement = attachements[3] { HStack {
makePreview(attachement: secondAttachement) 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 @ViewBuilder
private func makeFeaturedImagePreview(attachement: MediaAttachement) -> some View { private func makeFeaturedImagePreview(attachement: MediaAttachement) -> some View {
switch attachement.supportedType { switch attachement.supportedType {
@ -134,20 +152,21 @@ public struct StatusMediaPreviewView: View {
RoundedRectangle(cornerRadius: 4) RoundedRectangle(cornerRadius: 4)
.fill(Color.gray) .fill(Color.gray)
.frame(maxHeight: imageMaxHeight) .frame(maxHeight: imageMaxHeight)
.frame(width: proxy.frame(in: .local).width) .frame(width: isCompact ? imageMaxHeight : proxy.frame(in: .local).width)
.shimmering() .shimmering()
} }
} }
.frame(width: proxy.frame(in: .local).width) .frame(width: isCompact ? imageMaxHeight : proxy.frame(in: .local).width)
.frame(height: imageMaxHeight) .frame(height: imageMaxHeight)
case .gifv: case .gifv:
if let url = attachement.url { if let url = attachement.url {
VideoPlayerView(viewModel: .init(url: 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(height: imageMaxHeight)
} }
} }
} }
.frame(width: isCompact ? imageMaxHeight : nil)
.frame(height: imageMaxHeight) .frame(height: imageMaxHeight)
} }
.onTapGesture { .onTapGesture {

View File

@ -18,12 +18,12 @@ public struct StatusRowView: View {
public var body: some View { public var body: some View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
if !viewModel.isEmbed { if !viewModel.isCompact {
reblogView reblogView
replyView replyView
} }
statusView statusView
if !viewModel.isEmbed { if !viewModel.isCompact {
StatusActionsView(viewModel: viewModel) StatusActionsView(viewModel: viewModel)
.padding(.vertical, 8) .padding(.vertical, 8)
.tint(viewModel.isFocused ? theme.tintColor : .gray) .tint(viewModel.isFocused ? theme.tintColor : .gray)
@ -35,7 +35,7 @@ public struct StatusRowView: View {
} }
.onAppear { .onAppear {
viewModel.client = client viewModel.client = client
if !viewModel.isEmbed { if !viewModel.isCompact {
Task { Task {
await viewModel.loadEmbededStatus() await viewModel.loadEmbededStatus()
} }
@ -85,7 +85,7 @@ public struct StatusRowView: View {
private var statusView: some View { private var statusView: some View {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
if let status: AnyStatus = viewModel.status.reblog ?? viewModel.status { if let status: AnyStatus = viewModel.status.reblog ?? viewModel.status {
if !viewModel.isEmbed { if !viewModel.isCompact {
HStack(alignment: .top) { HStack(alignment: .top) {
Button { Button {
routeurPath.navigate(to: .accountDetailWithAccount(account: status.account)) routeurPath.navigate(to: .accountDetailWithAccount(account: status.account))
@ -122,7 +122,7 @@ public struct StatusRowView: View {
routeurPath.handleStatus(status: status, url: url) routeurPath.handleStatus(status: status, url: url)
}) })
if !viewModel.isEmbed, let embed = viewModel.embededStatus { if !viewModel.isCompact, let embed = viewModel.embededStatus {
StatusEmbededView(status: embed) StatusEmbededView(status: embed)
} }
@ -131,14 +131,10 @@ public struct StatusRowView: View {
} }
if !status.mediaAttachments.isEmpty { if !status.mediaAttachments.isEmpty {
if viewModel.isEmbed { StatusMediaPreviewView(attachements: status.mediaAttachments, isCompact: viewModel.isCompact)
Image(systemName: "paperclip") .padding(.vertical, 4)
} else {
StatusMediaPreviewView(attachements: status.mediaAttachments)
.padding(.vertical, 4)
}
} }
if let card = status.card, !viewModel.isEmbed { if let card = status.card, !viewModel.isCompact {
StatusCardView(card: card) StatusCardView(card: card)
} }
} }

View File

@ -5,7 +5,7 @@ import Network
@MainActor @MainActor
public class StatusRowViewModel: ObservableObject { public class StatusRowViewModel: ObservableObject {
let status: Status let status: Status
let isEmbed: Bool let isCompact: Bool
let isFocused: Bool let isFocused: Bool
@Published var favouritesCount: Int @Published var favouritesCount: Int
@ -19,10 +19,10 @@ public class StatusRowViewModel: ObservableObject {
var client: Client? var client: Client?
public init(status: Status, public init(status: Status,
isEmbed: Bool = false, isCompact: Bool = false,
isFocused: Bool = false) { isFocused: Bool = false) {
self.status = status self.status = status
self.isEmbed = isEmbed self.isCompact = isCompact
self.isFocused = isFocused self.isFocused = isFocused
if let reblog = status.reblog { if let reblog = status.reblog {
self.isFavourited = reblog.favourited == true self.isFavourited = reblog.favourited == true