Compare commits

...

17 Commits

Author SHA1 Message Date
Caten
70c2018ddf Add a hint; Update code to v1.0.17 2024-08-11 12:12:58 +08:00
Caten
b1895af653 Auto enable getifaddrs bridge; Fix Signal 9 page 2024-08-11 12:04:32 +08:00
Caten
62995e0a5d Signal9Page Change & More 2024-08-11 10:49:28 +08:00
Caten
5ebbaf7073 Add more mirror site for box86/box64 and wine 2024-08-10 18:15:59 +08:00
Caten
cb1f4b23ee Turnip Option Added 2024-08-10 15:49:35 +08:00
Caten
0d2f5f4e91 Fix wps link; electron folder open 2024-08-10 12:28:31 +08:00
Caten
d54d1ef6f3 v1.0.16, fix wps link 2024-07-31 00:09:05 +08:00
Caten
0377aa7b8f Ads removal 2024-06-01 17:48:29 +08:00
Caten
232afe9929 Ready for v1.0.16 2024-06-01 06:43:48 +08:00
Caten
fcb472594d Adjust FAQ message 2024-04-16 20:20:24 +08:00
Caten
aa6d0feed7 Enable pip 2024-04-16 12:47:33 +08:00
Caten
1ee935105e Various fixes
1. 强制更新后调整系统语言到中文(Debian小版本更新时经常会出现系统语言变默认的问题);
2. 安装WPS时强制修复安装(经常有用户出现安装时清后台导致安装损坏的情况,索性每次安装前执行修复命令);
3. 第一次进入容器时修改默认分辨率到与用户设备一致
2024-04-16 12:43:44 +08:00
Caten
7242c45e38 Merge pull request #33 from wyq0918dev/avnc_md3
修复:AVNC原生界面与Flutter界面样式不符
2024-04-02 20:43:47 +08:00
王泳淇
d0a539d6dc 修复:AVNC原生界面与Flutter界面样式不符
1.更新Android端material库为1.11.0
2.在Android端启用动态颜色
3.更改主题样式为Material3
4.为两个原生页面启用边倒边沉浸
5.修改Android端项目名称为TinyComputer(顺手改了, 默认为android)
2024-04-02 14:57:50 +08:00
Caten
3e443ceedc Update code to v1.0.15 2024-03-12 22:06:10 +08:00
Caten
b50622787d Various fixes 2024-03-10 18:20:48 +08:00
Caten
231d1167e0 Change default settings 2024-03-05 22:20:37 +08:00
23 changed files with 694 additions and 839 deletions

View File

@@ -50,7 +50,7 @@ android {
applicationId "com.fct.tiny"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion 24 //ffmpeg_kit; flutter.minSdkVersion
minSdkVersion 27 //proot version //ffmpeg_kit; flutter.minSdkVersion
targetSdkVersion 28 //https://github.com/termux/termux-app/issues/1072; native; linker; flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
@@ -119,7 +119,7 @@ dependencies {
kapt "androidx.room:room-compiler:$roomVersion"
implementation "com.google.android.material:material:1.7.0"
implementation "com.google.android.material:material:1.11.0"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0"
implementation "org.connectbot:sshlib:2.2.23"

View File

@@ -8,7 +8,7 @@
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<application
android:label="小小电脑"
android:name="${applicationName}"
android:name=".MainApplication"
android:icon="@mipmap/ic_launcher"
android:usesCleartextTraffic="true"
android:theme="@style/App.Theme">
@@ -33,6 +33,7 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name="com.example.tiny_computer.Signal9Activity" />
<activity-alias
android:name="com.gaurav.avnc.UriReceiverActivity"
android:targetActivity="com.gaurav.avnc.ui.vnc.IntentReceiverActivity">

View File

@@ -45,6 +45,10 @@ class MainActivity: FlutterActivity() {
startActivity(Intent(this, com.gaurav.avnc.ui.about.AboutActivity::class.java))
result.success(0)
}
"launchSignal9Page" -> {
startActivity(Intent(this, Signal9Activity::class.java))
result.success(0)
}
else -> {
// 不支持的方法名
result.notImplemented()

View File

@@ -0,0 +1,12 @@
package com.example.tiny_computer
import com.google.android.material.color.DynamicColors
import io.flutter.app.FlutterApplication
class MainApplication : FlutterApplication() {
override fun onCreate() {
super.onCreate()
DynamicColors.applyToActivitiesIfAvailable(this@MainApplication)
}
}

View File

@@ -0,0 +1,129 @@
package com.example.tiny_computer
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.view.Gravity
import android.view.View
import android.widget.Button
import android.widget.ScrollView
import android.widget.TextView
import android.widget.Toast
import android.widget.LinearLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat.startActivity
class Signal9Activity : AppCompatActivity() {
private val helperLink = "https://www.vmos.cn/zhushou.htm"
private val helperLink2 = "https://b23.tv/WwqOqW6"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val rootLayout = LinearLayout(this).apply {
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
gravity = Gravity.CENTER
orientation = LinearLayout.VERTICAL
setPadding(16, 16, 16, 16)
setBackgroundColor(Color.parseColor("#4A148C"))
}
val scrollView = ScrollView(this).apply {
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT
)
}
val fullScreen = LinearLayout(this).apply {
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT
)
orientation = LinearLayout.VERTICAL
setBackgroundColor(Color.parseColor("#4A148C"))
}
val text1 = TextView(this).apply {
text = ":(\n发生了什么?"
textSize = 32f
setTextColor(Color.WHITE)
textAlignment = View.TEXT_ALIGNMENT_CENTER
}
val text2 = TextView(this).apply {
text = "终端异常退出, 返回错误码9\n此错误通常是高版本安卓系统(12+)限制进程造成的, \n可以使用以下工具修复:"
textSize = 16f
setTextColor(Color.WHITE)
textAlignment = View.TEXT_ALIGNMENT_CENTER
setPadding(0, 16, 0, 0)
}
val helperLinkText = TextView(this).apply {
text = helperLink
textSize = 16f
setTextColor(Color.WHITE)
textAlignment = View.TEXT_ALIGNMENT_CENTER
setPadding(0, 16, 0, 0)
setOnClickListener { copyToClipboard(helperLink) }
}
val copyHintText = TextView(this).apply {
text = "(复制链接到浏览器查看)"
textSize = 16f
setTextColor(Color.WHITE)
textAlignment = View.TEXT_ALIGNMENT_CENTER
setPadding(0, 8, 0, 0)
}
val copyButton = Button(this).apply {
text = "复制"
textSize = 16f
setOnClickListener { copyToClipboard(helperLink) }
}
val tutorialText = TextView(this).apply {
text = "如果你的设备版本大于等于安卓14可以在开发者选项里开启“停止限制子进程”选项即可无需额外修复。\n\n如果不能解决请参考此教程: "
textSize = 16f
setTextColor(Color.WHITE)
textAlignment = View.TEXT_ALIGNMENT_CENTER
setPadding(0, 16, 0, 0)
}
val viewButton = Button(this).apply {
text = "查看"
textSize = 16f
setOnClickListener { copyToClipboard(helperLink2) }
}
rootLayout.addView(text1)
rootLayout.addView(text2)
rootLayout.addView(helperLinkText)
rootLayout.addView(copyHintText)
rootLayout.addView(copyButton)
rootLayout.addView(tutorialText)
rootLayout.addView(viewButton)
scrollView.addView(rootLayout)
fullScreen.addView(scrollView)
setContentView(fullScreen)
}
private fun copyToClipboard(text: String) {
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val clip = ClipData.newPlainText("Copied Text", text)
clipboard.setPrimaryClip(clip)
Toast.makeText(this, "已复制", Toast.LENGTH_SHORT).show()
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(text))
startActivity(this, intent, null)
}
}

View File

