Various fixes to share as image + copy text

This commit is contained in:
Thomas Ricouard 2024-09-27 15:22:34 +02:00
parent 93beb2fbd9
commit 27b3856e05
7 changed files with 156 additions and 91 deletions

View file

@ -23,6 +23,7 @@ public struct StatusRowView: View {
@Environment(Client.self) private var client @Environment(Client.self) private var client
@State private var showSelectableText: Bool = false @State private var showSelectableText: Bool = false
@State private var isShareAsImageSheetPresented: Bool = false
@State private var isBlockConfirmationPresented = false @State private var isBlockConfirmationPresented = false
public enum Context { case timeline, detail } public enum Context { case timeline, detail }
@ -33,7 +34,8 @@ public struct StatusRowView: View {
var contextMenu: some View { var contextMenu: some View {
StatusRowContextMenu(viewModel: viewModel, StatusRowContextMenu(viewModel: viewModel,
showTextForSelection: $showSelectableText, showTextForSelection: $showSelectableText,
isBlockConfirmationPresented: $isBlockConfirmationPresented) isBlockConfirmationPresented: $isBlockConfirmationPresented,
isShareAsImageSheetPresented: $isShareAsImageSheetPresented)
} }
public var body: some View { public var body: some View {
@ -213,7 +215,7 @@ public struct StatusRowView: View {
} }
.sheet(isPresented: $showSelectableText) { .sheet(isPresented: $showSelectableText) {
let content = viewModel.status.reblog?.content.asSafeMarkdownAttributedString ?? viewModel.status.content.asSafeMarkdownAttributedString let content = viewModel.status.reblog?.content.asSafeMarkdownAttributedString ?? viewModel.status.content.asSafeMarkdownAttributedString
SelectTextView(content: content) StatusRowSelectableTextView(content: content)
} }
.environment( .environment(
StatusDataControllerProvider.shared.dataController(for: viewModel.finalStatus, StatusDataControllerProvider.shared.dataController(for: viewModel.finalStatus,

View file

@ -10,12 +10,15 @@ struct StatusRowActionsView: View {
@Environment(CurrentAccount.self) private var currentAccount @Environment(CurrentAccount.self) private var currentAccount
@Environment(StatusDataController.self) private var statusDataController @Environment(StatusDataController.self) private var statusDataController
@Environment(UserPreferences.self) private var userPreferences @Environment(UserPreferences.self) private var userPreferences
@Environment(Client.self) private var client
@Environment(SceneDelegate.self) private var sceneDelegate
@Environment(\.openWindow) private var openWindow @Environment(\.openWindow) private var openWindow
@Environment(\.isStatusFocused) private var isFocused @Environment(\.isStatusFocused) private var isFocused
@Environment(\.horizontalSizeClass) var horizontalSizeClass @Environment(\.horizontalSizeClass) var horizontalSizeClass
@State private var showTextForSelection: Bool = false @State private var showTextForSelection: Bool = false
@State private var isShareAsImageSheetPresented: Bool = false
@Binding var isBlockConfirmationPresented: Bool @Binding var isBlockConfirmationPresented: Bool
@ -190,7 +193,8 @@ struct StatusRowActionsView: View {
Menu { Menu {
StatusRowContextMenu(viewModel: viewModel, StatusRowContextMenu(viewModel: viewModel,
showTextForSelection: $showTextForSelection, showTextForSelection: $showTextForSelection,
isBlockConfirmationPresented: $isBlockConfirmationPresented) isBlockConfirmationPresented: $isBlockConfirmationPresented,
isShareAsImageSheetPresented: $isShareAsImageSheetPresented)
.onAppear { .onAppear {
Task { Task {
await viewModel.loadAuthorRelationship() await viewModel.loadAuthorRelationship()
@ -215,7 +219,36 @@ struct StatusRowActionsView: View {
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.sheet(isPresented: $showTextForSelection) { .sheet(isPresented: $showTextForSelection) {
let content = viewModel.status.reblog?.content.asSafeMarkdownAttributedString ?? viewModel.status.content.asSafeMarkdownAttributedString let content = viewModel.status.reblog?.content.asSafeMarkdownAttributedString ?? viewModel.status.content.asSafeMarkdownAttributedString
SelectTextView(content: content) StatusRowSelectableTextView(content: content)
.tint(theme.tintColor)
}
.sheet(isPresented: $isShareAsImageSheetPresented) {
let view =
HStack {
StatusRowView(viewModel: viewModel, context: .timeline)
.padding(8)
}
.environment(\.isInCaptureMode, true)
.environment(RouterPath())
.environment(QuickLook.shared)
.environment(theme)
.environment(client)
.environment(sceneDelegate)
.environment(UserPreferences.shared)
.environment(CurrentAccount.shared)
.environment(CurrentInstance.shared)
.environment(statusDataController)
.preferredColorScheme(theme.selectedScheme == .dark ? .dark : .light)
.foregroundColor(theme.labelColor)
.background(theme.primaryBackgroundColor)
.frame(width: sceneDelegate.windowWidth - 12)
.tint(theme.tintColor)
let renderer = ImageRenderer(content: AnyView(view))
renderer.isOpaque = true
renderer.scale = 3.0
return StatusRowShareAsImageView(viewModel: viewModel,
renderer: renderer)
.tint(theme.tintColor)
} }
} }

View file

@ -9,7 +9,6 @@ import SwiftUI
public struct StatusRowCardView: View { public struct StatusRowCardView: View {
@Environment(\.openURL) private var openURL @Environment(\.openURL) private var openURL
@Environment(\.openWindow) private var openWindow @Environment(\.openWindow) private var openWindow
@Environment(\.isInCaptureMode) private var isInCaptureMode: Bool
@Environment(\.isCompact) private var isCompact: Bool @Environment(\.isCompact) private var isCompact: Bool
@Environment(Theme.self) private var theme @Environment(Theme.self) private var theme
@ -106,7 +105,7 @@ public struct StatusRowCardView: View {
@ViewBuilder @ViewBuilder
private func defaultLinkPreview(_ title: String, _ url: URL) -> some View { private func defaultLinkPreview(_ title: String, _ url: URL) -> some View {
if let imageURL = card.image, !isInCaptureMode { if let imageURL = card.image {
DefaultPreviewImage(url: imageURL, originalWidth: card.width, originalHeight: card.height) DefaultPreviewImage(url: imageURL, originalWidth: card.width, originalHeight: card.height)
} }
@ -134,7 +133,7 @@ public struct StatusRowCardView: View {
private func compactLinkPreview(_ title: String, _ url: URL) -> some View { private func compactLinkPreview(_ title: String, _ url: URL) -> some View {
HStack(alignment: .top) { HStack(alignment: .top) {
if let imageURL = card.image, !isInCaptureMode { if let imageURL = card.image {
LazyResizableImage(url: imageURL) { state, _ in LazyResizableImage(url: imageURL) { state, _ in
if let image = state.image { if let image = state.image {
image image
@ -212,7 +211,7 @@ public struct StatusRowCardView: View {
private func iconLinkPreview(_ title: String, _ url: URL) -> some View { private func iconLinkPreview(_ title: String, _ url: URL) -> some View {
// ..where the image is known to be a square icon // ..where the image is known to be a square icon
HStack { HStack {
if let imageURL = card.image, !isInCaptureMode { if let imageURL = card.image {
LazyResizableImage(url: imageURL) { state, _ in LazyResizableImage(url: imageURL) { state, _ in
if let image = state.image { if let image = state.image {
image image

View file

@ -6,7 +6,6 @@ import SwiftUI
@MainActor @MainActor
struct StatusRowContextMenu: View { struct StatusRowContextMenu: View {
@Environment(\.displayScale) var displayScale
@Environment(\.openWindow) var openWindow @Environment(\.openWindow) var openWindow
@Environment(Client.self) private var client @Environment(Client.self) private var client
@ -21,6 +20,7 @@ struct StatusRowContextMenu: View {
var viewModel: StatusRowViewModel var viewModel: StatusRowViewModel
@Binding var showTextForSelection: Bool @Binding var showTextForSelection: Bool
@Binding var isBlockConfirmationPresented: Bool @Binding var isBlockConfirmationPresented: Bool
@Binding var isShareAsImageSheetPresented: Bool
var boostLabel: some View { var boostLabel: some View {
if viewModel.status.visibility == .priv, viewModel.status.account.id == account.account?.id { if viewModel.status.visibility == .priv, viewModel.status.account.id == account.account?.id {
@ -101,30 +101,7 @@ struct StatusRowContextMenu: View {
} }
Button { Button {
let view = HStack { isShareAsImageSheetPresented = true
StatusRowView(viewModel: viewModel, context: .timeline)
.padding(16)
}
.environment(\.isInCaptureMode, true)
.environment(Theme.shared)
.environment(preferences)
.environment(account)
.environment(currentInstance)
.environment(SceneDelegate())
.environment(quickLook)
.environment(viewModel.client)
.environment(RouterPath())
.preferredColorScheme(Theme.shared.selectedScheme == .dark ? .dark : .light)
.foregroundColor(Theme.shared.labelColor)
.background(Theme.shared.primaryBackgroundColor)
.frame(width: sceneDelegate.windowWidth - 12)
.tint(Theme.shared.tintColor)
let renderer = ImageRenderer(content: view)
renderer.scale = displayScale
renderer.isOpaque = false
if let image = renderer.uiImage {
viewModel.routerPath.presentedSheet = .shareImage(image: image, status: viewModel.status)
}
} label: { } label: {
Label("status.action.share-image", systemImage: "photo") Label("status.action.share-image", systemImage: "photo")
} }
@ -283,60 +260,3 @@ struct StatusRowContextMenu: View {
} }
} }
} }
struct ActivityView: UIViewControllerRepresentable {
let image: Image
func makeUIViewController(context _: UIViewControllerRepresentableContext<ActivityView>) -> UIActivityViewController {
UIActivityViewController(activityItems: [image], applicationActivities: nil)
}
func updateUIViewController(_: UIActivityViewController, context _: UIViewControllerRepresentableContext<ActivityView>) {}
}
struct SelectTextView: View {
@Environment(\.dismiss) private var dismiss
let content: AttributedString
var body: some View {
NavigationStack {
SelectableText(content: content)
.padding()
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
dismiss()
} label: {
Text("action.done").bold()
}
}
}
.background(Theme.shared.primaryBackgroundColor)
.navigationTitle("status.action.select-text")
.navigationBarTitleDisplayMode(.inline)
}
}
}
struct SelectableText: UIViewRepresentable {
let content: AttributedString
func makeUIView(context _: Context) -> UITextView {
let attributedText = NSMutableAttributedString(content)
attributedText.addAttribute(
.font,
value: Font.scaledBodyFont,
range: NSRange(location: 0, length: content.characters.count)
)
let textView = UITextView()
textView.isEditable = false
textView.attributedText = attributedText
textView.textColor = UIColor(Color.label)
textView.backgroundColor = UIColor(Theme.shared.primaryBackgroundColor)
return textView
}
func updateUIView(_: UITextView, context _: Context) {}
func makeCoordinator() {}
}

View file

@ -6,7 +6,6 @@ import SwiftUI
@MainActor @MainActor
struct StatusRowHeaderView: View { struct StatusRowHeaderView: View {
@Environment(\.isInCaptureMode) private var isInCaptureMode: Bool
@Environment(\.isStatusFocused) private var isFocused @Environment(\.isStatusFocused) private var isFocused
@Environment(\.redactionReasons) private var redactionReasons @Environment(\.redactionReasons) private var redactionReasons

View file

@ -0,0 +1,56 @@
import SwiftUI
import DesignSystem
struct StatusRowSelectableTextView: View {
@Environment(\.dismiss) private var dismiss
@Environment(Theme.self) private var theme
let content: AttributedString
var body: some View {
NavigationStack {
SelectableText(content: content)
.padding()
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
dismiss()
} label: {
Text("action.done").bold()
}
}
}
.navigationTitle("status.action.select-text")
.navigationBarTitleDisplayMode(.inline)
}
.presentationBackground(.ultraThinMaterial)
.presentationCornerRadius(16)
}
}
fileprivate struct SelectableText: UIViewRepresentable {
let content: AttributedString
func makeUIView(context _: Context) -> UITextView {
let attributedText = NSMutableAttributedString(content)
attributedText.addAttribute(
.font,
value: Font.scaledBodyFont,
range: NSRange(location: 0, length: content.characters.count)
)
let textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.isEditable = false
textView.isScrollEnabled = true
textView.attributedText = attributedText
textView.textColor = UIColor(Color.label)
textView.select(textView)
textView.selectedRange = .init(location: 0, length: attributedText.string.utf8.count)
textView.backgroundColor = .clear
return textView
}
func updateUIView(_: UITextView, context _: Context) {}
func makeCoordinator() {}
}

View file

@ -0,0 +1,56 @@
import SwiftUI
import Env
import Network
import DesignSystem
struct StatusRowShareAsImageView: View {
@Environment(\.dismiss) private var dismiss
@Environment(Theme.self) private var theme
let viewModel: StatusRowViewModel
@StateObject var renderer: ImageRenderer<AnyView>
var rendererImage: Image {
Image(uiImage: renderer.uiImage ?? UIImage())
}
var body: some View {
NavigationStack {
Form {
Section {
Button {
viewModel.routerPath.presentedSheet = .shareImage(image: renderer.uiImage ?? UIImage(),
status: viewModel.status)
} label: {
Label("status.action.share-image", systemImage: "square.and.arrow.up")
}
}
#if !os(visionOS)
.listRowBackground(theme.primaryBackgroundColor.opacity(0.4))
#endif
Section {
rendererImage
.resizable()
.scaledToFit()
}
.listRowBackground(theme.primaryBackgroundColor)
}
.scrollContentBackground(.hidden)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
dismiss()
} label: {
Text("action.done")
.bold()
}
}
}
.navigationTitle("Share post as image")
.navigationBarTitleDisplayMode(.inline)
}
.presentationBackground(.ultraThinMaterial)
.presentationCornerRadius(16)
}
}