Organize the code

This commit is contained in:
Caten
2023-11-09 12:48:55 +08:00
parent 86ce2315d4
commit cf8ce47662
2 changed files with 524 additions and 501 deletions

View File

@@ -94,6 +94,276 @@ class _FakeLoadingStatusState extends State<FakeLoadingStatus> {
} }
} }
class SettingPage extends StatefulWidget {
const SettingPage({super.key});
@override
State<SettingPage> createState() => _SettingPageState();
}
class _SettingPageState extends State<SettingPage> {
final List<bool> _expandState = [false, false, false, false, false];
@override
Widget build(BuildContext context) {
return ExpansionPanelList(
elevation: 1,
expandedHeaderPadding: const EdgeInsets.all(0),
expansionCallback: (panelIndex, isExpanded) {
setState(() {
_expandState[panelIndex] = isExpanded;
});
},children: [
ExpansionPanel(
isExpanded: _expandState[0],
headerBuilder: ((context, isExpanded) {
return const ListTile(title: Text("高级设置"), subtitle: Text("修改后重启生效"));
}), body: Padding(padding: const EdgeInsets.all(12), child: Column(children: [
TextFormField(maxLines: null, initialValue: Util.getCurrentProp("name"), decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "容器名称"), onChanged: (value) async {
await Util.setCurrentProp("name", value);
//setState(() {});
}),
SizedBox.fromSize(size: const Size.square(8)),
TextFormField(maxLines: null, initialValue: Util.getCurrentProp("boot"), decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "启动命令"), onChanged: (value) async {
await Util.setCurrentProp("boot", value);
}),
SizedBox.fromSize(size: const Size.square(8)),
TextFormField(maxLines: null, initialValue: Util.getCurrentProp("vnc"), decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "vnc启动命令"), onChanged: (value) async {
await Util.setCurrentProp("vnc", value);
}),
SizedBox.fromSize(size: const Size.square(8)),
TextFormField(maxLines: null, initialValue: Util.getCurrentProp("vncUrl"), decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "网页跳转地址"), onChanged: (value) async {
await Util.setCurrentProp("vncUrl", value);
}),
],))),
ExpansionPanel(
isExpanded: _expandState[1],
headerBuilder: ((context, isExpanded) {
return const ListTile(title: Text("全局设置"), subtitle: Text("在这里关广告、开启终端编辑"));
}), body: Padding(padding: const EdgeInsets.all(12), child: Column(children: [
TextFormField(autovalidateMode: AutovalidateMode.onUserInteraction, initialValue: (Util.getGlobal("termMaxLines") as int).toString(), decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "终端最大行数(重启软件生效)"), readOnly: Util.shouldWatchAds(D.adsRequired["changeTermMaxLines"]!),
keyboardType: TextInputType.number,
onTap: () {
if (Util.shouldWatchAds(D.adsRequired["changeTermMaxLines"]!)) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("观看六次视频广告永久解锁><"))
);
}
},
validator: (value) {
return Util.validateBetween(value, 1024, 2147483647, () async {
await G.prefs.setInt("termMaxLines", int.parse(value!));
});
},),
SizedBox.fromSize(size: const Size.square(16)),
TextFormField(autovalidateMode: AutovalidateMode.onUserInteraction, initialValue: (Util.getGlobal("defaultAudioPort") as int).toString(), decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "pulseaudio接收端口"),
keyboardType: TextInputType.number,
validator: (value) {
return Util.validateBetween(value, 0, 65535, () async {
await G.prefs.setInt("defaultAudioPort", int.parse(value!));
});
}
),
SizedBox.fromSize(size: const Size.square(16)),
SwitchListTile(title: const Text("关闭横幅广告"), value: Util.getGlobal("isBannerAdsClosed") as bool, onChanged:(value) {
if (value && Util.shouldWatchAds(D.adsRequired["closeBannerAds"]!)) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("观看五次视频广告永久解锁><"))
);
return;
}
G.prefs.setBool("isBannerAdsClosed", value);
setState(() {});
},),
SizedBox.fromSize(size: const Size.square(8)),
SwitchListTile(title: const Text("启用终端"), value: Util.getGlobal("isTerminalWriteEnabled") as bool, onChanged:(value) {
if (value && Util.shouldWatchAds(D.adsRequired["enableTerminalWrite"]!)) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: const Text("观看两次视频广告永久解锁><"), action: SnackBarAction(label: "啊?", onPressed: () {
G.prefs.setBool("isTerminalWriteEnabled", value);
setState(() {});
},))
);
return;
}
G.prefs.setBool("isTerminalWriteEnabled", value);
setState(() {});
},),
SizedBox.fromSize(size: const Size.square(8)),
SwitchListTile(title: const Text("启用终端小键盘"), value: Util.getGlobal("isTerminalCommandsEnabled") as bool, onChanged:(value) {
if (value && Util.shouldWatchAds(D.adsRequired["enableTerminalCommands"]!)) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("观看三次视频广告永久解锁><"))
);
return;
}
G.prefs.setBool("isTerminalCommandsEnabled", value);
setState(() {});
},),
SizedBox.fromSize(size: const Size.square(8)),
SwitchListTile(title: const Text("终端粘滞键"), value: Util.getGlobal("isStickyKey") as bool, onChanged:(value) {
G.prefs.setBool("isStickyKey", value);
setState(() {});
},),
SizedBox.fromSize(size: const Size.square(8)),
SwitchListTile(title: const Text("开启时启动图形界面"), value: Util.getGlobal("autoLaunchVnc") as bool, onChanged:(value) {
G.prefs.setBool("autoLaunchVnc", value);
setState(() {});
},),
],))),
ExpansionPanel(
isExpanded: _expandState[2],
headerBuilder: ((context, isExpanded) {
return const ListTile(title: Text("相机推流"), subtitle: Text("实验性功能"));
}), body: Padding(padding: const EdgeInsets.all(12), child: Column(children: [
const Text("成功启动推流后可以点击快捷指令\"拉流测试\"并前往图形界面查看效果。\n注意这并不能为系统创建一个虚拟相机;\n另外使用相机是高耗电行为,不用时需及时关闭。"),
const SizedBox.square(dimension: 16),
Wrap(alignment: WrapAlignment.center, spacing: 4.0, runSpacing: 4.0, children: [
OutlinedButton(style: D.commandButtonStyle, child: const Text("申请相机权限"), onPressed: () {
Permission.camera.request();
}),
OutlinedButton(style: D.commandButtonStyle, child: const Text("申请麦克风权限"), onPressed: () {
Permission.microphone.request();
}),
OutlinedButton(style: D.commandButtonStyle, child: const Text("查看输出"), onPressed: () {
if (G.streamingOutput == "") {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("无输出"))
);
return;
}
showDialog(context: context, builder: (context) {
return AlertDialog(content: SingleChildScrollView(child:
Text(G.streamingOutput)), actions: [
TextButton(onPressed:() {
FlutterClipboard.copy(G.streamingOutput).then(( value ) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("已复制")));
});
Navigator.of(context).pop();
}, child: const Text("复制")),
TextButton(onPressed:() {
Navigator.of(context).pop();
}, child: const Text("取消")),
]);
});
}),
]),
const SizedBox.square(dimension: 16),
TextFormField(maxLines: null, initialValue: Util.getGlobal("defaultFFmpegCommand") as String, decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "ffmpeg推流命令"), readOnly: Util.shouldWatchAds(D.adsRequired["changeFFmpegCommand"]!),
onTap: () {
if (Util.shouldWatchAds(D.adsRequired["changeFFmpegCommand"]!)) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("观看八次视频广告永久解锁><"))
);
}
},
onChanged: (value) async {
await G.prefs.setString("defaultFFmpegCommand", value);
},
),
const SizedBox.square(dimension: 16),
SwitchListTile(title: const Text("启动推流服务器"), subtitle: const Text("mediamtx"), value: G.isStreamServerStarted, onChanged:(value) {
switch (value) {
case true: {
G.streamServerPty = Pty.start("/system/bin/sh");
G.streamServerPty.write(const Utf8Encoder().convert("${G.dataPath}/bin/mediamtx ${G.dataPath}/bin/mediamtx.yml & pid=\$(echo \$!)\n"));
G.streamServerPty.exitCode.then((value) {
G.isStreamServerStarted = false;
setState(() {});
});
}
break;
case false: {
G.streamServerPty.write(const Utf8Encoder().convert("kill \$pid\nexit\n"));
}
break;
}
G.isStreamServerStarted = value;
setState(() {});
},),
SizedBox.fromSize(size: const Size.square(8)),
SwitchListTile(title: const Text("启动推流"), value: G.isStreaming, onChanged:(value) {
switch (value) {
case true: {
FFmpegKit.execute(Util.getGlobal("defaultFFmpegCommand") as String).then((session) {
session.getOutput().then((value) async {
G.isStreaming = false;
G.streamingOutput = value??"";
setState(() {});
});
});
}
break;
case false: {
FFmpegKit.cancel();
}
break;
}
G.isStreaming = value;
setState(() {});
},),
SizedBox.fromSize(size: const Size.square(8))
],))),
ExpansionPanel(
isExpanded: _expandState[3],
headerBuilder: ((context, isExpanded) {
return const ListTile(title: Text("文件访问"));
}), body: Padding(padding: const EdgeInsets.all(12), child: Column(children: [
const Text("通过这里获取更多文件权限,以实现对特殊目录的访问。"),
SizedBox.fromSize(size: const Size.square(16)),
Wrap(alignment: WrapAlignment.center, spacing: 4.0, runSpacing: 4.0, children: [
OutlinedButton(style: D.commandButtonStyle, child: const Text("申请存储权限"), onPressed: () {
Permission.storage.request();
}),
OutlinedButton(style: D.commandButtonStyle, child: const Text("申请所有文件访问权限"), onPressed: () {
Permission.manageExternalStorage.request();
}),
]),
SizedBox.fromSize(size: const Size.square(16)),
],))),
ExpansionPanel(
isExpanded: _expandState[4],
headerBuilder: ((context, isExpanded) {
return const ListTile(title: Text("广告记录"), subtitle: Text("在这里看广告"));
}), body: Padding(padding: const EdgeInsets.all(12), child: Column(children: [
OutlinedButton(child: const Text("看一个广告"), onPressed: () {
if (AdManager.placements[AdManager.rewardedVideoAdPlacementId]!) {
AdManager.showAd(AdManager.rewardedVideoAdPlacementId, () {
final bonus = Util.getRandomBonus();
Util.applyBonus(bonus);
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("你获得了 ${bonus["name"]}*${bonus["amount"]}"))
);
setState(() {
});
}, () {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("已经看5个广告了, 今天也非常感谢><"))
);
});
}
}),
const SizedBox.square(dimension: 8),
Text(Util.getGlobal("adsBonus").map((element) {
final e = jsonDecode(element);
return e["amount"]==0?"":"${e["name"]}*${e["amount"]}\n";
}).join())
],))),
],);
}
}
class InfoPage extends StatefulWidget { class InfoPage extends StatefulWidget {
const InfoPage({super.key}); const InfoPage({super.key});
@@ -119,7 +389,12 @@ class _InfoPageState extends State<InfoPage> {
return const ListTile(title: Text("使用说明")); return const ListTile(title: Text("使用说明"));
}, },
body: const Padding(padding: EdgeInsets.all(8), child: Text(""" body: const Padding(padding: EdgeInsets.all(8), child: Text("""
第一次加载, 大概需要5到10分钟... 第一次加载大概需要5到10分钟...
加载完成后,软件会自动跳转到图形界面
在图形界面返回,可以回到终端界面和控制界面
你可以在控制界面安装更多软件或者阅读帮助信息
请不要在安装时退出软件 请不要在安装时退出软件
如果过了很长时间都没有加载完成 如果过了很长时间都没有加载完成
@@ -723,36 +998,34 @@ class LoadingPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return const Padding( return const Padding(
padding: EdgeInsets.all(8), padding: EdgeInsets.all(8),
child: Column( child: Column(
children: [ children: [
Padding( Padding(
padding: EdgeInsets.fromLTRB(16, 16, 16, 16), padding: EdgeInsets.fromLTRB(16, 16, 16, 16),
child: FractionallySizedBox( child: FractionallySizedBox(
widthFactor: 0.4, widthFactor: 0.4,
child: Image( child: Image(
image: AssetImage("images/icon.png") image: AssetImage("images/icon.png")
)
),
),
Padding(
padding: EdgeInsets.fromLTRB(0, 0, 0, 8),
child: Text("小小电脑", textScaleFactor: 2),
),
FakeLoadingStatus(),
Expanded(child:
Padding(padding: EdgeInsets.all(8), child: Card(child: Padding(padding: EdgeInsets.all(8), child:
Scrollbar(child:
SingleChildScrollView(
child: InfoPage()
)
)
))
,))
]
) )
); ),
),
Padding(
padding: EdgeInsets.fromLTRB(0, 0, 0, 8),
child: Text("小小电脑", textScaleFactor: 2),
),
FakeLoadingStatus(),
Expanded(child: Padding(padding: EdgeInsets.all(8), child: Card(child: Padding(padding: EdgeInsets.all(8), child:
Scrollbar(child:
SingleChildScrollView(
child: InfoPage()
)
)
))
,))
]
)
);
} }
} }
@@ -781,6 +1054,145 @@ RawGestureDetector forceScaleGestureDetector({
); );
} }
class TerminalPage extends StatelessWidget {
const TerminalPage({super.key});
@override
Widget build(BuildContext context) {
return Column(children: [Expanded(child: forceScaleGestureDetector(onScaleUpdate: (details) {
G.termFontScale.value = (details.scale * (Util.getGlobal("termFontScale") as double)).clamp(0.2, 5);
}, onScaleEnd: (details) async {
await G.prefs.setDouble("termFontScale", G.termFontScale.value);
}, child: ValueListenableBuilder(valueListenable: G.termFontScale, builder:(context, value, child) {
return TerminalView(G.termPtys[G.currentContainer]!.terminal, textScaleFactor: G.termFontScale.value, keyboardType: TextInputType.multiline);
},) )),
(Util.getGlobal("isTerminalCommandsEnabled") as bool)?Padding(padding: const EdgeInsets.all(8), child: Row(children: [AnimatedBuilder(
animation: G.keyboard,
builder: (context, child) => ToggleButtons(
constraints: const BoxConstraints(minWidth: 32, minHeight: 24),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
borderRadius: const BorderRadius.all(Radius.circular(8)),
isSelected: [G.keyboard.ctrl, G.keyboard.alt, G.keyboard.shift],
onPressed: (index) {
switch (index) {
case 0:
G.keyboard.ctrl = !G.keyboard.ctrl;
break;
case 1:
G.keyboard.alt = !G.keyboard.alt;
break;
case 2:
G.keyboard.shift = !G.keyboard.shift;
break;
}
},
children: const [Text('Ctrl'), Text('Alt'), Text('Shift')],
),
),
SizedBox.fromSize(size: const Size.square(8)),
Expanded(child: SizedBox(height: 24, child: ListView.separated(scrollDirection: Axis.horizontal, itemBuilder:(context, index) {
return OutlinedButton(style: D.controlButtonStyle, onPressed: () {
G.termPtys[G.currentContainer]!.terminal.keyInput(D.termCommands[index]["key"]! as TerminalKey);
}, child: Text(D.termCommands[index]["name"]! as String));
}, separatorBuilder:(context, index) {
return SizedBox.fromSize(size: const Size.square(4));
}, itemCount: D.termCommands.length))), SizedBox.fromSize(size: const Size(72, 0))])):SizedBox.fromSize(size: const Size.square(0))
]);
}
}
class FastCommands extends StatefulWidget {
const FastCommands({super.key});
@override
State<FastCommands> createState() => _FastCommandsState();
}
class _FastCommandsState extends State<FastCommands> {
@override
Widget build(BuildContext context) {
return Wrap(alignment: WrapAlignment.center, spacing: 4.0, runSpacing: 4.0, children: Util.getCurrentProp("commands")
.asMap().entries.map<Widget>((e) {
return OutlinedButton(style: D.commandButtonStyle, child: Text(e.value["name"]!), onPressed: () {
Util.termWrite(e.value["command"]!);
G.pageIndex.value = 0;
}, onLongPress: () {
String name = e.value["name"]!;
String command = e.value["command"]!;
showDialog(context: context, builder: (context) {
return AlertDialog(title: const Text("指令编辑"), content: SingleChildScrollView(child: Column(children: [
TextFormField(initialValue: name, decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "指令名称"), onChanged: (value) {
name = value;
}),
SizedBox.fromSize(size: const Size.square(8)),
TextFormField(maxLines: null, initialValue: command, decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "指令内容"), onChanged: (value) {
command = value;
}),
])), actions: [
TextButton(onPressed:() async {
await Util.setCurrentProp("commands", Util.getCurrentProp("commands")
..removeAt(e.key));
setState(() {});
if (!context.mounted) return;
Navigator.of(context).pop();
}, child: const Text("删除该项")),
TextButton(onPressed:() {
Navigator.of(context).pop();
}, child: const Text("取消")),
TextButton(onPressed:() async {
await Util.setCurrentProp("commands", Util.getCurrentProp("commands")
..setAll(e.key, [{"name": name, "command": command}]));
setState(() {});
if (!context.mounted) return;
Navigator.of(context).pop();
}, child: const Text("保存")),
]);
},);
},);
}).toList()..add(OutlinedButton(style: D.commandButtonStyle, onPressed:() {
String name = "";
String command = "";
showDialog(context: context, builder: (context) {
return AlertDialog(title: const Text("指令编辑"), content: SingleChildScrollView(child: Column(children: [
TextFormField(initialValue: name, decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "指令名称"), onChanged: (value) {
name = value;
}),
SizedBox.fromSize(size: const Size.square(8)),
TextFormField(maxLines: null, initialValue: command, decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "指令内容"), onChanged: (value) {
command = value;
}),
])), actions: [
TextButton(onPressed:() {
Navigator.of(context).pop();
}, child: const Text("取消")),
TextButton(onPressed:() async {
await Util.setCurrentProp("commands", Util.getCurrentProp("commands")
..add({"name": name, "command": command}));
setState(() {});
if (!context.mounted) return;
Navigator.of(context).pop();
}, child: const Text("添加")),
]);
},);
}, onLongPress: () {
showDialog(context: context, builder: (context) {
return AlertDialog(content: const Text("是否重置所有快捷指令?"), actions: [
TextButton(onPressed:() {
Navigator.of(context).pop();
}, child: const Text("取消")),
TextButton(onPressed:() async {
await Util.setCurrentProp("commands", D.commands);
setState(() {});
if (!context.mounted) return;
Navigator.of(context).pop();
}, child: const Text("")),
]);
});
}, child: const Text("添加快捷指令"))));
}
}
class MyHomePage extends StatefulWidget { class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title}); const MyHomePage({super.key, required this.title});
@@ -792,30 +1204,11 @@ class MyHomePage extends StatefulWidget {
class _MyHomePageState extends State<MyHomePage> { class _MyHomePageState extends State<MyHomePage> {
//高级设置,全局设置
final List<bool> _expandState = [false, false, false, false, false];
bool bannerAdsFailedToLoad = false; bool bannerAdsFailedToLoad = false;
//安装完成了吗? //安装完成了吗?
//完成后从加载界面切换到主界面 //完成后从加载界面切换到主界面
bool isLoadingComplete = false; bool isLoadingComplete = false;
//主界面索引
int pageIndex = 0;
final ButtonStyle commandButtonStyle = OutlinedButton.styleFrom(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
minimumSize: const Size(0, 0),
padding: const EdgeInsets.fromLTRB(4, 2, 4, 2)
);
final ButtonStyle controlButtonStyle = OutlinedButton.styleFrom(
textStyle: const TextStyle(fontWeight: FontWeight.w400),
side: const BorderSide(color: Color(0x1F000000)),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
minimumSize: const Size(0, 0),
padding: const EdgeInsets.fromLTRB(8, 4, 8, 4)
);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -847,48 +1240,8 @@ class _MyHomePageState extends State<MyHomePage> {
bannerAdsFailedToLoad = true; bannerAdsFailedToLoad = true;
}); });
}, },
), Expanded(child: AnimatedSwitcher( ), Expanded(child: ValueListenableBuilder(valueListenable: G.pageIndex, builder: (context, value, child) {
duration: const Duration(milliseconds: 256), return IndexedStack(index: G.pageIndex.value, children: [const TerminalPage(), Padding(
child: [
Column(children: [Expanded(child: forceScaleGestureDetector(onScaleUpdate: (details) {
setState(() {
G.termFontScale = (details.scale * (Util.getGlobal("termFontScale") as double)).clamp(0.2, 5);
});
}, onScaleEnd: (details) async {
await G.prefs.setDouble("termFontScale", G.termFontScale);
}, child: TerminalView(G.termPtys[G.currentContainer]!.terminal, textScaleFactor: G.termFontScale, keyboardType: TextInputType.multiline,))),
(Util.getGlobal("isTerminalCommandsEnabled") as bool)?Padding(padding: const EdgeInsets.all(8), child: Row(children: [AnimatedBuilder(
animation: G.keyboard,
builder: (context, child) => ToggleButtons(
constraints: const BoxConstraints(minWidth: 32, minHeight: 24),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
borderRadius: const BorderRadius.all(Radius.circular(8)),
isSelected: [G.keyboard.ctrl, G.keyboard.alt, G.keyboard.shift],
onPressed: (index) {
switch (index) {
case 0:
G.keyboard.ctrl = !G.keyboard.ctrl;
break;
case 1:
G.keyboard.alt = !G.keyboard.alt;
break;
case 2:
G.keyboard.shift = !G.keyboard.shift;
break;
}
},
children: const [Text('Ctrl'), Text('Alt'), Text('Shift')],
),
),
SizedBox.fromSize(size: const Size.square(8)),
Expanded(child: SizedBox(height: 24, child: ListView.separated(scrollDirection: Axis.horizontal, itemBuilder:(context, index) {
return OutlinedButton(style: controlButtonStyle, onPressed: () {
G.termPtys[G.currentContainer]!.terminal.keyInput(D.termCommands[index]["key"]! as TerminalKey);
}, child: Text(D.termCommands[index]["name"]! as String));
}, separatorBuilder:(context, index) {
return SizedBox.fromSize(size: const Size.square(4));
}, itemCount: D.termCommands.length))), SizedBox.fromSize(size: const Size(72, 0))])):SizedBox.fromSize(size: const Size.square(0))
]), Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: Scrollbar(child: SingleChildScrollView(restorationId: "control-scroll", child: Column( child: Scrollbar(child: SingleChildScrollView(restorationId: "control-scroll", child: Column(
children: [ children: [
@@ -905,374 +1258,39 @@ class _MyHomePageState extends State<MyHomePage> {
padding: const EdgeInsets.fromLTRB(0, 0, 0, 8), padding: const EdgeInsets.fromLTRB(0, 0, 0, 8),
child: Text(Util.getCurrentProp("name"), textScaleFactor: 2), child: Text(Util.getCurrentProp("name"), textScaleFactor: 2),
),*/ ),*/
Wrap(alignment: WrapAlignment.center, spacing: 4.0, runSpacing: 4.0, children: Util.getCurrentProp("commands") const FastCommands(),
.asMap().entries.map<Widget>((e) {
return OutlinedButton(style: commandButtonStyle, child: Text(e.value["name"]!), onPressed: () {
setState(() {
Util.termWrite(e.value["command"]!);
pageIndex = 0;
});
}, onLongPress: () {
String name = e.value["name"]!;
String command = e.value["command"]!;
showDialog(context: context, builder: (context) {
return AlertDialog(title: const Text("指令编辑"), content: SingleChildScrollView(child: Column(children: [
TextFormField(initialValue: name, decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "指令名称"), onChanged: (value) {
name = value;
}),
SizedBox.fromSize(size: const Size.square(8)),
TextFormField(maxLines: null, initialValue: command, decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "指令内容"), onChanged: (value) {
command = value;
}),
])), actions: [
TextButton(onPressed:() async {
await Util.setCurrentProp("commands", Util.getCurrentProp("commands")
..removeAt(e.key));
setState(() {});
if (!context.mounted) return;
Navigator.of(context).pop();
}, child: const Text("删除该项")),
TextButton(onPressed:() {
Navigator.of(context).pop();
}, child: const Text("取消")),
TextButton(onPressed:() async {
await Util.setCurrentProp("commands", Util.getCurrentProp("commands")
..setAll(e.key, [{"name": name, "command": command}]));
setState(() {});
if (!context.mounted) return;
Navigator.of(context).pop();
}, child: const Text("保存")),
]);
},);
},);
}).toList()..add(OutlinedButton(style: commandButtonStyle, onPressed:() {
String name = "";
String command = "";
showDialog(context: context, builder: (context) {
return AlertDialog(title: const Text("指令编辑"), content: SingleChildScrollView(child: Column(children: [
TextFormField(initialValue: name, decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "指令名称"), onChanged: (value) {
name = value;
}),
SizedBox.fromSize(size: const Size.square(8)),
TextFormField(maxLines: null, initialValue: command, decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "指令内容"), onChanged: (value) {
command = value;
}),
])), actions: [
TextButton(onPressed:() {
Navigator.of(context).pop();
}, child: const Text("取消")),
TextButton(onPressed:() async {
await Util.setCurrentProp("commands", Util.getCurrentProp("commands")
..add({"name": name, "command": command}));
setState(() {});
if (!context.mounted) return;
Navigator.of(context).pop();
}, child: const Text("添加")),
]);
},);
}, onLongPress: () {
showDialog(context: context, builder: (context) {
return AlertDialog(content: const Text("是否重置所有快捷指令?"), actions: [
TextButton(onPressed:() {
Navigator.of(context).pop();
}, child: const Text("取消")),
TextButton(onPressed:() async {
await Util.setCurrentProp("commands", D.commands);
setState(() {});
if (!context.mounted) return;
Navigator.of(context).pop();
}, child: const Text("")),
]);
});
}, child: const Text("添加快捷指令")))),
Padding(padding: const EdgeInsets.all(8), child: Card(child: Padding(padding: const EdgeInsets.all(8), child: Padding(padding: const EdgeInsets.all(8), child: Card(child: Padding(padding: const EdgeInsets.all(8), child:
Column(children: [ Column(children: [
ExpansionPanelList( const SettingPage(),
elevation: 1, SizedBox.fromSize(size: const Size.square(8)),
expandedHeaderPadding: const EdgeInsets.all(0), const InfoPage()
expansionCallback: (panelIndex, isExpanded) { ])
setState(() { )))
_expandState[panelIndex] = isExpanded;
});
},children: [
ExpansionPanel(
isExpanded: _expandState[0],
headerBuilder: ((context, isExpanded) {
return const ListTile(title: Text("高级设置"), subtitle: Text("修改后重启生效"));
}), body: Padding(padding: const EdgeInsets.all(12), child: Column(children: [
TextFormField(maxLines: null, initialValue: Util.getCurrentProp("name"), decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "容器名称"), onChanged: (value) async {
await Util.setCurrentProp("name", value);
setState(() {});
}),
SizedBox.fromSize(size: const Size.square(8)),
TextFormField(maxLines: null, initialValue: Util.getCurrentProp("boot"), decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "启动命令"), onChanged: (value) async {
await Util.setCurrentProp("boot", value);
}),
SizedBox.fromSize(size: const Size.square(8)),
TextFormField(maxLines: null, initialValue: Util.getCurrentProp("vnc"), decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "vnc启动命令"), onChanged: (value) async {
await Util.setCurrentProp("vnc", value);
}),
SizedBox.fromSize(size: const Size.square(8)),
TextFormField(maxLines: null, initialValue: Util.getCurrentProp("vncUrl"), decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "网页跳转地址"), onChanged: (value) async {
await Util.setCurrentProp("vncUrl", value);
}),
],))),
ExpansionPanel(
isExpanded: _expandState[1],
headerBuilder: ((context, isExpanded) {
return const ListTile(title: Text("全局设置"), subtitle: Text("在这里关广告、开启终端编辑"));
}), body: Padding(padding: const EdgeInsets.all(12), child: Column(children: [
TextFormField(autovalidateMode: AutovalidateMode.onUserInteraction, initialValue: (Util.getGlobal("termMaxLines") as int).toString(), decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "终端最大行数(重启软件生效)"), readOnly: Util.shouldWatchAds(G.adsRequired["changeTermMaxLines"]!),
keyboardType: TextInputType.number,
onTap: () {
if (Util.shouldWatchAds(G.adsRequired["changeTermMaxLines"]!)) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("观看六次视频广告永久解锁><"))
);
}
},
validator: (value) {
return Util.validateBetween(value, 1024, 2147483647, () async {
await G.prefs.setInt("termMaxLines", int.parse(value!));
});
},),
SizedBox.fromSize(size: const Size.square(16)),
TextFormField(autovalidateMode: AutovalidateMode.onUserInteraction, initialValue: (Util.getGlobal("defaultAudioPort") as int).toString(), decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "pulseaudio接收端口"),
keyboardType: TextInputType.number,
validator: (value) {
return Util.validateBetween(value, 0, 65535, () async {
await G.prefs.setInt("defaultAudioPort", int.parse(value!));
});
}
),
SizedBox.fromSize(size: const Size.square(16)),
SwitchListTile(title: const Text("关闭横幅广告"), value: Util.getGlobal("isBannerAdsClosed") as bool, onChanged:(value) {
if (value && Util.shouldWatchAds(G.adsRequired["closeBannerAds"]!)) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("观看五次视频广告永久解锁><"))
);
return;
}
G.prefs.setBool("isBannerAdsClosed", value);
setState(() {});
},),
SizedBox.fromSize(size: const Size.square(8)),
SwitchListTile(title: const Text("启用终端"), value: Util.getGlobal("isTerminalWriteEnabled") as bool, onChanged:(value) {
if (value && Util.shouldWatchAds(G.adsRequired["enableTerminalWrite"]!)) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: const Text("观看两次视频广告永久解锁><"), action: SnackBarAction(label: "啊?", onPressed: () {
G.prefs.setBool("isTerminalWriteEnabled", value);
setState(() {});
},))
);
return;
}
G.prefs.setBool("isTerminalWriteEnabled", value);
setState(() {});
},),
SizedBox.fromSize(size: const Size.square(8)),
SwitchListTile(title: const Text("启用终端小键盘"), value: Util.getGlobal("isTerminalCommandsEnabled") as bool, onChanged:(value) {
if (value && Util.shouldWatchAds(G.adsRequired["enableTerminalCommands"]!)) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("观看三次视频广告永久解锁><"))
);
return;
}
G.prefs.setBool("isTerminalCommandsEnabled", value);
setState(() {});
},),
SizedBox.fromSize(size: const Size.square(8)),
SwitchListTile(title: const Text("终端粘滞键"), value: Util.getGlobal("isStickyKey") as bool, onChanged:(value) {
G.prefs.setBool("isStickyKey", value);
setState(() {});
},),
SizedBox.fromSize(size: const Size.square(8)),
SwitchListTile(title: const Text("开启时启动图形界面"), value: Util.getGlobal("autoLaunchVnc") as bool, onChanged:(value) {
G.prefs.setBool("autoLaunchVnc", value);
setState(() {});
},),
],))),
ExpansionPanel(
isExpanded: _expandState[2],
headerBuilder: ((context, isExpanded) {
return const ListTile(title: Text("相机推流"), subtitle: Text("实验性功能"));
}), body: Padding(padding: const EdgeInsets.all(12), child: Column(children: [
const Text("成功启动推流后可以点击快捷指令\"拉流测试\"并前往图形界面查看效果。\n注意这并不能为系统创建一个虚拟相机;\n另外使用相机是高耗电行为,不用时需及时关闭。"),
const SizedBox.square(dimension: 16),
Wrap(alignment: WrapAlignment.center, spacing: 4.0, runSpacing: 4.0, children: [
OutlinedButton(style: commandButtonStyle, child: const Text("申请相机权限"), onPressed: () {
Permission.camera.request();
}),
OutlinedButton(style: commandButtonStyle, child: const Text("申请麦克风权限"), onPressed: () {
Permission.microphone.request();
}),
OutlinedButton(style: commandButtonStyle, child: const Text("查看输出"), onPressed: () {
if (G.streamingOutput == "") {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("无输出"))
);
return;
}
showDialog(context: context, builder: (context) {
return AlertDialog(content: SingleChildScrollView(child:
Text(G.streamingOutput)), actions: [
TextButton(onPressed:() {
FlutterClipboard.copy(G.streamingOutput).then(( value ) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("已复制")));
});
Navigator.of(context).pop();
}, child: const Text("复制")),
TextButton(onPressed:() {
Navigator.of(context).pop();
}, child: const Text("取消")),
]);
});
}),
]),
const SizedBox.square(dimension: 16),
TextFormField(maxLines: null, initialValue: Util.getGlobal("defaultFFmpegCommand") as String, decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "ffmpeg推流命令"), readOnly: Util.shouldWatchAds(G.adsRequired["changeFFmpegCommand"]!),
onTap: () {
if (Util.shouldWatchAds(G.adsRequired["changeFFmpegCommand"]!)) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("观看八次视频广告永久解锁><"))
);
}
},
onChanged: (value) async {
await G.prefs.setString("defaultFFmpegCommand", value);
},
),
const SizedBox.square(dimension: 16),
SwitchListTile(title: const Text("启动推流服务器"), subtitle: const Text("mediamtx"), value: G.isStreamServerStarted, onChanged:(value) {
switch (value) {
case true: {
G.streamServerPty = Pty.start("/system/bin/sh");
G.streamServerPty.write(const Utf8Encoder().convert("${G.dataPath}/bin/mediamtx ${G.dataPath}/bin/mediamtx.yml & pid=\$(echo \$!)\n"));
G.streamServerPty.exitCode.then((value) {
G.isStreamServerStarted = false;
setState(() {});
});
}
break;
case false: {
G.streamServerPty.write(const Utf8Encoder().convert("kill \$pid\nexit\n"));
}
break;
}
G.isStreamServerStarted = value;
setState(() {});
},),
SizedBox.fromSize(size: const Size.square(8)),
SwitchListTile(title: const Text("启动推流"), value: G.isStreaming, onChanged:(value) {
switch (value) {
case true: {
FFmpegKit.execute(Util.getGlobal("defaultFFmpegCommand") as String).then((session) {
session.getOutput().then((value) async {
G.isStreaming = false;
G.streamingOutput = value??"";
setState(() {});
});
});
}
break;
case false: {
FFmpegKit.cancel();
}
break;
}
G.isStreaming = value;
setState(() {});
},),
SizedBox.fromSize(size: const Size.square(8))
],))),
ExpansionPanel(
isExpanded: _expandState[3],
headerBuilder: ((context, isExpanded) {
return const ListTile(title: Text("文件访问"));
}), body: Padding(padding: const EdgeInsets.all(12), child: Column(children: [
const Text("通过这里获取更多文件权限,以实现对特殊目录的访问。"),
SizedBox.fromSize(size: const Size.square(16)),
Wrap(alignment: WrapAlignment.center, spacing: 4.0, runSpacing: 4.0, children: [
OutlinedButton(style: commandButtonStyle, child: const Text("申请存储权限"), onPressed: () {
Permission.storage.request();
}),
OutlinedButton(style: commandButtonStyle, child: const Text("申请所有文件访问权限"), onPressed: () {
Permission.manageExternalStorage.request();
}),
]),
SizedBox.fromSize(size: const Size.square(16)),
],))),
ExpansionPanel(
isExpanded: _expandState[4],
headerBuilder: ((context, isExpanded) {
return const ListTile(title: Text("广告记录"), subtitle: Text("在这里看广告"));
}), body: Padding(padding: const EdgeInsets.all(12), child: Column(children: [
OutlinedButton(child: const Text("看一个广告"), onPressed: () {
if (AdManager.placements[AdManager.rewardedVideoAdPlacementId]!) {
AdManager.showAd(AdManager.rewardedVideoAdPlacementId, () {
final bonus = Util.getRandomBonus();
Util.applyBonus(bonus);
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("你获得了 ${bonus["name"]}*${bonus["amount"]}"))
);
setState(() {
});
}, () {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("已经看5个广告了, 今天也非常感谢><"))
);
});
}
}),
const SizedBox.square(dimension: 8),
Text(Util.getGlobal("adsBonus").map((element) {
final e = jsonDecode(element);
return e["amount"]==0?"":"${e["name"]}*${e["amount"]}\n";
}).join())
],))),
],),
SizedBox.fromSize(size: const Size.square(8)),
const InfoPage()
]
)
))
,)
] ]
))) )))
)][pageIndex], )]);
transitionBuilder: (child, animation) { }))]):const LoadingPage(),
return FadeTransition(opacity: animation, child: child); bottomNavigationBar: ValueListenableBuilder(valueListenable: G.pageIndex, builder:(context, value, child) {
},))]):const LoadingPage(), return Visibility(visible: isLoadingComplete,
bottomNavigationBar: Visibility(visible: isLoadingComplete, child: BottomNavigationBar(currentIndex: G.pageIndex.value,
child: BottomNavigationBar(currentIndex: pageIndex, onTap: (index) {
onTap: (index) { G.pageIndex.value = index;
setState(() { },
pageIndex = index; items: const [
}); BottomNavigationBarItem(icon: Icon(Icons.monitor), label: "终端"),
}, BottomNavigationBarItem(icon: Icon(Icons.video_settings), label: "控制"),
items: const [ ],
BottomNavigationBarItem(icon: Icon(Icons.monitor), label: "终端"), )
BottomNavigationBarItem(icon: Icon(Icons.video_settings), label: "控制"), );}),
], floatingActionButton: ValueListenableBuilder(valueListenable: G.pageIndex, builder:(context, value, child) {
) return Visibility(visible: isLoadingComplete && (value == 0),
), child: FloatingActionButton(
floatingActionButton: Visibility(visible: isLoadingComplete && (pageIndex == 0), onPressed: () => Workflow.launchBrowser(),
child: FloatingActionButton( tooltip: "进入图形界面",
onPressed: () => Workflow.launchBrowser(), child: const Icon(Icons.play_arrow),
tooltip: "进入图形界面", ),
child: const Icon(Icons.play_arrow), );
), }), // This trailing comma makes auto-formatting nicer for build methods.
), // This trailing comma makes auto-formatting nicer for build methods.
); );
} }
} }

