Skip to content

Commit 27b3856

Browse files
committed
Various fixes to share as image + copy text
1 parent 93beb2f commit 27b3856

7 files changed

Lines changed: 156 additions & 91 deletions

File tree

Packages/StatusKit/Sources/StatusKit/Row/StatusRowView.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public struct StatusRowView: View {
2323
@Environment(Client.self) private var client
2424

2525
@State private var showSelectableText: Bool = false
26+
@State private var isShareAsImageSheetPresented: Bool = false
2627
@State private var isBlockConfirmationPresented = false
2728

2829
public enum Context { case timeline, detail }
@@ -33,7 +34,8 @@ public struct StatusRowView: View {
3334
var contextMenu: some View {
3435
StatusRowContextMenu(viewModel: viewModel,
3536
showTextForSelection: $showSelectableText,
36-
isBlockConfirmationPresented: $isBlockConfirmationPresented)
37+
isBlockConfirmationPresented: $isBlockConfirmationPresented,
38+
isShareAsImageSheetPresented: $isShareAsImageSheetPresented)
3739
}
3840

3941
public var body: some View {
@@ -213,7 +215,7 @@ public struct StatusRowView: View {
213215
}
214216
.sheet(isPresented: $showSelectableText) {
215217
let content = viewModel.status.reblog?.content.asSafeMarkdownAttributedString ?? viewModel.status.content.asSafeMarkdownAttributedString
216-
SelectTextView(content: content)
218+
StatusRowSelectableTextView(content: content)
217219
}
218220
.environment(
219221
StatusDataControllerProvider.shared.dataController(for: viewModel.finalStatus,

Packages/StatusKit/Sources/StatusKit/Row/Subviews/StatusRowActionsView.swift

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,15 @@ struct StatusRowActionsView: View {
1010
@Environment(CurrentAccount.self) private var currentAccount
1111
@Environment(StatusDataController.self) private var statusDataController
1212
@Environment(UserPreferences.self) private var userPreferences
13+
@Environment(Client.self) private var client
14+
@Environment(SceneDelegate.self) private var sceneDelegate
1315

1416
@Environment(\.openWindow) private var openWindow
1517
@Environment(\.isStatusFocused) private var isFocused
1618
@Environment(\.horizontalSizeClass) var horizontalSizeClass
1719

1820
@State private var showTextForSelection: Bool = false
21+
@State private var isShareAsImageSheetPresented: Bool = false
1922

2023
@Binding var isBlockConfirmationPresented: Bool
2124

@@ -190,7 +193,8 @@ struct StatusRowActionsView: View {
190193
Menu {
191194
StatusRowContextMenu(viewModel: viewModel,
192195
showTextForSelection: $showTextForSelection,
193-
isBlockConfirmationPresented: $isBlockConfirmationPresented)
196+
isBlockConfirmationPresented: $isBlockConfirmationPresented,
197+
isShareAsImageSheetPresented: $isShareAsImageSheetPresented)
194198
.onAppear {
195199
Task {
196200
await viewModel.loadAuthorRelationship()
@@ -215,7 +219,36 @@ struct StatusRowActionsView: View {
215219
.fixedSize(horizontal: false, vertical: true)
216220
.sheet(isPresented: $showTextForSelection) {
217221
let content = viewModel.status.reblog?.content.asSafeMarkdownAttributedString ?? viewModel.status.content.asSafeMarkdownAttributedString
218-
SelectTextView(content: content)
222+
StatusRowSelectableTextView(content: content)
223+
.tint(theme.tintColor)
224+
}
225+
.sheet(isPresented: $isShareAsImageSheetPresented) {
226+
let view =
227+
HStack {
228+
StatusRowView(viewModel: viewModel, context: .timeline)
229+
.padding(8)
230+
}
231+
.environment(\.isInCaptureMode, true)
232+
.environment(RouterPath())
233+
.environment(QuickLook.shared)
234+
.environment(theme)
235+
.environment(client)
236+
.environment(sceneDelegate)
237+
.environment(UserPreferences.shared)
238+
.environment(CurrentAccount.shared)
239+
.environment(CurrentInstance.shared)
240+
.environment(statusDataController)
241+
.preferredColorScheme(theme.selectedScheme == .dark ? .dark : .light)
242+
.foregroundColor(theme.labelColor)
243+
.background(theme.primaryBackgroundColor)
244+
.frame(width: sceneDelegate.windowWidth - 12)
245+
.tint(theme.tintColor)
246+
let renderer = ImageRenderer(content: AnyView(view))
247+
renderer.isOpaque = true
248+
renderer.scale = 3.0
249+
return StatusRowShareAsImageView(viewModel: viewModel,
250+
renderer: renderer)
251+
.tint(theme.tintColor)
219252
}
220253
}
221254

Packages/StatusKit/Sources/StatusKit/Row/Subviews/StatusRowCardView.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import SwiftUI
99
public struct StatusRowCardView: View {
1010
@Environment(\.openURL) private var openURL
1111
@Environment(\.openWindow) private var openWindow
12-
@Environment(\.isInCaptureMode) private var isInCaptureMode: Bool
1312
@Environment(\.isCompact) private var isCompact: Bool
1413

1514
@Environment(Theme.self) private var theme
@@ -106,7 +105,7 @@ public struct StatusRowCardView: View {
106105

107106
@ViewBuilder
108107
private func defaultLinkPreview(_ title: String, _ url: URL) -> some View {
109-
if let imageURL = card.image, !isInCaptureMode {
108+
if let imageURL = card.image {
110109
DefaultPreviewImage(url: imageURL, originalWidth: card.width, originalHeight: card.height)
111110
}
112111

@@ -134,7 +133,7 @@ public struct StatusRowCardView: View {
134133

135134
private func compactLinkPreview(_ title: String, _ url: URL) -> some View {
136135
HStack(alignment: .top) {
137-
if let imageURL = card.image, !isInCaptureMode {
136+
if let imageURL = card.image {
138137
LazyResizableImage(url: imageURL) { state, _ in
139138
if let image = state.image {
140139
image
@@ -212,7 +211,7 @@ public struct StatusRowCardView: View {
212211
private func iconLinkPreview(_ title: String, _ url: URL) -> some View {
213212
// ..where the image is known to be a square icon
214213
HStack {
215-
if let imageURL = card.image, !isInCaptureMode {
214+
if let imageURL = card.image {
216215
LazyResizableImage(url: imageURL) { state, _ in
217216
if let image = state.image {
218217
image

Packages/StatusKit/Sources/StatusKit/Row/Subviews/StatusRowContextMenu.swift

Lines changed: 2 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import SwiftUI
66

77
@MainActor
88
struct StatusRowContextMenu: View {
9-
@Environment(\.displayScale) var displayScale
109
@Environment(\.openWindow) var openWindow
1110

1211
@Environment(Client.self) private var client
@@ -21,6 +20,7 @@ struct StatusRowContextMenu: View {
2120
var viewModel: StatusRowViewModel
2221
@Binding var showTextForSelection: Bool
2322
@Binding var isBlockConfirmationPresented: Bool
23+
@Binding var isShareAsImageSheetPresented: Bool
2424

2525
var boostLabel: some View {
2626
if viewModel.status.visibility == .priv, viewModel.status.account.id == account.account?.id {
@@ -101,30 +101,7 @@ struct StatusRowContextMenu: View {
101101
}
102102

103103
Button {
104-
let view = HStack {
105-
StatusRowView(viewModel: viewModel, context: .timeline)
106-
.padding(16)
107-
}
108-
.environment(\.isInCaptureMode, true)
109-
.environment(Theme.shared)
110-
.environment(preferences)
111-
.environment(account)
112-
.environment(currentInstance)
113-
.environment(SceneDelegate())
114-
.environment(quickLook)
115-
.environment(viewModel.client)
116-
.environment(RouterPath())
117-
.preferredColorScheme(Theme.shared.selectedScheme == .dark ? .dark : .light)
118-
.foregroundColor(Theme.shared.labelColor)
119-
.background(Theme.shared.primaryBackgroundColor)
120-
.frame(width: sceneDelegate.windowWidth - 12)
121-
.tint(Theme.shared.tintColor)
122-
let renderer = ImageRenderer(content: view)
123-
renderer.scale = displayScale
124-
renderer.isOpaque = false
125-
if let image = renderer.uiImage {
126-
viewModel.routerPath.presentedSheet = .shareImage(image: image, status: viewModel.status)
127-
}
104+
isShareAsImageSheetPresented = true
128105
} label: {
129106
Label("status.action.share-image", systemImage: "photo")
130107
}
@@ -283,60 +260,3 @@ struct StatusRowContextMenu: View {
283260
}
284261
}
285262
}
286-
287-
struct ActivityView: UIViewControllerRepresentable {
288-
let image: Image
289-
290-
func makeUIViewController(context _: UIViewControllerRepresentableContext<ActivityView>) -> UIActivityViewController {
291-
UIActivityViewController(activityItems: [image], applicationActivities: nil)
292-
}
293-
294-
func updateUIViewController(_: UIActivityViewController, context _: UIViewControllerRepresentableContext<ActivityView>) {}
295-
}
296-
297-
struct SelectTextView: View {
298-
@Environment(\.dismiss) private var dismiss
299-
let content: AttributedString
300-
301-
var body: some View {
302-
NavigationStack {
303-
SelectableText(content: content)
304-
.padding()
305-
.toolbar {
306-
ToolbarItem(placement: .navigationBarTrailing) {
307-
Button {
308-
dismiss()
309-
} label: {
310-
Text("action.done").bold()
311-
}
312-
}
313-
}
314-
.background(Theme.shared.primaryBackgroundColor)
315-
.navigationTitle("status.action.select-text")
316-
.navigationBarTitleDisplayMode(.inline)
317-
}
318-
}
319-
}
320-
321-
struct SelectableText: UIViewRepresentable {
322-
let content: AttributedString
323-
324-
func makeUIView(context _: Context) -> UITextView {
325-
let attributedText = NSMutableAttributedString(content)
326-
attributedText.addAttribute(
327-
.font,
328-
value: Font.scaledBodyFont,
329-
range: NSRange(location: 0, length: content.characters.count)
330-
)
331-
332-
let textView = UITextView()
333-
textView.isEditable = false
334-
textView.attributedText = attributedText
335-
textView.textColor = UIColor(Color.label)
336-
textView.backgroundColor = UIColor(Theme.shared.primaryBackgroundColor)
337-
return textView
338-
}
339-
340-
func updateUIView(_: UITextView, context _: Context) {}
341-
func makeCoordinator() {}
342-
}

Packages/StatusKit/Sources/StatusKit/Row/Subviews/StatusRowHeaderView.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import SwiftUI
66

77
@MainActor
88
struct StatusRowHeaderView: View {
9-
@Environment(\.isInCaptureMode) private var isInCaptureMode: Bool
109
@Environment(\.isStatusFocused) private var isFocused
1110
@Environment(\.redactionReasons) private var redactionReasons
1211

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import SwiftUI
2+
import DesignSystem
3+
4+
struct StatusRowSelectableTextView: View {
5+
@Environment(\.dismiss) private var dismiss
6+
@Environment(Theme.self) private var theme
7+
8+
let content: AttributedString
9+
10+
var body: some View {
11+
NavigationStack {
12+
SelectableText(content: content)
13+
.padding()
14+
.toolbar {
15+
ToolbarItem(placement: .navigationBarTrailing) {
16+
Button {
17+
dismiss()
18+
} label: {
19+
Text("action.done").bold()
20+
}
21+
}
22+
}
23+
.navigationTitle("status.action.select-text")
24+
.navigationBarTitleDisplayMode(.inline)
25+
}
26+
.presentationBackground(.ultraThinMaterial)
27+
.presentationCornerRadius(16)
28+
}
29+
}
30+
31+
fileprivate struct SelectableText: UIViewRepresentable {
32+
let content: AttributedString
33+
34+
func makeUIView(context _: Context) -> UITextView {
35+
let attributedText = NSMutableAttributedString(content)
36+
attributedText.addAttribute(
37+
.font,
38+
value: Font.scaledBodyFont,
39+
range: NSRange(location: 0, length: content.characters.count)
40+
)
41+
42+
let textView = UITextView()
43+
textView.translatesAutoresizingMaskIntoConstraints = false
44+
textView.isEditable = false
45+
textView.isScrollEnabled = true
46+
textView.attributedText = attributedText
47+
textView.textColor = UIColor(Color.label)
48+
textView.select(textView)
49+
textView.selectedRange = .init(location: 0, length: attributedText.string.utf8.count)
50+
textView.backgroundColor = .clear
51+
return textView
52+
}
53+
54+
func updateUIView(_: UITextView, context _: Context) {}
55+
func makeCoordinator() {}
56+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import SwiftUI
2+
import Env
3+
import Network
4+
import DesignSystem
5+
6+
struct StatusRowShareAsImageView: View {
7+
@Environment(\.dismiss) private var dismiss
8+
@Environment(Theme.self) private var theme
9+
10+
let viewModel: StatusRowViewModel
11+
@StateObject var renderer: ImageRenderer<AnyView>
12+
13+
var rendererImage: Image {
14+
Image(uiImage: renderer.uiImage ?? UIImage())
15+
}
16+
17+
var body: some View {
18+
NavigationStack {
19+
Form {
20+
Section {
21+
Button {
22+
viewModel.routerPath.presentedSheet = .shareImage(image: renderer.uiImage ?? UIImage(),
23+
status: viewModel.status)
24+
} label: {
25+
Label("status.action.share-image", systemImage: "square.and.arrow.up")
26+
}
27+
}
28+
#if !os(visionOS)
29+
.listRowBackground(theme.primaryBackgroundColor.opacity(0.4))
30+
#endif
31+
32+
Section {
33+
rendererImage
34+
.resizable()
35+
.scaledToFit()
36+
}
37+
.listRowBackground(theme.primaryBackgroundColor)
38+
}
39+
.scrollContentBackground(.hidden)
40+
.toolbar {
41+
ToolbarItem(placement: .navigationBarTrailing) {
42+
Button {
43+
dismiss()
44+
} label: {
45+
Text("action.done")
46+
.bold()
47+
}
48+
}
49+
}
50+
.navigationTitle("Share post as image")
51+
.navigationBarTitleDisplayMode(.inline)
52+
}
53+
.presentationBackground(.ultraThinMaterial)
54+
.presentationCornerRadius(16)
55+
}
56+
}

0 commit comments

Comments
 (0)