@@ -9,7 +9,10 @@
package com.gaurav.avnc.ui.about
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.example.tiny_computer.R
/**
@@ -23,12 +26,19 @@ class AboutActivity : AppCompatActivity() {
}
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_about)
setSupportActionBar(findViewById(R.id.toolbar))
supportActionBar?.setDisplayHomeAsUpEnabled(true)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.about_main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_host, AboutFragment())

View File

@@ -12,9 +12,12 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.annotation.Keep
import androidx.appcompat.app.AppCompatActivity
import androidx.core.text.HtmlCompat
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference
@@ -26,10 +29,16 @@ class PrefsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPreference
override fun onCreate(savedInstanceState: Bundle?) {
DeviceAuthPrompt.applyFingerprintDialogFix(supportFragmentManager)
enableEdgeToEdge()
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.settings_main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
if (savedInstanceState == null) {
supportFragmentManager
.beginTransaction()

View File

@@ -28,7 +28,7 @@ class AppPreferences(context: Context) {
inner class Viewer {
val orientation; get() = prefs.getString("viewer_orientation", "landscape")
val fullscreen; get() = prefs.getBoolean("fullscreen_display", true)
val pipEnabled; get() = prefs.getBoolean("pip_enabled", false)
val pipEnabled; get() = prefs.getBoolean("pip_enabled", true)
val drawBehindCutout; get() = prefs.getBoolean("viewer_draw_behind_cutout", false)
val keepScreenOn; get() = prefs.getBoolean("keep_screen_on", true)
val toolbarAlignment; get() = prefs.getString("toolbar_alignment", "start")
@@ -40,7 +40,7 @@ class AppPreferences(context: Context) {
}
inner class Gesture {
val style; get() = prefs.getString("gesture_style", "touchscreen")!!
val style; get() = prefs.getString("gesture_style", "touchpad")!!
val tap1 = "left-click" //Preference UI was removed
val tap2; get() = prefs.getString("gesture_tap2", "open-keyboard")!!
val doubleTap; get() = prefs.getString("gesture_double_tap", "double-click")!!

View File

@@ -6,6 +6,7 @@
~ See COPYING.txt for more details.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/about_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

View File

@@ -7,6 +7,7 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/settings_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

View File

@@ -31,29 +31,29 @@
Note: Some custom ROMs don't respect/support the splash theme introduced in API 31.
On such devices, we still rely on the above mentioned workaround.
-->
<style name="App.SplashTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar" />
<style name="App.SplashTheme" parent="Theme.Material3.DayNight.NoActionBar" />
<style name="App.SplashTheme.Light" parent="Theme.MaterialComponents.Light.NoActionBar" />
<style name="App.SplashTheme.Light" parent="Theme.Material3.Light.NoActionBar" />
<style name="App.SplashTheme.Dark" parent="Theme.MaterialComponents.NoActionBar" />
<style name="App.SplashTheme.Dark" parent="Theme.Material3.Dark.NoActionBar" />
<!--
This base theme is used for configuration-specific styling (e.g. night mode, API 23)
-->
<style name="App.BaseTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar" />
<style name="App.BaseTheme" parent="Theme.Material3.DayNight.NoActionBar" />
<!--
This is the main theme.
-->
<style name="App.Theme" parent="App.BaseTheme">
<item name="appBarLayoutStyle">@style/Widget.MaterialComponents.AppBarLayout.Surface</item>
<item name="appBarLayoutStyle">@style/Widget.Material3.AppBarLayout</item>
</style>
<style name="AlertDialog.Dimmed" parent="ThemeOverlay.MaterialComponents.MaterialAlertDialog">
<style name="AlertDialog.Dimmed" parent="ThemeOverlay.Material3.MaterialAlertDialog">
<item name="android:backgroundDimAmount">.6</item>
</style>
<style name="UrlBar" parent="Widget.MaterialComponents.Toolbar">
<style name="UrlBar" parent="Widget.Material3.Toolbar">
<item name="android:background">@drawable/bg_urlbar</item>
<item name="android:layout_marginStart">@dimen/margin_normal</item>
<item name="android:layout_marginEnd">@dimen/margin_normal</item>
@@ -138,7 +138,7 @@
<item name="android:minWidth">50dp</item>
<item name="android:gravity">center</item>
<item name="android:background">?attr/selectableItemBackgroundBorderless</item>
<item name="android:textAppearance">@style/TextAppearance.MaterialComponents.Body2</item>
<item name="android:textAppearance">@style/TextAppearance.Material3.BodyMedium</item>
</style>
<style name="VirtualKey.Compact">

View File

@@ -15,7 +15,7 @@
app:title="@string/pref_gesture">
<com.gaurav.avnc.ui.prefs.ListPreferenceEx
app:defaultValue="touchscreen"
app:defaultValue="touchpad"
app:entries="@array/gesture_style_entries"
app:entryValues="@array/gesture_style_values"
app:key="gesture_style"

View File

@@ -19,7 +19,7 @@
app:useSimpleSummaryProvider="true" />
<SwitchPreference
app:defaultValue="false"
app:defaultValue="true"
app:key="pip_enabled"
app:title="@string/pref_enable_pip" />

View File

@@ -23,4 +23,5 @@ plugins {
id "org.jetbrains.kotlin.android" version "1.9.22" apply false
}
rootProject.name = "TinyComputer"
include ":app"

Binary file not shown.

View File

@@ -12,26 +12,46 @@ rm -rf $HOME/.local/share/tiny/tmp
mkdir $HOME/.local/share/tiny/tmp
cd $HOME/.local/share/tiny/tmp
git clone https://mirror.ghproxy.com/https://github.com/ptitSeb/box86
if [ $? -ne 0 ]; then
git clone https://github.com/ptitSeb/box86
if [ $? -ne 0 ]; then
box86_url="https://github.com/ptitSeb/box86"
box64_url="https://github.com/ptitSeb/box64"
mirror_sites=(
"https://github.moeyy.xyz/"
"https://mirror.ghproxy.com/"
""
)
for mirror in "${mirror_sites[@]}"; do
url="${mirror}${box86_url}"
echo "尝试从 $url 下载box86..."
git clone "${url}"
if [ $? -eq 0 ]; then
echo "成功下载box86"
break
fi
if [ -z "$mirror" ]; then
rm -rf $HOME/.local/share/tiny/tmp
echo '仓库克隆失败...退出安装...'
exit
fi
fi
done
git clone https://mirror.ghproxy.com/https://github.com/ptitSeb/box64
if [ $? -ne 0 ]; then
git clone https://github.com/ptitSeb/box64
if [ $? -ne 0 ]; then
for mirror in "${mirror_sites[@]}"; do
url="${mirror}${box64_url}"
echo "尝试从 $url 下载box64..."
git clone "${url}"
if [ $? -eq 0 ]; then
echo "成功下载box64"
break
fi
if [ -z "$mirror" ]; then
rm -rf $HOME/.local/share/tiny/tmp
echo '仓库克隆失败...退出安装...'
exit
fi
fi
done
echo "正在编译box86..."
cd box86
mkdir build
cd build
@@ -41,6 +61,7 @@ cd ../..
mv box86/build/box86 ../cross
mv box86/x86lib ../cross
echo "正在编译box64..."
cd box64
mkdir build
cd build

View File

@@ -4,27 +4,37 @@ rm -rf $HOME/.local/share/tiny/tmp
mkdir $HOME/.local/share/tiny/tmp
cd $HOME/.local/share/tiny/tmp
MIRROR_SITE=https://mirror.ghproxy.com
RELEASE_PAGE=https://github.com/doitsujin/dxvk/releases
LATEST_DXVK_TAG=$(curl -L $RELEASE_PAGE | grep -oP 'Version \K[^"]*</h2>' | cut -d "<" -f 1 | head -n 1)
if [ -z "$LATEST_DXVK_TAG" ]
then
LATEST_DXVK_TAG="2.3"
LATEST_DXVK_TAG="2.4"
fi
LATEST_DXVK_NAME="dxvk-$LATEST_DXVK_TAG.tar.gz"
LATEST_DXVK_LINK="https://github.com/doitsujin/dxvk/releases/download/v$LATEST_DXVK_TAG/$LATEST_DXVK_NAME"
wget $MIRROR_SITE/$LATEST_DXVK_LINK
if [ $? -ne 0 ]; then
wget $LATEST_DXVK_LINK
if [ $? -ne 0 ]; then
mirror_sites=(
"https://github.moeyy.xyz/"
"https://mirror.ghproxy.com/"
""
)
for mirror in "${mirror_sites[@]}"; do
url="${mirror}$LATEST_DXVK_LINK"
echo "尝试从 $url 下载dxvk..."
wget "${url}"
if [ $? -eq 0 ]; then
echo "成功下载dxvk"
break
fi
if [ -z "$mirror" ]; then
rm -rf $HOME/.local/share/tiny/tmp
echo '下载失败...退出安装...'
exit
fi
fi
done
wineboot
wine64 wineboot
tar xvf $LATEST_DXVK_NAME
mv dxvk-$LATEST_DXVK_TAG/x32/* ~/.wine/drive_c/windows/syswow64
mv dxvk-$LATEST_DXVK_TAG/x64/* ~/.wine/drive_c/windows/system32

View File

@@ -32,10 +32,9 @@ rm -rf $HOME/.local/share/tiny/tmp
mkdir $HOME/.local/share/tiny/tmp
cd $HOME/.local/share/tiny/tmp
MIRROR_SITE=https://mirror.ghproxy.com
RELEASE_PAGE=https://github.com/Kron4ek/Wine-Builds/releases
LATEST_WINE_TAG=proton-$(curl -L $RELEASE_PAGE | grep -oP 'Proton \K[^"]*</h2>' | cut -d "<" -f 1 | head -n 1) #proton
#LATEST_WINE_TAG=$(curl -L $RELEASE_PAGE | grep -oP 'Wine \K[^"]*</h2>' | cut -d "<" -f 1 | head -n 1) #vanilla
#LATEST_WINE_TAG=proton-$(curl -L $RELEASE_PAGE | grep -oP 'Proton \K[^"]*</h2>' | cut -d "<" -f 1 | head -n 1) #proton
LATEST_WINE_TAG=$(curl -L $RELEASE_PAGE | grep -oP 'Wine \K[^"]*</h2>' | cut -d "<" -f 1 | head -n 1) #vanilla
if [ -z "$LATEST_WINE_TAG" ] || [ "$LATEST_WINE_TAG" == "proton-" ]
then
LATEST_WINE_TAG=proton-8.0-4
@@ -43,15 +42,26 @@ fi
LATEST_WINE_NAME=wine-$LATEST_WINE_TAG-amd64.tar.xz
LATEST_WINE_LINK=$RELEASE_PAGE/download/$LATEST_WINE_TAG/$LATEST_WINE_NAME
wget $MIRROR_SITE/$LATEST_WINE_LINK
if [ $? -ne 0 ]; then
wget $LATEST_WINE_LINK
if [ $? -ne 0 ]; then
mirror_sites=(
"https://github.moeyy.xyz/"
"https://mirror.ghproxy.com/"
""
)
for mirror in "${mirror_sites[@]}"; do
url="${mirror}$LATEST_WINE_LINK"
echo "尝试从 $url 下载wine..."
wget "${url}"
if [ $? -eq 0 ]; then
echo "成功下载wine"
break
fi
if [ -z "$mirror" ]; then
rm -rf $HOME/.local/share/tiny/tmp
echo '下载失败...退出安装...'
exit
fi
fi
done
tar xvf $LATEST_WINE_NAME
mv wine-$LATEST_WINE_TAG-amd64 ../cross/wine

View File

@@ -31,7 +31,7 @@ import 'package:flutter_pty/flutter_pty.dart';
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
import 'package:network_info_plus/network_info_plus.dart';
import 'package:permission_handler/permission_handler.dart';
//import 'package:flutter/services.dart';
import 'package:flutter/services.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:flutter/material.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
@@ -39,8 +39,6 @@ import 'package:xterm/xterm.dart';
//import 'package:xterm/flutter.dart';
import 'package:tiny_computer/workflow.dart';
import 'package:unity_ads_plugin/unity_ads_plugin.dart';
import 'package:ffmpeg_kit_flutter_full_gpl/ffmpeg_kit.dart';
void main() {
@@ -117,7 +115,7 @@ class SettingPage extends StatefulWidget {
class _SettingPageState extends State<SettingPage> {
final List<bool> _expandState = [false, false, false, false, false, false, false, false];
final List<bool> _expandState = [false, false, false, false, false, false, false];
@override
Widget build(BuildContext context) {
@@ -134,6 +132,7 @@ class _SettingPageState extends State<SettingPage> {
headerBuilder: ((context, isExpanded) {
return const ListTile(title: Text("高级设置"), subtitle: Text("修改后重启生效"));
}), body: Padding(padding: const EdgeInsets.all(12), child: Column(children: [
Wrap(alignment: WrapAlignment.center, spacing: 4.0, runSpacing: 4.0, children: [
OutlinedButton(style: D.commandButtonStyle, child: const Text("重置启动命令"), onPressed: () {
showDialog(context: context, builder: (context) {
return AlertDialog(title: const Text("注意"), content: const Text("是否重置启动命令?"), actions: [
@@ -149,26 +148,30 @@ class _SettingPageState extends State<SettingPage> {
]);
});
}),
SizedBox.fromSize(size: const Size.square(8)),
OutlinedButton(style: D.commandButtonStyle, child: const Text("Signal9错误页面"), onPressed: () async {
await D.avncChannel.invokeMethod("launchSignal9Page", {});
}),
]),
const SizedBox.square(dimension: 8),
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)),
const SizedBox.square(dimension: 8),
ValueListenableBuilder(valueListenable: G.bootTextChange, builder:(context, v, child) {
return 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)),
const SizedBox.square(dimension: 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)),
const SizedBox.square(dimension: 8),
const Divider(height: 2, indent: 8, endIndent: 8),
SizedBox.fromSize(size: const Size.square(16)),
const SizedBox.square(dimension: 16),
const Text("你可以在当前所有同一网络下的设备连接同一WiFi的手机电脑等里使用小小电脑。\n\n点击下面的按钮分享链接到其他设备后使用浏览器打开即可。"),
SizedBox.fromSize(size: const Size.square(16)),
const SizedBox.square(dimension: 16),
Wrap(alignment: WrapAlignment.center, spacing: 4.0, runSpacing: 4.0, children: [
OutlinedButton(style: D.commandButtonStyle, child: const Text("复制分享链接"), onPressed: () async {
final String? ip = await NetworkInfo().getWifiIP();
@@ -180,7 +183,7 @@ class _SettingPageState extends State<SettingPage> {
);
return;
}
FlutterClipboard.copy((Util.getCurrentProp("vncUrl") as String).replaceFirst(RegExp.escape("localhost"), ip)).then((value) {
FlutterClipboard.copy((Util.getCurrentProp("vncUrl") as String).replaceAll(RegExp.escape("localhost"), ip)).then((value) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("已复制分享链接"))
@@ -188,37 +191,29 @@ class _SettingPageState extends State<SettingPage> {
});
}),
]),
SizedBox.fromSize(size: const Size.square(16)),
const SizedBox.square(dimension: 16),
TextFormField(maxLines: null, initialValue: Util.getCurrentProp("vncUrl"), decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "网页跳转地址"), onChanged: (value) async {
await Util.setCurrentProp("vncUrl", value);
}),
SizedBox.fromSize(size: const Size.square(8)),
const SizedBox.square(dimension: 8),
TextFormField(maxLines: null, initialValue: Util.getCurrentProp("vncUri"), decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "vnc链接"), onChanged: (value) async {
await Util.setCurrentProp("vncUri", value);
}),
SizedBox.fromSize(size: const Size.square(8)),
const SizedBox.square(dimension: 8),
],))),
ExpansionPanel(
isExpanded: _expandState[1],
headerBuilder: ((context, isExpanded) {
return const ListTile(title: Text("全局设置"), subtitle: Text("在这里关广告、开启终端编辑"));
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"]!),
TextFormField(autovalidateMode: AutovalidateMode.onUserInteraction, initialValue: (Util.getGlobal("termMaxLines") as int).toString(), decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "终端最大行数(重启软件生效)"),
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)),
const SizedBox.square(dimension: 16),
TextFormField(autovalidateMode: AutovalidateMode.onUserInteraction, initialValue: (Util.getGlobal("defaultAudioPort") as int).toString(), decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "pulseaudio接收端口"),
keyboardType: TextInputType.number,
validator: (value) {
@@ -227,80 +222,49 @@ class _SettingPageState extends State<SettingPage> {
});
}
),
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(() {
G.bannerAdsChange.value = !G.bannerAdsChange.value;
});
},),
SizedBox.fromSize(size: const Size.square(8)),
const SizedBox.square(dimension: 16),
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)),
const SizedBox.square(dimension: 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(() {
G.terminalPageChange.value = !G.terminalPageChange.value;
});
},),
SizedBox.fromSize(size: const Size.square(8)),
const SizedBox.square(dimension: 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)),
const SizedBox.square(dimension: 8),
SwitchListTile(title: const Text("屏幕常亮"), value: Util.getGlobal("wakelock") as bool, onChanged:(value) {
G.prefs.setBool("wakelock", value);
WakelockPlus.toggle(enable: value);
setState(() {});
},),
SizedBox.fromSize(size: const Size.square(8)),
const SizedBox.square(dimension: 8),
const Divider(height: 2, indent: 8, endIndent: 8),
SizedBox.fromSize(size: const Size.square(16)),
const SizedBox.square(dimension: 16),
const Text("以下选项修改后将在下次启动软件时生效。"),
SizedBox.fromSize(size: const Size.square(8)),
const SizedBox.square(dimension: 8),
SwitchListTile(title: const Text("开启时启动图形界面"), value: Util.getGlobal("autoLaunchVnc") as bool, onChanged:(value) {
G.prefs.setBool("autoLaunchVnc", value);
setState(() {});
},),
SizedBox.fromSize(size: const Size.square(8)),
const SizedBox.square(dimension: 8),
SwitchListTile(title: const Text("重新安装引导包"), value: Util.getGlobal("reinstallBootstrap") as bool, onChanged:(value) {
G.prefs.setBool("reinstallBootstrap", value);
setState(() {});
},),
SizedBox.fromSize(size: const Size.square(8)),
const SizedBox.square(dimension: 8),
SwitchListTile(title: const Text("getifaddrs桥接"), subtitle: const Text("修复安卓13设备getifaddrs无权限"), value: Util.getGlobal("getifaddrsBridge") as bool, onChanged:(value) {
G.prefs.setBool("getifaddrsBridge", value);
setState(() {});
},),
SizedBox.fromSize(size: const Size.square(8)),
const SizedBox.square(dimension: 8),
SwitchListTile(title: const Text("伪装系统为UOS"), subtitle: const Text("修复UOS微信无法启动"), value: Util.getGlobal("uos") as bool, onChanged:(value) {
G.prefs.setBool("uos", value);
setState(() {});
@@ -311,16 +275,27 @@ class _SettingPageState extends State<SettingPage> {
headerBuilder: ((context, isExpanded) {
return const ListTile(title: Text("显示设置"));
}), body: Padding(padding: const EdgeInsets.all(12), child: Column(children: [
SizedBox.fromSize(size: const Size.square(16)),
const SizedBox.square(dimension: 16),
const Text("""AVNC可以带来获得更好的操控体验
如触摸板触控,双击弹出键盘,自动剪切板,画中画模式等等。这是一个实验性功能。"""),
SizedBox.fromSize(size: const Size.square(16)),
如触摸板触控,双指单击弹出键盘,自动剪切板,画中画模式等等。这是一个实验性功能。"""),
const SizedBox.square(dimension: 16),
Wrap(alignment: WrapAlignment.center, spacing: 4.0, runSpacing: 4.0, children: [
OutlinedButton(style: D.commandButtonStyle, child: const Text("AVNC设置"), onPressed: () async {
await D.avncChannel.invokeMethod("launchPrefsPage", {});
}),
OutlinedButton(style: D.commandButtonStyle, child: const Text("关于AVNC"), onPressed: () async {
await D.avncChannel.invokeMethod("launchAboutPage", {});
}),
OutlinedButton(style: D.commandButtonStyle, child: const Text("AVNC启动时分辨率设置"), onPressed: () async {
String w = "1440";
String h = "720";
final s = WidgetsBinding.instance.platformDispatcher.views.first.physicalSize;
final w0 = max(s.width, s.height);
final h0 = min(s.width, s.height);
String w = (w0 * 0.75).round().toString();
String h = (h0 * 0.75).round().toString();
showDialog(context: context, builder: (context) {
return AlertDialog(title: const Text("分辨率设置"), content: SingleChildScrollView(child: Column(children: [
Text("你的设备屏幕分辨率是${w0.round()}x${h0.round()}"),
const SizedBox.square(dimension: 8),
TextFormField(autovalidateMode: AutovalidateMode.onUserInteraction, initialValue: w, decoration: const InputDecoration(border: OutlineInputBorder(), labelText: ""), keyboardType: TextInputType.number,
validator: (value) {
return Util.validateBetween(value, 200, 7680, () {
@@ -328,7 +303,7 @@ class _SettingPageState extends State<SettingPage> {
});
}
),
SizedBox.fromSize(size: const Size.square(8)),
const SizedBox.square(dimension: 8),
TextFormField(autovalidateMode: AutovalidateMode.onUserInteraction, initialValue: h, decoration: const InputDecoration(border: OutlineInputBorder(), labelText: ""), keyboardType: TextInputType.number,
validator: (value) {
return Util.validateBetween(value, 200, 7680, () {
@@ -353,48 +328,33 @@ sed -i -E "s@^(VNC_RESOLUTION)=.*@\\1=${w}x${h}@" \$(command -v startvnc)""");
]);
});
}),
OutlinedButton(style: D.commandButtonStyle, child: const Text("AVNC设置"), onPressed: () async {
await D.avncChannel.invokeMethod("launchPrefsPage", {});
}),
OutlinedButton(style: D.commandButtonStyle, child: const Text("关于AVNC"), onPressed: () async {
await D.avncChannel.invokeMethod("launchAboutPage", {});
}),
]),
SizedBox.fromSize(size: const Size.square(8)),
const SizedBox.square(dimension: 8),
SwitchListTile(title: const Text("默认使用AVNC"), subtitle: const Text("下次启动时生效"), value: Util.getGlobal("useAvnc") as bool, onChanged:(value) {
G.prefs.setBool("useAvnc", value);
setState(() {});
},),
SizedBox.fromSize(size: const Size.square(16)),
const SizedBox.square(dimension: 16),
const Divider(height: 2, indent: 8, endIndent: 8),
SizedBox.fromSize(size: const Size.square(16)),
const SizedBox.square(dimension: 16),
const Text("""高分辨率支持可以为大屏幕设备带来更高清的体验!
注意:
选项开启后显示会变得很大,请在图形界面的左栏设置里手动调整缩放到一个你认为合适的值
如果使用AVNC可以在上面设置一个更大的分辨率。
选项开启后显示会变得很大,请设置一个合适的分辨率
一些软件可能会存在显示问题,或者显示速度变慢。"""),
SizedBox.fromSize(size: const Size.square(16)),
TextFormField(maxLines: null, initialValue: Util.getGlobal("defaultHidpiOpt") as String, decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "HiDPI环境变量"), readOnly: Util.shouldWatchAds(D.adsRequired["changeHidpiOpt"]!),
onTap: () {
if (Util.shouldWatchAds(D.adsRequired["changeHidpiOpt"]!)) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("观看十二次视频广告永久解锁><"))
);
}
},
const SizedBox.square(dimension: 16),
TextFormField(maxLines: null, initialValue: Util.getGlobal("defaultHidpiOpt") as String, decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "HiDPI环境变量"),
onChanged: (value) async {
await G.prefs.setString("defaultHidpiOpt", value);
},
),
SizedBox.fromSize(size: const Size.square(8)),
const SizedBox.square(dimension: 8),
SwitchListTile(title: const Text("高分辨率支持"), subtitle: const Text("下次启动时生效"), value: Util.getGlobal("isHidpiEnabled") as bool, onChanged:(value) {
G.prefs.setBool("isHidpiEnabled", value);
setState(() {});
},),
SizedBox.fromSize(size: const Size.square(16)),
const SizedBox.square(dimension: 16),
],))),
ExpansionPanel(
isExpanded: _expandState[3],
@@ -436,15 +396,7 @@ sed -i -E "s@^(VNC_RESOLUTION)=.*@\\1=${w}x${h}@" \$(command -v startvnc)""");
}),
]),
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("观看八次视频广告永久解锁><"))
);
}
},
TextFormField(maxLines: null, initialValue: Util.getGlobal("defaultFFmpegCommand") as String, decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "ffmpeg推流命令"),
onChanged: (value) async {
await G.prefs.setString("defaultFFmpegCommand", value);
},
@@ -469,7 +421,7 @@ sed -i -E "s@^(VNC_RESOLUTION)=.*@\\1=${w}x${h}@" \$(command -v startvnc)""");
G.isStreamServerStarted = value;
setState(() {});
},),
SizedBox.fromSize(size: const Size.square(8)),
const SizedBox.square(dimension: 8),
SwitchListTile(title: const Text("启动推流"), value: G.isStreaming, onChanged:(value) {
switch (value) {
case true: {
@@ -490,7 +442,7 @@ sed -i -E "s@^(VNC_RESOLUTION)=.*@\\1=${w}x${h}@" \$(command -v startvnc)""");
G.isStreaming = value;
setState(() {});
},),
SizedBox.fromSize(size: const Size.square(8))
const SizedBox.square(dimension: 8)
],))),
ExpansionPanel(
isExpanded: _expandState[4],
@@ -498,7 +450,7 @@ sed -i -E "s@^(VNC_RESOLUTION)=.*@\\1=${w}x${h}@" \$(command -v startvnc)""");
return const ListTile(title: Text("文件访问"));
}), body: Padding(padding: const EdgeInsets.all(12), child: Column(children: [
const Text("通过这里获取更多文件权限,以实现对特殊目录的访问。"),
SizedBox.fromSize(size: const Size.square(16)),
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.storage.request();
@@ -507,64 +459,51 @@ sed -i -E "s@^(VNC_RESOLUTION)=.*@\\1=${w}x${h}@" \$(command -v startvnc)""");
Permission.manageExternalStorage.request();
}),
]),
SizedBox.fromSize(size: const Size.square(16)),
const SizedBox.square(dimension: 16),
],))),
ExpansionPanel(
isExpanded: _expandState[5],
headerBuilder: ((context, isExpanded) {
return const ListTile(title: Text("virgl加速"), subtitle: Text("实验性功能"));
return const ListTile(title: Text("图形加速"), subtitle: Text("实验性功能"));
}), body: Padding(padding: const EdgeInsets.all(12), child: Column(children: [
const Text("""virgl加速可部分利用设备GPU提升系统图形处理表现但由于设备差异也可能导致容器系统及软件运行不稳定甚至异常退出。
const Text("""图形加速可部分利用设备GPU提升系统图形处理表现但由于设备差异也可能导致容器系统及软件运行不稳定甚至异常退出。
请先开启测试按钮启动virgl服务器如果按钮没有自动关闭说明至少启动没问题
不过运行情况依然无法保证。
"""),
SizedBox.fromSize(size: const Size.square(16)),
Virgl可为使用OpenGL ES的应用提供加速。"""),
const SizedBox.square(dimension: 16),
TextFormField(maxLines: null, initialValue: Util.getGlobal("defaultVirglCommand") as String, decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "virgl服务器参数"),
onChanged: (value) async {
await G.prefs.setString("defaultVirglCommand", value);
},
),
SizedBox.fromSize(size: const Size.square(8)),
SwitchListTile(title: const Text("测试"), subtitle: const Text("启动virgl_test_server"), value: G.isVirglServerStarted, onChanged:(value) {
switch (value) {
case true: {
G.virglServerPty = Pty.start("/system/bin/sh");
G.virglServerPty.write(const Utf8Encoder().convert("export CONTAINER_DIR=${G.dataPath}/containers/${G.currentContainer}\n${G.dataPath}/bin/virgl_test_server ${Util.getGlobal("defaultVirglCommand")}\nexit\n"));
G.virglServerPty.exitCode.then((value) {
G.isVirglServerStarted = false;
setState(() {});
});
}
break;
case false: {
G.virglServerPty.write(const Utf8Encoder().convert("\x03exit\n"));
}
break;
}
G.isVirglServerStarted = value;
setState(() {});
},),
SizedBox.fromSize(size: const Size.square(16)),
TextFormField(maxLines: null, initialValue: Util.getGlobal("defaultVirglOpt") as String, decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "图形环境变量"),
const SizedBox.square(dimension: 8),
TextFormField(maxLines: null, initialValue: Util.getGlobal("defaultVirglOpt") as String, decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "virgl环境变量"),
onChanged: (value) async {
await G.prefs.setString("defaultVirglOpt", value);
},
),
SizedBox.fromSize(size: const Size.square(8)),
SwitchListTile(title: const Text("启用virgl加速"), subtitle: const Text("下次启动时生效"), value: Util.getGlobal("virgl") as bool, onChanged:(value) {
if (value && Util.shouldWatchAds(D.adsRequired["enableVirgl"]!)) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("观看十次视频广告永久解锁><"))
);
return;
}
const SizedBox.square(dimension: 8),
SwitchListTile(title: const Text("启用Virgl加速"), subtitle: const Text("下次启动时生效"), value: Util.getGlobal("virgl") as bool, onChanged:(value) {
G.prefs.setBool("virgl", value);
setState(() {});
},),
SizedBox.fromSize(size: const Size.square(16)),
const SizedBox.square(dimension: 16),
const Divider(height: 2, indent: 8, endIndent: 8),
const SizedBox.square(dimension: 16),
const Text("""搭载Adreno GPU的设备通常可以使用Turnip驱动加速使用Vulkan的软件。配合Zink驱动可实现加速使用OpenGL的软件。
(也就是搭载不太新也不太旧的骁龙处理器的设备)"""),
const SizedBox.square(dimension: 8),
TextFormField(maxLines: null, initialValue: Util.getGlobal("defaultTurnipOpt") as String, decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "Turnip环境变量"),
onChanged: (value) async {
await G.prefs.setString("defaultTurnipOpt", value);
},
),
const SizedBox.square(dimension: 8),
SwitchListTile(title: const Text("启用Turnip+Zink驱动"), subtitle: const Text("下次启动时生效"), value: Util.getGlobal("turnip") as bool, onChanged:(value) async {
G.prefs.setBool("turnip", value);
setState(() {});
},),
const SizedBox.square(dimension: 16),
],))),
ExpansionPanel(
isExpanded: _expandState[6],
@@ -575,9 +514,9 @@ sed -i -E "s@^(VNC_RESOLUTION)=.*@\\1=${w}x${h}@" \$(command -v startvnc)""");
运行windows程序需要经过架构和系统两层模拟不要对运行速度抱有期待。程序崩溃也是常有的。
你需要耐心。即使图形界面什么也没显示。看看终端,还在继续输出吗?还是停止在某个报错?
建议将要运行的Windows程序连同程序文件夹移至桌面运行。
如果不耐烦可以去看个广告消磨时间bushi
你需要耐心。即使图形界面什么也没显示。看看终端,还在继续输出吗?还是停止在某个报错?
或者寻找该windows软件官方是否提供linux arm64版本。
@@ -585,7 +524,7 @@ sed -i -E "s@^(VNC_RESOLUTION)=.*@\\1=${w}x${h}@" \$(command -v startvnc)""");
跨架构/跨系统提供类似binfmt_misc的支持。
你可以直接执行x86或x64的elf系统会自动调用box86/box64也可以直接执行exe文件系统会自动调用wine64
前提是这些文件拥有可执行权限。"""),
SizedBox.fromSize(size: const Size.square(8)),
const SizedBox.square(dimension: 8),
Wrap(alignment: WrapAlignment.center, spacing: 4.0, runSpacing: 4.0, children: [
OutlinedButton(style: D.commandButtonStyle, child: const Text("安装box86和box64"), onPressed: () {
Util.termWrite("bash ~/.local/share/tiny/extra/install-box");
@@ -632,9 +571,9 @@ sed -i -E "s@^(VNC_RESOLUTION)=.*@\\1=${w}x${h}@" \$(command -v startvnc)""");
G.pageIndex.value = 0;
}),
]),
SizedBox.fromSize(size: const Size.square(16)),
const SizedBox.square(dimension: 16),
const Divider(height: 2, indent: 8, endIndent: 8),
SizedBox.fromSize(size: const Size.square(16)),
const SizedBox.square(dimension: 16),
const Text("""开启wine后的常用指令点击后前往图形界面耐心等待。
任意程序启动参考时间:
@@ -645,7 +584,7 @@ sed -i -E "s@^(VNC_RESOLUTION)=.*@\\1=${w}x${h}@" \$(command -v startvnc)""");
初始化时间:
可能比本软件初始化还长
"""),
SizedBox.fromSize(size: const Size.square(8)),
const SizedBox.square(dimension: 8),
Wrap(alignment: WrapAlignment.center, spacing: 4.0, runSpacing: 4.0, children: D.wineCommands.asMap().entries.map<Widget>(
(e) {
return OutlinedButton(style: D.commandButtonStyle, child: Text(e.value["name"]!), onPressed: () {
@@ -654,11 +593,11 @@ sed -i -E "s@^(VNC_RESOLUTION)=.*@\\1=${w}x${h}@" \$(command -v startvnc)""");
});
}
).toList()),
SizedBox.fromSize(size: const Size.square(16)),
const SizedBox.square(dimension: 16),
const Divider(height: 2, indent: 8, endIndent: 8),
SizedBox.fromSize(size: const Size.square(16)),
const SizedBox.square(dimension: 16),
const Text("以下选项修改后将在下次启动软件时生效。"),
SizedBox.fromSize(size: const Size.square(8)),
const SizedBox.square(dimension: 8),
SwitchListTile(title: const Text("启用box86/box64"), subtitle: const Text("运行跨架构软件"), value: Util.getGlobal("isBoxEnabled") as bool, onChanged:(value) async {
//检测box64是否存在存在才开启
if (value && !await File("${G.dataPath}/tiny/cross/box64").exists()) {
@@ -698,36 +637,7 @@ fi""");
G.prefs.setBool("isWineEnabled", value);
setState(() {});
},),
SizedBox.fromSize(size: const Size.square(16)),
],))),
ExpansionPanel(
isExpanded: _expandState[7],
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 SizedBox.square(dimension: 16),
],))),
],);
}
@@ -757,128 +667,26 @@ class _InfoPageState extends State<InfoPage> {
elevation: 1,
expandedHeaderPadding: const EdgeInsets.all(0),
expansionCallback: (panelIndex, isExpanded) {
setState(() {
_expandState[panelIndex] = isExpanded;
});
WidgetsBinding.instance.addPostFrameCallback((_) => setState(() {}));
},
children: [
ExpansionPanel(
headerBuilder: (context, isExpanded) {
return const ListTile(title: Text("使用说明"));
},
body: const Padding(padding: EdgeInsets.all(8), child: Text("""
第一次加载大概需要5到10分钟...
正常情况下,加载完成后软件会自动跳转到图形界面
在图形界面时,点击即鼠标左键
双指点击为鼠标右键
双指划动为鼠标滚轮
在图形界面返回,可以回到终端界面和控制界面
你可以在控制界面安装更多软件或者阅读帮助信息
请不要在安装时退出软件
如果过了很长时间都没有加载完成
可以去设置里看看小小电脑占用空间是不是一直没变
如果是说明卡在什么地方了
建议清除本软件数据重来一次
(有一位网友提到过
自己无论怎么清软件数据都装不上
但在重启手机之后就装上了)
一些注意事项:
此软件以GPL协议免费开源
如果是买的就是被骗了, 请举报
源代码在这里: https://github.com/Cateners/tiny_computer
软件也会第一时间在这里更新
请尽可能在这里下载软件, 确保是正版
常见问题:
如果你的系统版本大于等于android 12
可能会在使用过程中异常退出(返回错误码9)
届时本软件会提供方案指引你修复
并不难
但是软件没有权限
不能帮你修复
如果你的系统版本大于等于android 13
那么很可能一些网页应用如jupyter notebook
bilibili客户端等等不可用
可以去全局设置开启getifaddrs桥接
如果你给了存储权限
那么通过主目录下的文件夹
就可以访问手机存储
有一些设备做了更多访问限制
比如下载文件夹可能不可写入
这样会导致把文件保存到下载目录时出现问题
(火狐浏览器可能因此无法下载文件)
不过这个很好解决
换个文件夹保存就行了
如果认为界面大小比例不合适
可以通过调整图形界面左栏设置-高级里的屏幕缩放比例
如果感觉界面卡卡的
可以适当调低图像质量或压缩等级
如果你想安装其他软件
可以使用容器自带的tmoe
但并不保证安装了能用哦
(事实上, 目前容器里的
VSCode、输入法
都是用tmoe安装的
就连系统本身也是用tmoe安装的)
也可以在网上搜索
"ubuntu安装xxx教程"
"linux安装xxx教程"等等
本软件也提供一些基本软件安装按钮
包括图形处理, 视频剪辑, 科学计算相关的软件
稍后你就会看到
如果你需要更多字体
在给了存储权限的情况下
直接将字体复制到手机存储的Fonts文件夹即可
一些常用的办公字体
可以在Windows电脑的C:\\Windows\\Fonts文件夹找到
由于可能的版权问题
软件不能帮你做
关于中文输入的问题
强烈建议不要使用安卓中文输入法直接输入中文
而是使用英文键盘通过容器的输入法(Ctrl+空格切换)输入中文
避免丢字错字
在之前的版本中有网友反馈过这些问题
还请注意:
三星Galaxy S21 Ultra, 安卓13, 黑屏
红米Note 12, 安卓13miui14, 黑屏
红米Note 11T Pro+ miui13.0.4,“无法连接”
Vivo Pad安卓13看不见鼠标移动可以去左栏设置开启显示原系统光标替代
关于这些
我目前没有什么好的解决办法
(毕竟我没有这些设备
也不方便定位原因)
如果你遇到了类似问题
不管解没解决
都可以去https://github.com/Cateners/tiny_computer/issues/留个言
如果软件里有程序正在正常运行
请不要强行关闭本软件
否则可能会损坏容器
特别是在安装某些比较大的软件的时候
感谢使用!
(顺带一提, 全部解压完大概需要4~5GB空间
解压途中占用空间可能更多
请确保有足够的空间
(这样真的Tiny吗><))
"""
body: Padding(padding: const EdgeInsets.all(8), child: Column(
children: [
ValueListenableBuilder(valueListenable: G.helpText, builder:(context, value, child) {
return Text(value);
}),
const SizedBox.square(dimension: 16),
Wrap(alignment: WrapAlignment.center, spacing: 4.0, runSpacing: 4.0, children: D.faq
.asMap().entries.map<Widget>((e) {
return OutlinedButton(style: D.commandButtonStyle, child: Text(e.value["q"]!), onPressed: () {
G.helpText.value = e.value["a"]!;
});
}).toList())],
)),
isExpanded: _expandState[0],
),
@@ -1256,47 +1064,19 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
------------
unity_ads_plugin
MIT License
Copyright (c) 2021 Pavel Zaichyk
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""))),
ExpansionPanel(
isExpanded: _expandState[2],
headerBuilder: ((context, isExpanded) {
return const ListTile(title: Text("隐私政策"));
}), body: const Padding(padding: EdgeInsets.all(8), child: Text("""
除由Unity提供的广告功能外, 本软件不会收集你的隐私信息。
本软件不会收集你的隐私信息。
当然,你在容器系统内部安装或使用的软件行为(包括通过快捷指令)就不受我控制了,我不对其负责。
本软件申请的权限用于以下目的:
文件相关权限:用于系统访问手机目录;
相机和麦克风:用于推流,默认不会开启。
关于广告获取隐私信息的说明, 在第一次看广告时Unity会向你做出告知。
届时你可以选择要向Unity提供哪些信息。
"""))),
ExpansionPanel(
isExpanded: _expandState[3],
@@ -1318,36 +1098,7 @@ SOFTWARE.
}), body: Column(
children: [
const Padding(padding: EdgeInsets.all(8), child: Text("""
这个软件预计会有一些广告
分为横幅广告和视频广告
横幅广告在终端和控制页面的顶端出现
(但不知道是不是因为代码没写对
反正我从没见横幅广告成功加载过)
视频广告在需要解锁某些功能时自行观看
这些功能需要累计完整观看对应数目广告后永久解锁:
启用终端: 观看2个广告
启用小键盘: 观看3个广告
关闭横幅广告: 观看5个广告
终端最大行数修改: 观看6个广告
推流参数修改: 观看8个广告
启用virgl加速: 观看10个广告
HiDPI环境变量修改: 观看12个广告
我设置了每天最多可以看5个广告。
只要看满1个广告, 就可以在本次使用期间临时解锁全部功能。
只要看满2个广告, 就可以在当日使用期间临时解锁全部功能。
总之为了良好的体验
在图形界面是不会出现广告的
这点还请放心
---注意事项---
我注意到Unity提供了一些不那么合适的广告
一般如果我看到这些广告就在后台直接禁了
不过也可能有漏网之鱼
可以联系我禁掉
如果认为好用的话,可以推荐给其他人用噢!
""")),
ElevatedButton(
onPressed: () {
@@ -1461,14 +1212,14 @@ class TerminalPage extends StatelessWidget {
children: const [Text('Ctrl'), Text('Alt'), Text('Shift')],
),
),
SizedBox.fromSize(size: const Size.square(8)),
const SizedBox.square(dimension: 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));
return const SizedBox.square(dimension: 4);
}, itemCount: D.termCommands.length))), SizedBox.fromSize(size: const Size(72, 0))])):const SizedBox.square(dimension: 0);
})
]);
}
@@ -1498,7 +1249,7 @@ class _FastCommandsState extends State<FastCommands> {
TextFormField(initialValue: name, decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "指令名称"), onChanged: (value) {
name = value;
}),
SizedBox.fromSize(size: const Size.square(8)),
const SizedBox.square(dimension: 8),
TextFormField(maxLines: null, initialValue: command, decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "指令内容"), onChanged: (value) {
command = value;
}),
@@ -1531,7 +1282,7 @@ class _FastCommandsState extends State<FastCommands> {
TextFormField(initialValue: name, decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "指令名称"), onChanged: (value) {
name = value;
}),
SizedBox.fromSize(size: const Size.square(8)),
const SizedBox.square(dimension: 8),
TextFormField(maxLines: null, initialValue: command, decoration: const InputDecoration(border: OutlineInputBorder(), labelText: "指令内容"), onChanged: (value) {
command = value;
}),
@@ -1597,29 +1348,19 @@ class _MyHomePageState extends State<MyHomePage> {
}
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky,overlays: []);
return Scaffold(
appBar: AppBar(
title: Text(isLoadingComplete?Util.getCurrentProp("name"):widget.title),
),
body: isLoadingComplete?Column(mainAxisSize: MainAxisSize.min, children: [
ValueListenableBuilder(valueListenable: G.bannerAdsChange, builder:(context, value, child) {
return (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),
body: isLoadingComplete?
ValueListenableBuilder(valueListenable: G.pageIndex, builder: (context, value, child) {
return IndexedStack(index: G.pageIndex.value, children: const [TerminalPage(), Padding(
padding: EdgeInsets.all(8),
child: Scrollbar(child: SingleChildScrollView(restorationId: "control-scroll", child: Column(
children: [
const Padding(
Padding(
padding: EdgeInsets.all(16),
child: FractionallySizedBox(
widthFactor: 0.4,
@@ -1628,22 +1369,18 @@ class _MyHomePageState extends State<MyHomePage> {
)
),
),
/*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:
FastCommands(),
Padding(padding: EdgeInsets.all(8), child: Card(child: Padding(padding: EdgeInsets.all(8), child:
Column(children: [
const SettingPage(),
SizedBox.fromSize(size: const Size.square(8)),
const InfoPage(openFirstInfo: false)
SettingPage(),
SizedBox.square(dimension: 8),
InfoPage(openFirstInfo: false)
])
)))
]
)))
)]);
}))]):const LoadingPage(),
}):const LoadingPage(),
bottomNavigationBar: ValueListenableBuilder(valueListenable: G.pageIndex, builder:(context, value, child) {
return Visibility(visible: isLoadingComplete,
// child: BottomNavigationBar(currentIndex: G.pageIndex.value,

View File

@@ -28,7 +28,6 @@ import 'package:retry/retry.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:xterm/xterm.dart';
@@ -39,11 +38,8 @@ import 'package:permission_handler/permission_handler.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:unity_ads_plugin/unity_ads_plugin.dart';
import 'package:clipboard/clipboard.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import 'package:device_info_plus/device_info_plus.dart';
class Util {
@@ -77,14 +73,10 @@ class Util {
//int defaultAudioPort = 4718: 默认pulseaudio端口(为了避免和其它软件冲突改成4718了原默认4713)
//bool autoLaunchVnc = true: 是否自动启动VNC并跳转
//String lastDate: 上次启动软件的日期yyyy-MM-dd
//int adsWatchedToday: 今日视频广告观看数量
//int adsWatchedTotal: 视频广告观看数量
//bool isBannerAdsClosed = false
//bool isTerminalWriteEnabled = false
//bool isTerminalCommandsEnabled = false
//int termMaxLines = 4095 终端最大行数
//double termFontScale = 1 终端字体大小
//int vip = 0 用户等级vip免广告你要改吗(ToT)
//bool isStickyKey = true 终端ctrl, shift, alt键是否粘滞
//String defaultFFmpegCommand 默认推流命令
//String defaultVirglCommand 默认virgl参数
@@ -103,8 +95,6 @@ class Util {
//String[] containersInfo: 所有容器信息(json)
//{name, boot:"\$DATA_DIR/bin/proot ...", vnc:"startnovnc", vncUrl:"...", commands:[{name:"更新和升级", command:"apt update -y && apt upgrade -y"},
// bind:[{name:"U盘", src:"/storage/xxxx", dst:"/media/meow"}]...]}
//String[] adsBonus: 观看广告获取的奖励(json)
//{name: "xxx", amount: xxx}
//TODO: 这么写还是不对劲,有空改成类试试?
static dynamic getGlobal(String key) {
bool b = G.prefs.containsKey(key);
@@ -113,14 +103,10 @@ class Util {
case "defaultAudioPort" : return b ? G.prefs.getInt(key)! : (value){G.prefs.setInt(key, value); return value;}(4718);
case "autoLaunchVnc" : return b ? G.prefs.getBool(key)! : (value){G.prefs.setBool(key, value); return value;}(true);
case "lastDate" : return b ? G.prefs.getString(key)! : (value){G.prefs.setString(key, value); return value;}("1970-01-01");
case "adsWatchedToday" : return b ? G.prefs.getInt(key)! : (value){G.prefs.setInt(key, value); return value;}(0);
case "adsWatchedTotal" : return b ? G.prefs.getInt(key)! : (value){G.prefs.setInt(key, value); return value;}(0);
case "isBannerAdsClosed" : return b ? G.prefs.getBool(key)! : (value){G.prefs.setBool(key, value); return value;}(false);
case "isTerminalWriteEnabled" : return b ? G.prefs.getBool(key)! : (value){G.prefs.setBool(key, value); return value;}(false);
case "isTerminalCommandsEnabled" : return b ? G.prefs.getBool(key)! : (value){G.prefs.setBool(key, value); return value;}(false);
case "termMaxLines" : return b ? G.prefs.getInt(key)! : (value){G.prefs.setInt(key, value); return value;}(4095);
case "termFontScale" : return b ? G.prefs.getDouble(key)! : (value){G.prefs.setDouble(key, value); return value;}(1.0);
case "vip" : return b ? G.prefs.getInt(key)! : (value){G.prefs.setInt(key, value); return value;}(1);
case "isStickyKey" : return b ? G.prefs.getBool(key)! : (value){G.prefs.setBool(key, value); return value;}(true);
case "reinstallBootstrap" : return b ? G.prefs.getBool(key)! : (value){G.prefs.setBool(key, value); return value;}(false);
case "getifaddrsBridge" : return b ? G.prefs.getBool(key)! : (value){G.prefs.setBool(key, value); return value;}(false);
@@ -128,15 +114,16 @@ class Util {
case "isBoxEnabled" : return b ? G.prefs.getBool(key)! : (value){G.prefs.setBool(key, value); return value;}(false);
case "isWineEnabled" : return b ? G.prefs.getBool(key)! : (value){G.prefs.setBool(key, value); return value;}(false);
case "virgl" : return b ? G.prefs.getBool(key)! : (value){G.prefs.setBool(key, value); return value;}(false);
case "turnip" : return b ? G.prefs.getBool(key)! : (value){G.prefs.setBool(key, value); return value;}(false);
case "wakelock" : return b ? G.prefs.getBool(key)! : (value){G.prefs.setBool(key, value); return value;}(false);
case "isHidpiEnabled" : return b ? G.prefs.getBool(key)! : (value){G.prefs.setBool(key, value); return value;}(false);
case "useAvnc" : return b ? G.prefs.getBool(key)! : (value){G.prefs.setBool(key, value); return value;}(false);
case "useAvnc" : return b ? G.prefs.getBool(key)! : (value){G.prefs.setBool(key, value); return value;}(true);
case "defaultFFmpegCommand" : return b ? G.prefs.getString(key)! : (value){G.prefs.setString(key, value); return value;}("-hide_banner -an -max_delay 1000000 -r 30 -f android_camera -camera_index 0 -i 0:0 -vf scale=iw/2:-1 -rtsp_transport udp -f rtsp rtsp://127.0.0.1:8554/stream");
case "defaultVirglCommand" : return b ? G.prefs.getString(key)! : (value){G.prefs.setString(key, value); return value;}("--socket-path=\$CONTAINER_DIR/tmp/.virgl_test");
case "defaultVirglOpt" : return b ? G.prefs.getString(key)! : (value){G.prefs.setString(key, value); return value;}("GALLIUM_DRIVER=virpipe MESA_GL_VERSION_OVERRIDE=4.0");
case "defaultVirglOpt" : return b ? G.prefs.getString(key)! : (value){G.prefs.setString(key, value); return value;}("GALLIUM_DRIVER=virpipe");
case "defaultTurnipOpt" : return b ? G.prefs.getString(key)! : (value){G.prefs.setString(key, value); return value;}("MESA_LOADER_DRIVER_OVERRIDE=zink VK_ICD_FILENAMES=/home/tiny/.local/share/tiny/extra/freedreno_icd.aarch64.json TU_DEBUG=noconform MESA_VK_WSI_DEBUG=sw");
case "defaultHidpiOpt" : return b ? G.prefs.getString(key)! : (value){G.prefs.setString(key, value); return value;}("GDK_SCALE=2 QT_FONT_DPI=192");
case "containersInfo" : return G.prefs.getStringList(key)!;
case "adsBonus" : return b ? G.prefs.getStringList(key)! : (value){G.prefs.setStringList(key, value); return value;}([].cast<String>());
}
}
@@ -186,43 +173,6 @@ class Util {
);
}
//返回单个G.bonusTable定义的item
static Map<String, dynamic> getRandomBonus() {
final random = Random();
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 D.bonusTable) {
cumulativeWeight += item['weight'];
if (randomIndex <= cumulativeWeight) {
return item;
}
}
return D.bonusTable[0];
}
//由getRandomBonus返回的数据
static Future<void> applyBonus(Map<String, dynamic> bonus) async {
bool flag = false;
List<String> ret = Util.getGlobal("adsBonus").map((e) {
Map<String, dynamic> item = jsonDecode(e);
return (item["name"] == bonus["name"])?
jsonEncode(item..update("amount", (v) {
flag = true;
return v + bonus["amount"];
})):e;
}).toList().cast<String>();
if (!flag) {
ret.add("""{"name": "${bonus["name"]}", "amount": ${bonus["amount"]}}""");
}
await G.prefs.setStringList("adsBonus", ret);
}
//根据已看广告量判断是否应该继续看广告
static bool shouldWatchAds(int expectNum) {
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
static String? validateBetween(String? value, int min, int max, Function opr) {
if (value == null || value.isEmpty) {
@@ -322,45 +272,7 @@ class TermPty {
}
//Signal 9 hint
if (code == -9) {
Navigator.push(G.homePageStateContext, MaterialPageRoute(builder: (context) {
const TextStyle ts = TextStyle(fontSize: 16, color: Colors.white, fontWeight: FontWeight.normal);
const String helperLink = "https://www.vmos.cn/zhushou.htm";
const String helperLink2 = "https://b23.tv/WwqOqW6";
return Scaffold(backgroundColor: Colors.deepPurple,
body: Center(
child: Scrollbar(child:
SingleChildScrollView(
child: Column(children: [
const Text(":(\n发生了什么?", textScaler: TextScaler.linear(2), style: ts, textAlign: TextAlign.center,),
const Text("终端异常退出, 返回错误码9\n此错误通常是高版本安卓系统(12+)限制进程造成的, \n可以使用以下工具修复:", style: ts, textAlign: TextAlign.center),
const SelectableText(helperLink, style: ts, textAlign: TextAlign.center),
const Text("(复制链接到浏览器查看)", style: ts, textAlign: TextAlign.center),
OutlinedButton(onPressed: () {
FlutterClipboard.copy(helperLink).then(( value ) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: const Text("已复制"), action: SnackBarAction(label: "跳转", onPressed: () {
launchUrl(Uri.parse(helperLink), mode: LaunchMode.externalApplication);
},))
);
});
}, child: const Text("复制", style: ts, textAlign: TextAlign.center)),
const Text("如果不能解决请参考此教程: ", style: ts, textAlign: TextAlign.center),
OutlinedButton(onPressed: () {
FlutterClipboard.copy(helperLink2).then(( value ) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: const Text("已复制"), action: SnackBarAction(label: "跳转", onPressed: () {
launchUrl(Uri.parse(helperLink2), mode: LaunchMode.externalApplication);
},))
);
});
}, child: const Text("查看", style: ts, textAlign: TextAlign.center))
]),
)
)
));
}));
D.avncChannel.invokeMethod("launchSignal9Page", {});
}
});
terminal.onOutput = (data) {
@@ -386,11 +298,84 @@ class TermPty {
//default values
class D {
//帮助信息
static const faq = [
{"q":"安卓12及以上注意事项:错误码9", "a":"""如果你的系统版本大于等于android 12
可能会在使用过程中异常退出(返回错误码9)
届时本软件会提供方案指引你修复
并不难
但是软件没有权限
不能帮你修复
你也可以在高级设置里手动前往错误页面"""},
{"q":"安卓13注意事项", "a":"""如果你的系统版本大于等于android 13
那么很可能一些网页应用如jupyter notebook
bilibili客户端等等不可用
可以去全局设置开启getifaddrs桥接"""},
{"q":"用一会就断掉", "a":"""这应该是出现了错误9的情况
下次出现此情况时
按设备的返回键(或用返回手势)
应该能看到软件提供的修复引导"""},
{"q":"如何访问设备文件?", "a":"""如果你给了存储权限
那么通过主目录下的文件夹
就可以访问设备存储
要访问整个设备存储可以访问sd文件夹
此外主文件夹的很多文件夹与设备文件夹绑定
比如主文件夹的下载文件夹就是设备的下载文件夹"""},
{"q":"如何访问SD卡文件", "a":"""首先用其他文件管理器查看SD卡路径
(通常为/storage/xxxx...
然后把该地址输入到小小电脑的文件管理器回车即可
注意不要只输/storage
或者从/storage文件夹点进去
那样是没有权限的"""},
{"q":"自带的火狐浏览器无法下载文件", "a":"""检查是否授予小小电脑存储权限
火狐下载的文件会保存在设备的下载文件夹
如果不想授予存储权限也可在火狐的设置里更改下载文件夹"""},
{"q":"安装更多软件?", "a":"""本软件的初衷是作为PC应用引擎的平替
所以我不会提供安装除WPS等软件外的帮助
另外你需要一些Linux系统使用经验
如果你想安装其他软件
可以使用容器自带的tmoe
但并不保证安装了能用哦
(事实上, 目前容器里的
VSCode、输入法
都是用tmoe安装的
就连系统本身也是用tmoe安装的)
也可以在网上搜索
"ubuntu安装xxx教程"
"linux安装xxx教程"等等
要注意容器环境和完整Linux有不同
你可能需要做一些修补工作
比如基于Electron的软件通常需要添加--no-sandbox参数才能使用"""},
{"q":"WPS没有常用字体", "a":"""如果你需要更多字体
在给了存储权限的情况下
直接将字体复制到设备存储的Fonts文件夹即可
一些常用的办公字体
可以在Windows电脑的C:\\Windows\\Fonts文件夹找到
由于可能的版权问题
软件不能帮你做"""},
{"q":"中文输入法?", "a":"""关于中文输入的问题
强烈建议不要使用安卓中文输入法直接输入中文
而是使用英文键盘通过容器的输入法(Ctrl+空格切换)输入中文
避免丢字错字"""},
{"q":"镜像正在同步", "a":"""偶尔会出现这种情况
一段时间后就会同步完成
请几个小时后再试一次"""},
{"q":"找不到sys/cdefs.h", "a":"""点击上面无法编译C语言程序的快捷指令"""},
{"q":"安装box86/box64/wine很慢", "a":"""请尝试使用魔法"""},
];
//默认快捷指令
static const commands = [{"name":"检查更新并升级", "command":"sudo apt update && sudo apt upgrade -y"},
static const commands = [{"name":"检查更新并升级", "command":"sudo dpkg --configure -a && sudo apt update && sudo apt full-upgrade -y && sudo apt autoremove -y && sudo localedef -c -i zh_CN -f UTF-8 zh_CN.UTF-8"},
{"name":"查看系统信息", "command":"neofetch -L && neofetch --off"},
{"name":"清屏", "command":"clear"},
{"name":"查看IP", "command":"hostname -I # 如果显示无权限(Permission denied)请在全局设置里开启getifaddrs桥接"},
{"name":"中断任务", "command":"\x03"},
{"name":"安装图形处理软件Krita", "command":"sudo apt update && sudo apt install -y krita krita-l10n"},
{"name":"卸载Krita", "command":"sudo apt autoremove --purge -y krita krita-l10n"},
@@ -398,9 +383,8 @@ class D {
{"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":r"""cat << 'EOF' | sh && sudo apt update && sudo apt install -y /tmp/wps.deb
url=$(curl -L https://linux.wps.cn/ | grep -oP 'href="\K[^"]*arm64\.deb' | head -n 1)
wget "${url}?k=$(eval echo -n '7f8faaaa468174dc1c9cd62e5f218a5b/$(echo -n ${url} | cut -d/ -f4-)0x7f53d55201314' | md5sum -t | cut -d' ' -f1)&t=0x7f53d55201314" --header="User-Agent: Mozilla/5.0 (X11; Linux aarch64; rv:109.0) Gecko/20100101 Firefox/115.0" --header="Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8" --header="Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2" --header="Accept-Encoding: gzip, deflate, br" --header="Connection: keep-alive" --header="Referer: https://www.wps.cn/" --header="Upgrade-Insecure-Requests: 1" --header="Sec-Fetch-Dest: document" --header="Sec-Fetch-Mode: navigate" --header="Sec-Fetch-Site: cross-site" --header="Sec-Fetch-User: ?1" -O /tmp/wps.deb
{"name":"安装WPS", "command":r"""cat << 'EOF' | sh && sudo dpkg --configure -a && sudo apt update && sudo apt install -y /tmp/wps.deb
wget https://mirrors.sdu.edu.cn/spark-store-repository/aarch64-store/office/wps-office/wps-office_11.1.0.11720_arm64.deb -O /tmp/wps.deb
EOF
rm /tmp/wps.deb"""},
{"name":"卸载WPS", "command":"sudo apt autoremove --purge -y wps-office"},
@@ -408,14 +392,13 @@ rm /tmp/wps.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_12.6.1-1_arm64_binner.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":"安装QQ", "command":"""wget \$(curl -L https://cdn-go.cn/qq-web/im.qq.com_new/latest/rainbow/linuxQQDownload.js | grep -oP 'deb":"\\K[^"]*arm64\\.deb' | head -n 1) -O /tmp/qq.deb && sudo apt update && sudo apt install -y /tmp/qq.deb && sed -i 's#Exec=/opt/QQ/qq %U#Exec=/opt/QQ/qq --no-sandbox %U#g' /usr/share/applications/qq.desktop; rm /tmp/qq.deb"""},
{"name":"安装QQ", "command":"""wget \$(curl -L https://cdn-go.cn/qq-web/im.qq.com_new/latest/rainbow/linuxQQDownload.js | grep -oP '(?<=armDownloadUrl":\\{"deb":")[^"]+') -O /tmp/qq.deb && sudo apt update && sudo apt install -y /tmp/qq.deb && sed -i 's#Exec=/opt/QQ/qq %U#Exec=/opt/QQ/qq --no-sandbox %U#g' /usr/share/applications/qq.desktop; rm /tmp/qq.deb"""},
{"name":"卸载QQ", "command":"sudo apt autoremove --purge -y linuxqq"},
{"name":"安装UOS微信", "command":"wget https://home-store-packages.uniontech.com/appstore/pool/appstore/c/com.tencent.weixin/com.tencent.weixin_2.1.10_arm64.deb -O /tmp/wechat.deb && sudo apt update && sudo apt install -y /tmp/wechat.deb /home/tiny/.local/share/tiny/wechat/deepin-elf-verify_all.deb /home/tiny/.local/share/tiny/wechat/libssl1.1_1.1.1n-0+deb10u6_arm64.deb && sed -i 's#/opt/apps/com.tencent.weixin/files/weixin/weixin#/opt/apps/com.tencent.weixin/files/weixin/weixin --no-sandbox#g' /opt/apps/com.tencent.weixin/files/weixin/weixin.sh && echo '该微信为UOS特供版只有账号实名且在UOS系统上运行时可用。在使用前请前往全局设置开启UOS伪装。\n如果你使用微信只是为了传输文件那么可以考虑使用支持SAF的文件管理器质感文件直接访问小小电脑所有文件。'; rm /tmp/wechat.deb"},
{"name":"卸载UOS微信", "command":"sudo apt autoremove --purge -y com.tencent.weixin deepin-elf-verify"},
{"name":"安装钉钉", "command":"""wget \$(curl -L https://g.alicdn.com/dingding/h5-home-download/0.2.4/js/index.js | grep -oP 'url:"\\K[^"]*arm64\\.deb' | head -n 1) -O /tmp/dingtalk.deb && sudo apt update && sudo apt install -y /tmp/dingtalk.deb && sed -i 's#\\./com.alibabainc.dingtalk#\\./com.alibabainc.dingtalk --no-sandbox#g' /opt/apps/com.alibabainc.dingtalk/files/Elevator.sh; rm /tmp/dingtalk.deb"""},
{"name":"安装UOS微信", "command":"wget https://home-store-packages.uniontech.com/appstore/pool/appstore/c/com.tencent.wechat/com.tencent.wechat_1.0.0.241_arm64.deb -O /tmp/wechat.deb && sudo apt update && sudo apt install -y /tmp/wechat.deb /home/tiny/.local/share/tiny/wechat/deepin-elf-verify_all.deb /home/tiny/.local/share/tiny/wechat/libssl1.1_1.1.1n-0+deb10u6_arm64.deb && ln -sf /opt/apps/com.tencent.wechat/entries/applications/com.tencent.wechat.desktop /usr/share/applications/com.tencent.wechat.desktop && ln -sf /opt/apps/com.tencent.wechat/entries/icons/hicolor /usr/share/icons/wechat && sed -i 's#/usr/bin/wechat#/opt/apps/com.tencent.wechat/files/wechat --no-sandbox#g' /usr/share/applications/com.tencent.wechat.desktop && echo '该微信为UOS特供版只有账号实名且在UOS系统上运行时可用。在使用前请前往全局设置开启UOS伪装。\n如果你使用微信只是为了传输文件那么可以考虑使用支持SAF的文件管理器质感文件直接访问小小电脑所有文件。'; rm /tmp/wechat.deb"},
{"name":"卸载UOS微信", "command":"sudo apt autoremove --purge -y com.tencent.wechat deepin-elf-verify && rm /usr/share/applications/com.tencent.wechat.desktop && rm /usr/share/icons/wechat"},
{"name":"安装钉钉", "command":"""wget \$(curl -L https://g.alicdn.com/dingding/h5-home-download/0.2.4/js/index.js | grep -oP 'url:"\\K[^"]*arm64\\.deb' | head -n 1) -O /tmp/dingtalk.deb && sudo apt update && sudo apt install -y /tmp/dingtalk.deb libglut3.12 libglu1-mesa && sed -i 's#\\./com.alibabainc.dingtalk#\\./com.alibabainc.dingtalk --no-sandbox#g' /opt/apps/com.alibabainc.dingtalk/files/Elevator.sh; rm /tmp/dingtalk.deb"""},
{"name":"卸载钉钉", "command":"sudo apt autoremove --purge -y com.alibabainc.dingtalk"},
{"name":"修复无法编译C语言程序", "command":"sudo apt update && sudo apt reinstall -y libc6-dev"},
{"name":"修复系统语言到中文", "command":"sudo localedef -c -i zh_CN -f UTF-8 zh_CN.UTF-8"},
{"name":"启用回收站", "command":"sudo apt update && sudo apt install -y gvfs && echo '安装完成, 重启软件即可使用回收站。'"},
{"name":"拉流测试", "command":"ffplay rtsp://127.0.0.1:8554/stream &"},
{"name":"关机", "command":"stopvnc\nexit\nexit"},
@@ -462,35 +445,7 @@ rm /tmp/wps.deb"""},
{"name": "F12", "key": TerminalKey.f12},
];
//看广告可以获得的奖励。
//weight抽奖权重singleUse使用一次花费的数量amount抽中可以获得的数量
static const List<Map<String, dynamic>> bonusTable = [
{"name": "开发者的祝福", "subtitle": "支持开发者的证明", "description": "(*'v'*)\n开发者由衷地感谢你!", "weight": 10000, "amount": 1, "singleUse": 0},
{"name": "记忆晶片", "subtitle": "看上去像平行四边形", "description": "组成记忆空间的基本元素。\n是从哪里掉下来的呢?", "weight": 50, "amount": 1, "singleUse": 0},
{"name": "Wishes Flower Part", "subtitle": "为1个人献上祝福", "description": "希望之花的花瓣。在想好为谁祝福后, 点击使用", "weight": 500, "amount": 1, "singleUse": 1},
{"name": "Wishes Flower Part", "subtitle": "为1个人献上祝福", "description": "希望之花的花瓣。在想好为谁祝福后, 点击使用", "weight": 100, "amount": 3, "singleUse": 1},
{"name": "Wishes Flower", "subtitle": "为3个人献上祝福", "description": "希望之花。在想好为谁祝福后, 点击使用", "weight": 50, "amount": 1, "singleUse": 1},
{"name": "Bonus Reward", "subtitle": "会有极好的事情发生", "description": "来自记忆空间的传说。\n使用后一天内必有极好的事情...\n就是你想象的那种事情...\n就会发生。\n不过, 大概只是个传说吧。", "weight": 10, "amount": 0.01, "singleUse": 1},
{"name": "Bonus Reward", "subtitle": "会有极好的事情发生", "description": "来自记忆空间的传说。\n使用后一天内必有极好的事情...\n就是你想象的那种事情...\n就会发生。\n不过, 大概只是个传说吧。", "weight": 1, "amount": 0.1, "singleUse": 1},
{"name": "Bonus Reward", "subtitle": "会有极好的事情发生", "description": "来自记忆空间的传说。\n使用后一天内必有极好的事情...\n就是你想象的那种事情...\n就会发生。\n不过, 大概只是个传说吧。", "weight": 1, "amount": 1, "singleUse": 1},
];
//某项功能开启需要的广告数。
static const Map<String, int> adsRequired = {
"closeBannerAds" : 5,
"enableTerminalWrite" : 2,
"enableTerminalCommands" : 3,
"changeTermMaxLines" : 6,
"changeFFmpegCommand" : 8,
"enableVirgl" : 10,
"changeHidpiOpt" : 12,
"unlockOnce" : 1, //临时解锁需要看的广告数
"unlockToday" : 2, //当日解锁需要看的广告数
};
static const String boot = "\$DATA_DIR/bin/proot -H --change-id=1000:1000 --pwd=/home/tiny --rootfs=\$CONTAINER_DIR --mount=/system --mount=/apex --mount=/sys --kill-on-exit --mount=/storage:/storage --sysvipc -L --link2symlink --mount=/proc:/proc --mount=/dev:/dev --mount=\$CONTAINER_DIR/tmp:/dev/shm --mount=/dev/urandom:/dev/random --mount=/proc/self/fd:/dev/fd --mount=/proc/self/fd/0:/dev/stdin --mount=/proc/self/fd/1:/dev/stdout --mount=/proc/self/fd/2:/dev/stderr --mount=/dev/null:/dev/tty0 --mount=/dev/null:/proc/sys/kernel/cap_last_cap --mount=/storage/self/primary:/media/sd --mount=\$DATA_DIR/share:/home/tiny/公共 --mount=\$DATA_DIR/tiny:/home/tiny/.local/share/tiny --mount=/storage/self/primary/Fonts:/usr/share/fonts/wpsm --mount=/storage/self/primary/AppFiles/Fonts:/usr/share/fonts/yozom --mount=/system/fonts:/usr/share/fonts/androidm --mount=/storage/self/primary/Pictures:/home/tiny/图片 --mount=/storage/self/primary/Music:/home/tiny/音乐 --mount=/storage/self/primary/Movies:/home/tiny/视频 --mount=/storage/self/primary/Download:/home/tiny/下载 --mount=/storage/self/primary/DCIM:/home/tiny/照片 --mount=/storage/self/primary/Documents:/home/tiny/文档 --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/.tmoe-container.stat:/proc/stat --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/.tmoe-container.version:/proc/version --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/bus:/proc/bus --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/buddyinfo:/proc/buddyinfo --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/cgroups:/proc/cgroups --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/consoles:/proc/consoles --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/crypto:/proc/crypto --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/devices:/proc/devices --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/diskstats:/proc/diskstats --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/execdomains:/proc/execdomains --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/fb:/proc/fb --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/filesystems:/proc/filesystems --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/interrupts:/proc/interrupts --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/iomem:/proc/iomem --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/ioports:/proc/ioports --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/kallsyms:/proc/kallsyms --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/keys:/proc/keys --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/key-users:/proc/key-users --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/kpageflags:/proc/kpageflags --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/loadavg:/proc/loadavg --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/locks:/proc/locks --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/misc:/proc/misc --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/modules:/proc/modules --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/pagetypeinfo:/proc/pagetypeinfo --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/partitions:/proc/partitions --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/sched_debug:/proc/sched_debug --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/softirqs:/proc/softirqs --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/timer_list:/proc/timer_list --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/uptime:/proc/uptime --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/vmallocinfo:/proc/vmallocinfo --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/vmstat:/proc/vmstat --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/zoneinfo:/proc/zoneinfo \$EXTRA_MOUNT /usr/bin/env -i HOSTNAME=TINY HOME=/home/tiny USER=tiny TERM=xterm-256color SDL_IM_MODULE=fcitx XMODIFIERS=@im=fcitx QT_IM_MODULE=fcitx GTK_IM_MODULE=fcitx TMOE_CHROOT=false TMOE_PROOT=true TMPDIR=/tmp MOZ_FAKE_NO_SANDBOX=1 DISPLAY=:4 PULSE_SERVER=tcp:127.0.0.1:4718 LANG=zh_CN.UTF-8 SHELL=/bin/bash PATH=/usr/local/sbin:/usr/local/bin:/bin:/usr/bin:/sbin:/usr/sbin:/usr/games:/usr/local/games \$EXTRA_OPT /bin/bash -l";
static const String boot = "\$DATA_DIR/bin/proot -H --change-id=1000:1000 --pwd=/home/tiny --rootfs=\$CONTAINER_DIR --mount=/system --mount=/apex --mount=/sys --mount=/data --kill-on-exit --mount=/storage --sysvipc -L --link2symlink --mount=/proc --mount=/dev --mount=\$CONTAINER_DIR/tmp:/dev/shm --mount=/dev/urandom:/dev/random --mount=/proc/self/fd:/dev/fd --mount=/proc/self/fd/0:/dev/stdin --mount=/proc/self/fd/1:/dev/stdout --mount=/proc/self/fd/2:/dev/stderr --mount=/dev/null:/dev/tty0 --mount=/dev/null:/proc/sys/kernel/cap_last_cap --mount=/storage/self/primary:/media/sd --mount=\$DATA_DIR/share:/home/tiny/公共 --mount=\$DATA_DIR/tiny:/home/tiny/.local/share/tiny --mount=/storage/self/primary/Fonts:/usr/share/fonts/wpsm --mount=/storage/self/primary/AppFiles/Fonts:/usr/share/fonts/yozom --mount=/system/fonts:/usr/share/fonts/androidm --mount=/storage/self/primary/Pictures:/home/tiny/图片 --mount=/storage/self/primary/Music:/home/tiny/音乐 --mount=/storage/self/primary/Movies:/home/tiny/视频 --mount=/storage/self/primary/Download:/home/tiny/下载 --mount=/storage/self/primary/DCIM:/home/tiny/照片 --mount=/storage/self/primary/Documents:/home/tiny/文档 --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/.tmoe-container.stat:/proc/stat --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/.tmoe-container.version:/proc/version --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/bus:/proc/bus --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/buddyinfo:/proc/buddyinfo --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/cgroups:/proc/cgroups --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/consoles:/proc/consoles --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/crypto:/proc/crypto --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/devices:/proc/devices --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/diskstats:/proc/diskstats --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/execdomains:/proc/execdomains --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/fb:/proc/fb --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/filesystems:/proc/filesystems --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/interrupts:/proc/interrupts --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/iomem:/proc/iomem --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/ioports:/proc/ioports --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/kallsyms:/proc/kallsyms --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/keys:/proc/keys --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/key-users:/proc/key-users --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/kpageflags:/proc/kpageflags --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/loadavg:/proc/loadavg --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/locks:/proc/locks --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/misc:/proc/misc --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/modules:/proc/modules --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/pagetypeinfo:/proc/pagetypeinfo --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/partitions:/proc/partitions --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/sched_debug:/proc/sched_debug --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/softirqs:/proc/softirqs --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/timer_list:/proc/timer_list --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/uptime:/proc/uptime --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/vmallocinfo:/proc/vmallocinfo --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/vmstat:/proc/vmstat --mount=\$CONTAINER_DIR/usr/local/etc/tmoe-linux/proot_proc/zoneinfo:/proc/zoneinfo \$EXTRA_MOUNT /usr/bin/env -i HOSTNAME=TINY HOME=/home/tiny USER=tiny TERM=xterm-256color SDL_IM_MODULE=fcitx XMODIFIERS=@im=fcitx QT_IM_MODULE=fcitx GTK_IM_MODULE=fcitx TMOE_CHROOT=false TMOE_PROOT=true TMPDIR=/tmp MOZ_FAKE_NO_SANDBOX=1 DISPLAY=:4 PULSE_SERVER=tcp:127.0.0.1:4718 LANG=zh_CN.UTF-8 SHELL=/bin/bash PATH=/usr/local/sbin:/usr/local/bin:/bin:/usr/bin:/sbin:/usr/sbin:/usr/games:/usr/local/games \$EXTRA_OPT /bin/bash -l";
static final ButtonStyle commandButtonStyle = OutlinedButton.styleFrom(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
@@ -519,24 +474,70 @@ class G {
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? streamingPid;
static String streamingOutput = "";
static late Pty streamServerPty;
static bool isVirglServerStarted = false;
static late Pty virglServerPty;
//static int? virglPid;
static ValueNotifier<int> pageIndex = ValueNotifier(0); //主界面索引
static ValueNotifier<bool> terminalPageChange = ValueNotifier(true); //更改值,用于刷新小键盘
static ValueNotifier<bool> bannerAdsChange = ValueNotifier(true); //更改值用于刷新banner广告
static ValueNotifier<bool> bootTextChange = ValueNotifier(true); //更改值,用于刷新启动命令
static ValueNotifier<String> updateText = ValueNotifier("小小电脑"); //加载界面的说明文字
static ValueNotifier<String> helpText = ValueNotifier("""
第一次加载大概需要5到10分钟...
正常情况下,加载完成后软件会自动跳转到图形界面
在图形界面时,点击即鼠标左键
长按为鼠标右键
双指点击弹出键盘
双指划动为鼠标滚轮
!!!在图形界面返回,可以回到终端界面和控制界面!!!
你可以在控制界面安装更多软件或者阅读帮助信息
请不要在安装时退出软件
如果过了很长时间都没有加载完成
可以去设置里看看小小电脑占用空间是不是一直没变
如果是说明卡在什么地方了
建议清除本软件数据重来一次
(有一位网友提到过
自己无论怎么清软件数据都装不上
但在重启手机之后就装上了)
一些注意事项:
此软件以GPL协议免费开源
如果是买的就是被骗了, 请举报
源代码在这里: https://github.com/Cateners/tiny_computer
软件也会第一时间在这里更新
请尽可能在这里下载软件, 确保是正版
如果你遇到了问题
可以去https://github.com/Cateners/tiny_computer/issues/
留言反馈
如果软件里有程序正在正常运行
请不要强行关闭本软件
否则可能会损坏容器
(如dpkg被中断)
特别是在安装WPS的时候
可能以为卡20%了
其实耐心等待就好
感谢使用!
(顺带一提, 全部解压完大概需要4~5GB空间
解压途中占用空间可能更多
请确保有足够的空间
(这样真的Tiny吗><))
常见问题:"""); //帮助页的说明文字
static String postCommand = ""; //第一次进入容器时额外运行的命令
static bool wasBoxEnabled = false; //本次启动时是否启用了box86/64
static bool wasWineEnabled = false; //本次启动时是否启用了wine
@@ -545,78 +546,6 @@ class G {
static late SharedPreferences prefs;
}
class AdManager {
static Map<String, bool> placements = {
interstitialVideoAdPlacementId: false,
rewardedVideoAdPlacementId: false,
};
static void loadAds() {
for (var placementId in placements.keys) {
loadAd(placementId);
}
}
static void loadAd(String placementId) {
UnityAds.load(
placementId: placementId,
onComplete: (placementId) {
debugPrint('Load Complete $placementId');
placements[placementId] = true;
},
onFailed: (placementId, error, message) => debugPrint('Load Failed $placementId: $error $message'),
);
}
static void showAd(String placementId, Function completeExtra, Function full) {
if ((Util.getGlobal("adsWatchedToday") as int) >= 5) {
full();
return;
}
placements[placementId] = false;
UnityAds.showVideoAd(
placementId: placementId,
onComplete: (placementId) async {
debugPrint('Video Ad $placementId completed');
loadAd(placementId);
G.adsWatchedThisTime++;
await G.prefs.setInt("adsWatchedTotal", (Util.getGlobal("adsWatchedTotal") as int)+1);
await G.prefs.setInt("adsWatchedToday", (Util.getGlobal("adsWatchedToday") as int)+1);
completeExtra();
},
onFailed: (placementId, error, message) {
debugPrint('Video Ad $placementId failed: $error $message');
loadAd(placementId);
},
onStart: (placementId) => debugPrint('Video Ad $placementId started'),
onClick: (placementId) => debugPrint('Video Ad $placementId click'),
onSkipped: (placementId) {
debugPrint('Video Ad $placementId skipped');
loadAd(placementId);
},
);
}
static String get gameId {
if (defaultTargetPlatform == TargetPlatform.android) {
return '5403132';
}
if (defaultTargetPlatform == TargetPlatform.iOS) {
return '5403133';
}
return '';
}
static String bannerAdPlacementId = 'Banner_Android';
static String interstitialVideoAdPlacementId = 'Interstitial_Android';
static String rewardedVideoAdPlacementId = 'Rewarded_Android';
}
class Workflow {
static Future<void> grantPermissions() async {
@@ -737,12 +666,19 @@ done
final String currentDate = DateFormat("yyyy-MM-dd").format(DateTime.now());
if (currentDate != (Util.getGlobal("lastDate") as String)) {
await G.prefs.setString("lastDate", currentDate);
await G.prefs.setInt("adsWatchedToday", 0);
//await G.prefs.setInt("adsWatchedToday", 0);
}
//如果没有这个key说明是初次启动
if (!G.prefs.containsKey("defaultContainer")) {
await initForFirstTime();
//根据用户的屏幕调整分辨率
final s = WidgetsBinding.instance.platformDispatcher.views.first.physicalSize;
final String w = (max(s.width, s.height) * 0.75).round().toString();
final String h = (min(s.width, s.height) * 0.75).round().toString();
G.postCommand = """sed -i -E "s@(geometry)=.*@\\1=${w}x${h}@" /etc/tigervnc/vncserver-config-tmoe
sed -i -E "s@^(VNC_RESOLUTION)=.*@\\1=${w}x${h}@" \$(command -v startvnc)""";
await G.prefs.setBool("getifaddrsBridge", (await DeviceInfoPlugin().androidInfo).version.sdkInt >= 31);
}
G.currentContainer = Util.getGlobal("defaultContainer") as int;
@@ -757,29 +693,6 @@ done
G.controller = WebViewController()..setJavaScriptMode(JavaScriptMode.unrestricted);
//恢复临时开启的功能
if (Util.shouldWatchAds(D.adsRequired["changeFFmpegCommand"]!)) {
await G.prefs.remove("defaultFFmpegCommand");
}
if (Util.shouldWatchAds(D.adsRequired["changeHidpiOpt"]!)) {
await G.prefs.remove("defaultHidpiOpt");
}
if (Util.shouldWatchAds(D.adsRequired["changeTermMaxLines"]!)) {
await G.prefs.setInt("termMaxLines", 4095);
}
if (Util.shouldWatchAds(D.adsRequired["closeBannerAds"]!)) {
await G.prefs.setBool("isBannerAdsClosed", false);
}
if (Util.shouldWatchAds(D.adsRequired["enableTerminalWrite"]!)) {
await G.prefs.setBool("isTerminalWriteEnabled", false);
}
if (Util.shouldWatchAds(D.adsRequired["enableTerminalCommands"]!)) {
await G.prefs.setBool("isTerminalCommandsEnabled", false);
}
if (Util.shouldWatchAds(D.adsRequired["enableVirgl"]!)) {
await G.prefs.setBool("virgl", false);
}
//设置屏幕常亮
WakelockPlus.toggle(enable: Util.getGlobal("wakelock"));
}
@@ -790,18 +703,6 @@ done
}
}
static Future<void> initAds() async {
UnityAds.init(
gameId: AdManager.gameId,
testMode: false,
onComplete: () {
debugPrint('Initialization Complete');
AdManager.loadAds();
},
onFailed: (error, message) => debugPrint('Initialization Failed: $error $message'),
);
}
static Future<void> setupAudio() async {
G.audioPty?.kill();
G.audioPty = Pty.start(
@@ -842,6 +743,9 @@ export CONTAINER_DIR=\$DATA_DIR/containers/${G.currentContainer}
${G.dataPath}/bin/virgl_test_server ${Util.getGlobal("defaultVirglCommand")}""");
extraOpt += "${Util.getGlobal("defaultVirglOpt")} ";
}
if (Util.getGlobal("turnip")) {
extraOpt += "${Util.getGlobal("defaultTurnipOpt")} ";
}
if (Util.getGlobal("isBoxEnabled")) {
G.wasBoxEnabled = true;
extraMount += "--x86=/home/tiny/.local/bin/box86 --x64=/home/tiny/.local/bin/box64 ";
@@ -876,6 +780,7 @@ 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
${Util.getCurrentProp("boot")}
${G.postCommand}
${(Util.getGlobal("autoLaunchVnc") as bool)?Util.getCurrentProp("vnc"):""}
clear""");
}
@@ -892,11 +797,6 @@ clear""");
static Future<void> launchBrowser() async {
G.controller.loadRequest(Uri.parse(Util.getCurrentProp("vncUrl")));
Navigator.push(G.homePageStateContext, MaterialPageRoute(builder: (context) {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky,overlays: []);
SystemChrome.setSystemUIChangeCallback((systemOverlaysAreVisible) async {
await Future.delayed(const Duration(seconds: 1));
SystemChrome.restoreSystemUIOverlays();
});
return Focus(
onKeyEvent: (node, event) {
// Allow webview to handle cursor keys. Without this, the
@@ -929,7 +829,6 @@ clear""");
static Future<void> workflow() async {
grantPermissions();
await initData();
await initAds();
await initTerminalForCurrent();
setupAudio();
launchCurrentContainer();

View File

@@ -5,6 +5,7 @@
import FlutterMacOS
import Foundation
import device_info_plus
import dynamic_color
import ffmpeg_kit_flutter_full_gpl
import network_info_plus
@@ -15,6 +16,7 @@ import url_launcher_macos
import wakelock_plus
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
FFmpegKitFlutterPlugin.register(with: registry.registrar(forPlugin: "FFmpegKitFlutterPlugin"))
NetworkInfoPlusPlugin.register(with: registry.registrar(forPlugin: "NetworkInfoPlusPlugin"))

View File

@@ -5,10 +5,10 @@ packages:
dependency: transitive
description:
name: args
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
url: "https://pub.dev"
source: hosted
version: "2.4.2"
version: "2.5.0"
async:
dependency: transitive
description:
@@ -73,14 +73,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.7.10"
device_info_plus:
dependency: "direct main"
description:
name: device_info_plus
sha256: a7fd703482b391a87d60b6061d04dfdeab07826b96f9abd8f5ed98068acc0074
url: "https://pub.dev"
source: hosted
version: "10.1.2"
device_info_plus_platform_interface:
dependency: transitive
description:
name: device_info_plus_platform_interface
sha256: "282d3cf731045a2feb66abfe61bbc40870ae50a3ed10a4d3d217556c35c8c2ba"
url: "https://pub.dev"
source: hosted
version: "7.0.1"
dynamic_color:
dependency: "direct main"
description:
name: dynamic_color
sha256: a866f1f8947bfdaf674d7928e769eac7230388a2e7a2542824fad4bb5b87be3b
sha256: eae98052fa6e2826bdac3dd2e921c6ce2903be15c6b7f8b6d8a5d49b5086298d
url: "https://pub.dev"
source: hosted
version: "1.6.9"
version: "1.7.0"
equatable:
dependency: transitive
description:
@@ -138,10 +154,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_lints
sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7
sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
version: "4.0.0"
flutter_pty:
dependency: "direct main"
description:
@@ -172,10 +188,10 @@ packages:
dependency: "direct main"
description:
name: http
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010
url: "https://pub.dev"
source: hosted
version: "1.2.1"
version: "1.2.2"
http_parser:
dependency: transitive
description:
@@ -192,46 +208,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.19.0"
js:
dependency: transitive
description:
name: js
sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
url: "https://pub.dev"
source: hosted
version: "0.7.1"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
url: "https://pub.dev"
source: hosted
version: "10.0.0"
version: "10.0.4"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "3.0.3"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "3.0.1"
lints:
dependency: transitive
description:
name: lints
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
version: "4.0.0"
matcher:
dependency: transitive
description:
@@ -252,26 +260,26 @@ packages:
dependency: transitive
description:
name: meta
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
url: "https://pub.dev"
source: hosted
version: "1.11.0"
version: "1.12.0"
network_info_plus:
dependency: "direct main"
description:
name: network_info_plus
sha256: "4601b815b1c6a46d84839f65cd774a7d999738471d910fae00d813e9e98b04e1"
sha256: "0754302af09e58a60c7cd6800029d00e03c68f289a19fd9df31941ab91da8903"
url: "https://pub.dev"
source: hosted
version: "4.1.0+1"
version: "6.0.0"
network_info_plus_platform_interface:
dependency: transitive
description:
name: network_info_plus_platform_interface
sha256: "881f5029c5edaf19c616c201d3d8b366c5b1384afd5c1da5a49e4345de82fb8b"
sha256: b7f35f4a7baef511159e524499f3c15464a49faa5ec10e92ee0bce265e664906
url: "https://pub.dev"
source: hosted
version: "1.1.3"
version: "2.0.1"
nm:
dependency: transitive
description:
@@ -284,18 +292,18 @@ packages:
dependency: transitive
description:
name: package_info_plus
sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017"
sha256: a75164ade98cb7d24cfd0a13c6408927c6b217fa60dee5a7ff5c116a58f28918
url: "https://pub.dev"
source: hosted
version: "4.2.0"
version: "8.0.2"
package_info_plus_platform_interface:
dependency: transitive
description:
name: package_info_plus_platform_interface
sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6"
sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "3.0.1"
path:
dependency: transitive
description:
@@ -308,26 +316,26 @@ packages:
dependency: "direct main"
description:
name: path_provider
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.1.4"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
sha256: "490539678396d4c3c0b06efdaab75ae60675c3e0c66f72bc04c2e2c1e0e2abeb"
url: "https://pub.dev"
source: hosted
version: "2.2.2"
version: "2.2.9"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
url: "https://pub.dev"
source: hosted
version: "2.3.2"
version: "2.4.0"
path_provider_linux:
dependency: transitive
description:
@@ -348,50 +356,50 @@ packages:
dependency: transitive
description:
name: path_provider_windows
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.2.1"
version: "2.3.0"
permission_handler:
dependency: "direct main"
description:
name: permission_handler
sha256: "74e962b7fad7ff75959161bb2c0ad8fe7f2568ee82621c9c2660b751146bfe44"
sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb"
url: "https://pub.dev"
source: hosted
version: "11.3.0"
version: "11.3.1"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: "1acac6bae58144b442f11e66621c062aead9c99841093c38f5bcdcc24c1c3474"
sha256: eaf2a1ec4472775451e88ca6a7b86559ef2f1d1ed903942ed135e38ea0097dca
url: "https://pub.dev"
source: hosted
version: "12.0.5"
version: "12.0.8"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: bdafc6db74253abb63907f4e357302e6bb786ab41465e8635f362ee71fd8707b
sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0
url: "https://pub.dev"
source: hosted
version: "9.4.0"
version: "9.4.5"
permission_handler_html:
dependency: transitive
description:
name: permission_handler_html
sha256: "54bf176b90f6eddd4ece307e2c06cf977fb3973719c35a93b85cc7093eb6070d"
sha256: "6cac773d389e045a8d4f85418d07ad58ef9e42a56e063629ce14c4c26344de24"
url: "https://pub.dev"
source: hosted
version: "0.1.1"
version: "0.1.2"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: "23dfba8447c076ab5be3dee9ceb66aad345c4a648f0cac292c77b1eb0e800b78"
sha256: fe0ffe274d665be8e34f9c59705441a7d248edebbe5d9e3ec2665f88b79358ea
url: "https://pub.dev"
source: hosted
version: "4.2.0"
version: "4.2.2"
permission_handler_windows:
dependency: transitive
description:
@@ -412,10 +420,10 @@ packages:
dependency: transitive
description:
name: platform
sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
url: "https://pub.dev"
source: hosted
version: "3.1.4"
version: "3.1.5"
plugin_platform_interface:
dependency: transitive
description:
@@ -444,58 +452,58 @@ packages:
dependency: "direct main"
description:
name: shared_preferences
sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02"
sha256: c272f9cabca5a81adc9b0894381e9c1def363e980f960fa903c604c471b22f68
url: "https://pub.dev"
source: hosted
version: "2.2.2"
version: "2.3.1"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06"
sha256: a7e8467e9181cef109f601e3f65765685786c1a738a83d7fbbde377589c0d974
url: "https://pub.dev"
source: hosted
version: "2.2.1"
version: "2.3.1"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c"
sha256: "776786cff96324851b656777648f36ac772d88bc4c669acff97b7fce5de3c849"
url: "https://pub.dev"
source: hosted
version: "2.3.5"
version: "2.5.1"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa"
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a"
sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e
url: "https://pub.dev"
source: hosted
version: "2.3.0"
version: "2.4.2"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59"
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
version: "2.4.1"
sky_engine:
dependency: transitive
description: flutter
@@ -545,10 +553,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
url: "https://pub.dev"
source: hosted
version: "0.6.1"
version: "0.7.0"
typed_data:
dependency: transitive
description:
@@ -557,54 +565,46 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.2"
unity_ads_plugin:
dependency: "direct main"
description:
name: unity_ads_plugin
sha256: "636858a9ff573bf3477cb1d2f2663d73ef360790fa7638acbd9631adb24c76c9"
url: "https://pub.dev"
source: hosted
version: "0.3.11"
url_launcher:
dependency: "direct main"
description:
name: url_launcher
sha256: "0ecc004c62fd3ed36a2ffcbe0dd9700aee63bd7532d0b642a488b1ec310f492e"
sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3"
url: "https://pub.dev"
source: hosted
version: "6.2.5"
version: "6.3.0"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: d4ed0711849dd8e33eb2dd69c25db0d0d3fdc37e0a62e629fe32f57a22db2745
sha256: "94d8ad05f44c6d4e2ffe5567ab4d741b82d62e3c8e288cc1fcea45965edf47c9"
url: "https://pub.dev"
source: hosted
version: "6.3.0"
version: "6.3.8"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5"
sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e
url: "https://pub.dev"
source: hosted
version: "6.2.5"
version: "6.3.1"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811
sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af
url: "https://pub.dev"
source: hosted
version: "3.1.1"
version: "3.2.0"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234
sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
version: "3.2.0"
url_launcher_platform_interface:
dependency: transitive
description:
@@ -617,18 +617,18 @@ packages:
dependency: transitive
description:
name: url_launcher_web
sha256: "3692a459204a33e04bc94f5fb91158faf4f2c8903281ddd82915adecdb1a901d"
sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e"
url: "https://pub.dev"
source: hosted
version: "2.3.0"
version: "2.3.3"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7
sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
version: "3.1.2"
vector_math:
dependency: transitive
description:
@@ -641,50 +641,50 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
url: "https://pub.dev"
source: hosted
version: "13.0.0"
version: "14.2.1"
wakelock_plus:
dependency: "direct main"
description:
name: wakelock_plus
sha256: "26ebc8b5e0037c15e2a1b661dcec8a475cb7205befcce8a33f545ae8c86b367c"
sha256: "4fa83a128b4127619e385f686b4f080a5d2de46cff8e8c94eccac5fcf76550e5"
url: "https://pub.dev"
source: hosted
version: "1.1.6"
version: "1.2.7"
wakelock_plus_platform_interface:
dependency: transitive
description:
name: wakelock_plus_platform_interface
sha256: "40fabed5da06caff0796dc638e1f07ee395fb18801fbff3255a2372db2d80385"
sha256: "422d1cdbb448079a8a62a5a770b69baa489f8f7ca21aef47800c726d404f9d16"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
version: "1.2.1"
web:
dependency: transitive
description:
name: web
sha256: "1d9158c616048c38f712a6646e317a3426da10e884447626167240d45209cbad"
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
url: "https://pub.dev"
source: hosted
version: "0.5.0"
version: "0.5.1"
webview_flutter:
dependency: "direct main"
description:
name: webview_flutter
sha256: "25e1b6e839e8cbfbd708abc6f85ed09d1727e24e08e08c6b8590d7c65c9a8932"
sha256: "6869c8786d179f929144b4a1f86e09ac0eddfe475984951ea6c634774c16b522"
url: "https://pub.dev"
source: hosted
version: "4.7.0"
version: "4.8.0"
webview_flutter_android:
dependency: transitive
description:
name: webview_flutter_android
sha256: "3e5f4e9d818086b0d01a66fb1ff9cc72ab0cc58c71980e3d3661c5685ea0efb0"
sha256: c66651fba15f9d7ddd31daec42da8d6bce46c85610a7127e3ebcb39a4395c3c9
url: "https://pub.dev"
source: hosted
version: "3.15.0"
version: "3.16.6"
webview_flutter_platform_interface:
dependency: transitive
description:
@@ -697,18 +697,26 @@ packages:
dependency: transitive
description:
name: webview_flutter_wkwebview
sha256: "9bf168bccdf179ce90450b5f37e36fe263f591c9338828d6bf09b6f8d0f57f86"
sha256: "9c62cc46fa4f2d41e10ab81014c1de470a6c6f26051a2de32111b2ee55287feb"
url: "https://pub.dev"
source: hosted
version: "3.12.0"
version: "3.14.0"
win32:
dependency: transitive
description:
name: win32
sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8"
sha256: "015002c060f1ae9f41a818f2d5640389cc05283e368be19dc8d77cecb43c40c9"
url: "https://pub.dev"
source: hosted
version: "5.2.0"
version: "5.5.3"
win32_registry:
dependency: transitive
description:
name: win32_registry
sha256: "723b7f851e5724c55409bb3d5a32b203b3afe8587eaf5dafb93a5fed8ecda0d6"
url: "https://pub.dev"
source: hosted
version: "1.1.4"
xdg_directories:
dependency: transitive
description:
@@ -742,5 +750,5 @@ packages:
source: hosted
version: "0.0.6"
sdks:
dart: ">=3.3.0 <4.0.0"
flutter: ">=3.19.0"
dart: ">=3.4.0 <4.0.0"
flutter: ">=3.22.0"

View File

@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.14+10
version: 1.0.17+13
environment:
sdk: '>=3.1.0 <4.0.0'
@@ -33,20 +33,20 @@ dependencies:
xterm: ^4.0.0
flutter_pty: ^0.4.0
path_provider: ^2.1.2
webview_flutter: ^4.7.0
permission_handler: ^11.3.0
http: ^1.2.1
webview_flutter: ^4.8.0
permission_handler: ^11.3.1
http: ^1.2.2
retry: ^3.1.2
url_launcher: ^6.2.5
shared_preferences: ^2.2.2
url_launcher: ^6.3.0
shared_preferences: ^2.2.3
intl: ^0.19.0 #日期字符串转换
unity_ads_plugin: ^0.3.11
clipboard: ^0.1.3
ffmpeg_kit_flutter_full_gpl: ^6.0.3
wakelock_plus: ^1.1.6
dynamic_color: ^1.6.9
wakelock_plus: ^1.2.7
dynamic_color: ^1.7.0
flutter_speed_dial: ^7.0.0
network_info_plus: ^4.1.0+1
network_info_plus: ^6.0.0
device_info_plus: ^10.1.2
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
@@ -61,7 +61,7 @@ dev_dependencies:
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^3.0.1
flutter_lints: ^4.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec