Skip to content

Commit 14d13d6

Browse files
committed
feat: widgets
1 parent 0a8848f commit 14d13d6

71 files changed

Lines changed: 7732 additions & 1296 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- [@nativescript/rive](packages/rive/README.md)
66
- [@nativescript/swift-ui](packages/swift-ui/README.md)
77
- [@nativescript/ui-charts](packages/ui-charts/README.md)
8+
- [@nativescript/widgets](packages/widgets/README.md)
89

910
# How to use?
1011

apps/demo-angular/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"@nativescript/jetpack-compose": "file:../../dist/packages/jetpack-compose",
99
"@nativescript/rive": "file:../../dist/packages/rive",
1010
"@nativescript/swift-ui": "file:../../dist/packages/swift-ui",
11-
"@nativescript/ui-charts": "file:../../dist/packages/ui-charts"
11+
"@nativescript/ui-charts": "file:../../dist/packages/ui-charts",
12+
"@nativescript/widgets": "file:../../dist/packages/widgets"
1213
},
1314
"devDependencies": {
1415
"@nativescript/android": "~8.9.0",

apps/demo-angular/src/app-routing.module.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const routes: Routes = [
1414
{ path: 'rive', loadChildren: () => import('./plugin-demos/rive.module').then((m) => m.RiveModule) },
1515
{ path: 'swift-ui', loadChildren: () => import('./plugin-demos/swift-ui.module').then((m) => m.SwiftUiModule) },
1616
{ path: 'ui-charts', loadChildren: () => import('./plugin-demos/ui-charts.module').then((m) => m.UiChartsModule) },
17+
{ path: 'widgets', loadChildren: () => import('./plugin-demos/widgets.module').then((m) => m.WidgetsModule) },
1718
];
1819

1920
@NgModule({

apps/demo-angular/src/home.component.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,8 @@ export class HomeComponent {
2727
{
2828
name: 'ui-charts',
2929
},
30+
{
31+
name: 'widgets',
32+
},
3033
];
3134
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<ActionBar title="widgets" class="action-bar"> </ActionBar>
2+
<StackLayout class="p-20">
3+
<ScrollView class="h-full">
4+
<StackLayout>
5+
<Button text="Test widgets" (tap)="demoShared.testIt()" class="btn btn-primary"></Button>
6+
</StackLayout>
7+
</ScrollView>
8+
</StackLayout>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Component, NgZone } from '@angular/core';
2+
import { DemoSharedWidgets } from '@demo/shared';
3+
import {} from '@nativescript/widgets';
4+
5+
@Component({
6+
selector: 'demo-widgets',
7+
templateUrl: 'widgets.component.html',
8+
})
9+
export class WidgetsComponent {
10+
demoShared: DemoSharedWidgets;
11+
12+
constructor(private _ngZone: NgZone) {}
13+
14+
ngOnInit() {
15+
this.demoShared = new DemoSharedWidgets();
16+
}
17+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
2+
import { NativeScriptCommonModule, NativeScriptRouterModule } from '@nativescript/angular';
3+
import { WidgetsComponent } from './widgets.component';
4+
5+
@NgModule({
6+
imports: [NativeScriptCommonModule, NativeScriptRouterModule.forChild([{ path: '', component: WidgetsComponent }])],
7+
declarations: [WidgetsComponent],
8+
schemas: [NO_ERRORS_SCHEMA],
9+
})
10+
export class WidgetsModule {}

apps/demo-angular/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"@nativescript/jetpack-compose": ["../../packages/jetpack-compose/index.d.ts"],
1212
"@nativescript/rive": ["../../packages/rive/index.d.ts"],
1313
"@nativescript/swift-ui": ["../../packages/swift-ui/index.d.ts"],
14-
"@nativescript/ui-charts": ["../../packages/ui-charts/index.d.ts"]
14+
"@nativescript/ui-charts": ["../../packages/ui-charts/index.d.ts"],
15+
"@nativescript/widgets": ["../../packages/widgets/index.d.ts"]
1516
}
1617
},
1718
"files": ["./references.d.ts", "./src/main.ts", "./src/polyfills.ts"],

apps/demo/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"@nativescript/jetpack-compose": "file:../../packages/jetpack-compose",
1212
"@nativescript/rive": "file:../../packages/rive",
1313
"@nativescript/swift-ui": "file:../../packages/swift-ui",
14-
"@nativescript/ui-charts": "file:../../packages/ui-charts"
14+
"@nativescript/ui-charts": "file:../../packages/ui-charts",
15+
"@nativescript/widgets": "file:../../packages/widgets"
1516
},
1617
"devDependencies": {
1718
"@nativescript/android": "~8.9.0",

apps/demo/src/app.ts

Lines changed: 220 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Application } from '@nativescript/core';
2-
1+
import { Application, Http, ImageSource } from '@nativescript/core';
2+
import { registerWidgetListener, LinearLayout, ImageView, Image, Button, VStack, List, Text, updateWidget, Root, HStack, ButtonView, Flipper, Chronometer, Clock, Grid, ProgressBar, Stack, Spacer } from '@nativescript/widgets';
33
// uncomment to test Flutter
44
// import { init } from '@nativescript/flutter';
55
// init();
@@ -17,4 +17,222 @@ import { Application } from '@nativescript/core';
1717
// IonicPortalManager.register('<portal-api-key>');
1818
// });
1919

