mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-12-22 21:08:15 +01:00
Add media grid on user profile
This commit is contained in:
parent
123f05538a
commit
9fa19aa132
@ -68,8 +68,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/kean/Nuke",
|
||||
"state" : {
|
||||
"revision" : "311016d972aa751ae8ab0cd5897422ebe7db0501",
|
||||
"version" : "12.7.3"
|
||||
"revision" : "0ead44350d2737db384908569c012fe67c421e4d",
|
||||
"version" : "12.8.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -24,6 +24,8 @@ extension View {
|
||||
AccountDetailView(account: account, scrollToTopSignal: .constant(0))
|
||||
case let .accountSettingsWithAccount(account, appAccount):
|
||||
AccountSettingsView(account: account, appAccount: appAccount)
|
||||
case let .accountMediaGridView(account, initialMedia):
|
||||
AccountDetailMediaGridView(account: account, initialMediaStatuses: initialMedia)
|
||||
case let .statusDetail(id):
|
||||
StatusDetailView(statusId: id)
|
||||
case let .statusDetailWithStatus(status):
|
||||
|
@ -79,6 +79,22 @@ public struct AccountDetailView: View {
|
||||
if viewModel.selectedTab == .statuses {
|
||||
pinnedPostsView
|
||||
}
|
||||
if viewModel.selectedTab == .media {
|
||||
HStack {
|
||||
Label("Media Grid", systemImage: "square.grid.2x2")
|
||||
Spacer()
|
||||
Image(systemName: "chevron.right")
|
||||
}
|
||||
.onTapGesture {
|
||||
if let account = viewModel.account {
|
||||
routerPath.navigate(to: .accountMediaGridView(account: account,
|
||||
initialMediaStatuses: viewModel.statusesMedias))
|
||||
}
|
||||
}
|
||||
#if !os(visionOS)
|
||||
.listRowBackground(theme.primaryBackgroundColor)
|
||||
#endif
|
||||
}
|
||||
StatusesListView(fetcher: viewModel,
|
||||
client: client,
|
||||
routerPath: routerPath)
|
||||
|
@ -84,6 +84,9 @@ import SwiftUI
|
||||
private var tabTask: Task<Void, Never>?
|
||||
|
||||
private(set) var statuses: [Status] = []
|
||||
var statusesMedias: [MediaStatus] {
|
||||
statuses.filter{ !$0.mediaAttachments.isEmpty }.flatMap{ $0.asMediaStatus}
|
||||
}
|
||||
|
||||
var boosts: [Status] = []
|
||||
|
||||
|
@ -0,0 +1,116 @@
|
||||
import SwiftUI
|
||||
import DesignSystem
|
||||
import NukeUI
|
||||
import Env
|
||||
import MediaUI
|
||||
import Models
|
||||
import Network
|
||||
|
||||
@MainActor
|
||||
public struct AccountDetailMediaGridView: View {
|
||||
@Environment(Theme.self) private var theme
|
||||
@Environment(RouterPath.self) private var routerPath
|
||||
@Environment(Client.self) private var client
|
||||
@Environment(QuickLook.self) private var quickLook
|
||||
|
||||
let account: Account
|
||||
@State var mediaStatuses: [MediaStatus]
|
||||
|
||||
public init(account: Account, initialMediaStatuses: [MediaStatus]) {
|
||||
self.account = account
|
||||
self.mediaStatuses = initialMediaStatuses
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
ScrollView(.vertical) {
|
||||
LazyVGrid(columns: [.init(.flexible(minimum: 100), spacing: 4),
|
||||
.init(.flexible(minimum: 100), spacing: 4),
|
||||
.init(.flexible(minimum: 100), spacing: 4)],
|
||||
spacing: 4) {
|
||||
ForEach(mediaStatuses) { status in
|
||||
GeometryReader { proxy in
|
||||
if let url = status.attachment.url {
|
||||
Group {
|
||||
switch status.attachment.supportedType {
|
||||
case .image:
|
||||
LazyImage(url: url, transaction: Transaction(animation: .easeIn)) { state in
|
||||
if let image = state.image {
|
||||
image
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: proxy.size.width, height: proxy.size.width)
|
||||
} else {
|
||||
ProgressView()
|
||||
.frame(width: proxy.size.width, height: proxy.size.width)
|
||||
}
|
||||
}
|
||||
.processors([.resize(size: proxy.size)])
|
||||
.transition(.opacity)
|
||||
case .gifv, .video:
|
||||
MediaUIAttachmentVideoView(viewModel: .init(url: url))
|
||||
case .none:
|
||||
EmptyView()
|
||||
case .some(.audio):
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
.onTapGesture {
|
||||
routerPath.navigate(to: .statusDetailWithStatus(status: status.status))
|
||||
}
|
||||
.contextMenu {
|
||||
Button {
|
||||
quickLook.prepareFor(selectedMediaAttachment: status.attachment,
|
||||
mediaAttachments: status.status.mediaAttachments)
|
||||
} label: {
|
||||
Label("Open Media", systemImage: "photo")
|
||||
}
|
||||
MediaUIShareLink(url: url, type: status.attachment.supportedType == .image ? .image : .av)
|
||||
Button {
|
||||
Task {
|
||||
let transferable = MediaUIImageTransferable(url: url)
|
||||
UIPasteboard.general.image = UIImage(data: await transferable.fetchData())
|
||||
}
|
||||
} label: {
|
||||
Label("status.media.contextmenu.copy", systemImage: "doc.on.doc")
|
||||
}
|
||||
Button {
|
||||
UIPasteboard.general.url = url
|
||||
} label: {
|
||||
Label("status.action.copy-link", systemImage: "link")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.clipped()
|
||||
.aspectRatio(1, contentMode: .fit)
|
||||
}
|
||||
|
||||
VStack {
|
||||
Spacer()
|
||||
NextPageView {
|
||||
try await fetchNextPage()
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle(account.displayName ?? "")
|
||||
#if !os(visionOS)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(theme.primaryBackgroundColor)
|
||||
#endif
|
||||
}
|
||||
|
||||
private func fetchNextPage() async throws {
|
||||
let client = client
|
||||
let newStatuses: [Status] =
|
||||
try await client.get(endpoint: Accounts.statuses(id: account.id,
|
||||
sinceId: mediaStatuses.last?.id,
|
||||
tag: nil,
|
||||
onlyMedia: true,
|
||||
excludeReplies: true,
|
||||
excludeReblogs: true,
|
||||
pinned: nil))
|
||||
mediaStatuses.append(contentsOf: newStatuses.flatMap{ $0.asMediaStatus })
|
||||
}
|
||||
}
|
@ -19,7 +19,7 @@ let package = Package(
|
||||
dependencies: [
|
||||
.package(name: "Models", path: "../Models"),
|
||||
.package(name: "Env", path: "../Env"),
|
||||
.package(url: "https://github.com/kean/Nuke", exact: "12.7.3"),
|
||||
.package(url: "https://github.com/kean/Nuke", exact: "12.8.0"),
|
||||
.package(url: "https://github.com/divadretlaw/EmojiText", exact: "4.0.0"),
|
||||
],
|
||||
targets: [
|
||||
|
@ -9,6 +9,7 @@ public enum RouterDestination: Hashable {
|
||||
case accountDetail(id: String)
|
||||
case accountDetailWithAccount(account: Account)
|
||||
case accountSettingsWithAccount(account: Account, appAccount: AppAccount)
|
||||
case accountMediaGridView(account: Account, initialMediaStatuses: [MediaStatus])
|
||||
case statusDetail(id: String)
|
||||
case statusDetailWithStatus(status: Status)
|
||||
case remoteStatusDetail(url: URL)
|
||||
|
@ -1,11 +1,11 @@
|
||||
import Models
|
||||
import SwiftUI
|
||||
|
||||
enum DisplayType {
|
||||
public enum DisplayType {
|
||||
case image
|
||||
case av
|
||||
|
||||
init(from attachmentType: MediaAttachment.SupportedType) {
|
||||
public init(from attachmentType: MediaAttachment.SupportedType) {
|
||||
switch attachmentType {
|
||||
case .image:
|
||||
self = .image
|
||||
|
@ -2,12 +2,12 @@ import Models
|
||||
import NukeUI
|
||||
import SwiftUI
|
||||
|
||||
struct MediaUIAttachmentImageView: View {
|
||||
let url: URL
|
||||
public struct MediaUIAttachmentImageView: View {
|
||||
public let url: URL
|
||||
|
||||
@GestureState private var zoom = 1.0
|
||||
|
||||
var body: some View {
|
||||
public var body: some View {
|
||||
MediaUIZoomableContainer {
|
||||
LazyImage(url: url) { state in
|
||||
if let image = state.image {
|
||||
|
@ -1,10 +1,15 @@
|
||||
import SwiftUI
|
||||
|
||||
struct MediaUIShareLink: View, @unchecked Sendable {
|
||||
public struct MediaUIShareLink: View, @unchecked Sendable {
|
||||
let url: URL
|
||||
let type: DisplayType
|
||||
|
||||
var body: some View {
|
||||
public init(url: URL, type: DisplayType) {
|
||||
self.url = url
|
||||
self.type = type
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
if type == .image {
|
||||
let transferable = MediaUIImageTransferable(url: url)
|
||||
ShareLink(item: transferable, preview: .init("status.media.contextmenu.share",
|
||||
|
@ -2,10 +2,14 @@ import CoreTransferable
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
struct MediaUIImageTransferable: Codable, Transferable {
|
||||
let url: URL
|
||||
public struct MediaUIImageTransferable: Codable, Transferable {
|
||||
public let url: URL
|
||||
|
||||
func fetchData() async -> Data {
|
||||
public init(url: URL) {
|
||||
self.url = url
|
||||
}
|
||||
|
||||
public func fetchData() async -> Data {
|
||||
do {
|
||||
return try await URLSession.shared.data(from: url).0
|
||||
} catch {
|
||||
@ -13,7 +17,7 @@ struct MediaUIImageTransferable: Codable, Transferable {
|
||||
}
|
||||
}
|
||||
|
||||
static var transferRepresentation: some TransferRepresentation {
|
||||
public static var transferRepresentation: some TransferRepresentation {
|
||||
DataRepresentation(exportedContentType: .jpeg) { transferable in
|
||||
await transferable.fetchData()
|
||||
}
|
||||
|
15
Packages/Models/Sources/Models/MediaStatus.swift
Normal file
15
Packages/Models/Sources/Models/MediaStatus.swift
Normal file
@ -0,0 +1,15 @@
|
||||
import Foundation
|
||||
|
||||
public struct MediaStatus: Sendable, Identifiable, Hashable {
|
||||
public var id: String {
|
||||
attachment.id
|
||||
}
|
||||
|
||||
public let status: Status
|
||||
public let attachment: MediaAttachment
|
||||
|
||||
public init(status: Status, attachment: MediaAttachment) {
|
||||
self.status = status
|
||||
self.attachment = attachment
|
||||
}
|
||||
}
|
@ -77,6 +77,10 @@ public final class Status: AnyStatus, Codable, Identifiable, Equatable, Hashable
|
||||
public var isHidden: Bool {
|
||||
filtered?.first?.filter.filterAction == .hide
|
||||
}
|
||||
|
||||
public var asMediaStatus: [MediaStatus] {
|
||||
mediaAttachments.map{ .init(status: self, attachment: $0)}
|
||||
}
|
||||
|
||||
public init(id: String, content: HTMLString, account: Account, createdAt: ServerDate, editedAt: ServerDate?, reblog: ReblogStatus?, mediaAttachments: [MediaAttachment], mentions: [Mention], repliesCount: Int, reblogsCount: Int, favouritesCount: Int, card: Card?, favourited: Bool?, reblogged: Bool?, pinned: Bool?, bookmarked: Bool?, emojis: [Emoji], url: String?, application: Application?, inReplyToId: String?, inReplyToAccountId: String?, visibility: Visibility, poll: Poll?, spoilerText: HTMLString, filtered: [Filtered]?, sensitive: Bool, language: String?) {
|
||||
self.id = id
|
||||
|
Loading…
Reference in New Issue
Block a user