Skip to content
Closed
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
1 change: 1 addition & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>

Expand Down
4 changes: 2 additions & 2 deletions android/build/reports/problems/problems-report.html

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions assets/scripts/examples/task_test/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Task Test Plugin

var counter = 0;

function on_load() {
console.log("Task Test: on_load called, scheduling task...");
tasks.schedule(5, "tick");
}

function tick() {
counter++;
console.log("Task Test: tick #" + counter);
notify("Task Test", "Tick #" + counter);
}
14 changes: 14 additions & 0 deletions assets/scripts/examples/task_test/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"id": "com.example.task_test",
"name": "Task Test",
"version": "1.0.0",
"author": "Island",
"description": "Test plugin for the background task system (tasks.schedule).",
"entry": "main.js",
"permissions": [
"commandsRegister",
"notify",
"tasksSchedule",
"uiRender"
]
}
146 changes: 146 additions & 0 deletions assets/scripts/examples/ui_test/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// UI Test Plugin — exercises every widget type and pane system

var clickCount = 0;
var textInput = "";

function on_load() {
console.log("UI Test: loaded, registering dashboard item + commands");
ui.register_dashboard_item("ui_demo", "UI Demo", "onDashboardRender", "palette");
commands.register_command("ui.demo", "Open UI demo pane", "cmdDemo", "dashboard");
commands.register_command("ui.all", "Show all widget types", "cmdShowAll", "widgets");
commands.register_command("ui.interactive", "Interactive widgets", "cmdInteractive", "hand_gesture");
commands.register_command("ui.open_pane", "Open pane via ui.open()", "cmdOpenPane", "open_in_new");
}

function on_unload() {
console.log("UI Test: unloaded");
}

function onDashboardRender() {
return ui.card("UI Test Plugin", "Click any command to open a floating pane", [
ui.button("Demo Pane", "cmdDemo"),
ui.button("All Widgets", "cmdShowAll"),
ui.button("Interactive", "cmdInteractive"),
ui.button("ui.open()", "cmdOpenPane")
]);
}

function cmdDemo() {
return ui.page("UI Demo", [
ui.text("This is a floating pane — drag the title bar to move it."),
ui.spacing(8),
ui.text("Resize from the bottom-right corner."),
ui.spacing(8),
ui.section("Quick Actions", [
ui.button("Show All Widgets", "cmdShowAll"),
ui.button("Interactive Demo", "cmdInteractive"),
ui.button("ui.open()", "cmdOpenPane"),
]),
]);
}

function cmdShowAll() {
return ui.page("All Widget Types", [
ui.section("Typography", [
ui.text("Plain text block."),
ui.text("Another text line."),
]),
ui.divider(),
ui.section("Buttons", [
ui.row([
ui.button("Click Me", "onButtonClick"),
ui.button("Disabled", null),
]),
]),
ui.divider(),
ui.section("Card", [
ui.card("Card Title", "This is a card with title, body, and actions.", [
ui.button("Action", "onButtonClick"),
]),
]),
ui.divider(),
ui.section("Lists", [
ui.list_items([
"Item one",
"Item two",
"Item three",
ui.button("Action in list", "onButtonClick"),
]),
]),
ui.divider(),
ui.section("Links & Icons", [
ui.row([
ui.icon("home", 24, null, null),
ui.icon("favorite", 24, null, null),
ui.icon("settings", 24, null, null),
ui.link("Flutter", "https://flutter.dev"),
]),
]),
ui.divider(),
ui.section("Row / Column nesting", [
ui.row([
ui.text("Left"),
ui.text("Center"),
ui.text("Right"),
]),
ui.spacing(8),
ui.column([
ui.text("Above"),
ui.text("Below"),
]),
]),
ui.spacing(8),
ui.text("That's all 19 widget types!"),
]);
}

function cmdInteractive() {
return ui.page("Interactive Demo", [
ui.text("Click count: " + clickCount),
ui.spacing(8),
ui.row([
ui.button("Increment", "onIncrement"),
ui.button("Reset", "onReset"),
]),
ui.divider(),
ui.text("Type something and press Enter:"),
ui.spacing(4),
ui.input("Your name", "Enter name...", "onNameInput"),
ui.spacing(4),
ui.text(textInput.length > 0 ? "Hello, " + textInput + "!" : ""),
]);
}

