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
3 changes: 2 additions & 1 deletion apps/toolbox/src/main-page.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" class="page">
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" class="page" statusBarStyle="dark">
<Page.actionBar>
<ActionBar title="Dev Toolbox" icon="" class="action-bar" iosLargeTitle="true" iosShadow="false">
</ActionBar>
Expand All @@ -20,6 +20,7 @@
<Button text="list-page" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo" />
<Button text="root-layout" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo" />
<Button text="scroll-view" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo" />
<Button text="status-bar" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo" />
<Button text="switch" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo" />
<Button text="touch-animations" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo" />
<Button text="transitions" tap="{{ viewDemo }}" class="btn btn-primary btn-view-demo" />
Expand Down
22 changes: 22 additions & 0 deletions apps/toolbox/src/pages/status-bar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Page, Observable, EventData, Dialogs, ShowModalOptions } from '@nativescript/core';

let page: Page;

export function navigatingTo(args: EventData) {
page = <Page>args.object;
page.bindingContext = new StatusBarModel();
}

export class StatusBarModel extends Observable {
onOpenModal() {
page.showModal('pages/status-bar/status-bar-modal', {
fullscreen: true,
ios: {
statusBarStyle: 'dark',
},
closeCallback(args) {
// console.log('close modal callback', args);
},
} as ShowModalOptions);
}
}
11 changes: 11 additions & 0 deletions apps/toolbox/src/pages/status-bar.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" class="page" backgroundColor="black" statusBarStyle="light">
<!-- Note we set statusBarStyle="light" to explicitly contrast it against our black background -->
<Page.actionBar>
<ActionBar title="Status Bar" color="white">
</ActionBar>
</Page.actionBar>
<GridLayout rows="*,auto,auto,*">
<Label row="1" text="Note status bar color, then Open Modal" fontSize="12" class="text-center c-white" />
<Button row="2" text="Open Modal" tap="{{ onOpenModal }}" class="btn btn-primary btn-view-demo" marginTop="20" />
</GridLayout>
</Page>
22 changes: 22 additions & 0 deletions apps/toolbox/src/pages/status-bar/status-bar-modal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Observable, ShownModallyData, LoadEventData, Page, ShowModalOptions } from '@nativescript/core';

let page: Page;
let closeCallback: Function;
export function onShownModally(args: ShownModallyData) {
closeCallback = args.closeCallback;

if (args.context) {
args.context.shownModally = true;
}
}

export function onLoaded(args: LoadEventData) {
page = args.object as Page;
page.bindingContext = new StatusBarModalPage();
}

export class StatusBarModalPage extends Observable {
close() {
closeCallback();
}
}
9 changes: 9 additions & 0 deletions apps/toolbox/src/pages/status-bar/status-bar-modal.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Page xmlns="http://schemas.nativescript.org/tns.xsd" loaded="onLoaded" shownModally="onShownModally" statusBarStyle="dark">

<GridLayout rows="auto,auto,auto,*" verticalAlignment="top" padding="20">
<Button text="Close" tap="{{close}}" horizontalAlignment="right" marginRight="20" />

<Label row="1" text="Status bar color changed to 'dark'." verticalAlignment="top" marginTop="20" class="text-center" fontSize="12" color="black" />
<Label row="2" text="Close and watch it change back." verticalAlignment="top" marginTop="20" class="text-center" fontSize="12" color="black" marginTop="10" />
</GridLayout>
</Page>
2 changes: 1 addition & 1 deletion packages/core/references.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
/// <reference path="../types-ios/src/lib/ios/objc-x86_64/objc!Darwin.d.ts" />
/// <reference path="../types-ios/src/lib/ios/objc-x86_64/objc!DarwinFoundation.d.ts" />
/// <reference path="../types-ios/src/lib/ios/objc-x86_64/objc!Symbols.d.ts" />
/// <reference path="../types-android/src/lib/android-29.d.ts" />
/// <reference path="../types-android/src/lib/android-30.d.ts" />
/// <reference path="./platforms/ios/typings/objc!MaterialComponents.d.ts" />
/// <reference path="./platforms/ios/typings/objc!NativeScriptUtils.d.ts" />
/// <reference path="./global-types.d.ts" />
4 changes: 4 additions & 0 deletions packages/core/ui/core/view-base/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ export interface ShowModalOptions {
* height of the popup dialog
*/
height?: number;
/**
* The preferred status bar style for the modal view
*/
statusBarStyle?: 'light' | 'dark';
};
android?: {
/**
Expand Down
89 changes: 88 additions & 1 deletion packages/core/ui/core/view/index.android.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Point, Position } from './view-interfaces';
import type { GestureTypes, GestureEventData } from '../../gestures';
import { getNativeScriptGlobals } from '../../../globals/global-utils';
import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUserInteractionEnabledProperty, testIDProperty, AndroidHelper } from './view-common';
import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUserInteractionEnabledProperty, testIDProperty, AndroidHelper, statusBarStyleProperty } from './view-common';
import { paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty } from '../../styling/style-properties';
import { Length } from '../../styling/length-shared';
import { layout } from '../../../utils';
Expand Down Expand Up @@ -52,6 +52,10 @@ const GRAVITY_FILL_HORIZONTAL = 7; // android.view.Gravity.FILL_HORIZONTAL
const GRAVITY_CENTER_VERTICAL = 16; // android.view.Gravity.CENTER_VERTICAL
const GRAVITY_FILL_VERTICAL = 112; // android.view.Gravity.FILL_VERTICAL

const SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = 0x00002000;
const STATUS_BAR_LIGHT_BCKG = -657931;
const STATUS_BAR_DARK_BCKG = 1711276032;

const modalMap = new Map<number, DialogOptions>();

let TouchListener: TouchListener;
Expand Down Expand Up @@ -920,6 +924,89 @@ export class View extends ViewCommon {
}
}

[statusBarStyleProperty.getDefault]() {
return this.style.statusBarStyle;
}

[statusBarStyleProperty.setNative](value: 'dark' | 'light' | { color: number; systemUiVisibility: number }) {
this.updateStatusBarStyle(value);
}

updateStatusBarStyle(value: 'dark' | 'light' | { color: number; systemUiVisibility: number }) {
if (SDK_VERSION < 21) return; // nothing we can do

const window = this.getClosestWindow();
// Ensure the window draws system bar backgrounds (required to color status bar)
window.clearFlags(android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.addFlags(android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);

const decorView = window.getDecorView();

// API 30+ path (preferred)
const controller = window.getInsetsController?.();
if (controller && SDK_VERSION >= 30) {
const APPEARANCE_LIGHT_STATUS_BARS = android.view.WindowInsetsController?.APPEARANCE_LIGHT_STATUS_BARS;

if (typeof value === 'string') {
this.style.statusBarStyle = value;
if (value === 'light') {
// light icons/text
controller.setSystemBarsAppearance(0, APPEARANCE_LIGHT_STATUS_BARS);
} else {
// dark icons/text
controller.setSystemBarsAppearance(APPEARANCE_LIGHT_STATUS_BARS, APPEARANCE_LIGHT_STATUS_BARS);
}
} else {
if (value.color != null) window.setStatusBarColor(value.color);
// No direct passthrough for systemUiVisibility on API 30+, use appearances instead
}
return;
}

// API 23–29 path (systemUiVisibility)
if (SDK_VERSION >= 23) {
if (typeof value === 'string') {
this.style.statusBarStyle = value;
let flags = decorView.getSystemUiVisibility();
if (value === 'light') {
// Add the LIGHT_STATUS_BAR bit without clobbering other flags
flags |= android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
decorView.setSystemUiVisibility(flags);
} else {
// Remove only the LIGHT_STATUS_BAR bit
flags &= ~android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
decorView.setSystemUiVisibility(flags);
}
} else {
if (value.color != null) window.setStatusBarColor(value.color);
if (value.systemUiVisibility != null) {
// Preserve existing flags, don’t blindly overwrite to 0
const merged = (decorView.getSystemUiVisibility() & ~android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) | (value.systemUiVisibility & android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
decorView.setSystemUiVisibility(merged);
}
}
return;
}

// API 21–22: you can only change the background color; icon color is fixed (light)
if (typeof value === 'object' && value.color != null) {
window.setStatusBarColor(value.color);
}
}

getClosestWindow(): android.view.Window {
// When it comes to modals, check parent as it may not be the modal root view itself
const view = this.parent ?? this;
const dialogFragment = (view as this)._dialogFragment;
if (dialogFragment) {
const dialog = dialogFragment.getDialog();
if (dialog) {
return dialog.getWindow();
}
}
return this._context.getWindow();
}

[testIDProperty.setNative](value: string) {
this.setAccessibilityIdentifier(this.nativeViewProtected, value);
}
Expand Down
16 changes: 16 additions & 0 deletions packages/core/ui/core/view/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,22 @@ export abstract class View extends ViewCommon {
*/
cssType: string;

/**
* Gets or sets the status bar style for this view.
* Platform Notes:
* - Android: When using this property throughout navigations, ensure starting views have it set as well. Ensures it will reset on back navigation.
* - iOS: You must remove Info.plist key `UIViewControllerBasedStatusBarAppearance`
* It defaults to true when not present: https://developer.apple.com/documentation/bundleresources/information-property-list/uiviewcontrollerbasedstatusbarappearance
* Or you can explicitly set it to true:
* <key>UIViewControllerBasedStatusBarAppearance</key>
* <true/>
*
* False value will make this property have no effect.
*
* @nsProperty
*/
statusBarStyle: 'light' | 'dark';

cssClasses: Set<string>;
cssPseudoClasses: Set<string>;

Expand Down
43 changes: 37 additions & 6 deletions packages/core/ui/core/view/index.ios.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Point, Position } from './view-interfaces';
import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUserInteractionEnabledProperty, testIDProperty, iosGlassEffectProperty, GlassEffectType, GlassEffectVariant } from './view-common';
import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUserInteractionEnabledProperty, testIDProperty, iosGlassEffectProperty, GlassEffectType, GlassEffectVariant, statusBarStyleProperty } from './view-common';
import { isAccessibilityServiceEnabled } from '../../../application';
import { updateA11yPropertiesCallback } from '../../../application/helpers-common';
import { ShowModalOptions, hiddenProperty } from '../view-base';
Expand Down Expand Up @@ -560,12 +560,21 @@ export class View extends ViewCommon {
}
}

