plugin_framework, debug config, mid commit

Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
fufesou
2023-04-24 18:45:22 +08:00
parent 8340573277
commit d8dc56a743
15 changed files with 623 additions and 276 deletions

View File

@@ -12,6 +12,8 @@ import 'package:flutter_hbb/models/platform_model.dart';
import 'package:flutter_hbb/models/server_model.dart';
import 'package:flutter_hbb/plugin/desc.dart';
import 'package:flutter_hbb/plugin/model.dart';
import 'package:flutter_hbb/plugin/common.dart';
import 'package:flutter_hbb/plugin/widget.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
@@ -1394,6 +1396,103 @@ class _AccountState extends State<_Account> {
}
}
class _Checkbox extends StatefulWidget {
final String label;
final bool Function() getValue;
final Future<void> Function(bool) setValue;
const _Checkbox(
{Key? key,
required this.label,
required this.getValue,
required this.setValue})
: super(key: key);
@override
State<_Checkbox> createState() => _CheckboxState();
}
class _CheckboxState extends State<_Checkbox> {
var value = false;
@override
initState() {
super.initState();
value = widget.getValue();
}
@override
Widget build(BuildContext context) {
onChanged(bool b) async {
await widget.setValue(b);
setState(() {
value = widget.getValue();
});
}
return GestureDetector(
child: Row(
children: [
Checkbox(
value: value,
onChanged: (_) => onChanged(!value),
).marginOnly(right: 5),
Expanded(
child: Text(translate(widget.label)),
)
],
).marginOnly(left: _kCheckBoxLeftMargin),
onTap: () => onChanged(!value),
);
}
}
class PluginCard extends StatefulWidget {
final PluginId pluginId;
final Desc desc;
const PluginCard({
Key? key,
required this.pluginId,
required this.desc,
}) : super(key: key);
@override
State<PluginCard> createState() => PluginCardState();
}
class PluginCardState extends State<PluginCard> {
@override
Widget build(BuildContext context) {
final children = [
_Button(
'Reload',
() => bind.pluginReload(id: widget.pluginId),
),
_Checkbox(
label: 'Enable',
getValue: () => bind.pluginIdIsEnabled(id: widget.pluginId),
setValue: (bool v) async {
if (!v) {
clearPlugin(widget.pluginId);
}
await bind.pluginIdEnable(id: widget.pluginId, v: v);
},
),
];
final model = getPluginModel(kLocationHostMainPlugin, widget.pluginId);
if (model != null) {
children.add(PluginItem(
pluginId: widget.pluginId,
peerId: '',
location: kLocationHostMainPlugin,
pluginModel: model,
isMenu: false,
));
}
return _Card(title: widget.desc.name, children: children);
}
}
class _Plugin extends StatefulWidget {
const _Plugin({Key? key}) : super(key: key);
@@ -1403,44 +1502,15 @@ class _Plugin extends StatefulWidget {
class _PluginState extends State<_Plugin> {
// temp checkbox widget
Widget _checkbox(
String label,
bool Function() getValue,
Future<void> Function(bool) setValue,
) {
final value = getValue();
onChanged(bool b) async {
await setValue(b);
setState(() {});
}
return GestureDetector(
child: Row(
children: [
Checkbox(
value: bind.pluginIsEnabled(),
onChanged: (_) => onChanged(!value),
).marginOnly(right: 5),
Expanded(
child: Text(translate(label)),
)
],
).marginOnly(left: _kCheckBoxLeftMargin),
onTap: () => onChanged(!value));
}
@override
Widget build(BuildContext context) {
final scrollController = ScrollController();
buildCards(DescModel model) {
final cards = <Widget>[
List<Widget> _buildCards(DescModel model) => [
_Card(
title: 'Plugin',
children: [
_checkbox(
'Enable',
() => bind.pluginIsEnabled() ?? false,
(bool v) async {
_Checkbox(
label: 'Enable',
getValue: () => bind.pluginIsEnabled() ?? false,
setValue: (bool v) async {
if (!v) {
clearLocations();
}
@@ -1449,28 +1519,14 @@ class _PluginState extends State<_Plugin> {
),
],
),
...model.all.entries
.map((entry) => PluginCard(pluginId: entry.key, desc: entry.value))
.toList(),
];
model.all.forEach((key, value) {
cards.add(_Card(title: key, children: [
_Button(
'Reload',
() => bind.pluginReload(id: key),
),
_checkbox(
'Enable',
() => bind.pluginIdIsEnabled(id: key),
(bool v) async {
if (!v) {
clearPlugin(key);
}
await bind.pluginIdEnable(id: key, v: v);
},
),
]));
});
return cards;
}
@override
Widget build(BuildContext context) {
final scrollController = ScrollController();
return DesktopScrollWrapper(
scrollController: scrollController,
child: ChangeNotifierProvider.value(
@@ -1479,7 +1535,7 @@ class _PluginState extends State<_Plugin> {
return ListView(
physics: DraggableNeverScrollableScrollPhysics(),
controller: scrollController,
children: buildCards(model),
children: _buildCards(model),
).marginOnly(bottom: _kListViewBottomMargin);
}),
),

View File

@@ -657,7 +657,7 @@ class _DisplayMenu extends StatefulWidget {
final FFI ffi;
final MenubarState state;
final Function(bool) setFullscreen;
final LocationItem pluginItem;
final Widget pluginItem;
_DisplayMenu(
{Key? key,
required this.id,
@@ -668,6 +668,7 @@ class _DisplayMenu extends StatefulWidget {
id,
ffi,
kLocationClientRemoteToolbarDisplay,
true,
),
super(key: key);

View File

@@ -3,8 +3,7 @@ import 'dart:convert';
typedef PluginId = String;
// ui location
const String kLocationHostMainDisplayOthers =
'host|main|settings|display|others';
const String kLocationHostMainPlugin = 'host|main|settings|plugin';
const String kLocationClientRemoteToolbarDisplay =
'client|remote|toolbar|display';

View File

@@ -2,6 +2,8 @@ import 'dart:convert';
import 'dart:collection';
import 'package:flutter/foundation.dart';
import './common.dart';
const String kValueTrue = '1';
const String kValueFalse = '0';
@@ -59,7 +61,7 @@ class UiCheckbox extends UiType {
class Location {
// location key:
// host|main|settings|display|others
// host|main|settings|plugin
// client|remote|toolbar|display
HashMap<String, UiType> ui;
@@ -76,14 +78,12 @@ class Location {
class ConfigItem {
String key;
String value;
String description;
String defaultValue;
ConfigItem(this.key, this.value, this.defaultValue, this.description);
ConfigItem(this.key, this.defaultValue, this.description);
ConfigItem.fromJson(Map<String, dynamic> json)
: key = json['key'] ?? '',
value = json['value'] ?? '',
description = json['description'] ?? '',
defaultValue = json['default'] ?? '';
@@ -151,7 +151,7 @@ class Desc {
}
class DescModel with ChangeNotifier {
final data = <String, Desc>{};
final data = <PluginId, Desc>{};
DescModel._();
@@ -169,7 +169,7 @@ class DescModel with ChangeNotifier {
return data[id];
}
Map<String, Desc> get all => data;
Map<PluginId, Desc> get all => data;
static final DescModel _instance = DescModel._();
static DescModel get instance => _instance;

View File

@@ -55,16 +55,17 @@ class LocationModel with ChangeNotifier {
}
void addLocationUi(String location, PluginId id, UiType ui) {
_locationModels[location]?.add(id, ui);
}
LocationModel addLocation(String location) {
if (_locationModels[location] == null) {
_locationModels[location] = LocationModel();
}
return _locationModels[location]!;
_locationModels[location]?.add(id, ui);
}
LocationModel? getLocationModel(String location) => _locationModels[location];
PluginModel? getPluginModel(String location, PluginId id) =>
_locationModels[location]?.pluginModels[id];
void clearPlugin(PluginId pluginId) {
for (var element in _locationModels.values) {
element.pluginModels.remove(pluginId);
@@ -77,7 +78,7 @@ void clearLocations() {
}
}
OptionModel addOptionModel(
OptionModel getOptionModel(
String location, PluginId pluginId, String peer, String key) {
final k = OptionModel.key(location, pluginId, peer, key);
if (_optionModels[k] == null) {

View File

@@ -2,8 +2,10 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/models/model.dart';
import 'package:provider/provider.dart';
import 'package:get/get.dart';
// to-do: do not depend on desktop
import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart';
import 'package:flutter_hbb/models/platform_model.dart';
@@ -12,11 +14,15 @@ import './desc.dart';
import './model.dart';
import './common.dart';
// dup to flutter\lib\desktop\pages\desktop_setting_page.dart
const double _kCheckBoxLeftMargin = 10;
class LocationItem extends StatelessWidget {
final String peerId;
final FFI ffi;
final String location;
final LocationModel locationModel;
final bool isMenu;
LocationItem({
Key? key,
@@ -24,19 +30,23 @@ class LocationItem extends StatelessWidget {
required this.ffi,
required this.location,
required this.locationModel,
required this.isMenu,
}) : super(key: key);
bool get isEmpty => locationModel.isEmpty;
static LocationItem createLocationItem(
String peerId, FFI ffi, String location) {
final model = addLocation(location);
return LocationItem(
peerId: peerId,
ffi: ffi,
location: location,
locationModel: model,
);
static Widget createLocationItem(
String peerId, FFI ffi, String location, bool isMenu) {
final model = getLocationModel(location);
return model == null
? Container()
: LocationItem(
peerId: peerId,
ffi: ffi,
location: location,
locationModel: model,
isMenu: isMenu,
);
}
@override
@@ -59,23 +69,26 @@ class LocationItem extends StatelessWidget {
ffi: ffi,
location: location,
pluginModel: model,
isMenu: isMenu,
);
}
class PluginItem extends StatelessWidget {
final PluginId pluginId;
final String peerId;
final FFI ffi;
final FFI? ffi;
final String location;
final PluginModel pluginModel;
final isMenu;
PluginItem({
Key? key,
required this.pluginId,
required this.peerId,
required this.ffi,
this.ffi,
required this.location,
required this.pluginModel,
required this.isMenu,
}) : super(key: key);
bool get isEmpty => pluginModel.isEmpty;
@@ -95,74 +108,88 @@ class PluginItem extends StatelessWidget {
}
Widget _buildItem(UiType ui) {
late Widget child;
Widget? child;
switch (ui.runtimeType) {
case UiButton:
child = _buildMenuButton(ui as UiButton);
if (isMenu) {
if (ffi != null) {
child = _buildMenuButton(ui as UiButton, ffi!);
}
} else {
child = _buildButton(ui as UiButton);
}
break;
case UiCheckbox:
child = _buildCheckboxMenuButton(ui as UiCheckbox);
if (isMenu) {
if (ffi != null) {
child = _buildCheckboxMenuButton(ui as UiCheckbox, ffi!);
}
} else {
child = _buildCheckbox(ui as UiCheckbox);
}
break;
default:
child = Container();
break;
}
// to-do: add plugin icon and tooltip
return child;
return child ?? Container();
}
Uint8List _makeEvent(
String key, {
bool? v,
}) {
final event = MsgFromUi(
id: pluginId,
name: getDesc(pluginId)?.name ?? '',
location: location,
key: key,
value:
v != null ? (v ? ConfigItem.trueValue : ConfigItem.falseValue) : '',
action: '',
);
return Uint8List.fromList(event.toString().codeUnits);
}
Widget _buildMenuButton(UiButton ui) {
return MenuButton(
Widget _buildButton(UiButton ui) {
return TextButton(
onPressed: () => bind.pluginEvent(
id: pluginId,
peer: peerId,
event: _makeEvent(ui.key),
),
// to-do: support trailing icon, but it will cause tree shake error.
// ```
// This application cannot tree shake icons fonts. It has non-constant instances of IconData at the following locations:
// Target release_macos_bundle_flutter_assets failed: Exception: Avoid non-constant invocations of IconData or try to build again with --no-tree-shake-icons.
// ```
//
// trailingIcon: Icon(
// IconData(int.parse(ui.icon, radix: 16), fontFamily: 'MaterialIcons')),
//
// to-do: RustDesk translate or plugin translate ?
child: Text(ui.text),
ffi: ffi,
);
}
String? getOption(OptionModel model, String key) {
var v = model.value;
if (v == null) {
if (peerId.isEmpty) {
v = bind.pluginGetSharedOption(id: pluginId, key: key);
} else {
v = bind.pluginGetSessionOption(id: pluginId, peer: peerId, key: key);
Widget _buildCheckbox(UiCheckbox ui) {
getChild(OptionModel model) {
final v = _getOption(model, ui.key);
if (v == null) {
// session or plugin not found
return Container();
}
onChanged(bool value) {
bind.pluginEvent(
id: pluginId,
peer: peerId,
event: _makeEvent(ui.key, v: value),
);
}
final value = ConfigItem.isTrue(v);
return GestureDetector(
child: Row(
children: [
Checkbox(
value: value,
onChanged: (_) => onChanged(!value),
).marginOnly(right: 5),
Expanded(
child: Text(translate(ui.text)),
)
],
).marginOnly(left: _kCheckBoxLeftMargin),
onTap: () => onChanged(!value),
);
}
return v;
return ChangeNotifierProvider.value(
value: getOptionModel(location, pluginId, peerId, ui.key),
child: Consumer<OptionModel>(
builder: (context, model, child) => getChild(model),
),
);
}
Widget _buildCheckboxMenuButton(UiCheckbox ui) {
Widget _buildCheckboxMenuButton(UiCheckbox ui, FFI ffi) {
getChild(OptionModel model) {
final v = getOption(model, ui.key);
final v = _getOption(model, ui.key);
if (v == null) {
// session or plugin not found
return Container();
@@ -184,16 +211,63 @@ class PluginItem extends StatelessWidget {
);
}
final optionModel = addOptionModel(location, pluginId, peerId, ui.key);
return ChangeNotifierProvider.value(
value: optionModel,
value: getOptionModel(location, pluginId, peerId, ui.key),
child: Consumer<OptionModel>(
builder: (context, model, child) {
return getChild(model);
},
builder: (context, model, child) => getChild(model),
),
);
}
Widget _buildMenuButton(UiButton ui, FFI ffi) {
return MenuButton(
onPressed: () => bind.pluginEvent(
id: pluginId,
peer: peerId,
event: _makeEvent(ui.key),
),
// to-do: support trailing icon, but it will cause tree shake error.
// ```
// This application cannot tree shake icons fonts. It has non-constant instances of IconData at the following locations:
// Target release_macos_bundle_flutter_assets failed: Exception: Avoid non-constant invocations of IconData or try to build again with --no-tree-shake-icons.
// ```
//
// trailingIcon: Icon(
// IconData(int.parse(ui.icon, radix: 16), fontFamily: 'MaterialIcons')),
//
// to-do: RustDesk translate or plugin translate ?
child: Text(ui.text),
ffi: ffi,
);
}
Uint8List _makeEvent(
String key, {
bool? v,
}) {
final event = MsgFromUi(
id: pluginId,
name: getDesc(pluginId)?.name ?? '',
location: location,
key: key,
value:
v != null ? (v ? ConfigItem.trueValue : ConfigItem.falseValue) : '',
action: '',
);
return Uint8List.fromList(event.toString().codeUnits);
}
String? _getOption(OptionModel model, String key) {
var v = model.value;
if (v == null) {
if (peerId.isEmpty) {
v = bind.pluginGetSharedOption(id: pluginId, key: key);
} else {
v = bind.pluginGetSessionOption(id: pluginId, peer: peerId, key: key);
}
}
return v;
}
}
void handleReloading(Map<String, dynamic> evt, String peer) {