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)
.withAppRouteur()
.withSheetDestinations(sheetDestinations: $routeurPath.presentedSheet)
.toolbar {
statusEditorToolbarItem(routeurPath: routeurPath)
}
} else {
AccountDetailView(account: .placeholder())
.redacted(reason: .placeholder)

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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)
@ -43,22 +53,23 @@ public struct StatusMediaPreviewView: View {
await quickLook.prepareFor(urls: attachements.compactMap{ $0.url }, selectedURL: attachement.url!)
}
}
} else {
if isCompact {
HStack {
makeAttachementView(for: 0)
makeAttachementView(for: 1)
makeAttachementView(for: 2)
makeAttachementView(for: 3)
}
} else {
VStack {
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)
}
HStack {
if attachements.count > 2, let secondAttachement = attachements[2] {
makePreview(attachement: secondAttachement)
}
if attachements.count > 3, let secondAttachement = attachements[3] {
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
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 {

View file

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

View file

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