View File

@@ -132,16 +132,16 @@ class Util {
//返回单个G.bonusTable定义的item //返回单个G.bonusTable定义的item
static Map<String, dynamic> getRandomBonus() { static Map<String, dynamic> getRandomBonus() {
final random = Random(); final random = Random();
final totalWeight = G.bonusTable.fold(0.0, (sum, item) => sum + item['weight']); final totalWeight = D.bonusTable.fold(0.0, (sum, item) => sum + item['weight']);
final randomIndex = random.nextDouble() * totalWeight; final randomIndex = random.nextDouble() * totalWeight;
var cumulativeWeight = 0.0; var cumulativeWeight = 0.0;
for (final item in G.bonusTable) { for (final item in D.bonusTable) {
cumulativeWeight += item['weight']; cumulativeWeight += item['weight'];
if (randomIndex <= cumulativeWeight) { if (randomIndex <= cumulativeWeight) {
return item; return item;
} }
} }
return G.bonusTable[0]; return D.bonusTable[0];
} }
//由getRandomBonus返回的数据 //由getRandomBonus返回的数据
@@ -163,7 +163,7 @@ class Util {
//根据已看广告量判断是否应该继续看广告 //根据已看广告量判断是否应该继续看广告
static bool shouldWatchAds(int expectNum) { static bool shouldWatchAds(int expectNum) {
return ((Util.getGlobal("adsWatchedTotal") as int) < expectNum) && ((Util.getGlobal("vip") as int) < 1) && ((Util.getGlobal("adsWatchedToday") as int) < G.adsRequired["unlockToday"]!) && (G.adsWatchedThisTime < G.adsRequired["unlockOnce"]!); return ((Util.getGlobal("adsWatchedTotal") as int) < expectNum) && ((Util.getGlobal("vip") as int) < 1) && ((Util.getGlobal("adsWatchedToday") as int) < D.adsRequired["unlockToday"]!) && (G.adsWatchedThisTime < D.adsRequired["unlockOnce"]!);
} }
//限定字符串在min和max之间, 给文本框的validator //限定字符串在min和max之间, 给文本框的validator
@@ -230,7 +230,7 @@ class VirtualKeyboard extends TerminalInputHandler with ChangeNotifier {
shift: event.shift || _shift, shift: event.shift || _shift,
alt: event.alt || _alt, alt: event.alt || _alt,
)); ));
G.maybeCtrlJ = event.key.name == "keyJ"; G.maybeCtrlJ = event.key.name == "keyJ"; //这个是为了稍后区分按键到底是Enter还是Ctrl+J
if (!(Util.getGlobal("isStickyKey") as bool)) { if (!(Util.getGlobal("isStickyKey") as bool)) {
G.keyboard.ctrl = false; G.keyboard.ctrl = false;
G.keyboard.shift = false; G.keyboard.shift = false;
@@ -352,7 +352,7 @@ class D {
{"name":"拉流测试", "command":"ffplay rtsp://127.0.0.1:8554/stream &"}, {"name":"拉流测试", "command":"ffplay rtsp://127.0.0.1:8554/stream &"},
{"name":"关机", "command":"stopvnc\nexit\nexit"}, {"name":"关机", "command":"stopvnc\nexit\nexit"},
{"name":"???", "command":"timeout 8 cmatrix"}]; {"name":"???", "command":"timeout 8 cmatrix"}];
//默认快捷指令 //默认小键盘
static const termCommands = [ static const termCommands = [
{"name": "Esc", "key": TerminalKey.escape}, {"name": "Esc", "key": TerminalKey.escape},
{"name": "Tab", "key": TerminalKey.tab}, {"name": "Tab", "key": TerminalKey.tab},
@@ -378,27 +378,6 @@ class D {
{"name": "F11", "key": TerminalKey.f11}, {"name": "F11", "key": TerminalKey.f11},
{"name": "F12", "key": TerminalKey.f12}, {"name": "F12", "key": TerminalKey.f12},
]; ];
}
// Global variables
class G {
static late final String dataPath;
static Pty? audioPty;
static late WebViewController controller;
static late BuildContext homePageStateContext;
static late int currentContainer; //目前运行第几个容器
static late Map<int, TermPty> termPtys; //为容器<int>存放TermPty数据
static late AdManager ads; //广告实例
static late VirtualKeyboard keyboard; //存储ctrl, shift, alt状态
static bool maybeCtrlJ = false; //为了区分按下的ctrl+J和enter而准备的变量
static double termFontScale = 1; //终端字体大小存储为G.prefs的termFontScale
static int adsWatchedThisTime = 0; //本次启动应用看的广告数
static bool isStreamServerStarted = false;
static bool isStreaming = false;
static int? streamingId;
static String streamingOutput = "";
static late Pty streamServerPty;
//看广告可以获得的奖励。 //看广告可以获得的奖励。
//weight抽奖权重singleUse使用一次花费的数量amount抽中可以获得的数量 //weight抽奖权重singleUse使用一次花费的数量amount抽中可以获得的数量
@@ -426,6 +405,44 @@ class G {
}; };
static final ButtonStyle commandButtonStyle = OutlinedButton.styleFrom(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
minimumSize: const Size(0, 0),
padding: const EdgeInsets.fromLTRB(4, 2, 4, 2)
);
static final ButtonStyle controlButtonStyle = OutlinedButton.styleFrom(
textStyle: const TextStyle(fontWeight: FontWeight.w400),
side: const BorderSide(color: Color(0x1F000000)),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
minimumSize: const Size(0, 0),
padding: const EdgeInsets.fromLTRB(8, 4, 8, 4)
);
}
// Global variables
class G {
static late final String dataPath;
static Pty? audioPty;
static late WebViewController controller;
static late BuildContext homePageStateContext;
static late int currentContainer; //目前运行第几个容器
static late Map<int, TermPty> termPtys; //为容器<int>存放TermPty数据
static late AdManager ads; //广告实例
static late VirtualKeyboard keyboard; //存储ctrl, shift, alt状态
static bool maybeCtrlJ = false; //为了区分按下的ctrl+J和enter而准备的变量
static ValueNotifier<double> termFontScale = ValueNotifier(1); //终端字体大小存储为G.prefs的termFontScale
static int adsWatchedThisTime = 0; //本次启动应用看的广告数
static bool isStreamServerStarted = false;
static bool isStreaming = false;
static int? streamingId;
static String streamingOutput = "";
static late Pty streamServerPty;
static ValueNotifier<int> pageIndex = ValueNotifier(0); //主界面索引
static late SharedPreferences prefs; static late SharedPreferences prefs;
} }
@@ -592,18 +609,6 @@ done
"vncUrl":"http://localhost:36082/vnc.html?host=localhost&port=36082&autoconnect=true&resize=remote&password=12345678", "vncUrl":"http://localhost:36082/vnc.html?host=localhost&port=36082&autoconnect=true&resize=remote&password=12345678",
"commands":${jsonEncode(D.commands)} "commands":${jsonEncode(D.commands)}
}"""]); }"""]);
// await G.prefs.setStringList("adsBonus", []);
// await G.prefs.setInt("adsWatchedTotal", 0);
// await G.prefs.setBool("isTerminalCommandsEnabled", false);
// await G.prefs.setBool("isTerminalWriteEnabled", false);
// await G.prefs.setBool("isBannerAdsClosed", false);
// await G.prefs.setBool("autoLaunchVnc", true);
// await G.prefs.setInt("defaultAudioPort", 4718);
// await G.prefs.setInt("defaultContainer", 0);
// await G.prefs.setInt("termMaxLines", 4095);
// await G.prefs.setDouble("termFontScale", 1);
// await G.prefs.setInt("vip", 0);
// await G.prefs.setBool("isStickyKey", true);
} }
static Future<void> initData() async { static Future<void> initData() async {
@@ -629,24 +634,24 @@ done
} }
G.currentContainer = Util.getGlobal("defaultContainer") as int; G.currentContainer = Util.getGlobal("defaultContainer") as int;
G.termFontScale = Util.getGlobal("termFontScale") as double; G.termFontScale.value = Util.getGlobal("termFontScale") as double;
G.controller = WebViewController()..setJavaScriptMode(JavaScriptMode.unrestricted); G.controller = WebViewController()..setJavaScriptMode(JavaScriptMode.unrestricted);
//恢复临时开启的功能 //恢复临时开启的功能
if (Util.shouldWatchAds(G.adsRequired["changeFFmpegCommand"]!)) { if (Util.shouldWatchAds(D.adsRequired["changeFFmpegCommand"]!)) {
await G.prefs.remove("defaultFFmpegCommand"); await G.prefs.remove("defaultFFmpegCommand");
} }
if (Util.shouldWatchAds(G.adsRequired["changeTermMaxLines"]!)) { if (Util.shouldWatchAds(D.adsRequired["changeTermMaxLines"]!)) {
await G.prefs.setInt("termMaxLines", 4095); await G.prefs.setInt("termMaxLines", 4095);
} }
if (Util.shouldWatchAds(G.adsRequired["closeBannerAds"]!)) { if (Util.shouldWatchAds(D.adsRequired["closeBannerAds"]!)) {
await G.prefs.setBool("isBannerAdsClosed", false); await G.prefs.setBool("isBannerAdsClosed", false);
} }
if (Util.shouldWatchAds(G.adsRequired["enableTerminalWrite"]!)) { if (Util.shouldWatchAds(D.adsRequired["enableTerminalWrite"]!)) {
await G.prefs.setBool("isTerminalWriteEnabled", false); await G.prefs.setBool("isTerminalWriteEnabled", false);
} }
if (Util.shouldWatchAds(G.adsRequired["enableTerminalCommands"]!)) { if (Util.shouldWatchAds(D.adsRequired["enableTerminalCommands"]!)) {
await G.prefs.setBool("isTerminalCommandsEnabled", false); await G.prefs.setBool("isTerminalCommandsEnabled", false);
} }