20+
// 1. Countdown timer widget
21+
function countdownWidget(provider: string, ids: number[]) {
22+
const targetTime = Date.now() + 3600000; // 1 hour from now
23+
const root = Root(() =>
24+
VStack(() => [
25+
Text('⏱️ Countdown').setColor('#1a1a2e').setTextSize(18),
26+
Spacer(12),
27+
Chronometer({
28+
targetTime: targetTime, // Use targetTime for Unix timestamps
29+
countDown: true,
30+
started: true,
31+
})
32+
.setColor('#e74c3c')
33+
.setTextSize(32),
34+
Spacer(8),
35+
Text('until deadline').setColor('#6c757d').setTextSize(12),
36+
]),
37+
)
38+
.setBackgroundColor('#ffffff')
39+
.setPadding(16);
40+
for (const id of ids) updateWidget(provider, root, id);
41+
}
42+
43+
// 2. Clock widget
44+
function clockWidget(provider: string, ids: number[]) {
45+
const root = Root(() => VStack(() => [Text('🕐 Current Time').setColor('#ecf0f1').setTextSize(14), Spacer(12), Clock({ color: '#3498db' }).setTextSize(42), Spacer(8), Text('Local timezone').setColor('#7f8c8d').setTextSize(11)]))
46+
.setBackgroundColor('#1a1a2e')
47+
.setPadding(16);
48+
for (const id of ids) updateWidget(provider, root, id);
49+
}
50+
51+
// 3. Progress indicator
52+
function progressWidget(provider: string, ids: number[]) {
53+
const root = Root(() => VStack(() => [HStack(() => [Text('⬇️ Downloading').setColor('#2c3e50').setTextSize(16), Spacer(), Text('65%').setColor('#27ae60').setTextSize(16)]), Spacer(12), ProgressBar({ progress: 65, max: 100 }), Spacer(8), Text('file_backup.zip • 650 MB / 1 GB').setColor('#7f8c8d').setTextSize(11)]))
54+
.setBackgroundColor('#ffffff')
55+
.setPadding(16);
56+
for (const id of ids) updateWidget(provider, root, id);
57+
}
58+
59+
// 4. List with click handlers
60+
function listWidget(provider: string, ids: number[]) {
61+
const items = [
62+
{ icon: '📥', name: 'Inbox', count: 12 },
63+
{ icon: '⭐', name: 'Starred', count: 3 },
64+
{ icon: '📤', name: 'Sent', count: 0 },
65+
{ icon: '📝', name: 'Drafts', count: 2 },
66+
{ icon: '🗑️', name: 'Trash', count: 0 },
67+
];
68+
const root = Root(() =>
69+
VStack(() => [
70+
Text('📧 Mail').setColor('#2c3e50').setTextSize(18),
71+
Spacer(8),
72+
List(
73+
items.length,
74+
(index) => HStack(() => [Text(`${items[index].icon} ${items[index].name}`).setColor('#34495e').setTextSize(14), Spacer(), items[index].count > 0 ? Text(`${items[index].count}`).setColor('#3498db').setTextSize(12) : Text('').setTextSize(12)]).setMargin(4, 8, 4, 8),
75+
() => Text('No folders').setColor('#95a5a6'),
76+
),
77+
]),
78+
)
79+
.setBackgroundColor('#ffffff')
80+
.setPadding(12);
81+
for (const id of ids) updateWidget(provider, root, id);
82+
}
83+
84+
// 5. Image flipper/slideshow
85+
async function slideshowWidget(provider: string, ids: number[], loadImages = false) {
86+
const imageUrls = ['https://picsum.photos/seed/1/400/300', 'https://picsum.photos/seed/2/400/300', 'https://picsum.photos/seed/3/400/300', 'https://picsum.photos/seed/4/400/300'];
87+
88+
const images = loadImages ? await Promise.all(imageUrls.map((url) => ImageSource.fromUrl(url))) : imageUrls;
89+
90+
const root = Root(() => VStack(() => [Text('📷 Gallery').setColor('#ffffff').setTextSize(16), Spacer(8), Flipper(images as never[], 3000, (src) => Image(src)), Spacer(8), Text('Auto-advances every 3s').setColor('#adb5bd').setTextSize(11)]))
91+
.setBackgroundColor('#2c3e50')
92+
.setPadding(12);
93+
94+
for (const id of ids) updateWidget(provider, root, id);
95+
}
96+
97+
// 6. Grid layout
98+
function gridWidget(provider: string, ids: number[]) {
99+
const actions = [
100+
{ icon: '📞', label: 'Call', color: '#3498db', bg: '#ebf5fb' },
101+
{ icon: '💬', label: 'Message', color: '#9b59b6', bg: '#f5eef8' },
102+
{ icon: '📧', label: 'Email', color: '#e74c3c', bg: '#fdedec' },
103+
{ icon: '📅', label: 'Calendar', color: '#27ae60', bg: '#eafaf1' },
104+
{ icon: '📷', label: 'Camera', color: '#f39c12', bg: '#fef9e7' },
105+
{ icon: '🎵', label: 'Music', color: '#e91e63', bg: '#fce4ec' },
106+
];
107+
108+
const root = Root(() =>
109+
VStack(() => [
110+
HStack(() => [Text('⚡ Quick Actions').setColor('#1a1a2e').setTextSize(18), Spacer(), Text('6 apps').setColor('#95a5a6').setTextSize(12)]),
111+
Spacer(16),
112+
Grid(3, 12, () =>
113+
actions.map((action) =>
114+
VStack(() => [
115+
VStack(() => [Text(action.icon).setTextSize(28), Spacer(6), Text(action.label).setColor(action.color).setTextSize(11)])
116+
.setBackgroundColor(action.bg)
117+
.setPadding(12),
118+
Spacer(4),
119+
]),
120+
),
121+
),
122+
]),
123+
)
124+
.setBackgroundColor('#ffffff')
125+
.setPadding(16);
126+
for (const id of ids) updateWidget(provider, root, id);
127+
}
128+
129+
// 7. StackView (swipeable cards)
130+
function stackWidget(provider: string, ids: number[]) {
131+
const cards = [
132+
{ title: 'Meeting Notes', subtitle: 'Project sync @ 2pm', color: '#3498db' },
133+
{ title: 'Shopping List', subtitle: '5 items remaining', color: '#e74c3c' },
134+
{ title: 'Workout Plan', subtitle: 'Leg day tomorrow', color: '#27ae60' },
135+
];
136+
const root = Root(() =>
137+
VStack(() => [
138+
Text('📚 Cards').setColor('#ffffff').setTextSize(16),
139+
Spacer(8),
140+
Stack(cards.length, (index) =>
141+
VStack(() => [Text(cards[index].title).setColor('#2c3e50').setTextSize(16), Spacer(4), Text(cards[index].subtitle).setColor('#7f8c8d').setTextSize(12)])
142+
.setBackgroundColor('#ffffff')
143+
.setPadding(12),
144+
),
145+
Spacer(8),
146+
Text('Swipe to browse').setColor('#adb5bd').setTextSize(11),
147+
]),
148+
)
149+
.setBackgroundColor('#34495e')
150+
.setPadding(12);
151+
for (const id of ids) updateWidget(provider, root, id);
152+
}
153+
154+
// 8. Complex combined layout
155+
function dashboardWidget(provider: string, ids: number[]) {
156+
const tasks = [
157+
{ name: 'Review PR #142', done: true },
158+
{ name: 'Update documentation', done: true },
159+
{ name: 'Fix login bug', done: false },
160+
{ name: 'Deploy to staging', done: false },
161+
{ name: 'Team standup', done: false },
162+
];
163+
const completed = tasks.filter((t) => t.done).length;
164+
const progress = Math.round((completed / tasks.length) * 100);
165+
166+
const root = Root(() =>
167+
VStack(() => [
168+
// Header row
169+
HStack(() => [Text('📊 Dashboard').setColor('#1a1a2e').setTextSize(18), Spacer(), Clock({ color: '#6c757d' }).setTextSize(14)]),
170+
171+
Spacer(12),
172+
173+
// Progress section
174+
Text('Sprint Progress').setColor('#495057').setTextSize(12),
175+
Spacer(4),
176+
HStack(() => [ProgressBar({ progress, max: 100, flex: true }), Spacer(8), Text(`${progress}%`).setColor('#27ae60').setTextSize(14)]),
177+
178+
Spacer(16),
179+
180+
// Tasks section
181+
HStack(() => [Text('Tasks').setColor('#495057').setTextSize(12), Spacer(), Text(`${completed}/${tasks.length}`).setColor('#6c757d').setTextSize(11)]),
182+
Spacer(4),
183+
List(tasks.length, (i) =>
184+
Text(`${tasks[i].done ? '✓' : '○'} ${tasks[i].name}`)
185+
.setColor(tasks[i].done ? '#27ae60' : '#212529')
186+
.setTextSize(13),
187+
),
188+
]),
189+
)
190+
.setBackgroundColor('#ffffff')
191+
.setPadding(16);
192+
for (const id of ids) updateWidget(provider, root, id);
193+
}
194+
195+
registerWidgetListener('org.nativescript.plugindemo.PluginDemoWidgetProvider', {
196+
onClick(event) {
197+
console.log('Widget clicked', event);
198+
},
199+
onUpdate: (event) => {
200+
// dashboardWidget(event.provider, event.appWidgetIds);
201+
//countdownWidget(event.provider, event.appWidgetIds);
202+
// clockWidget(event.provider, event.appWidgetIds);
203+
// progressWidget(event.provider, event.appWidgetIds);
204+
// listWidget(event.provider, event.appWidgetIds);
205+
// slideshowWidget(event.provider, event.appWidgetIds);
206+
gridWidget(event.provider, event.appWidgetIds);
207+
//stackWidget(event.provider, event.appWidgetIds);
208+
/* const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
209+
const list = List(
210+
data.length,
211+
(index) =>
212+
VStack(() =>
213+
HStack(() => {
214+
const img = Image('').setSize(20, 20) as ImageView;
215+
const ret = [img, Text(`Item ${index}`), Button('Click', () => console.log(`Clicked item ${index}`))];
216+
rows.push({ row: ret as never, src: `https://picsum.photos/seed/${data[index]}/300/300` });
217+
return ret;
218+
}),
219+
),
220+
() => Text('No content'),
221+
).setBackgroundColor('white');
222+
223+
const root = Root(() => list)
224+
.setBackgroundColor('lightgray')
225+
.setPadding(20);
226+
227+
rootView = root;
228+
for (const id of event.appWidgetIds) {
229+
updateWidget(event.provider, root, id);
230+
}
231+
*/
232+
},
233+
async onUpdateAsync(event) {
234+
// slideshowWidget(event.provider, event.appWidgetIds, true);
235+
},
236+
});
237+
20238
Application.run({ moduleName: 'app-root' });

0 commit comments

Comments
 (0)