mirror of
https://github.com/Cateners/tiny_computer.git
synced 2026-05-20 08:35:46 +08:00
Add font scale, terminal max line, vip skip ads feature
This commit is contained in:
@@ -23,6 +23,8 @@ import 'dart:math';
|
||||
|
||||
//import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:xterm/xterm.dart';
|
||||
@@ -140,17 +142,9 @@ class _InfoPageState extends State<InfoPage> {
|
||||
就可以访问手机存储
|
||||
|
||||
如果认为界面大小比例不合适
|
||||
可以通过调整左栏设置-高级设置里的scale
|
||||
快捷调整界面缩放
|
||||
这个功能是原本的noVNC里没有的哦!
|
||||
具体的改动可以在这里看到:
|
||||
https://github.com/Cateners/noVNC/tree/scale_factor
|
||||
|
||||
其余两个选项是
|
||||
quality(图像质量)和compression(压缩等级)
|
||||
...是noVNC中本来就有的选项。
|
||||
可以通过调整图形界面左栏设置-高级里的屏幕缩放比例
|
||||
如果感觉界面卡卡的
|
||||
可以适当调低
|
||||
可以适当调低图像质量或压缩等级
|
||||
|
||||
如果你想安装其他软件
|
||||
可以使用容器自带的tmoe
|
||||
@@ -184,7 +178,7 @@ VSCode、输入法
|
||||
三星Galaxy S21 Ultra, 安卓13, 黑屏
|
||||
红米Note 12, 安卓13(miui14), 黑屏
|
||||
红米Note 11T Pro+, miui13.0.4,“无法连接”
|
||||
Vivo Pad,安卓13,看不见鼠标移动
|
||||
Vivo Pad,安卓13,看不见鼠标移动(可以去左栏设置开启显示原系统光标替代)
|
||||
关于这个
|
||||
我目前没有什么好的解决办法
|
||||
(毕竟我没有这些设备
|
||||
@@ -662,6 +656,10 @@ SOFTWARE.
|
||||
启用终端: 观看2个广告
|
||||
启用小键盘: 观看3个广告
|
||||
关闭横幅广告: 观看5个广告
|
||||
终端最大行数修改: 观看6个广告
|
||||
|
||||
我设置了每天最多可以看5个广告。
|
||||
只要看满5个广告, 就可以临时解锁全部功能。
|
||||
|
||||
(本来最开始设置是看一个广告就能全部解锁的
|
||||
然后我自己测试的时候
|
||||
@@ -734,6 +732,31 @@ class LoadingPage extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class ForceScaleGestureRecognizer extends ScaleGestureRecognizer {
|
||||
@override
|
||||
void rejectGesture(int pointer) {
|
||||
super.acceptGesture(pointer);
|
||||
}
|
||||
}
|
||||
|
||||
RawGestureDetector forceScaleGestureDetector({
|
||||
GestureScaleUpdateCallback? onScaleUpdate,
|
||||
GestureScaleEndCallback? onScaleEnd,
|
||||
Widget? child,
|
||||
}) {
|
||||
return RawGestureDetector(
|
||||
gestures: {
|
||||
ForceScaleGestureRecognizer:GestureRecognizerFactoryWithHandlers<ForceScaleGestureRecognizer>(() {
|
||||
return ForceScaleGestureRecognizer();
|
||||
}, (detector) {
|
||||
detector.onUpdate = onScaleUpdate;
|
||||
detector.onEnd = onScaleEnd;
|
||||
})
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
const MyHomePage({super.key, required this.title});
|
||||
|
||||
@@ -800,10 +823,16 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
bannerAdsFailedToLoad = true;
|
||||
});
|
||||
},
|
||||
),Expanded(flex: 1, child: AnimatedSwitcher(
|
||||
), Expanded(flex: 1, child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 256),
|
||||
child: [
|
||||
Column(children: [Expanded(child: TerminalView(G.termPtys[G.currentContainer]!.terminal)),
|
||||
Column(children: [Expanded(child: forceScaleGestureDetector(onScaleUpdate: (details) {
|
||||
setState(() {
|
||||
G.termFontScale = (details.scale * G.prefs.getDouble("termFontScale")!).clamp(0.2, 5);
|
||||
});
|
||||
}, onScaleEnd: (details) async {
|
||||
await G.prefs.setDouble("termFontScale", G.termFontScale);
|
||||
}, child: TerminalView(G.termPtys[G.currentContainer]!.terminal, textScaleFactor: G.termFontScale))),
|
||||
G.prefs.getBool("isTerminalCommandsEnabled")!?Padding(padding: const EdgeInsets.all(8), child:
|
||||
SingleChildScrollView(scrollDirection: Axis.horizontal, child: Row(children: [AnimatedBuilder(
|
||||
animation: G.keyboard,
|
||||
@@ -995,12 +1024,33 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
headerBuilder: ((context, isExpanded) {
|
||||
return const ListTile(title: Text("全局设置"), subtitle: Text("在这里关广告、开启终端编辑"));
|
||||
}), body: Padding(padding: const EdgeInsets.all(12), child: Column(children: [
|
||||
TextFormField(maxLines: null, initialValue: G.prefs.getString("defaultAudioPort"), decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "pulseaudio接收端口"), onChanged: (value) async {
|
||||
await G.prefs.setString("defaultAudioPort", value);
|
||||
}),
|
||||
SizedBox.fromSize(size: const Size.square(8)),
|
||||
TextFormField(autovalidateMode: AutovalidateMode.onUserInteraction, initialValue: G.prefs.getInt("termMaxLines")!.toString(), decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "终端最大行数(重启软件生效)"), readOnly: Util.shouldWatchAds(6),
|
||||
keyboardType: TextInputType.number,
|
||||
onTap: () {
|
||||
if (Util.shouldWatchAds(6)) {
|
||||
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: G.prefs.getInt("defaultAudioPort")!.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: G.prefs.getBool("isBannerAdsClosed")!, onChanged:(value) {
|
||||
if (value && (G.prefs.getInt("adsWatchedTotal")! < 5)) {
|
||||
if (value && Util.shouldWatchAds(5)) {
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text("观看五次视频广告永久解锁><"))
|
||||
@@ -1012,7 +1062,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
},),
|
||||
SizedBox.fromSize(size: const Size.square(8)),
|
||||
SwitchListTile(title: const Text("启用终端"), value: G.prefs.getBool("isTerminalWriteEnabled")!, onChanged:(value) {
|
||||
if (value && (G.prefs.getInt("adsWatchedTotal")! < 2)) {
|
||||
if (value && Util.shouldWatchAds(2)) {
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: const Text("观看两次视频广告永久解锁><"), action: SnackBarAction(label: "啊?", onPressed: () {
|
||||
@@ -1027,7 +1077,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
},),
|
||||
SizedBox.fromSize(size: const Size.square(8)),
|
||||
SwitchListTile(title: const Text("启用终端小键盘"), value: G.prefs.getBool("isTerminalCommandsEnabled")!, onChanged:(value) {
|
||||
if (value && (G.prefs.getInt("adsWatchedTotal")! < 3)) {
|
||||
if (value && Util.shouldWatchAds(3)) {
|
||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text("观看三次视频广告永久解锁><"))
|
||||
|
||||
@@ -114,6 +114,27 @@ class Util {
|
||||
}
|
||||
await G.prefs.setStringList("adsBonus", ret);
|
||||
}
|
||||
|
||||
//根据已看广告量判断是否应该继续看广告
|
||||
static bool shouldWatchAds(int expectNum) {
|
||||
return (G.prefs.getInt("adsWatchedTotal")! < expectNum) && (G.prefs.getInt("vip")! < 1) && (G.prefs.getInt("adsWatchedToday")! < 5);
|
||||
}
|
||||
|
||||
//限定字符串在min和max之间, 给文本框的validator
|
||||
static String? validateBetween(String? value, int min, int max, Function opr) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return "请输入数字";
|
||||
}
|
||||
int? parsedValue = int.tryParse(value);
|
||||
if (parsedValue == null) {
|
||||
return "请输入有效的数字";
|
||||
}
|
||||
if (parsedValue < min || parsedValue > max) {
|
||||
return "请输入$min到$max之间的数字";
|
||||
}
|
||||
opr();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
//来自xterms关于操作ctrl, shift, alt键的示例
|
||||
@@ -171,7 +192,7 @@ class TermPty {
|
||||
late final Pty pty;
|
||||
|
||||
TermPty() {
|
||||
terminal = Terminal(inputHandler: G.keyboard);
|
||||
terminal = Terminal(inputHandler: G.keyboard, maxLines: G.prefs.getInt("termMaxLines")!);
|
||||
pty = Pty.start(
|
||||
"/system/bin/sh",
|
||||
workingDirectory: G.dataPath,
|
||||
@@ -240,6 +261,8 @@ class G {
|
||||
static late Map<int, TermPty> termPtys; //为容器<int>存放TermPty数据
|
||||
static late AdManager ads;//广告实例
|
||||
static late VirtualKeyboard keyboard;
|
||||
//终端字体大小,存储为G.prefs的termFontScale
|
||||
static double termFontScale = 1;
|
||||
|
||||
|
||||
//看广告可以获得的奖励。
|
||||
@@ -257,7 +280,7 @@ class G {
|
||||
|
||||
//所有key
|
||||
//int defaultContainer = 0: 默认启动第0个容器
|
||||
//String defaultAudioPort = 4713: 默认pulseaudio端口(为了避免和其它软件冲突改成4718了) !!!注意!这个值是String类型
|
||||
//int defaultAudioPort = 4718: 默认pulseaudio端口(为了避免和其它软件冲突改成4718了,原默认4713)
|
||||
//bool autoLaunchVnc = true: 是否自动启动VNC并跳转
|
||||
//String lastDate: 上次启动软件的日期,yyyy-MM-dd
|
||||
//int adsWatchedToday: 今日视频广告观看数量
|
||||
@@ -267,6 +290,9 @@ class G {
|
||||
//bool isTerminalWriteEnabled = false
|
||||
//bool terminalWriteCanBeEnabled = false 看一次视频广告永久开启,历史遗留
|
||||
//bool isTerminalCommandsEnabled = false
|
||||
//int termMaxLines = 4095 终端最大行数
|
||||
//double termFontScale = 1 终端字体大小
|
||||
//int vip = 0 用户等级,vip免广告,你要改吗?(ToT)
|
||||
//? int bootstrapVersion: 启动包版本
|
||||
//String[] containersInfo: 所有容器信息(json)
|
||||
//{name, boot:"\$DATA_DIR/bin/proot ...", vnc:"startnovnc", vncUrl:"...", commands:[{name:"更新和升级", command:"apt update -y && apt upgrade -y"}, ...]}
|
||||
@@ -362,7 +388,7 @@ class Workflow {
|
||||
//给proot的tmp文件夹,虽然我不知道为什么proot要这个
|
||||
Util.createDirFromString("${G.dataPath}/proot_tmp");
|
||||
//解压后得到bin文件夹和libexec文件夹
|
||||
//bin存放了proot和pulseaudio
|
||||
//bin存放了proot, pulseaudio, tar等
|
||||
//libexec存放了proot loader
|
||||
await Util.copyAsset(
|
||||
"assets/assets.zip",
|
||||
@@ -410,7 +436,7 @@ export PATH=\$DATA_DIR/bin:\$PATH
|
||||
export PROOT_TMP_DIR=\$DATA_DIR/proot_tmp
|
||||
export PROOT_LOADER=\$DATA_DIR/libexec/proot/loader
|
||||
export PROOT_LOADER_32=\$DATA_DIR/libexec/proot/loader32
|
||||
#export PROOT_L2S_DIR=\$CONTAINER_DIR/.l2s
|
||||
export PROOT_L2S_DIR=\$CONTAINER_DIR/.l2s
|
||||
\$DATA_DIR/bin/proot --link2symlink sh -c "cat xa* | \$DATA_DIR/bin/tar x -J --delay-directory-restore --preserve-permissions -v -C containers/0"
|
||||
#Script from proot-distro
|
||||
chmod u+rw "\$CONTAINER_DIR/etc/passwd" "\$CONTAINER_DIR/etc/shadow" "\$CONTAINER_DIR/etc/group" "\$CONTAINER_DIR/etc/gshadow"
|
||||
@@ -444,22 +470,27 @@ done
|
||||
{"name":"卸载视频剪辑软件Kdenlive", "command":"sudo apt autoremove --purge -y kdenlive"},
|
||||
{"name":"安装科学计算软件Octave", "command":"sudo apt update && sudo apt install -y octave"},
|
||||
{"name":"卸载科学计算软件Octave", "command":"sudo apt autoremove --purge -y octave"},
|
||||
{"name":"安装WPS", "command":"wget https://wps-linux-personal.wpscdn.cn/wps/download/ep/Linux2019/11704/wps-office_11.1.0.11704_arm64.deb -O /tmp/wps.deb; sudo apt update; sudo apt install /tmp/wps.deb -y; rm /tmp/wps.deb"},
|
||||
{"name":"安装WPS", "command":"wget https://wps-linux-personal.wpscdn.cn/wps/download/ep/Linux2019/11704/wps-office_11.1.0.11704_arm64.deb -O /tmp/wps.deb && sudo apt update && sudo apt install -y /tmp/wps.deb; rm /tmp/wps.deb"},
|
||||
{"name":"卸载WPS", "command":"sudo apt autoremove --purge -y wps-office"},
|
||||
{"name":"安装CAJViewer", "command":"wget https://download.cnki.net/net.cnki.cajviewer_1.3.20-1_arm64.deb -O /tmp/caj.deb && sudo apt update && sudo apt install -y /tmp/caj.deb && bash /home/tiny/.local/share/tiny/caj/postinst; rm /tmp/caj.deb"},
|
||||
{"name":"卸载CAJViewer", "command":"sudo apt autoremove --purge -y net.cnki.cajviewer && bash /home/tiny/.local/share/tiny/caj/postrm"},
|
||||
{"name":"安装亿图图示", "command":"wget https://www.edrawsoft.cn/2download/aarch64/edrawmax_11.5.6-3_arm64.deb -O /tmp/edraw.deb && sudo apt update && sudo apt install -y /tmp/edraw.deb && bash /home/tiny/.local/share/tiny/edraw/postinst; rm /tmp/edraw.deb"},
|
||||
{"name":"卸载亿图图示", "command":"sudo apt autoremove --purge -y edrawmax libldap-2.4-2"},
|
||||
{"name":"修复无法编译C语言程序", "command":"sudo apt update && sudo apt reinstall -y libc6-dev"},
|
||||
{"name":"启用回收站", "command":"sudo apt update && sudo apt install -y gvfs && echo '安装完成, 重启软件即可使用回收站。'"},
|
||||
{"name":"???", "command":"timeout 8 cmatrix"}]
|
||||
}"""]);
|
||||
await G.prefs.setStringList("adsBonus", []);
|
||||
await G.prefs.setInt("adsWatchedTotal", 0);
|
||||
//await G.prefs.setBool("terminalWriteCanBeEnabled", false);
|
||||
//G.prefs.setBool("isTerminalWriteEnabled", false);
|
||||
await G.prefs.setBool("isTerminalCommandsEnabled", false);
|
||||
await G.prefs.setBool("isTerminalWriteEnabled", false);
|
||||
//await G.prefs.setBool("bannerAdsCanBeClosed", false);
|
||||
await G.prefs.setBool("isBannerAdsClosed", false);
|
||||
//G.prefs.setBool("autoLaunchVnc", true);
|
||||
await G.prefs.setBool("autoLaunchVnc", true);
|
||||
await G.prefs.setString("defaultAudioPort", "4718");
|
||||
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);
|
||||
}
|
||||
|
||||
static Future<void> initData() async {
|
||||
@@ -485,6 +516,8 @@ done
|
||||
}
|
||||
G.currentContainer = G.prefs.getInt("defaultContainer")!;
|
||||
|
||||
G.termFontScale = G.prefs.getDouble("termFontScale")!;
|
||||
|
||||
G.controller = WebViewController()..setJavaScriptMode(JavaScriptMode.unrestricted);
|
||||
|
||||
}
|
||||
@@ -498,7 +531,7 @@ done
|
||||
static Future<void> initAds() async {
|
||||
UnityAds.init(
|
||||
gameId: AdManager.gameId,
|
||||
testMode: true,
|
||||
testMode: false,
|
||||
onComplete: () {
|
||||
debugPrint('Initialization Complete');
|
||||
AdManager.loadAds();
|
||||
@@ -521,7 +554,7 @@ export TMPDIR=\$PWD/cache
|
||||
cd \$DATA_DIR
|
||||
export HOME=\$DATA_DIR/share
|
||||
export LD_LIBRARY_PATH=\$DATA_DIR/bin
|
||||
\$DATA_DIR/busybox sed "s/4713/${G.prefs.getString("defaultAudioPort")!}/g" \$DATA_DIR/bin/pulseaudio.conf > \$DATA_DIR/bin/pulseaudio.conf.tmp
|
||||
\$DATA_DIR/busybox sed "s/4713/${G.prefs.getInt("defaultAudioPort")!}/g" \$DATA_DIR/bin/pulseaudio.conf > \$DATA_DIR/bin/pulseaudio.conf.tmp
|
||||
\$DATA_DIR/bin/pulseaudio -F \$DATA_DIR/bin/pulseaudio.conf.tmp
|
||||
exit
|
||||
"""));
|
||||
@@ -533,7 +566,7 @@ exit
|
||||
"""
|
||||
export DATA_DIR=${G.dataPath}
|
||||
export CONTAINER_DIR=\$DATA_DIR/containers/${G.currentContainer}
|
||||
#export PROOT_L2S_DIR=\$DATA_DIR/containers/0/.l2s
|
||||
export PROOT_L2S_DIR=\$DATA_DIR/containers/0/.l2s
|
||||
cd \$DATA_DIR
|
||||
export PROOT_TMP_DIR=\$DATA_DIR/proot_tmp
|
||||
export PROOT_LOADER=\$DATA_DIR/libexec/proot/loader
|
||||
|
||||
Reference in New Issue
Block a user