Skip to content
Merged
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
22 changes: 11 additions & 11 deletions example/screens/testing-grounds/weather/WeatherTestingScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { reloadWidgets, scheduleWidget, updateWidget, VoltraWidgetPreview, Widge

import { Button } from '~/components/Button'
import { Card } from '~/components/Card'
import { WeatherWidget } from '~/widgets/ios/IosWeatherWidget'
import { IosWeatherWidget } from '~/widgets/ios/IosWeatherWidget'
import { SAMPLE_WEATHER_DATA, type WeatherCondition, type WeatherData } from '~/widgets/weather-types'

const WIDGET_FAMILIES: { id: WidgetFamily; title: string; description: string }[] = [
Expand Down Expand Up @@ -74,9 +74,9 @@ export default function WeatherTestingScreen() {
setIsUpdating(true)
try {
await updateWidget('weather', {
systemSmall: <WeatherWidget weather={weatherData} />,
systemMedium: <WeatherWidget weather={weatherData} />,
systemLarge: <WeatherWidget weather={weatherData} />,
systemSmall: <IosWeatherWidget weather={weatherData} />,
systemMedium: <IosWeatherWidget weather={weatherData} />,
systemLarge: <IosWeatherWidget weather={weatherData} />,
})
await reloadWidgets(['weather'])
} catch (error) {
Expand Down Expand Up @@ -107,9 +107,9 @@ export default function WeatherTestingScreen() {
setIsUpdating(true)
try {
await updateWidget('weather', {
systemSmall: <WeatherWidget weather={customWeather} />,
systemMedium: <WeatherWidget weather={customWeather} />,
systemLarge: <WeatherWidget weather={customWeather} />,
systemSmall: <IosWeatherWidget weather={customWeather} />,
systemMedium: <IosWeatherWidget weather={customWeather} />,
systemLarge: <IosWeatherWidget weather={customWeather} />,
})
await reloadWidgets(['weather'])
} catch (error) {
Expand Down Expand Up @@ -237,9 +237,9 @@ export default function WeatherTestingScreen() {

try {
await updateWidget('weather', {
systemSmall: <WeatherWidget weather={weatherData} />,
systemMedium: <WeatherWidget weather={weatherData} />,
systemLarge: <WeatherWidget weather={weatherData} />,
systemSmall: <IosWeatherWidget weather={weatherData} />,
systemMedium: <IosWeatherWidget weather={weatherData} />,
systemLarge: <IosWeatherWidget weather={weatherData} />,
})
// Don't call reloadWidgets here to avoid resetting scheduled timelines
} catch (error) {
Expand Down Expand Up @@ -348,7 +348,7 @@ export default function WeatherTestingScreen() {
</Card.Text>
<View style={styles.previewContainer}>
<VoltraWidgetPreview family={selectedFamily} style={widgetPreviewStyle}>
<WeatherWidget weather={currentWeather} />
<IosWeatherWidget weather={currentWeather} />
</VoltraWidgetPreview>
</View>
</Card>
Expand Down
2 changes: 0 additions & 2 deletions packages/voltra/ios/shared/VoltraNode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,6 @@ struct VoltraElementView: View {
case "Chart":
if #available(iOS 16.0, macOS 13.0, *) {
VoltraChart(element)
} else {
EmptyView()
}

default:
Expand Down
32 changes: 29 additions & 3 deletions packages/voltra/ios/target/VoltraHomeWidget.swift
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,9 @@ public struct VoltraHomeWidgetProvider: TimelineProvider {
public struct VoltraHomeWidgetView: View {
public var entry: VoltraHomeWidgetEntry

@Environment(\.showsWidgetContainerBackground) private var showsWidgetContainerBackground
@Environment(\.widgetRenderingMode) private var widgetRenderingMode

public init(entry: VoltraHomeWidgetEntry) {
self.entry = entry
}
Expand All @@ -285,12 +288,22 @@ public struct VoltraHomeWidgetView: View {
}

public var body: some View {
let mappedRenderingMode = mapWidgetRenderingMode(widgetRenderingMode)

Group {
if let root = entry.rootNode {
// No parsing here - just render the pre-parsed AST
let content = Voltra(root: root, activityId: "widget")
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.widgetURL(resolveDeepLinkURL(entry))
let content = Voltra(
root: root,
activityId: "widget",
widget: VoltraWidgetEnvironment(
isHomeScreenWidget: true,
renderingMode: mappedRenderingMode,
showsContainerBackground: showsWidgetContainerBackground
)
)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.widgetURL(resolveDeepLinkURL(entry))

if showRefreshButton {
content.overlay(alignment: .topTrailing) {
Expand All @@ -306,6 +319,19 @@ public struct VoltraHomeWidgetView: View {
.disableWidgetMarginsIfAvailable()
}

private func mapWidgetRenderingMode(_ mode: WidgetRenderingMode) -> VoltraWidgetRenderingMode {
switch mode {
case .fullColor:
return .fullColor
case .accented:
return .accented
case .vibrant:
return .vibrant
default:
return .unknown
}
}

@ViewBuilder
private var refreshButton: some View {
if #available(iOSApplicationExtension 17.0, *) {
Expand Down
12 changes: 12 additions & 0 deletions packages/voltra/ios/tests/JSGradientParserTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -186,4 +186,16 @@ final class JSColorParserTests: XCTestCase {
XCTAssertNil(JSColorParser.parse("rgb(255,0)"))
XCTAssertNil(JSColorParser.parse("hsl(120, 100, 50%)"))
}

func testReducedPresentationPrimaryColorDetectionTreatsNeutralColorsAsAdaptive() {
XCTAssertTrue(JSColorParser.shouldUsePrimaryColorInReducedPresentation("#F9FAFB"))
XCTAssertTrue(JSColorParser.shouldUsePrimaryColorInReducedPresentation("#6B7280"))
XCTAssertTrue(JSColorParser.shouldUsePrimaryColorInReducedPresentation("white"))
}

func testReducedPresentationPrimaryColorDetectionPreservesSemanticAccents() {
XCTAssertFalse(JSColorParser.shouldUsePrimaryColorInReducedPresentation("#34D399"))
XCTAssertFalse(JSColorParser.shouldUsePrimaryColorInReducedPresentation("#F87171"))
XCTAssertFalse(JSColorParser.shouldUsePrimaryColorInReducedPresentation("green"))
}
}
2 changes: 1 addition & 1 deletion packages/voltra/ios/ui/Layout/FlexContainerHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ struct FlexContainerStyleModifier: ViewModifier {

content
.modifier(LayoutModifier(style: layoutWithoutPadding))
.modifier(DecorationModifier(style: values.decoration))
.modifier(DecorationModifier(style: values.decoration, layout: layout))
.modifier(RenderingModifier(style: values.rendering))
.voltraIfLet(layout.margin) { c, margin in
c.background(.clear).padding(margin)
Expand Down
4 changes: 2 additions & 2 deletions packages/voltra/ios/ui/Style/CompositeStyle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ struct CompositeStyleModifier: ViewModifier {
content
.voltraIfLet(layout.padding) { c, p in c.padding(p) }
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: contentAlignment)
.modifier(DecorationModifier(style: decoration))
.modifier(DecorationModifier(style: decoration, layout: layout))
.modifier(RenderingModifier(style: rendering))
.layoutValue(key: FlexItemLayoutKey.self, value: FlexItemValues(
flexGrow: layout.flexGrow,
Expand Down Expand Up @@ -53,7 +53,7 @@ struct CompositeStyleModifier: ViewModifier {
alignment: alignment
)
}
.modifier(DecorationModifier(style: decoration))
.modifier(DecorationModifier(style: decoration, layout: layout))
.modifier(RenderingModifier(style: rendering))
}
}
Expand Down
40 changes: 38 additions & 2 deletions packages/voltra/ios/ui/Style/DecorationStyle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ struct DecorationStyle {

struct DecorationModifier: ViewModifier {
let style: DecorationStyle
let layout: LayoutStyle?
@Environment(\.voltraEnvironment) private var voltraEnvironment

private func point(from unitPoint: UnitPoint, in size: CGSize) -> CGPoint {
CGPoint(x: unitPoint.x * size.width, y: unitPoint.y * size.height)
Expand Down Expand Up @@ -105,9 +107,43 @@ struct DecorationModifier: ViewModifier {
.allowsHitTesting(false)
}

private var suppressesDecorativeContainerEffects: Bool {
voltraEnvironment.widget?.suppressesDecorativeContainerEffects == true
}

private var isFullBleedBackgroundCandidate: Bool {
guard let layout else { return false }

if let flex = layout.flex, flex > 0 {
return true
}

if layout.flexGrow > 0 {
return true
}

return layout.width == .fill && layout.height == .fill
}

private var resolvedBackgroundColor: BackgroundValue? {
guard suppressesDecorativeContainerEffects, isFullBleedBackgroundCandidate else {
return style.backgroundColor
}

return nil
}

private var resolvedGlassEffect: GlassEffect? {
guard suppressesDecorativeContainerEffects else {
return style.glassEffect
}

return nil
}

func body(content: Content) -> some View {
content
.voltraIfLet(style.backgroundColor) { content, bg in
.voltraIfLet(resolvedBackgroundColor) { content, bg in
switch bg {
case let .color(color):
content.background(color)
Expand Down Expand Up @@ -151,7 +187,7 @@ struct DecorationModifier: ViewModifier {
y: shadow.offset.height
)
}
.voltraIfLet(style.glassEffect) { content, glassEffect in
.voltraIfLet(resolvedGlassEffect) { content, glassEffect in
if #available(iOS 26.0, *) {
switch glassEffect {
case .clear:
Expand Down
Loading
Loading