Skip to content
Open
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
237 changes: 167 additions & 70 deletions example/pubspec.lock

Large diffs are not rendered by default.

268 changes: 120 additions & 148 deletions lib/src/menu.dart
Original file line number Diff line number Diff line change
@@ -1,148 +1,120 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

import 'menu_item.dart';
import 'utils.dart';

const String _kChannelName = "flutter/system_tray/menu_manager";

const String _kCreateContextMenu = "CreateContextMenu";

const String _kMenuIdKey = 'menu_id';
const String _kMenuItemIdKey = 'menu_item_id';
const String _kMenuListKey = 'menu_list';

const String _kMenuItemSelectedCallbackMethod = 'MenuItemSelectedCallback';

class Menu {
static const MethodChannel _platformChannel = MethodChannel(_kChannelName);

static final Map<int, Menu> _menuMap = {};

/// The ID to use the next time a menu needs an ID assigned.
static int _nextMenuId = 1;

List<MenuItemBase>? _menus;

int _menuId = 1;

int _menuItemId = 1;

bool _updateInProgress = false;

Menu() {
_platformChannel.setMethodCallHandler(_callbackHandler);
}

int get menuId => _menuId;

int get nextMenuItemId {
return _menuItemId++;
}

Future<bool> buildFrom(List<MenuItemBase> menus) async {
_menuId = _nextMenuId++;
_menus = menus;
_menuMap.putIfAbsent(_menuId, () => this);
return await _createContextMenu(_menus!);
}

T? findItemByName<T>(final String name) {
return _findItemByName(name, _menus!) as T;
}

MenuItemBase? _findItemByName(
final String name, final List<MenuItemBase> menus) {
MenuItemBase? item;
for (final menuItem in menus) {
if (menuItem is SubMenu) {
item = _findItemByName(name, menuItem.children);
} else if (menuItem.name == name) {
item = menuItem;
}

if (item != null) {
break;
}
}
return item;
}

MenuItemBase? _findItemById(
final int? menuItemId, final List<MenuItemBase>? menus) {
MenuItemBase? item;
if (menuItemId != null && menus != null) {
for (final menuItem in menus) {
if (menuItem is SubMenu) {
item = _findItemById(menuItemId, menuItem.children);
} else if (menuItem.menuItemId == menuItemId) {
item = menuItem;
}

if (item != null) {
break;
}
}
}
return item;
}

Future<bool> _createContextMenu(List<MenuItemBase> menus) async {
bool result = false;
try {
_updateInProgress = true;

await _channelRepresentationForMenus(menus);

result = await _platformChannel
.invokeMethod(_kCreateContextMenu, <String, dynamic>{
_kMenuIdKey: _menuId,
_kMenuListKey: menus.map((e) => e.toJson()).toList(),
});
_updateInProgress = false;
} on PlatformException catch (e) {
debugPrint('Platform exception create context menu: ${e.message}');
}
return result;
}

Future<void> _channelRepresentationForMenus(List<MenuItemBase> menus) async {
_menuItemId = 1;
await _channelRepresentationForMenu(menus);
}

Future<void> _channelRepresentationForMenu(List<MenuItemBase> menus) async {
for (final menuItem in menus) {
menuItem.channel = _platformChannel;
menuItem.menuId = menuId;
menuItem.menuItemId = nextMenuItemId;
menuItem.imageAbsolutePath = await Utils.getIcon(menuItem.image);

if (menuItem is SubMenu) {
await _channelRepresentationForMenu(menuItem.children);
}
}
}

Future<void> _callbackHandler(MethodCall methodCall) async {
if (methodCall.method == _kMenuItemSelectedCallbackMethod) {
if (_updateInProgress) {
debugPrint(
'Warning: Menu selection callback received during menu update.');
return;
}

final int? menuId = methodCall.arguments[_kMenuIdKey];
final int? menuItemId = methodCall.arguments[_kMenuItemIdKey];
final MenuItemBase? menuItem =
_findItemById(menuItemId, _menuMap[menuId]?._menus);

debugPrint('MenuItemBase select menuId:$menuId menuItemId:$menuItemId');

final callback = menuItem?.onClicked;
if (callback != null) {
callback(menuItem!);
}
}
}
}
import 'package:flutter/material.dart';

import 'menu_item.dart';
import 'platform/system_tray_platform_interface.dart';

class Menu {
static final Map<int, Menu> _menuMap = {};

/// The ID to use the next time a menu needs an ID assigned.
static int _nextMenuId = 1;

List<MenuItemBase>? _menus;

int _menuId = 1;

int _menuItemId = 1;

bool _updateInProgress = false;

Menu() {
SystemTrayPlatform.instance.onMenuItemSelected ??= _callbackHandler;
}

int get menuId => _menuId;

int get nextMenuItemId {
return _menuItemId++;
}

Future<bool> buildFrom(List<MenuItemBase> menus) async {
_menuId = _nextMenuId++;
_menus = menus;
_menuMap.putIfAbsent(_menuId, () => this);

_prepareMenuItems(menus);

_updateInProgress = true;
bool result = await SystemTrayPlatform.instance.createContextMenu(_menuId, menus);
_updateInProgress = false;

return result;
}

void _prepareMenuItems(List<MenuItemBase> menus) {
_menuItemId = 1;
_assignIds(menus);
}

void _assignIds(List<MenuItemBase> menus) {
for (final menuItem in menus) {
menuItem.menuId = menuId;
menuItem.menuItemId = nextMenuItemId;

if (menuItem is SubMenu) {
_assignIds(menuItem.children);
}
}
}

T? findItemByName<T>(final String name) {
return _findItemByName(name, _menus!) as T;
}

MenuItemBase? _findItemByName(
final String name, final List<MenuItemBase> menus) {
MenuItemBase? item;
for (final menuItem in menus) {
if (menuItem is SubMenu) {
item = _findItemByName(name, menuItem.children);
} else if (menuItem.name == name) {
item = menuItem;
}

if (item != null) {
break;
}
}
return item;
}

MenuItemBase? _findItemById(
final int? menuItemId, final List<MenuItemBase>? menus) {
MenuItemBase? item;
if (menuItemId != null && menus != null) {
for (final menuItem in menus) {
if (menuItem is SubMenu) {
item = _findItemById(menuItemId, menuItem.children);
} else if (menuItem.menuItemId == menuItemId) {
item = menuItem;
}

if (item != null) {
break;
}
}
}
return item;
}

static void _callbackHandler(int menuId, int menuItemId) {
final Menu? menu = _menuMap[menuId];
if (menu != null) {
if (menu._updateInProgress) {
debugPrint(
'Warning: Menu selection callback received during menu update.');
return;
}

final MenuItemBase? menuItem =
menu._findItemById(menuItemId, menu._menus);

debugPrint('MenuItemBase select menuId:$menuId menuItemId:$menuItemId');

final callback = menuItem?.onClicked;
if (callback != null) {
callback(menuItem!);
}
}
}
}
58 changes: 15 additions & 43 deletions lib/src/menu_item.dart
Original file line number Diff line number Diff line change
@@ -1,15 +1,5 @@
import 'package:flutter/services.dart';
import 'package:system_tray/src/utils.dart';
import 'package:system_tray/src/platform/system_tray_platform_interface.dart';

const String _kSetLabel = "SetLabel";
const String _kSetImage = "SetImage";
const String _kSetEnable = "SetEnable";
const String _kSetCheck = "SetCheck";

const String _kMenuIdKey = 'menu_id';
const String _kMenuItemIdKey = 'menu_item_id';
const String _kIdKey = 'id';
const String _kTypeKey = 'type';
const String _kMenuTypeLabel = 'label';
const String _kMenuTypeCheckbox = 'checkbox';
const String _kMenuTypeSubMenu = 'submenu';
Expand All @@ -19,6 +9,8 @@ const String _kImageKey = 'image';
const String _kSubMenuKey = 'submenu';
const String _kEnabledKey = 'enabled';
const String _kCheckedKey = 'checked';
const String _kIdKey = 'id';
const String _kTypeKey = 'type';

/// A callback provided to [MenuItemBase] to handle menu selection.
typedef MenuItemSelectedCallback = void Function(MenuItemBase);
Expand All @@ -40,57 +32,37 @@ abstract class MenuItemBase {
}

Future<void> setLabel(String label) async {
bool result = await channel?.invokeMethod(_kSetLabel, {
_kMenuIdKey: menuId ?? -1,
_kMenuItemIdKey: menuItemId ?? -1,
_kLabelKey: label,
});
if (result) {
this.label = label;
if (menuId != null && menuItemId != null) {
await SystemTrayPlatform.instance.setMenuItemLabel(menuId!, menuItemId!, label);
}
this.label = label;
}

Future<void> setImage(String image) async {
String? imageAbsolutePath = await Utils.getIcon(image);

bool result = await channel?.invokeMethod(_kSetImage, {
_kMenuIdKey: menuId ?? -1,
_kMenuItemIdKey: menuItemId ?? -1,
_kImageKey: imageAbsolutePath,
});
if (result) {
this.image = image;
this.imageAbsolutePath = imageAbsolutePath;
if (menuId != null && menuItemId != null) {
await SystemTrayPlatform.instance.setMenuItemImage(menuId!, menuItemId!, image);
}
this.image = image;
}

Future<void> setEnable(bool enabled) async {
bool result = await channel?.invokeMethod(_kSetEnable, {
_kMenuIdKey: menuId ?? -1,
_kMenuItemIdKey: menuItemId ?? -1,
_kEnabledKey: enabled,
});
if (result) {
this.enabled = enabled;
if (menuId != null && menuItemId != null) {
await SystemTrayPlatform.instance.setMenuItemEnable(menuId!, menuItemId!, enabled);
}
this.enabled = enabled;
}

Future<void> setCheck(bool checked) async {
if (type != _kMenuTypeCheckbox) {
return;
}

bool result = await channel?.invokeMethod(_kSetCheck, {
_kMenuIdKey: menuId ?? -1,
_kMenuItemIdKey: menuItemId ?? -1,
_kCheckedKey: checked,
});
if (result) {
this.checked = checked;
if (menuId != null && menuItemId != null) {
await SystemTrayPlatform.instance.setMenuItemCheck(menuId!, menuItemId!, checked);
}
this.checked = checked;
}

MethodChannel? channel;
int? menuId;
int? menuItemId;
String? imageAbsolutePath;
Expand Down
Loading