Skip to content

Add ButtonRepresentable/TextRepresentable protocols for rendering Alert/Confirmation Dialog/Menu content #160

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 4 additions & 11 deletions Sources/SkipUI/SkipUI/Commands/Menu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,23 +126,16 @@ public final class Menu : View {

@Composable static func ComposeDropdownMenuItems(for itemViews: [View], selection: Hashable? = nil, context: ComposeContext, replaceMenu: (Menu?) -> Void) {
for itemView in itemViews {
if var strippedItemView = itemView.strippingModifiers(perform: { $0 }) {
if let shareLink = strippedItemView as? ShareLink {
shareLink.ComposeAction()
strippedItemView = shareLink.content
} else if let link = strippedItemView as? Link {
link.ComposeAction()
strippedItemView = link.content
}
if let button = strippedItemView as? Button {
if let strippedItemView = itemView.strippingModifiers(perform: { $0 }) {
if let buttonRep = strippedItemView as? ButtonRepresentable {
let isSelected: Bool?
if let tagView = itemView as? TagModifierView, tagView.role == ComposeModifierRole.tag {
isSelected = tagView.value == selection
} else {
isSelected = nil
}
ComposeDropdownMenuItem(for: button.label, context: context, isSelected: isSelected) {
button.action()
ComposeDropdownMenuItem(for: buttonRep.makeComposeLabel(), context: context, isSelected: isSelected) {
buttonRep.action()
replaceMenu(nil)
}
} else if let text = strippedItemView as? Text {
Expand Down
26 changes: 17 additions & 9 deletions Sources/SkipUI/SkipUI/Components/Link.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,31 @@ import androidx.compose.runtime.Composable

// Use a class to be able to update our openURL action on compose by reference.
// SKIP @bridge
public final class Link : View {
let content: Button
public final class Link : View, ButtonRepresentable {
var action: () -> Void
let label: ComposeBuilder
let role: ButtonRole? = nil

var openURL = OpenURLAction.default

public init(destination: URL, @ViewBuilder label: () -> any View) {
#if SKIP
content = Button(action: { self.openURL(destination) }, label: label)
self.action = { self.openURL(destination) }
self.label = ComposeBuilder.from(label)
#else
content = Button("", action: {})
self.action = {}
self.label = ComposeBuilder(view: EmptyView())
#endif
}

// SKIP @bridge
public init(destination: URL, bridgedLabel: any View) {
#if SKIP
content = Button(bridgedRole: nil, action: { self.openURL(destination) }, bridgedLabel: bridgedLabel)
self.action = { self.openURL(destination) }
self.label = ComposeBuilder.from { bridgedLabel }
#else
content = Button("", action: {})
self.action = {}
self.label = ComposeBuilder(view: EmptyView())
#endif
}

Expand All @@ -39,12 +46,13 @@ public final class Link : View {

#if SKIP
@Composable override func ComposeContent(context: ComposeContext) {
ComposeAction()
content.Compose(context: context)
let label = makeComposeLabel()
Button(action: action, label: { label }).Compose(context: context)
}

@Composable func ComposeAction() {
@Composable func makeComposeLabel() -> ComposeBuilder {
openURL = EnvironmentValues.shared.openURL
return label
}
#else
public var body: some View {
Expand Down
22 changes: 11 additions & 11 deletions Sources/SkipUI/SkipUI/Components/ShareLink.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,25 @@ import androidx.core.content.ContextCompat.startActivity
#endif

// Use a class to be able to update our openURL action on compose by reference.
public final class ShareLink : View {
public final class ShareLink : View, ButtonRepresentable {
private static let defaultSystemImageName = "square.and.arrow.up"

let text: Text
let subject: Text?
let message: Text?
let content: Button
var action: () -> Void
let label: ComposeBuilder
let role: ButtonRole? = nil

init(text: Text, subject: Text? = nil, message: Text? = nil, @ViewBuilder label: () -> any View) {
self.text = text
self.subject = subject
self.message = message
self.action = { }
#if SKIP
self.content = Button(action: { self.action() }, label: label)
self.label = ComposeBuilder.from(label)
#else
self.content = Button("", action: {})
self.label = ComposeBuilder(view: EmptyView())
#endif
}

Expand All @@ -40,14 +41,12 @@ public final class ShareLink : View {
}

public convenience init(item: URL, subject: Text? = nil, message: Text? = nil) {
self.init(text: Text(item.absoluteString), subject: subject, message: message) {
Image(systemName: Self.defaultSystemImageName)
}
self.init(item: item.absoluteString, subject: subject, message: message)
}

public convenience init(item: String, subject: Text? = nil, message: Text? = nil) {
self.init(text: Text(item), subject: subject, message: message) {
Image(systemName: Self.defaultSystemImageName)
Label("Share...", systemImage: Self.defaultSystemImageName)
}
}

Expand Down Expand Up @@ -89,11 +88,11 @@ public final class ShareLink : View {

#if SKIP
@Composable override func ComposeContent(context: ComposeContext) {
ComposeAction()
content.Compose(context: context)
let label = makeComposeLabel()
Button(action: action, label: { label }).Compose(context: context)
}

@Composable func ComposeAction() {
@Composable func makeComposeLabel() -> ComposeBuilder {
let localContext = LocalContext.current

let intent = Intent().apply {
Expand All @@ -109,6 +108,7 @@ public final class ShareLink : View {
let shareIntent = Intent.createChooser(intent, nil)
localContext.startActivity(shareIntent)
}
return label
}
#else
public var body: some View {
Expand Down
6 changes: 6 additions & 0 deletions Sources/SkipUI/SkipUI/Compose/ComposeBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ public struct ComposeBuilder: View {
content(viewCollectingContext)
return views
}

@Composable func textRepresentation(context: ComposeContext) -> Text? {
return collectViews(context: context).compactMap {
$0.strippingModifiers { $0 as? TextRepresentable }
}.first?.textRepresentation(context: context)
}
#else
public var body: some View {
stubView()
Expand Down
15 changes: 12 additions & 3 deletions Sources/SkipUI/SkipUI/Containers/Navigation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ public struct NavigationStack : View {
}
return
}

let toolbarItems = ToolbarItems(content: toolbarContent.value.reduced.content ?? [])
let topLeadingItems = toolbarItems.filterTopBarLeading()
let topTrailingItems = toolbarItems.filterTopBarTrailing()
Expand Down Expand Up @@ -1139,10 +1139,13 @@ struct NavigationTitlePreferenceKey: PreferenceKey {
#endif

// SKIP @bridge
public struct NavigationLink : View, ListItemAdapting {
public struct NavigationLink : View, ListItemAdapting, ButtonRepresentable {
let value: Any?
let destination: ComposeBuilder?

var action: () -> Void = {}
let label: ComposeBuilder
let role: ButtonRole? = nil

private static let minimumNavigationInterval = 0.35
private static var lastNavigationTime = 0.0
Expand Down Expand Up @@ -1187,6 +1190,12 @@ public struct NavigationLink : View, ListItemAdapting {
Button.ComposeButton(label: label, context: context, isEnabled: isNavigationEnabled(), action: navigationAction())
}

@Composable func makeComposeLabel() -> ComposeBuilder {
let navigator = LocalNavigator.current
action = navigationAction()
return label
}

@Composable func shouldComposeListItem() -> Bool {
let buttonStyle = EnvironmentValues.shared._buttonStyle
return buttonStyle == nil || buttonStyle == .automatic || buttonStyle == .plain
Expand All @@ -1213,7 +1222,7 @@ public struct NavigationLink : View, ListItemAdapting {
return (value != nil || destination != nil) && EnvironmentValues.shared.isEnabled
}

@Composable internal func navigationAction() -> () -> Void {
@Composable func navigationAction() -> () -> Void {
let navigator = LocalNavigator.current
return {
// Hack to prevent multiple quick taps from pushing duplicate entries
Expand Down
21 changes: 19 additions & 2 deletions Sources/SkipUI/SkipUI/Controls/Button.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,18 @@ import struct CoreGraphics.CGFloat
import struct CoreGraphics.CGRect
#endif

protocol ButtonRepresentable {
var action: () -> Void { get set }
var label: ComposeBuilder { get }
var role: ButtonRole? { get }
#if SKIP
@Composable func makeComposeLabel() -> ComposeBuilder
#endif
}

// SKIP @bridge
public struct Button : View, ListItemAdapting {
let action: () -> Void
public struct Button : View, ListItemAdapting, ButtonRepresentable, TextRepresentable {
var action: () -> Void
let label: ComposeBuilder
let role: ButtonRole?

Expand Down Expand Up @@ -77,6 +86,14 @@ public struct Button : View, ListItemAdapting {
Self.ComposeButton(label: label, context: context, role: role, action: action)
}

@Composable func textRepresentation(context: ComposeContext) -> Text? {
return label.textRepresentation(context: context)
}

@Composable func makeComposeLabel() -> ComposeBuilder {
return label
}

@Composable func shouldComposeListItem() -> Bool {
let buttonStyle = EnvironmentValues.shared._buttonStyle
return buttonStyle == nil || buttonStyle == .automatic || buttonStyle == .plain
Expand Down
Loading