plugin_framework, test install plugin

Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
fufesou
2023-05-10 18:58:45 +08:00
parent b06fad0e43
commit 4ee0fd9676
12 changed files with 271 additions and 166 deletions

View File

@@ -0,0 +1,301 @@
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';
import '../manager.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,
required this.peerId,
required this.ffi,
required this.location,
required this.locationModel,
required this.isMenu,
}) : super(key: key);
bool get isEmpty => locationModel.isEmpty;
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
Widget build(BuildContext context) {
return ChangeNotifierProvider.value(
value: locationModel,
child: Consumer<LocationModel>(builder: (context, model, child) {
return Column(
children: model.pluginModels.entries
.map((entry) => _buildPluginItem(entry.key, entry.value))
.toList(),
);
}),
);
}
Widget _buildPluginItem(PluginId id, PluginModel model) => PluginItem(
pluginId: id,
peerId: peerId,
ffi: ffi,
location: location,
pluginModel: model,
isMenu: isMenu,
);
}
class PluginItem extends StatelessWidget {
final PluginId pluginId;
final String peerId;
final FFI? ffi;
final String location;
final PluginModel pluginModel;
final bool isMenu;
PluginItem({
Key? key,
required this.pluginId,
required this.peerId,
this.ffi,
required this.location,
required this.pluginModel,
required this.isMenu,
}) : super(key: key);
bool get isEmpty => pluginModel.isEmpty;
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider.value(
value: pluginModel,
child: Consumer<PluginModel>(
builder: (context, pluginModel, child) {
return Column(
children: pluginModel.uiList.map((ui) => _buildItem(ui)).toList(),
);
},
),
);
}
Widget _buildItem(UiType ui) {
Widget? child;
switch (ui.runtimeType) {
case UiButton:
if (isMenu) {
if (ffi != null) {
child = _buildMenuButton(ui as UiButton, ffi!);
}
} else {
child = _buildButton(ui as UiButton);
}
break;
case UiCheckbox:
if (isMenu) {
if (ffi != null) {
child = _buildCheckboxMenuButton(ui as UiCheckbox, ffi!);
}
} else {
child = _buildCheckbox(ui as UiCheckbox);
}
break;
default:
break;
}
// to-do: add plugin icon and tooltip
return child ?? Container();
}
Widget _buildButton(UiButton ui) {
return TextButton(
onPressed: () => bind.pluginEvent(
id: pluginId,
peer: peerId,
event: _makeEvent(ui.key),
),
child: Text(ui.text),
);
}
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 ChangeNotifierProvider.value(
value: getOptionModel(location, pluginId, peerId, ui.key),
child: Consumer<OptionModel>(
builder: (context, model, child) => getChild(model),
),
);
}
Widget _buildCheckboxMenuButton(UiCheckbox ui, FFI ffi) {
getChild(OptionModel model) {
final v = _getOption(model, ui.key);
if (v == null) {
// session or plugin not found
return Container();
}
return CkbMenuButton(
value: ConfigItem.isTrue(v),
onChanged: (v) {
if (v != null) {
bind.pluginEvent(
id: pluginId,
peer: peerId,
event: _makeEvent(ui.key, v: v),
);
}
},
// to-do: RustDesk translate or plugin translate ?
child: Text(ui.text),
ffi: ffi,
);
}
return ChangeNotifierProvider.value(
value: getOptionModel(location, pluginId, peerId, ui.key),
child: Consumer<OptionModel>(
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: pluginManager.getPlugin(pluginId)?.meta.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) {
try {
if (peerId.isEmpty) {
v = bind.pluginGetSharedOption(id: pluginId, key: key);
} else {
v = bind.pluginGetSessionOption(id: pluginId, peer: peerId, key: key);
}
} catch (e) {
debugPrint('Failed to get option "$key", $e');
v = null;
}
}
return v;
}
}
void handleReloading(Map<String, dynamic> evt, String peer) {
if (evt['id'] == null || evt['location'] == null) {
return;
}
try {
final uiList = <UiType>[];
for (var e in json.decode(evt['ui'] as String)) {
final ui = UiType.create(e);
if (ui != null) {
uiList.add(ui);
}
}
if (uiList.isNotEmpty) {
addLocationUi(evt['location']!, evt['id']!, uiList);
}
} catch (e) {
debugPrint('Failed handleReloading, json decode of ui, $e ');
}
}
void handleOption(Map<String, dynamic> evt, String peer) {
updateOption(
evt['location'], evt['id'], evt['peer'] ?? '', evt['key'], evt['value']);
}