function cmdOpenPane() {
ui.open("Opened via ui.open()", ui.card("Floating Card", "This pane was opened by calling ui.open() from a plugin. It can contain anything a normal pane can.", [
ui.button("Close this pane", "onCloseThisPane"),
]));
return ui.text("A new pane should appear!");
}

function onButtonClick() {
notify("Button Clicked", "You clicked a button in the test plugin!");
return ui.text("Button was clicked!");
}

function onIncrement() {
clickCount++;
return cmdInteractive();
}

function onReset() {
clickCount = 0;
return cmdInteractive();
}

function onNameInput(value) {
textInput = value;
return cmdInteractive();
}

function onCloseThisPane() {
// ui.open() returns nothing that can close the pane from JS side,
// but we can show a notification
notify("Pane", "Close the pane manually with the X button");
return ui.text("Click the X button in the title bar to close this pane.");
}
8 changes: 8 additions & 0 deletions assets/scripts/examples/ui_test/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"id": "dev.test.ui_test",
"name": "UI Test",
"version": "1.0.0",
"description": "Test harness for plugin UI system — draggable panes, commands, and all widget types",
"permissions": ["uiRender", "commandsRegister", "notify"],
"entry": "main.js"
}
46 changes: 7 additions & 39 deletions lib/command_palette/palette.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import 'package:island/core/services/event_bus.dart';
import 'package:island/core/widgets/draggable_log_overlay.dart';
import 'package:island/core/debug_sheet.dart';
import 'package:island/plugins/icons/plugin_icon_font_registry.dart';
import 'package:island/plugins/widgets/plugin_pane_host.dart';
import 'package:island/plugins/widgets/plugin_ui_bridge.dart';
import 'package:island_plugin_foundation/island_plugin_foundation.dart';
import 'package:url_launcher/url_launcher.dart';
Expand Down Expand Up @@ -471,45 +472,12 @@ class CommandPaletteWidget extends HookConsumerWidget {
)
: null;
if (descriptor != null) {
// The palette is rendered as a DesktopWindowFrame overlay (sibling of
// the app content), so its BuildContext is not under a Navigator.
// Always show results via the root router navigator.
final navigatorContext =
ref.read(routerProvider).navigatorKey.currentContext;
if (navigatorContext == null || !navigatorContext.mounted) {
return;
}

PluginUiDescriptor currentDescriptor = descriptor;
showDialog<void>(
context: navigatorContext,
useRootNavigator: true,
builder: (_) => StatefulBuilder(
builder: (dialogContext, setDialogState) => Dialog.fullscreen(
child: PluginUiRenderer(
descriptor: currentDescriptor,
onCallback: (callback, [value]) {
final next = runtime.callFunction(
callback,
value == null ? null : [value],
);
final nextDescriptor = next is String
? PluginUiRenderer.parse(next)
: next is Map && next['type'] is String
? PluginUiDescriptor(
type: next['type'] as String,
data: next.map(
(key, value) => MapEntry(key.toString(), value),
),
)
: null;
if (nextDescriptor != null) {
setDialogState(() => currentDescriptor = nextDescriptor);
}
},
),
),
),
final host = PluginPaneHost.instance;
host.addPane(
descriptor: descriptor,
pluginId: '',
pluginName: 'Command',
title: 'Result',
);
return;
}
Expand Down
33 changes: 33 additions & 0 deletions lib/core/services/notify.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,39 @@ StreamSubscription? setupNotificationListener(
}
}

/// Show a local notification (fixed id = 0, replacing previous).
/// Main isolate only (plugin initialised by [initializeLocalNotifications]).
Future<void> showLocalNotificationFromFcm({
required String title,
String? body,
String? payload,
}) async {
if (kIsWeb || Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
return;
}
return universal_notify.showLocalNotificationFromFcm(
title: title,
body: body,
payload: payload,
);
}

/// Background-isolate variant. Creates a fresh plugin instance per call.
Future<void> showBackgroundNotificationFromFcm({
required String title,
String? body,
String? payload,
}) async {
if (kIsWeb || Platform.isWindows || Platform.isLinux || Platform.isMacOS) {
return;
}
await universal_notify.ensureFcmNotificationPlugin(
title: title,
body: body,
payload: payload,
);
}

Future<void> showDebugLocalNotification(WidgetRef ref) async {
if (kIsWeb) {
return;
Expand Down
Loading
Loading