diff --git a/android/app/src/main/res/drawable/widget_frame.xml b/android/app/src/main/res/drawable/widget_frame.xml new file mode 100644 index 00000000..1e0d98a9 --- /dev/null +++ b/android/app/src/main/res/drawable/widget_frame.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/android/app/src/main/res/drawable/widget_surface.xml b/android/app/src/main/res/drawable/widget_surface.xml new file mode 100644 index 00000000..730f8463 --- /dev/null +++ b/android/app/src/main/res/drawable/widget_surface.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/android/app/src/main/res/layout-night/rn_widget.xml b/android/app/src/main/res/layout-night/rn_widget.xml new file mode 100644 index 00000000..0eb7803b --- /dev/null +++ b/android/app/src/main/res/layout-night/rn_widget.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + diff --git a/android/app/src/main/res/layout/rn_widget.xml b/android/app/src/main/res/layout/rn_widget.xml new file mode 100644 index 00000000..2d50ffec --- /dev/null +++ b/android/app/src/main/res/layout/rn_widget.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + diff --git a/android/app/src/main/res/values-night/colors.xml b/android/app/src/main/res/values-night/colors.xml index 8c8b16ea..32088aa9 100644 --- a/android/app/src/main/res/values-night/colors.xml +++ b/android/app/src/main/res/values-night/colors.xml @@ -3,4 +3,6 @@ #09090b #09090b + + #0a0a0a \ No newline at end of file diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml index a8383caf..ee512c47 100644 --- a/android/app/src/main/res/values/colors.xml +++ b/android/app/src/main/res/values/colors.xml @@ -6,4 +6,8 @@ #09090b #ffffff + + #ffffff + + #4d808080 \ No newline at end of file diff --git a/android/app/src/main/res/values/dimens.xml b/android/app/src/main/res/values/dimens.xml new file mode 100644 index 00000000..44ea3ed1 --- /dev/null +++ b/android/app/src/main/res/values/dimens.xml @@ -0,0 +1,4 @@ + + + 22dp + diff --git a/src/widgets/layouts/calendar.tsx b/src/widgets/layouts/calendar.tsx index ced4a0fe..171a4437 100644 --- a/src/widgets/layouts/calendar.tsx +++ b/src/widgets/layouts/calendar.tsx @@ -29,8 +29,6 @@ import type { EventItem, WidgetSnapshot } from '../snapshot'; import { chipBackground, normalizeHex, type WidgetPalette } from '../theme'; import type { Layout } from './types'; -/** Surface's 1dp frame; Android adds a border to the padding, so it eats space. */ -const FRAME = 1; const HOUR = 3600000; const GRID_START_HOUR = 8; const GRID_END_HOUR = 18; @@ -207,7 +205,7 @@ export const AgendaLayout: Layout = ({ s, p, f, now, height }) => { ); } // Header bar plus its 1dp rule. - const room = height - 2 * FRAME - headerHeight - 1; + const room = height - headerHeight - 1; const { sections, hidden } = planAgenda(days, room); const rows: React.JSX.Element[] = []; sections.forEach((day, d) => { @@ -366,12 +364,12 @@ export const WeekLayout: Layout = ({ s, p, f, now, width, height }) => { const events = s.calendar.events; const first = startOfWeek(now, s.weekStart); const days = Array.from({ length: 7 }, (_, i) => addDays(first, i)); - const colWidth = (width - 2 * FRAME - 2 * WEEK_PAD) / 7; + const colWidth = (width - 2 * WEEK_PAD) / 7; const segments = allDaySegments(events, days); const strip = segments.some((seg) => seg.event !== null); const gridHeight = Math.max( 0, - height - 2 * FRAME - WEEK_PAD - WEEK_LABEL_HEIGHT - 2 - WEEK_DAY_SIZE - WEEK_GAP + height - WEEK_PAD - WEEK_LABEL_HEIGHT - 2 - WEEK_DAY_SIZE - WEEK_GAP - (strip ? WEEK_STRIP_HEIGHT + WEEK_GAP : 0) - 1, ); const windowStart = startOfDay(now) + GRID_START_HOUR * HOUR; @@ -566,11 +564,11 @@ export const MonthLayout: Layout = ({ s, p, f, now, width, height }) => { const weeks = monthGrid(s.calendar.events, now, s.weekStart); const next = upcoming(s.calendar.events, now); const headerHeight = height >= 340 ? 44 : 38; - const cellWidth = (width - 2 * FRAME - 2 * MONTH_PAD) / 7; + const cellWidth = (width - 2 * MONTH_PAD) / 7; const chipsHeight = (n: number) => n === 0 ? 0 : MONTH_CHIPS_TOP + n * MONTH_CHIP_HEIGHT + (n - 1) * 4 + MONTH_CHIPS_BOTTOM; const gridRoom = (n: number) => - height - 2 * FRAME - headerHeight - MONTH_LABEL_HEIGHT - MONTH_GRID_TOP - chipsHeight(n); + height - headerHeight - MONTH_LABEL_HEIGHT - MONTH_GRID_TOP - chipsHeight(n); // Two chips when the weeks still get a comfortable row, else one, else none. let chips = Math.min(2, next.length); while (chips > 0 && gridRoom(chips) / weeks.length < MONTH_CELL_MIN) chips -= 1; @@ -700,7 +698,7 @@ function TodayColumn({ const tomorrowList = eventsOnDay(events, addDays(today, 1)); // The "N more this week" line is always reserved; chips share the rest. - const room = height - 2 * FRAME - 2 * TODAY_PAD - TODAY_LINE; + const room = height - 2 * TODAY_PAD - TODAY_LINE; const row = TODAY_CHIP + TODAY_GAP; let withTomorrow = tomorrowList.length > 0; let slots = Math.floor((room - (withTomorrow ? TOMORROW_BLOCK : 0) + TODAY_GAP) / row); @@ -792,7 +790,7 @@ function TodayColumn({ export const TodayEventsLayout: Layout = ({ s, p, f, now, height }) => { // The date block is ~108dp tall at 40sp; a short 4x2 gets a smaller number // and, if still tight, loses the month line. - const room = height - 2 * FRAME - 2 * TODAY_PAD; + const room = height - 2 * TODAY_PAD; const big = room >= 108 ? 40 : 32; const showMonth = room >= 3 * 18 + Math.ceil(big * 1.33); return ( @@ -820,7 +818,7 @@ export const NextEventLayout: Layout = ({ s, p, f, now, height }) => { const when = running ? f.t('widgets.calendar.now', 'Now') : capitalize(f.relative(e.start, now), f.locale); // Status line, gap, one title line and the time range always show; then the // place, the calendar and a second title line, as long as they fit. - const room = height - 2 * FRAME - 28; + const room = height - 28; let used = 16 + 6 + 22 + 18; const place = placeOf(f, e); const showPlace = !!place && used + 16 <= room; @@ -888,7 +886,7 @@ function TwoLineChip({ f, e, now }: { f: Fmt; e: EventItem; now: number }) { } export const DateNextLayout: Layout = ({ s, p, f, now, height }) => { - const room = height - 2 * FRAME - 2 * DATE_PAD - DATE_CIRCLE - 8; + const room = height - 2 * DATE_PAD - DATE_CIRCLE - 8; const count = room >= 2 * DATE_CHIP + 4 ? 2 : 1; const next = s.calendar.supported ? upcoming(s.calendar.events, now).slice(0, count) : []; const chips: React.JSX.Element[] = []; diff --git a/src/widgets/layouts/hubs.tsx b/src/widgets/layouts/hubs.tsx index 71bc13be..955e2638 100644 --- a/src/widgets/layouts/hubs.tsx +++ b/src/widgets/layouts/hubs.tsx @@ -306,8 +306,8 @@ export const MailAndNextLayout: Layout = ({ s, p, f, now, height }) => { height: 'match_parent', padding: pad, backgroundColor: unread > 0 ? p.unreadRow : p.bg, - borderTopLeftRadius: WIDGET_RADIUS - 1, - borderBottomLeftRadius: WIDGET_RADIUS - 1, + borderTopLeftRadius: WIDGET_RADIUS, + borderBottomLeftRadius: WIDGET_RADIUS, }} > diff --git a/src/widgets/primitives.tsx b/src/widgets/primitives.tsx index 0c22f23d..dce01a75 100644 --- a/src/widgets/primitives.tsx +++ b/src/widgets/primitives.tsx @@ -17,9 +17,14 @@ import { chipBackground, normalizeHex, type WidgetPalette } from './theme'; import { filledIcon, icon, type FilledIconName, type IconName } from './icons'; import type { WidgetClick } from './clicks'; -/** Corner radius of the widget surface; launchers clip to roughly this on Android 12+. */ +/** Corner radius of the widget surface; `widget_radius` in android/app/src/main/res/values/dimens.xml. */ export const WIDGET_RADIUS = 22; +/** + * The widget's background. Its edge and the clip to rounded corners come from + * the root layout (android/app/src/main/res/layout/rn_widget.xml), which has the + * launcher's real size; this bitmap is drawn at the reported one and can be cut off. + */ export function Surface({ p, children, @@ -39,8 +44,6 @@ export function Surface({ width: 'match_parent', backgroundColor: p.bg, borderRadius: WIDGET_RADIUS, - borderWidth: 1, - borderColor: p.frame, overflow: 'hidden', ...style, }} diff --git a/src/widgets/theme.ts b/src/widgets/theme.ts index 3eabe612..cbaa1f08 100644 --- a/src/widgets/theme.ts +++ b/src/widgets/theme.ts @@ -10,15 +10,16 @@ export type WidgetScheme = 'light' | 'dark'; export interface WidgetPalette { scheme: WidgetScheme; - /** Widget surface (webmail `--color-background`). */ + /** + * Widget surface (webmail `--color-background`). The edge around it is drawn + * natively, as `widget_frame` in android/app/src/main/res/values/colors.xml. + */ bg: ColorProp; fg: ColorProp; muted: ColorProp; /** Search pill, segmented track, quota ring track (webmail `--color-muted`). */ mutedBg: ColorProp; border: ColorProp; - /** Outer widget edge. */ - frame: ColorProp; primary: ColorProp; primaryFg: ColorProp; accent: ColorProp; @@ -66,7 +67,6 @@ export const LIGHT: WidgetPalette = { muted: '#64748b', mutedBg: '#f1f5f9', border: '#e2e8f0', - frame: '#e2e8f0', primary: '#3b82f6', primaryFg: '#ffffff', accent: '#dbeafe', @@ -108,7 +108,6 @@ export const DARK: WidgetPalette = { muted: '#a3a3a3', mutedBg: '#262626', border: '#2d2d2d', - frame: 'rgba(128, 128, 128, 0.3)', primary: '#fafafa', primaryFg: '#171717', accent: '#1e3a8a',