diff --git a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/theme/generated/MobileDesignTokens.kt b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/theme/generated/MobileDesignTokens.kt index 6f8faac6a2..28936f7881 100644 --- a/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/theme/generated/MobileDesignTokens.kt +++ b/src/apps/mobile/android/app/src/main/kotlin/com/openbitfun/mobile/app/ui/theme/generated/MobileDesignTokens.kt @@ -12,6 +12,7 @@ internal object MobileDesignColors { val StartWindowBackground = Color(0xFFFFFFFF) val Transparent = Color(0x00000000) val PageBg = Color(0xFFFFFFFF) + val WelcomePageBg = Color(0xFFFFFFFF) val PageBgFade = Color(0x00FFFFFF) val PageBgOverlay = Color(0xB8FFFFFF) val Ink = Color(0xFF171717) @@ -75,6 +76,7 @@ internal object MobileDesignColors { val StartWindowBackground = Color(0xFF000000) val Transparent = Color(0x00000000) val PageBg = Color(0xFF151514) + val WelcomePageBg = Color(0xFF252522) val PageBgFade = Color(0x00151514) val PageBgOverlay = Color(0xB8151514) val Ink = Color(0xFFF4F3EF) @@ -127,7 +129,7 @@ internal object MobileDesignColors { val CodeProperty = Color(0xFF9CC8D0) val CodeTargetBg = Color(0xFF5A4E24) val BrandDot = Color(0xFF16B9CE) - val WelcomeDock = Color(0xFF171917) + val WelcomeDock = Color(0xFF000000) val WelcomeButton = Color(0xFFFFFFFF) val WelcomeButtonLabel = Color(0xFF171917) val WelcomeSecondary = Color(0xFFB9BCB9) diff --git a/src/apps/mobile/design-system/preview/generated/mobile-design-data.js b/src/apps/mobile/design-system/preview/generated/mobile-design-data.js index d9080f6bb6..11baa9add1 100644 --- a/src/apps/mobile/design-system/preview/generated/mobile-design-data.js +++ b/src/apps/mobile/design-system/preview/generated/mobile-design-data.js @@ -18,6 +18,10 @@ export const mobileTokens = { "light": "#FFFFFF", "dark": "#151514" }, + "welcome_page_bg": { + "light": "#FFFFFF", + "dark": "#252522" + }, "page_bg_fade": { "light": "#00FFFFFF", "dark": "#00151514" @@ -228,7 +232,7 @@ export const mobileTokens = { }, "welcome_dock": { "light": "#171917", - "dark": "#171917" + "dark": "#000000" }, "welcome_button": { "light": "#FFFFFF", diff --git a/src/apps/mobile/design-system/tokens/mobile-tokens.json b/src/apps/mobile/design-system/tokens/mobile-tokens.json index 5d18b06fc9..d81f3b2d6f 100644 --- a/src/apps/mobile/design-system/tokens/mobile-tokens.json +++ b/src/apps/mobile/design-system/tokens/mobile-tokens.json @@ -17,6 +17,10 @@ "light": "#FFFFFF", "dark": "#151514" }, + "welcome_page_bg": { + "light": "#FFFFFF", + "dark": "#252522" + }, "page_bg_fade": { "light": "#00FFFFFF", "dark": "#00151514" @@ -227,7 +231,7 @@ }, "welcome_dock": { "light": "#171917", - "dark": "#171917" + "dark": "#000000" }, "welcome_button": { "light": "#FFFFFF", diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/generated/MobileDesignTokens.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/generated/MobileDesignTokens.ets index b4508bbff4..8626cd781e 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/generated/MobileDesignTokens.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/generated/MobileDesignTokens.ets @@ -14,6 +14,7 @@ export class MobileDesignColors { static readonly startWindowBackground: MobileColorPair = new MobileColorPair("#FFFFFF", "#000000"); static readonly transparent: MobileColorPair = new MobileColorPair("#00000000", "#00000000"); static readonly pageBg: MobileColorPair = new MobileColorPair("#FFFFFF", "#151514"); + static readonly welcomePageBg: MobileColorPair = new MobileColorPair("#FFFFFF", "#252522"); static readonly pageBgFade: MobileColorPair = new MobileColorPair("#00FFFFFF", "#00151514"); static readonly pageBgOverlay: MobileColorPair = new MobileColorPair("#B8FFFFFF", "#B8151514"); static readonly ink: MobileColorPair = new MobileColorPair("#171717", "#F4F3EF"); @@ -66,7 +67,7 @@ export class MobileDesignColors { static readonly codeProperty: MobileColorPair = new MobileColorPair("#466D78", "#9CC8D0"); static readonly codeTargetBg: MobileColorPair = new MobileColorPair("#FFF1BE", "#5A4E24"); static readonly brandDot: MobileColorPair = new MobileColorPair("#16B9CE", "#16B9CE"); - static readonly welcomeDock: MobileColorPair = new MobileColorPair("#171917", "#171917"); + static readonly welcomeDock: MobileColorPair = new MobileColorPair("#171917", "#000000"); static readonly welcomeButton: MobileColorPair = new MobileColorPair("#FFFFFF", "#FFFFFF"); static readonly welcomeButtonLabel: MobileColorPair = new MobileColorPair("#171917", "#171917"); static readonly welcomeSecondary: MobileColorPair = new MobileColorPair("#B9BCB9", "#B9BCB9"); diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/Theme.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/Theme.ets index 3fb5f1ea60..5b356d8209 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/Theme.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/Theme.ets @@ -1,4 +1,5 @@ export const PAGE_BG: ResourceColor = $r('app.color.page_bg'); +export const WELCOME_PAGE_BG: ResourceColor = $r('app.color.welcome_page_bg'); /** PAGE_BG at zero alpha, per theme — the transparent end of a fade-out. */ export const PAGE_BG_FADE: ResourceColor = $r('app.color.page_bg_fade'); /** diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/WelcomeHome.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/WelcomeHome.ets index 10b2a4982d..7e638a136b 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/WelcomeHome.ets +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/pages/components/WelcomeHome.ets @@ -1,8 +1,9 @@ import { RemoteI18n } from '../../i18n/RemoteI18n'; import { MobileDesignTypography, MobileDesignGeometry as G } from '../../generated/MobileDesignTokens'; import { MotionPreference } from '../../services/MotionPreference'; +import { WindowSystemBarService } from '../../services/WindowSystemBarService'; import { WelcomeBrandFlow } from './WelcomeBrandFlow'; -import { INK, PAGE_BG, MUTED, TRANSPARENT } from './Theme'; +import { INK, PAGE_BG, WELCOME_PAGE_BG, MUTED, TRANSPARENT } from './Theme'; /** A welcome composition with the C staggered-slide phrase timeline. */ @ComponentV2 @@ -13,10 +14,22 @@ export struct WelcomeHome { @Local elapsed: number = 0; @Local pageHeight: number = 700; @Local pageWidth: number = 390; + @Local topInset: number = 0; private timer: number = -1; private started: number = 0; + private immersiveCompact: boolean = false; - aboutToDisappear(): void { this.stop(); } + aboutToDisappear(): void { + this.stop(); + if (this.immersiveCompact) { + const context = this.getUIContext().getHostContext(); + if (context) { + void WindowSystemBarService.setLayoutFullScreen(context, false); + } + this.immersiveCompact = false; + this.topInset = 0; + } + } private stop(): void { if (this.timer >= 0) clearInterval(this.timer); this.timer = -1; } private play(): void { this.stop(); @@ -57,7 +70,6 @@ export struct WelcomeHome { private landscape(): boolean { return this.pageWidth >= 840 && this.pageWidth / Math.max(1, this.pageHeight) >= 1.2; } - build() { Scroll() { Column() { @@ -80,21 +92,52 @@ export struct WelcomeHome { }.width('100%').constraintSize({ minHeight: Math.max(540, this.pageHeight - G.welcomeHeaderHeight) }) .justifyContent(FlexAlign.Center).padding({ bottom: 64 }) } else { - Column() { this.Hero() } - .justifyContent(FlexAlign.Center).width('100%') - .height(Math.max(300, this.pageHeight - G.welcomeHeaderHeight - 228)) - .padding({ bottom: 24 }) - this.Actions() + Column() { + Column() { this.Hero() } + .justifyContent(FlexAlign.Center).width('100%') + .layoutWeight(1) + .constraintSize({ minHeight: 300 }) + .padding({ bottom: 24 }) + this.Actions() + }.width('100%').height(Math.max(0, + this.pageHeight - this.topInset - G.welcomeHeaderHeight)) } }.width('100%') - }.width('100%').height('100%').scrollBar(BarState.Off).backgroundColor(PAGE_BG) + }.width('100%').height('100%').scrollBar(BarState.Off).backgroundColor(WELCOME_PAGE_BG) + .padding({ top: this.topInset }) .onSizeChange((_old: SizeOptions, size: SizeOptions) => { this.pageHeight = Number(size.height); this.pageWidth = Number(size.width); + this.updateWindowLayout(); }) .onVisibleAreaChange([0], (visible: boolean) => { if (visible) this.play(); else this.stop(); }) } + private updateWindowLayout(): void { + const compact = this.pageWidth < 600; + if (compact === this.immersiveCompact) { + return; + } + const context = this.getUIContext().getHostContext(); + if (!context) { + return; + } + this.immersiveCompact = compact; + if (!compact) { + this.topInset = 0; + void WindowSystemBarService.setLayoutFullScreen(context, false); + return; + } + void WindowSystemBarService.setLayoutFullScreen(context, true).then(async (): Promise => { + if (this.immersiveCompact) { + const topInsetPx: number = await WindowSystemBarService.topSystemInsetPx(context); + if (this.immersiveCompact) { + this.topInset = this.getUIContext().px2vp(topInsetPx); + } + } + }); + } + @Builder private Hero() { Column({ space: this.wide() ? 16 : 20 }) { @@ -126,6 +169,7 @@ export struct WelcomeHome { .padding({ left: G.welcomeGutter, right: G.welcomeGutter, top: this.wide() ? 0 : G.welcomeGutter, bottom: this.wide() ? 0 : G.welcomeDockBottom }) .backgroundColor(this.wide() ? TRANSPARENT : $r('app.color.welcome_dock')) + .expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM]) .borderRadius({ topLeft: G.welcomeDockRadius, topRight: G.welcomeDockRadius }) } @Builder diff --git a/src/apps/mobile/harmonyos/entry/src/main/ets/services/WindowSystemBarService.ets b/src/apps/mobile/harmonyos/entry/src/main/ets/services/WindowSystemBarService.ets new file mode 100644 index 0000000000..53dc37dca1 --- /dev/null +++ b/src/apps/mobile/harmonyos/entry/src/main/ets/services/WindowSystemBarService.ets @@ -0,0 +1,23 @@ +import { Context } from '@kit.AbilityKit'; +import { window } from '@kit.ArkUI'; + +/** Platform window adapter for surfaces that need to draw behind system bars. */ +export class WindowSystemBarService { + static async setLayoutFullScreen(context: Context, enabled: boolean): Promise { + try { + const appWindow = await window.getLastWindow(context); + await appWindow.setWindowLayoutFullScreen(enabled); + } catch (_err) { + // Window layout is best effort and must not block page rendering. + } + } + + static async topSystemInsetPx(context: Context): Promise { + try { + const appWindow = await window.getLastWindow(context); + return appWindow.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM).topRect.height; + } catch (_err) { + return 0; + } + } +} diff --git a/src/apps/mobile/harmonyos/entry/src/main/resources/base/element/color.json b/src/apps/mobile/harmonyos/entry/src/main/resources/base/element/color.json index 94428cf3ce..b48b8b2d8c 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/resources/base/element/color.json +++ b/src/apps/mobile/harmonyos/entry/src/main/resources/base/element/color.json @@ -12,6 +12,10 @@ "name": "page_bg", "value": "#FFFFFF" }, + { + "name": "welcome_page_bg", + "value": "#FFFFFF" + }, { "name": "page_bg_fade", "value": "#00FFFFFF" diff --git a/src/apps/mobile/harmonyos/entry/src/main/resources/dark/element/color.json b/src/apps/mobile/harmonyos/entry/src/main/resources/dark/element/color.json index 918fb093dd..44c8bff439 100644 --- a/src/apps/mobile/harmonyos/entry/src/main/resources/dark/element/color.json +++ b/src/apps/mobile/harmonyos/entry/src/main/resources/dark/element/color.json @@ -12,6 +12,10 @@ "name": "page_bg", "value": "#151514" }, + { + "name": "welcome_page_bg", + "value": "#252522" + }, { "name": "page_bg_fade", "value": "#00151514" @@ -222,7 +226,7 @@ }, { "name": "welcome_dock", - "value": "#171917" + "value": "#000000" }, { "name": "welcome_button", diff --git a/src/apps/mobile/ios/OpenBitFun/Features/DesignSystem/GeneratedMobileDesignTokens.swift b/src/apps/mobile/ios/OpenBitFun/Features/DesignSystem/GeneratedMobileDesignTokens.swift index 719dc875da..4c04d1102b 100644 --- a/src/apps/mobile/ios/OpenBitFun/Features/DesignSystem/GeneratedMobileDesignTokens.swift +++ b/src/apps/mobile/ios/OpenBitFun/Features/DesignSystem/GeneratedMobileDesignTokens.swift @@ -25,6 +25,7 @@ enum MobileDesignColors { static let startWindowBackground = dynamic(light: 0xFFFFFFFF, dark: 0xFF000000) static let transparent = dynamic(light: 0x00000000, dark: 0x00000000) static let pageBg = dynamic(light: 0xFFFFFFFF, dark: 0xFF151514) + static let welcomePageBg = dynamic(light: 0xFFFFFFFF, dark: 0xFF252522) static let pageBgFade = dynamic(light: 0x00FFFFFF, dark: 0x00151514) static let pageBgOverlay = dynamic(light: 0xB8FFFFFF, dark: 0xB8151514) static let ink = dynamic(light: 0xFF171717, dark: 0xFFF4F3EF) @@ -77,7 +78,7 @@ enum MobileDesignColors { static let codeProperty = dynamic(light: 0xFF466D78, dark: 0xFF9CC8D0) static let codeTargetBg = dynamic(light: 0xFFFFF1BE, dark: 0xFF5A4E24) static let brandDot = dynamic(light: 0xFF16B9CE, dark: 0xFF16B9CE) - static let welcomeDock = dynamic(light: 0xFF171917, dark: 0xFF171917) + static let welcomeDock = dynamic(light: 0xFF171917, dark: 0xFF000000) static let welcomeButton = dynamic(light: 0xFFFFFFFF, dark: 0xFFFFFFFF) static let welcomeButtonLabel = dynamic(light: 0xFF171917, dark: 0xFF171917) static let welcomeSecondary = dynamic(light: 0xFFB9BCB9, dark: 0xFFB9BCB9)