if (options.ios && options.ios.presentationStyle) {
const presentationStyle = options.ios.presentationStyle;
controller.modalPresentationStyle = presentationStyle;
if (options.ios) {
if (options.ios.presentationStyle) {
const presentationStyle = options.ios.presentationStyle;
controller.modalPresentationStyle = presentationStyle;

if (presentationStyle === UIModalPresentationStyle.Popover) {
this._setupPopoverControllerDelegate(controller, parent);
if (presentationStyle === UIModalPresentationStyle.Popover) {
this._setupPopoverControllerDelegate(controller, parent);
}
}
if (options.ios.statusBarStyle) {
/**
* https://developer.apple.com/documentation/uikit/uiviewcontroller/modalpresentationcapturesstatusbarappearance
*/
controller.modalPresentationCapturesStatusBarAppearance = true;
this.statusBarStyle = options.ios.statusBarStyle;
}
}

Expand Down Expand Up @@ -895,6 +904,28 @@ export class View extends ViewCommon {
}
}

[statusBarStyleProperty.getDefault]() {
return this.style.statusBarStyle;
}
[statusBarStyleProperty.setNative](value: 'light' | 'dark') {
this.style.statusBarStyle = value;
const parent = this.parent;
if (parent) {
const ctrl = parent.ios?.controller;
if (ctrl && ctrl instanceof UINavigationController) {
const navigationBar = ctrl.navigationBar;
if (!navigationBar) return;

if (typeof value === 'string') {
console.log('here:', value);
navigationBar.barStyle = value === 'dark' ? UIBarStyle.Black : UIBarStyle.Default;
} else {
navigationBar.barStyle = value;
}
}
}
}

[iosGlassEffectProperty.setNative](value: GlassEffectType) {
if (!this.nativeViewProtected || !supportsGlass()) {
return;
Expand Down
37 changes: 36 additions & 1 deletion packages/core/ui/core/view/view-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { layout } from '../../../utils';
import { isObject } from '../../../utils/types';
import { sanitizeModuleName } from '../../../utils/common';
import { Color } from '../../../color';
import { Property, InheritedProperty } from '../properties';
import { Property, InheritedProperty, CssProperty } from '../properties';
import { Style } from '../../styling/style';
import { EventData } from '../../../data/observable';
import { ViewHelper } from './view-helper';
import { setupAccessibleView } from '../../../application/helpers';
Expand Down Expand Up @@ -193,6 +194,12 @@ export abstract class ViewCommon extends ViewBase {
super.onLoaded();

setupAccessibleView(this);

if (this.statusBarStyle) {
// reapply status bar style on load
// helps back navigation cases to restore if overridden
this.updateStatusBarStyle(this.statusBarStyle);
}
}

public _closeAllModalViewsInternal(): boolean {
Expand Down Expand Up @@ -970,6 +977,14 @@ export abstract class ViewCommon extends ViewBase {
this.style.androidDynamicElevationOffset = value;
}

/**
* (Android only) Gets closest window parent considering modals.
*/
getClosestWindow(): android.view.Window {
// platform impl
return null;
}

//END Style property shortcuts

public originX: number;
Expand All @@ -995,6 +1010,17 @@ export abstract class ViewCommon extends ViewBase {
this._cssType = type.toLowerCase();
}

get statusBarStyle(): 'light' | 'dark' {
return this.style.statusBarStyle;
}
set statusBarStyle(value: 'light' | 'dark') {
this.style.statusBarStyle = value;
}

updateStatusBarStyle(value: 'dark' | 'light') {
// platform specific impl
}

get isLayoutRequired(): boolean {
return true;
}
Expand Down Expand Up @@ -1288,6 +1314,15 @@ export const isUserInteractionEnabledProperty = new Property<ViewCommon, boolean
});
isUserInteractionEnabledProperty.register(ViewCommon);

/**
* Property backing statusBarStyle.
*/
export const statusBarStyleProperty = new CssProperty<Style, 'light' | 'dark'>({
name: 'statusBarStyle',
cssName: 'status-bar-style',
});
statusBarStyleProperty.register(Style);

// Apple only
export const iosOverflowSafeAreaProperty = new Property<ViewCommon, boolean>({
name: 'iosOverflowSafeArea',
Expand Down
14 changes: 14 additions & 0 deletions packages/core/ui/core/view/view-helper/index.ios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,20 @@ class UILayoutViewController extends UIViewController {
}
}
}

// @ts-ignore
public get preferredStatusBarStyle(): UIStatusBarStyle {
const owner = this.owner?.deref();
if (owner) {
if (SDK_VERSION >= 13) {
return owner.statusBarStyle === 'dark' ? UIStatusBarStyle.DarkContent : UIStatusBarStyle.LightContent;
} else {
return owner.statusBarStyle === 'dark' ? UIStatusBarStyle.LightContent : UIStatusBarStyle.Default;
}
} else {
return UIStatusBarStyle.Default;
}
}
}

@NativeClass
Expand Down
Loading