mirror of
https://github.com/Cateners/tiny_computer.git
synced 2026-05-20 16:35:47 +08:00
Organize the code
This commit is contained in:
760
lib/main.dart
760
lib/main.dart
@@ -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 {
|
||||
const InfoPage({super.key});
|
||||
|
||||
@@ -119,7 +389,12 @@ class _InfoPageState extends State<InfoPage> {
|
||||
return const ListTile(title: Text("使用说明"));
|
||||
},
|
||||
body: const Padding(padding: EdgeInsets.all(8), child: Text("""
|
||||
第一次加载, 大概需要5到10分钟...
|
||||
第一次加载大概需要5到10分钟...
|
||||
加载完成后,软件会自动跳转到图形界面
|
||||
|
||||
在图形界面返回,可以回到终端界面和控制界面
|
||||
你可以在控制界面安装更多软件或者阅读帮助信息
|
||||
|
||||
请不要在安装时退出软件
|
||||
|
||||
如果过了很长时间都没有加载完成
|
||||
@@ -740,9 +1015,7 @@ class LoadingPage extends StatelessWidget {
|
||||
child: Text("小小电脑", textScaleFactor: 2),
|
||||
),
|
||||
FakeLoadingStatus(),
|
||||
Expanded(child:
|
||||
Padding(padding: EdgeInsets.all(8), child: Card(child: Padding(padding: EdgeInsets.all(8), child:
|
||||
|
||||
Expanded(child: Padding(padding: EdgeInsets.all(8), child: Card(child: Padding(padding: EdgeInsets.all(8), child:
|
||||
Scrollbar(child:
|
||||
SingleChildScrollView(
|
||||
child: InfoPage()
|
||||
@@ -781,82 +1054,18 @@ RawGestureDetector forceScaleGestureDetector({
|
||||
);
|
||||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
const MyHomePage({super.key, required this.title});
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
State<MyHomePage> createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
|
||||
//高级设置,全局设置
|
||||
final List<bool> _expandState = [false, false, false, false, false];
|
||||
|
||||
bool bannerAdsFailedToLoad = 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)
|
||||
);
|
||||
class TerminalPage extends StatelessWidget {
|
||||
const TerminalPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
G.homePageStateContext = context;
|
||||
|
||||
if (!isLoadingComplete) {
|
||||
Workflow.workflow().then((value) {
|
||||
setState(() {
|
||||
isLoadingComplete = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
||||
title: Text(isLoadingComplete?Util.getCurrentProp("name"):widget.title),
|
||||
),
|
||||
body: isLoadingComplete?Column(mainAxisSize: MainAxisSize.min, children: [
|
||||
(Util.getGlobal("isBannerAdsClosed") as bool)||bannerAdsFailedToLoad?SizedBox.fromSize(size: const Size.square(0)):UnityBannerAd(
|
||||
placementId: AdManager.bannerAdPlacementId,
|
||||
onLoad: (placementId) => debugPrint('Banner loaded: $placementId'),
|
||||
onClick: (placementId) => debugPrint('Banner clicked: $placementId'),
|
||||
onFailed: (placementId, error, message) {
|
||||
debugPrint('Banner Ad $placementId failed: $error $message');
|
||||
setState(() {
|
||||
bannerAdsFailedToLoad = true;
|
||||
});
|
||||
},
|
||||
), Expanded(child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 256),
|
||||
child: [
|
||||
Column(children: [Expanded(child: forceScaleGestureDetector(onScaleUpdate: (details) {
|
||||
setState(() {
|
||||
G.termFontScale = (details.scale * (Util.getGlobal("termFontScale") as double)).clamp(0.2, 5);
|
||||
});
|
||||
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);
|
||||
}, child: TerminalView(G.termPtys[G.currentContainer]!.terminal, textScaleFactor: G.termFontScale, keyboardType: TextInputType.multiline,))),
|
||||
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(
|
||||
@@ -882,36 +1091,32 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
),
|
||||
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: () {
|
||||
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))
|
||||
]), Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Scrollbar(child: SingleChildScrollView(restorationId: "control-scroll", child: Column(
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: FractionallySizedBox(
|
||||
widthFactor: 0.4,
|
||||
child: Image(
|
||||
image: AssetImage("images/icon.png")
|
||||
)
|
||||
),
|
||||
),
|
||||
/*Padding(
|
||||
padding: const EdgeInsets.fromLTRB(0, 0, 0, 8),
|
||||
child: Text(Util.getCurrentProp("name"), textScaleFactor: 2),
|
||||
),*/
|
||||
Wrap(alignment: WrapAlignment.center, spacing: 4.0, runSpacing: 4.0, children: Util.getCurrentProp("commands")
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
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: commandButtonStyle, child: Text(e.value["name"]!), onPressed: () {
|
||||
setState(() {
|
||||
return OutlinedButton(style: D.commandButtonStyle, child: Text(e.value["name"]!), onPressed: () {
|
||||
Util.termWrite(e.value["command"]!);
|
||||
pageIndex = 0;
|
||||
});
|
||||
G.pageIndex.value = 0;
|
||||
}, onLongPress: () {
|
||||
String name = e.value["name"]!;
|
||||
String command = e.value["command"]!;
|
||||
@@ -945,7 +1150,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
]);
|
||||
},);
|
||||
},);
|
||||
}).toList()..add(OutlinedButton(style: commandButtonStyle, onPressed:() {
|
||||
}).toList()..add(OutlinedButton(style: D.commandButtonStyle, onPressed:() {
|
||||
String name = "";
|
||||
String command = "";
|
||||
showDialog(context: context, builder: (context) {
|
||||
@@ -984,295 +1189,108 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
}, child: const Text("是")),
|
||||
]);
|
||||
});
|
||||
}, child: const Text("添加快捷指令")))),
|
||||
}, child: const Text("添加快捷指令"))));
|
||||
}
|
||||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
const MyHomePage({super.key, required this.title});
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
State<MyHomePage> createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
|
||||
bool bannerAdsFailedToLoad = false;
|
||||
|
||||
//安装完成了吗?
|
||||
//完成后从加载界面切换到主界面
|
||||
bool isLoadingComplete = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
G.homePageStateContext = context;
|
||||
|
||||
if (!isLoadingComplete) {
|
||||
Workflow.workflow().then((value) {
|
||||
setState(() {
|
||||
isLoadingComplete = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
||||
title: Text(isLoadingComplete?Util.getCurrentProp("name"):widget.title),
|
||||
),
|
||||
body: isLoadingComplete?Column(mainAxisSize: MainAxisSize.min, children: [
|
||||
(Util.getGlobal("isBannerAdsClosed") as bool)||bannerAdsFailedToLoad?SizedBox.fromSize(size: const Size.square(0)):UnityBannerAd(
|
||||
placementId: AdManager.bannerAdPlacementId,
|
||||
onLoad: (placementId) => debugPrint('Banner loaded: $placementId'),
|
||||
onClick: (placementId) => debugPrint('Banner clicked: $placementId'),
|
||||
onFailed: (placementId, error, message) {
|
||||
debugPrint('Banner Ad $placementId failed: $error $message');
|
||||
setState(() {
|
||||
bannerAdsFailedToLoad = true;
|
||||
});
|
||||
},
|
||||
), Expanded(child: ValueListenableBuilder(valueListenable: G.pageIndex, builder: (context, value, child) {
|
||||
return IndexedStack(index: G.pageIndex.value, children: [const TerminalPage(), Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Scrollbar(child: SingleChildScrollView(restorationId: "control-scroll", child: Column(
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: FractionallySizedBox(
|
||||
widthFactor: 0.4,
|
||||
child: Image(
|
||||
image: AssetImage("images/icon.png")
|
||||
)
|
||||
),
|
||||
),
|
||||
/*Padding(
|
||||
padding: const EdgeInsets.fromLTRB(0, 0, 0, 8),
|
||||
child: Text(Util.getCurrentProp("name"), textScaleFactor: 2),
|
||||
),*/
|
||||
const FastCommands(),
|
||||
Padding(padding: const EdgeInsets.all(8), child: Card(child: Padding(padding: const EdgeInsets.all(8), child:
|
||||
Column(children: [
|
||||
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(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())
|
||||
],))),
|
||||
],),
|
||||
const SettingPage(),
|
||||
SizedBox.fromSize(size: const Size.square(8)),
|
||||
const InfoPage()
|
||||
]
|
||||
)
|
||||
))
|
||||
,)
|
||||
])
|
||||
)))
|
||||
]
|
||||
)))
|
||||
)][pageIndex],
|
||||
transitionBuilder: (child, animation) {
|
||||
return FadeTransition(opacity: animation, child: child);
|
||||
},))]):const LoadingPage(),
|
||||
bottomNavigationBar: Visibility(visible: isLoadingComplete,
|
||||
child: BottomNavigationBar(currentIndex: pageIndex,
|
||||
)]);
|
||||
}))]):const LoadingPage(),
|
||||
bottomNavigationBar: ValueListenableBuilder(valueListenable: G.pageIndex, builder:(context, value, child) {
|
||||
return Visibility(visible: isLoadingComplete,
|
||||
child: BottomNavigationBar(currentIndex: G.pageIndex.value,
|
||||
onTap: (index) {
|
||||
setState(() {
|
||||
pageIndex = index;
|
||||
});
|
||||
G.pageIndex.value = index;
|
||||
},
|
||||
items: const [
|
||||
BottomNavigationBarItem(icon: Icon(Icons.monitor), label: "终端"),
|
||||
BottomNavigationBarItem(icon: Icon(Icons.video_settings), label: "控制"),
|
||||
],
|
||||
)
|
||||
),
|
||||
floatingActionButton: Visibility(visible: isLoadingComplete && (pageIndex == 0),
|
||||
);}),
|
||||
floatingActionButton: ValueListenableBuilder(valueListenable: G.pageIndex, builder:(context, value, child) {
|
||||
return Visibility(visible: isLoadingComplete && (value == 0),
|
||||
child: FloatingActionButton(
|
||||
onPressed: () => Workflow.launchBrowser(),
|
||||
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.
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,16 +132,16 @@ class Util {
|
||||
//返回单个G.bonusTable定义的item
|
||||
static Map<String, dynamic> getRandomBonus() {
|
||||
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;
|
||||
var cumulativeWeight = 0.0;
|
||||
for (final item in G.bonusTable) {
|
||||
for (final item in D.bonusTable) {
|
||||
cumulativeWeight += item['weight'];
|
||||
if (randomIndex <= cumulativeWeight) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return G.bonusTable[0];
|
||||
return D.bonusTable[0];
|
||||
}
|
||||
|
||||
//由getRandomBonus返回的数据
|
||||
@@ -163,7 +163,7 @@ class Util {
|
||||
|
||||
//根据已看广告量判断是否应该继续看广告
|
||||
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
|
||||
@@ -230,7 +230,7 @@ class VirtualKeyboard extends TerminalInputHandler with ChangeNotifier {
|
||||
shift: event.shift || _shift,
|
||||
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)) {
|
||||
G.keyboard.ctrl = false;
|
||||
G.keyboard.shift = false;
|
||||
@@ -352,7 +352,7 @@ class D {
|
||||
{"name":"拉流测试", "command":"ffplay rtsp://127.0.0.1:8554/stream &"},
|
||||
{"name":"关机", "command":"stopvnc\nexit\nexit"},
|
||||
{"name":"???", "command":"timeout 8 cmatrix"}];
|
||||
//默认快捷指令
|
||||
//默认小键盘
|
||||
static const termCommands = [
|
||||
{"name": "Esc", "key": TerminalKey.escape},
|
||||
{"name": "Tab", "key": TerminalKey.tab},
|
||||
@@ -378,27 +378,6 @@ class D {
|
||||
{"name": "F11", "key": TerminalKey.f11},
|
||||
{"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抽中可以获得的数量
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -592,18 +609,6 @@ done
|
||||
"vncUrl":"http://localhost:36082/vnc.html?host=localhost&port=36082&autoconnect=true&resize=remote&password=12345678",
|
||||
"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 {
|
||||
@@ -629,24 +634,24 @@ done
|
||||
}
|
||||
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);
|
||||
|
||||
//恢复临时开启的功能
|
||||
if (Util.shouldWatchAds(G.adsRequired["changeFFmpegCommand"]!)) {
|
||||
if (Util.shouldWatchAds(D.adsRequired["changeFFmpegCommand"]!)) {
|
||||
await G.prefs.remove("defaultFFmpegCommand");
|
||||
}
|
||||
if (Util.shouldWatchAds(G.adsRequired["changeTermMaxLines"]!)) {
|
||||
if (Util.shouldWatchAds(D.adsRequired["changeTermMaxLines"]!)) {
|
||||
await G.prefs.setInt("termMaxLines", 4095);
|
||||
}
|
||||
if (Util.shouldWatchAds(G.adsRequired["closeBannerAds"]!)) {
|
||||
if (Util.shouldWatchAds(D.adsRequired["closeBannerAds"]!)) {
|
||||
await G.prefs.setBool("isBannerAdsClosed", false);
|
||||
}
|
||||
if (Util.shouldWatchAds(G.adsRequired["enableTerminalWrite"]!)) {
|
||||
if (Util.shouldWatchAds(D.adsRequired["enableTerminalWrite"]!)) {
|
||||
await G.prefs.setBool("isTerminalWriteEnabled", false);
|
||||
}
|
||||
if (Util.shouldWatchAds(G.adsRequired["enableTerminalCommands"]!)) {
|
||||
if (Util.shouldWatchAds(D.adsRequired["enableTerminalCommands"]!)) {
|
||||
await G.prefs.setBool("isTerminalCommandsEnabled", false);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user