mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-11-22 08:20:59 +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
|
||||
|
||||
@State private var showSelectableText: Bool = false
|
||||
@State private var isShareAsImageSheetPresented: Bool = false
|
||||
@State private var isBlockConfirmationPresented = false
|
||||
|
||||
public enum Context { case timeline, detail }
|
||||
|
@ -33,7 +34,8 @@ public struct StatusRowView: View {
|
|||
var contextMenu: some View {
|
||||
StatusRowContextMenu(viewModel: viewModel,
|
||||
showTextForSelection: $showSelectableText,
|
||||
isBlockConfirmationPresented: $isBlockConfirmationPresented)
|
||||
isBlockConfirmationPresented: $isBlockConfirmationPresented,
|
||||
isShareAsImageSheetPresented: $isShareAsImageSheetPresented)
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
|
@ -213,7 +215,7 @@ public struct StatusRowView: View {
|
|||
}
|
||||
.sheet(isPresented: $showSelectableText) {
|
||||
let content = viewModel.status.reblog?.content.asSafeMarkdownAttributedString ?? viewModel.status.content.asSafeMarkdownAttributedString
|
||||
SelectTextView(content: content)
|
||||
StatusRowSelectableTextView(content: content)
|
||||
}
|
||||
.environment(
|
||||
StatusDataControllerProvider.shared.dataController(for: viewModel.finalStatus,
|
||||
|
|
|
@ -10,12 +10,15 @@ struct StatusRowActionsView: View {
|
|||
@Environment(CurrentAccount.self) private var currentAccount
|
||||
@Environment(StatusDataController.self) private var statusDataController
|
||||
@Environment(UserPreferences.self) private var userPreferences
|
||||
@Environment(Client.self) private var client
|
||||
@Environment(SceneDelegate.self) private var sceneDelegate
|
||||
|
||||
@Environment(\.openWindow) private var openWindow
|
||||
@Environment(\.isStatusFocused) private var isFocused
|
||||
@Environment(\.horizontalSizeClass) var horizontalSizeClass
|
||||
|
||||
@State private var showTextForSelection: Bool = false
|
||||
@State private var isShareAsImageSheetPresented: Bool = false
|
||||
|
||||
@Binding var isBlockConfirmationPresented: Bool
|
||||
|
||||
|
@ -190,7 +193,8 @@ struct StatusRowActionsView: View {
|
|||
Menu {
|
||||
StatusRowContextMenu(viewModel: viewModel,
|
||||
showTextForSelection: $showTextForSelection,
|
||||
isBlockConfirmationPresented: $isBlockConfirmationPresented)
|
||||
isBlockConfirmationPresented: $isBlockConfirmationPresented,
|
||||
isShareAsImageSheetPresented: $isShareAsImageSheetPresented)
|
||||
.onAppear {
|
||||
Task {
|
||||
await viewModel.loadAuthorRelationship()
|
||||
|
@ -215,7 +219,36 @@ struct StatusRowActionsView: View {
|
|||
.fixedSize(horizontal: false, vertical: true)
|
||||
.sheet(isPresented: $showTextForSelection) {
|
||||
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 {
|
||||
@Environment(\.openURL) private var openURL
|
||||
@Environment(\.openWindow) private var openWindow
|
||||
@Environment(\.isInCaptureMode) private var isInCaptureMode: Bool
|
||||
@Environment(\.isCompact) private var isCompact: Bool
|
||||
|
||||
@Environment(Theme.self) private var theme
|
||||
|
@ -106,7 +105,7 @@ public struct StatusRowCardView: View {
|
|||
|
||||
@ViewBuilder
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -134,7 +133,7 @@ public struct StatusRowCardView: View {
|
|||
|
||||
private func compactLinkPreview(_ title: String, _ url: URL) -> some View {
|
||||
HStack(alignment: .top) {
|
||||
if let imageURL = card.image, !isInCaptureMode {
|
||||
if let imageURL = card.image {
|
||||
LazyResizableImage(url: imageURL) { state, _ in
|
||||
if let image = state.image {
|
||||
image
|
||||
|
@ -212,7 +211,7 @@ public struct StatusRowCardView: View {
|
|||
private func iconLinkPreview(_ title: String, _ url: URL) -> some View {
|
||||
// ..where the image is known to be a square icon
|
||||
HStack {
|
||||
if let imageURL = card.image, !isInCaptureMode {
|
||||
if let imageURL = card.image {
|
||||
LazyResizableImage(url: imageURL) { state, _ in
|
||||
if let image = state.image {
|
||||
image
|
||||
|
|
|
@ -6,7 +6,6 @@ import SwiftUI
|
|||
|
||||
@MainActor
|
||||
struct StatusRowContextMenu: View {
|
||||
@Environment(\.displayScale) var displayScale
|
||||
@Environment(\.openWindow) var openWindow
|
||||
|
||||
@Environment(Client.self) private var client
|
||||
|
@ -21,6 +20,7 @@ struct StatusRowContextMenu: View {
|
|||
var viewModel: StatusRowViewModel
|
||||
@Binding var showTextForSelection: Bool
|
||||
@Binding var isBlockConfirmationPresented: Bool
|
||||
@Binding var isShareAsImageSheetPresented: Bool
|
||||
|
||||
var boostLabel: some View {
|
||||
if viewModel.status.visibility == .priv, viewModel.status.account.id == account.account?.id {
|
||||
|
@ -101,30 +101,7 @@ struct StatusRowContextMenu: View {
|
|||
}
|
||||
|
||||
Button {
|
||||
let view = HStack {
|
||||
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)
|
||||
}
|
||||
isShareAsImageSheetPresented = true
|
||||
} label: {
|
||||
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
|
||||
struct StatusRowHeaderView: View {
|
||||
@Environment(\.isInCaptureMode) private var isInCaptureMode: Bool
|
||||
@Environment(\.isStatusFocused) private var isFocused
|
||||
@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