View File

@@ -0,0 +1,199 @@
import 'package:flutter/material.dart';
import 'package:flutter_hbb/common.dart';
import 'package:flutter_hbb/models/platform_model.dart';
import 'package:flutter_hbb/plugin/model.dart';
import 'package:flutter_hbb/plugin/common.dart';
import 'package:get/get.dart';
import '../manager.dart';
import './desc_ui.dart';
// to-do: use settings from desktop_setting_page.dart
const double _kCardFixedWidth = 540;
const double _kCardLeftMargin = 15;
const double _kContentHMargin = 15;
const double _kTitleFontSize = 20;
const double _kVersionFontSize = 12;
class DesktopSettingsCard extends StatefulWidget {
final PluginInfo plugin;
DesktopSettingsCard({
Key? key,
required this.plugin,
}) : super(key: key);
@override
State<DesktopSettingsCard> createState() => _DesktopSettingsCardState();
}
class _DesktopSettingsCardState extends State<DesktopSettingsCard> {
PluginInfo get plugin => widget.plugin;
bool get installed => plugin.installedVersion.isNotEmpty;
@override
Widget build(BuildContext context) {
return Row(
children: [
Flexible(
child: SizedBox(
width: _kCardFixedWidth,
child: Card(
child: Column(
children: [
header(),
body(),
],
).marginOnly(bottom: 10),
).marginOnly(left: _kCardLeftMargin, top: 15),
),
),
],
);
}
Widget header() {
return Row(
children: [
headerNameVersion(),
headerInstallEnable(),
],
).marginOnly(
left: _kContentHMargin,
top: 10,
bottom: 10,
right: _kContentHMargin,
);
}
Widget headerNameVersion() {
return Expanded(
child: Row(
children: [
Text(
translate(widget.plugin.meta.name),
textAlign: TextAlign.start,
style: const TextStyle(
fontSize: _kTitleFontSize,
),
),
SizedBox(
width: 5,
),
Text(
plugin.meta.version,
textAlign: TextAlign.start,
style: const TextStyle(
fontSize: _kVersionFontSize,
),
)
],
),
);
}
Widget headerButton(String label, VoidCallback onPressed) {
return Container(
child: ElevatedButton(
onPressed: onPressed,
child: Text(label),
),
);
}
Widget headerInstallEnable() {
final installButton = headerButton(installed ? 'uninstall' : 'install', () {
bind.pluginInstall(
id: plugin.meta.id,
b: !installed,
);
});
if (installed) {
final needUpdate =
plugin.installedVersion.compareTo(plugin.meta.version) < 0;
final updateButton = needUpdate
? headerButton('update', () {
bind.pluginInstall(
id: plugin.meta.id,
b: !installed,
);
})
: Container();
final isEnabled = bind.pluginIsEnabled(id: plugin.meta.id);
final enableButton = !installed
? Container()
: headerButton(isEnabled ? 'disable' : 'enable', () {
if (isEnabled) {
clearPlugin(plugin.meta.id);
}
bind.pluginEnable(id: plugin.meta.id, v: !isEnabled);
setState(() {});
});
return Row(
children: [
updateButton,
SizedBox(
width: 10,
),
installButton,
SizedBox(
width: 10,
),
enableButton,
],
);
} else {
return installButton;
}
}
Widget body() {
return Column(children: [
author(),
description(),
more(),
]).marginOnly(
left: _kCardLeftMargin,
top: 4,
right: _kContentHMargin,
);
}
Widget author() {
return Align(
alignment: Alignment.centerLeft,
child: Text(plugin.meta.author),
);
}
Widget description() {
return Align(
alignment: Alignment.centerLeft,
child: Text(plugin.meta.description),
);
}
Widget more() {
if (!installed) {
return Container();
}
final List<Widget> children = [];
final model = getPluginModel(kLocationHostMainPlugin, plugin.meta.id);
if (model != null) {
children.add(PluginItem(
pluginId: plugin.meta.id,
peerId: '',
location: kLocationHostMainPlugin,
pluginModel: model,
isMenu: false,
));
}
return ExpansionTile(
title: Text('Options'),
controlAffinity: ListTileControlAffinity.leading,
children: children,
);
}
}