mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-11-25 09:41:02 +00:00
Various fixes to share as image + copy text
This commit is contained in:
parent
93beb2fbd9
commit
27b3856e05
7 changed files with 156 additions and 91 deletions
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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() {}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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() {}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue