diff --git a/IceCubesApp/App/Tabs/AccountTab.swift b/IceCubesApp/App/Tabs/AccountTab.swift index 62ef62fe..3ea338b8 100644 --- a/IceCubesApp/App/Tabs/AccountTab.swift +++ b/IceCubesApp/App/Tabs/AccountTab.swift @@ -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) diff --git a/IceCubesApp/App/Tabs/ExploreTab.swift b/IceCubesApp/App/Tabs/ExploreTab.swift index c4e00440..f7ac1ae8 100644 --- a/IceCubesApp/App/Tabs/ExploreTab.swift +++ b/IceCubesApp/App/Tabs/ExploreTab.swift @@ -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 diff --git a/IceCubesApp/App/Tabs/NotificationTab.swift b/IceCubesApp/App/Tabs/NotificationTab.swift index d7588118..40f8c804 100644 --- a/IceCubesApp/App/Tabs/NotificationTab.swift +++ b/IceCubesApp/App/Tabs/NotificationTab.swift @@ -15,6 +15,9 @@ struct NotificationsTab: View { NotificationsListView() .withAppRouteur() .withSheetDestinations(sheetDestinations: $routeurPath.presentedSheet) + .toolbar { + statusEditorToolbarItem(routeurPath: routeurPath) + } } .onAppear { routeurPath.client = client diff --git a/IceCubesApp/App/Tabs/Settings/SettingsTab.swift b/IceCubesApp/App/Tabs/Settings/SettingsTab.swift index bfebd7c9..2812340a 100644 --- a/IceCubesApp/App/Tabs/Settings/SettingsTab.swift +++ b/IceCubesApp/App/Tabs/Settings/SettingsTab.swift @@ -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 diff --git a/IceCubesApp/App/Tabs/TimelineTab.swift b/IceCubesApp/App/Tabs/TimelineTab.swift index 1848760e..167c44e0 100644 --- a/IceCubesApp/App/Tabs/TimelineTab.swift +++ b/IceCubesApp/App/Tabs/TimelineTab.swift @@ -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 } } diff --git a/Packages/DesignSystem/Sources/DesignSystem/Views/StatusEditorToolbarItem.swift b/Packages/DesignSystem/Sources/DesignSystem/Views/StatusEditorToolbarItem.swift new file mode 100644 index 00000000..21ecde43 --- /dev/null +++ b/Packages/DesignSystem/Sources/DesignSystem/Views/StatusEditorToolbarItem.swift @@ -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") + } + } + } +} diff --git a/Packages/Explore/Sources/Explore/ExploreView.swift b/Packages/Explore/Sources/Explore/ExploreView.swift index e2442a42..cc3aaaf6 100644 --- a/Packages/Explore/Sources/Explore/ExploreView.swift +++ b/Packages/Explore/Sources/Explore/ExploreView.swift @@ -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) } diff --git a/Packages/Notifications/Sources/Notifications/NotificationRowView.swift b/Packages/Notifications/Sources/Notifications/NotificationRowView.swift index 32c66d18..9610dd9e 100644 --- a/Packages/Notifications/Sources/Notifications/NotificationRowView.swift +++ b/Packages/Notifications/Sources/Notifications/NotificationRowView.swift @@ -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)") diff --git a/Packages/Notifications/Sources/Notifications/NotificationsViewModel.swift b/Packages/Notifications/Sources/Notifications/NotificationsViewModel.swift index 4243cd1e..f71fe7ff 100644 --- a/Packages/Notifications/Sources/Notifications/NotificationsViewModel.swift +++ b/Packages/Notifications/Sources/Notifications/NotificationsViewModel.swift @@ -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, diff --git a/Packages/Status/Sources/Status/Detail/StatusDetailVIew.swift b/Packages/Status/Sources/Status/Detail/StatusDetailVIew.swift index 3b0566a2..af399534 100644 --- a/Packages/Status/Sources/Status/Detail/StatusDetailVIew.swift +++ b/Packages/Status/Sources/Status/Detail/StatusDetailVIew.swift @@ -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) } diff --git a/Packages/Status/Sources/Status/Embed/StatusEmbededView.swift b/Packages/Status/Sources/Status/Embed/StatusEmbededView.swift index 17742194..0d4fe4e8 100644 --- a/Packages/Status/Sources/Status/Embed/StatusEmbededView.swift +++ b/Packages/Status/Sources/Status/Embed/StatusEmbededView.swift @@ -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() } diff --git a/Packages/Status/Sources/Status/List/StatusesListView.swift b/Packages/Status/Sources/Status/List/StatusesListView.swift index 6a3ed601..f0ef12ea 100644 --- a/Packages/Status/Sources/Status/List/StatusesListView.swift +++ b/Packages/Status/Sources/Status/List/StatusesListView.swift @@ -15,7 +15,7 @@ public struct StatusesListView: 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: 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) } diff --git a/Packages/Status/Sources/Status/Poll/StatusPollView.swift b/Packages/Status/Sources/Status/Poll/StatusPollView.swift index ba91cd3f..57e764c4 100644 --- a/Packages/Status/Sources/Status/Poll/StatusPollView.swift +++ b/Packages/Status/Sources/Status/Poll/StatusPollView.swift @@ -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 { diff --git a/Packages/Status/Sources/Status/Row/StatusMediaPreviewView.swift b/Packages/Status/Sources/Status/Row/StatusMediaPreviewView.swift index 46086f05..d0592b68 100644 --- a/Packages/Status/Sources/Status/Row/StatusMediaPreviewView.swift +++ b/Packages/Status/Sources/Status/Row/StatusMediaPreviewView.swift @@ -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 { diff --git a/Packages/Status/Sources/Status/Row/StatusRowView.swift b/Packages/Status/Sources/Status/Row/StatusRowView.swift index 6645d37c..8a581117 100644 --- a/Packages/Status/Sources/Status/Row/StatusRowView.swift +++ b/Packages/Status/Sources/Status/Row/StatusRowView.swift @@ -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) } } diff --git a/Packages/Status/Sources/Status/Row/StatusRowViewModel.swift b/Packages/Status/Sources/Status/Row/StatusRowViewModel.swift index 4973012d..3e87f0ee 100644 --- a/Packages/Status/Sources/Status/Row/StatusRowViewModel.swift +++ b/Packages/Status/Sources/Status/Row/StatusRowViewModel.swift @@ -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