fix: catch crash of fg service (#669)

This commit is contained in:
lollipopkit🏳️‍⚧️
2025-01-04 16:22:20 +08:00
committed by GitHub
parent 0bbd0b43b3
commit 7f58237589
40 changed files with 497 additions and 272 deletions

View File

@@ -5,10 +5,24 @@ import android.content.Intent
import android.os.Build
import android.os.IBinder
import android.util.Log
import java.io.File
import java.util.*
class ForegroundService : Service() {
private val chanId = "ForegroundServiceChannel"
private fun logError(message: String, error: Throwable? = null) {
Log.e("ForegroundService", message, error)
try {
val logFile = File(getExternalFilesDir(null), "server_box.log")
val timestamp = java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US).format(Date())
val logMessage = "$timestamp [ForegroundService] ERROR: $message\n${error?.stackTraceToString() ?: ""}\n"
logFile.appendText(logMessage)
} catch (e: Exception) {
Log.e("ForegroundService", "Failed to write log", e)
}
}
override fun onCreate() {
super.onCreate()
Log.d("ForegroundService", "Service onCreate")
@@ -16,6 +30,17 @@ class ForegroundService : Service() {
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
androidx.core.content.ContextCompat.checkSelfPermission(
this, android.Manifest.permission.POST_NOTIFICATIONS
) != android.content.pm.PackageManager.PERMISSION_GRANTED
) {
Log.w("ForegroundService", "Notification permission denied. Stopping service.")
stopForegroundService()
return START_NOT_STICKY
}
if (intent == null) {
Log.w("ForegroundService", "onStartCommand called with null intent")
stopForegroundService()
@@ -25,17 +50,33 @@ class ForegroundService : Service() {
val action = intent.action
Log.d("ForegroundService", "onStartCommand action=$action")
// Create notification before starting foreground
val notification = createNotification()
// Use try-catch for startForeground
try {
startForeground(1, notification)
} catch (e: Exception) {
logError("Failed to start foreground", e)
stopSelf()
return START_NOT_STICKY
}
return when (action) {
"ACTION_STOP_FOREGROUND" -> {
stopForegroundService()
START_NOT_STICKY
}
else -> {
val notification = createNotification()
startForeground(1, notification)
START_STICKY
}
}
} catch (e: Exception) {
logError("Error in onStartCommand", e)
stopSelf()
return START_NOT_STICKY
}
}
override fun onBind(intent: Intent?): IBinder? {
@@ -61,6 +102,7 @@ class ForegroundService : Service() {
}
private fun createNotification(): Notification {
try {
val notificationIntent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
this,
@@ -92,13 +134,21 @@ class ForegroundService : Service() {
.setContentIntent(pendingIntent)
.addAction(android.R.drawable.ic_delete, "Stop", deletePendingIntent)
.build()
} catch (e: Exception) {
logError("Error creating notification", e)
// Return a basic notification as fallback
return Notification.Builder(this)
.setContentTitle("Server Box")
.setSmallIcon(R.mipmap.ic_launcher)
.build()
}
}
private fun stopForegroundService() {
try {
stopForeground(true)
} catch (e: Exception) {
Log.e("ForegroundService", "Error stopping foreground: ${e.message}")
logError("Error stopping foreground", e)
}
stopSelf()
Log.d("ForegroundService", "ForegroundService stopped")

View File

@@ -25,6 +25,7 @@ class MainActivity: FlutterFragmentActivity() {
result.success(null)
}
"startService" -> {
try {
reqPerm()
val serviceIntent = Intent(this@MainActivity, ForegroundService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -32,6 +33,12 @@ class MainActivity: FlutterFragmentActivity() {
} else {
startService(serviceIntent)
}
result.success(null)
} catch (e: Exception) {
// Log error but don't crash
android.util.Log.e("MainActivity", "Failed to start service: ${e.message}")
result.error("SERVICE_ERROR", e.message, null)
}
}
"stopService" -> {
val serviceIntent = Intent(this@MainActivity, ForegroundService::class.java)
@@ -54,13 +61,20 @@ class MainActivity: FlutterFragmentActivity() {
private fun reqPerm() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
// Check if we already have the permission to avoid unnecessary prompts
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
!= PackageManager.PERMISSION_GRANTED) {
try {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
123,
)
} catch (e: Exception) {
// Log error but don't crash
android.util.Log.e("MainActivity", "Failed to request permissions: ${e.message}")
}
}
}
}

View File

@@ -65,6 +65,8 @@ class HomeWidget : AppWidgetProvider() {
views.setViewVisibility(R.id.error_message, View.VISIBLE)
views.setTextViewText(R.id.error_message, "Please configure the widget URL.")
views.setViewVisibility(R.id.widget_content, View.GONE)
views.setFloat(R.id.widget_name, "setAlpha", 1f)
views.setFloat(R.id.error_message, "setAlpha", 1f)
appWidgetManager.updateAppWidget(appWidgetId, views)
return
} else {
@@ -100,6 +102,12 @@ class HomeWidget : AppWidgetProvider() {
views.setTextViewText(R.id.widget_net, net)
val timeStr = android.text.format.DateFormat.format("HH:mm", java.util.Date()).toString()
views.setTextViewText(R.id.widget_time, timeStr)
views.setFloat(R.id.widget_name, "setAlpha", 1f)
views.setFloat(R.id.widget_cpu_label, "setAlpha", 1f)
views.setFloat(R.id.widget_mem_label, "setAlpha", 1f)
views.setFloat(R.id.widget_disk_label, "setAlpha", 1f)
views.setFloat(R.id.widget_net_label, "setAlpha", 1f)
views.setFloat(R.id.widget_time, "setAlpha", 1f)
appWidgetManager.updateAppWidget(appWidgetId, views)
}
} else {
@@ -113,6 +121,8 @@ class HomeWidget : AppWidgetProvider() {
views.setViewVisibility(R.id.error_message, View.VISIBLE)
views.setTextViewText(R.id.error_message, "Failed to retrieve data.")
views.setViewVisibility(R.id.widget_content, View.GONE)
views.setFloat(R.id.widget_name, "setAlpha", 1f)
views.setFloat(R.id.error_message, "setAlpha", 1f)
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}

View File

@@ -16,6 +16,8 @@
android:textSize="23sp"
android:textStyle="bold"
android:maxLines="1"
android:alpha="0"
android:animateLayoutChanges="true"
tools:text="Server Name" />
<!-- Wrap the content in a LinearLayout for easy visibility management -->
@@ -32,7 +34,8 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:paddingTop="13dp">
android:paddingTop="13dp"
android:animateLayoutChanges="true">
<LinearLayout
android:id="@+id/widget_cpu_label"
@@ -155,6 +158,8 @@
android:textColor="@color/widgetSummaryText"
android:textSize="12sp"
android:visibility="gone"
android:alpha="0"
android:animateLayoutChanges="true"
tools:text="Error message" />
<TextView
@@ -165,6 +170,8 @@
android:maxLines="2"
android:textColor="@color/widgetSummaryText"
android:textSize="11sp"
android:alpha="0"
android:animateLayoutChanges="true"
tools:text="UpdateTime" />
</RelativeLayout>

View File

@@ -6,6 +6,13 @@ PODS:
- file_picker (0.0.1):
- Flutter
- Flutter (1.0.0)
- flutter_inappwebview_ios (0.0.1):
- Flutter
- flutter_inappwebview_ios/Core (= 0.0.1)
- OrderedSet (~> 6.0.3)
- flutter_inappwebview_ios/Core (0.0.1):
- Flutter
- OrderedSet (~> 6.0.3)
- flutter_native_splash (2.4.3):
- Flutter
- icloud_storage (0.0.1):
@@ -13,6 +20,7 @@ PODS:
- local_auth_darwin (0.0.1):
- Flutter
- FlutterMacOS
- OrderedSet (6.0.3)
- package_info_plus (0.4.5):
- Flutter
- path_provider_foundation (0.0.1):
@@ -31,15 +39,13 @@ PODS:
- Flutter
- watch_connectivity (0.0.1):
- Flutter
- webview_flutter_wkwebview (0.0.1):
- Flutter
- FlutterMacOS
DEPENDENCIES:
- app_links (from `.symlinks/plugins/app_links/ios`)
- camera_avfoundation (from `.symlinks/plugins/camera_avfoundation/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`)
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- icloud_storage (from `.symlinks/plugins/icloud_storage/ios`)
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
@@ -51,7 +57,10 @@ DEPENDENCIES:
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
- watch_connectivity (from `.symlinks/plugins/watch_connectivity/ios`)
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`)
SPEC REPOS:
trunk:
- OrderedSet
EXTERNAL SOURCES:
app_links:
@@ -62,6 +71,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/file_picker/ios"
Flutter:
:path: Flutter
flutter_inappwebview_ios:
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
flutter_native_splash:
:path: ".symlinks/plugins/flutter_native_splash/ios"
icloud_storage:
@@ -84,17 +95,17 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/wakelock_plus/ios"
watch_connectivity:
:path: ".symlinks/plugins/watch_connectivity/ios"
webview_flutter_wkwebview:
:path: ".symlinks/plugins/webview_flutter_wkwebview/darwin"
SPEC CHECKSUMS:
app_links: e7a6750a915a9e161c58d91bc610e8cd1d4d0ad0
camera_avfoundation: dd002b0330f4981e1bbcb46ae9b62829237459a4
file_picker: c79185e70b9b45728cde2a8d8da454e0cb43f287
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4
flutter_native_splash: e8a1e01082d97a8099d973f919f57904c925008a
icloud_storage: d9ac7a33ced81df08ba7ea1bf3099cc0ee58f60a
local_auth_darwin: 66e40372f1c29f383a314c738c7446e2f7fdadc3
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
plain_notification_token: b36467dc91939a7b6754267c701bbaca14996ee1
@@ -103,7 +114,6 @@ SPEC CHECKSUMS:
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1
watch_connectivity: 715eb484685e05846eab74795348a44bb2809b82
webview_flutter_wkwebview: 0982481e3d9c78fd5c6f62a002fcd24fc791f1e4
PODFILE CHECKSUM: ec6ef69056f066e8b21a3391082f23b5ad2d37f8

View File

@@ -672,7 +672,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 1117;
CURRENT_PROJECT_VERSION = 1124;
DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
@@ -682,7 +682,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.1117;
MARKETING_VERSION = 1.0.1124;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -808,7 +808,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 1117;
CURRENT_PROJECT_VERSION = 1124;
DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
@@ -818,7 +818,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.1117;
MARKETING_VERSION = 1.0.1124;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -836,7 +836,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 1117;
CURRENT_PROJECT_VERSION = 1124;
DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
@@ -846,7 +846,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.1117;
MARKETING_VERSION = 1.0.1124;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -867,7 +867,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1117;
CURRENT_PROJECT_VERSION = 1124;
DEVELOPMENT_TEAM = BA88US33G6;
GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES;
@@ -880,7 +880,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0.1117;
MARKETING_VERSION = 1.0.1124;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
@@ -906,7 +906,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1117;
CURRENT_PROJECT_VERSION = 1124;
DEVELOPMENT_TEAM = BA88US33G6;
GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES;
@@ -919,7 +919,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0.1117;
MARKETING_VERSION = 1.0.1124;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -942,7 +942,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1117;
CURRENT_PROJECT_VERSION = 1124;
DEVELOPMENT_TEAM = BA88US33G6;
GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES;
@@ -955,7 +955,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 1.0.1117;
MARKETING_VERSION = 1.0.1124;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -978,7 +978,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1117;
CURRENT_PROJECT_VERSION = 1124;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_PREVIEWS = YES;
@@ -990,7 +990,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.1117;
MARKETING_VERSION = 1.0.1124;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
@@ -1019,7 +1019,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1117;
CURRENT_PROJECT_VERSION = 1124;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_PREVIEWS = YES;
@@ -1031,7 +1031,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.1117;
MARKETING_VERSION = 1.0.1124;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
PRODUCT_NAME = ServerBox;
@@ -1057,7 +1057,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1117;
CURRENT_PROJECT_VERSION = 1124;
DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_PREVIEWS = YES;
@@ -1069,7 +1069,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0.1117;
MARKETING_VERSION = 1.0.1124;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
PRODUCT_NAME = ServerBox;

View File

@@ -1,5 +1,6 @@
import 'package:flutter/services.dart';
import 'package:server_box/data/res/misc.dart';
import 'package:server_box/data/res/store.dart';
abstract final class MethodChans {
static const _channel = MethodChannel('${Miscs.pkgName}/main_chan');
@@ -8,14 +9,14 @@ abstract final class MethodChans {
_channel.invokeMethod('sendToBackground');
}
/// TODO: try fix the fn, then uncomment it and [stopService]
/// Issues #639
static void startService() {
// _channel.invokeMethod('startService');
if (Stores.setting.fgService.fetch() != true) return;
_channel.invokeMethod('startService');
}
static void stopService() {
// _channel.invokeMethod('stopService');
if (Stores.setting.fgService.fetch() != true) return;
_channel.invokeMethod('stopService');
}
static void updateHomeWidget() async {

View File

@@ -3,6 +3,6 @@
abstract class BuildData {
static const String name = "ServerBox";
static const int build = 1117;
static const int build = 1124;
static const int script = 59;
}

View File

@@ -235,6 +235,9 @@ class SettingStore extends HiveStore {
/// Set it empty to use local editor GUI.
late final sftpEditor = propertyDefault('sftpEditor', '');
/// Run foreground service on Android, if the SSH terminal is running
late final fgService = propertyDefault('fgService', false);
// Never show these settings for users
//
// ------BEGIN------

View File

@@ -434,6 +434,18 @@ abstract class AppLocalizations {
/// **'If you downloaded this app from F-Droid, it is recommended to turn off this option.'**
String get fdroidReleaseTip;
/// No description provided for @fgService.
///
/// In en, this message translates to:
/// **'Foreground Service'**
String get fgService;
/// No description provided for @fgServiceTip.
///
/// In en, this message translates to:
/// **'After enabling, some device models may crash. Disabling it may cause some models to be unable to maintain SSH connections in the background. Please allow ServerBox notification permissions, background running, and self-wake-up in system settings.'**
String get fgServiceTip;
/// No description provided for @fileTooLarge.
///
/// In en, this message translates to:

View File

@@ -173,6 +173,12 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get fdroidReleaseTip => 'Wenn Sie diese App von F-Droid heruntergeladen haben, wird empfohlen, diese Option zu deaktivieren.';
@override
String get fgService => 'Vordergrund-Dienst';
@override
String get fgServiceTip => 'Nach dem Einschalten kann es bei einigen Gerätemodellen zu Abstürzen kommen. Das Ausschalten kann bei einigen Modellen dazu führen, dass SSH-Verbindungen im Hintergrund nicht aufrechterhalten werden können. Bitte erlauben Sie ServerBox in den Systemeinstellungen Benachrichtigungsrechte, Hintergrundausführung und Selbstaktivierung.';
@override
String fileTooLarge(Object file, Object size, Object sizeMax) {
return 'Datei \'$file\' ist zu groß $size, max $sizeMax';

View File

@@ -173,6 +173,12 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get fdroidReleaseTip => 'If you downloaded this app from F-Droid, it is recommended to turn off this option.';
@override
String get fgService => 'Foreground Service';
@override
String get fgServiceTip => 'After enabling, some device models may crash. Disabling it may cause some models to be unable to maintain SSH connections in the background. Please allow ServerBox notification permissions, background running, and self-wake-up in system settings.';
@override
String fileTooLarge(Object file, Object size, Object sizeMax) {
return 'File \'$file\' too large $size, max $sizeMax';

View File

@@ -173,6 +173,12 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get fdroidReleaseTip => 'Si descargaste esta aplicación desde F-Droid, se recomienda desactivar esta opción.';
@override
String get fgService => 'Servicio en primer plano';
@override
String get fgServiceTip => 'Después de activarlo, algunos modelos de dispositivos pueden bloquearse. Desactivarlo puede hacer que algunos modelos no puedan mantener las conexiones SSH en segundo plano. Por favor, permita los permisos de notificación de ServerBox, la ejecución en segundo plano y el auto-despertar en la configuración del sistema.';
@override
String fileTooLarge(Object file, Object size, Object sizeMax) {
return 'El archivo \'$file\' es demasiado grande \'$size\', supera el $sizeMax';

View File

@@ -173,6 +173,12 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get fdroidReleaseTip => 'Si vous avez téléchargé cette application depuis F-Droid, il est recommandé de désactiver cette option.';
@override
String get fgService => 'Service de premier plan';
@override
String get fgServiceTip => 'Après l\'activation, certains modèles d\'appareils peuvent planter. La désactivation peut empêcher certains modèles de maintenir les connexions SSH en arrière-plan. Veuillez autoriser les permissions de notification ServerBox, l\'exécution en arrière-plan et l\'auto-réveil dans les paramètres système.';
@override
String fileTooLarge(Object file, Object size, Object sizeMax) {
return 'Fichier \'$file\' trop volumineux $size, max $sizeMax';

View File

@@ -173,6 +173,12 @@ class AppLocalizationsId extends AppLocalizations {
@override
String get fdroidReleaseTip => 'Jika Anda mengunduh aplikasi ini dari F-Droid, disarankan untuk mematikan opsi ini.';
@override
String get fgService => 'Layanan Latar Depan';
@override
String get fgServiceTip => 'Setelah diaktifkan, beberapa model perangkat mungkin crash. Menonaktifkannya dapat menyebabkan beberapa model tidak dapat mempertahankan koneksi SSH di latar belakang. Harap izinkan perizinan notifikasi ServerBox, menjalankan di latar belakang, dan bangun mandiri di pengaturan sistem.';
@override
String fileTooLarge(Object file, Object size, Object sizeMax) {
return 'File \'$file\' terlalu besar $size, max $sizeMax';

View File

@@ -173,6 +173,12 @@ class AppLocalizationsJa extends AppLocalizations {
@override
String get fdroidReleaseTip => 'このアプリをF-Droidからダウンロードした場合、このオプションをオフにすることをお勧めします。';
@override
String get fgService => 'フォアグラウンドサービス';
@override
String get fgServiceTip => '有効にすると、一部の機種でクラッシュする可能性があります。無効にすると、一部の機種でバックグラウンドでのSSH接続を維持できなくなる可能性があります。システム設定でServerBoxの通知権限、バックグラウンド実行、自己起動を許可してください。';
@override
String fileTooLarge(Object file, Object size, Object sizeMax) {
return 'ファイル \'$file\' は大きすぎます \'$size\'$sizeMax を超えています';

View File

@@ -173,6 +173,12 @@ class AppLocalizationsNl extends AppLocalizations {
@override
String get fdroidReleaseTip => 'Als u deze app van F-Droid heeft gedownload, wordt aanbevolen deze optie uit te schakelen.';
@override
String get fgService => 'Voorgrondservice';
@override
String get fgServiceTip => 'Na het inschakelen kunnen sommige apparaatmodellen crashen. Uitschakelen kan ertoe leiden dat sommige modellen SSH-verbindingen niet op de achtergrond kunnen behouden. Sta ServerBox notificatierechten, achtergronduitvoering en zelf-ontwaken toe in systeeminstellingen.';
@override
String fileTooLarge(Object file, Object size, Object sizeMax) {
return 'Bestand \'$file\' te groot $size, max $sizeMax';

View File

@@ -173,6 +173,12 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get fdroidReleaseTip => 'Se você baixou este aplicativo do F-Droid, é recomendado desativar esta opção.';
@override
String get fgService => 'Serviço em primeiro plano';
@override
String get fgServiceTip => 'Após ativar, alguns modelos de dispositivos podem travar. Desativar pode fazer com que alguns modelos não consigam manter conexões SSH em segundo plano. Por favor, permita as permissões de notificação do ServerBox, execução em segundo plano e auto-despertar nas configurações do sistema.';
@override
String fileTooLarge(Object file, Object size, Object sizeMax) {
return 'Arquivo \'$file\' muito grande \'$size\', excedendo $sizeMax';

View File

@@ -173,6 +173,12 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get fdroidReleaseTip => 'Если вы скачали это приложение с F-Droid, рекомендуется отключить эту опцию.';
@override
String get fgService => 'Сервис переднего плана';
@override
String get fgServiceTip => 'После включения некоторые модели устройств могут вылетать. Отключение может привести к тому, что некоторые модели не смогут поддерживать SSH-соединения в фоновом режиме. Пожалуйста, разрешите ServerBox права на уведомления, фоновую работу и самопробуждение в системных настройках.';
@override
String fileTooLarge(Object file, Object size, Object sizeMax) {
return 'Файл \'$file\' слишком большой \'$size\', превышает $sizeMax';

View File

@@ -173,6 +173,12 @@ class AppLocalizationsTr extends AppLocalizations {
@override
String get fdroidReleaseTip => 'Bu uygulamayı F-Droid\'den indirdiyseniz, bu seçeneği kapatmanız önerilir.';
@override
String get fgService => 'Ön plan hizmeti';
@override
String get fgServiceTip => 'Etkinleştirdikten sonra, bazı cihaz modellerinde çökme olabilir. Devre dışı bırakmak, bazı modellerin SSH bağlantılarını arka planda sürdürememesine neden olabilir. Lütfen sistem ayarlarında ServerBox bildirim izinlerine, arka planda çalışmaya ve kendiliğinden uyanmaya izin verin.';
@override
String fileTooLarge(Object file, Object size, Object sizeMax) {
return '\'$file\' dosyası çok büyük $size, maksimum $sizeMax';

View File

@@ -173,6 +173,12 @@ class AppLocalizationsUk extends AppLocalizations {
@override
String get fdroidReleaseTip => 'Якщо ви завантажили цей застосунок з F-Droid, рекомендується відключити цю опцію.';
@override
String get fgService => 'Служба переднього плану';
@override
String get fgServiceTip => 'Після увімкнення деякі моделі пристроїв можуть вилітати. Вимкнення може призвести до того, що деякі моделі не зможуть підтримувати SSH-з\'єднання у фоновому режимі. Будь ласка, дозвольте ServerBox права на сповіщення, фонову роботу та самопробудження в системних налаштуваннях.';
@override
String fileTooLarge(Object file, Object size, Object sizeMax) {
return 'Файл \'$file\' занадто великий ($size), макс $sizeMax';

View File

@@ -173,6 +173,12 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get fdroidReleaseTip => '如果你是从 F-Droid 下载的本应用,推荐关闭此选项';
@override
String get fgService => '前台服务';
@override
String get fgServiceTip => '开启后,可能会导致部分机型闪退。关闭可能导致部分机型无法后台保持 SSH 连接。请在系统设置内允许 ServerBox 通知权限、后台运行、自我唤醒。';
@override
String fileTooLarge(Object file, Object size, Object sizeMax) {
return '文件 \'$file\' 过大 \'$size\',超过了 $sizeMax';
@@ -869,6 +875,12 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
@override
String get fdroidReleaseTip => '如果你是從 F-Droid 下載的本應用,推薦關閉此選項';
@override
String get fgService => '前台服務';
@override
String get fgServiceTip => '開啟後,可能會導致部分機型閃退。關閉可能導致部分機型無法後台保持 SSH 連接。請在系統設置內允許 ServerBox 通知權限、後台運行、自我喚醒。';
@override
String fileTooLarge(Object file, Object size, Object sizeMax) {
return '文件 \'$file\' 過大 \'$size\',超過了 $sizeMax';

View File

@@ -53,6 +53,8 @@
"extraArgs": "Extra args",
"fallbackSshDest": "SSH-Fallback-Ziel",
"fdroidReleaseTip": "Wenn Sie diese App von F-Droid heruntergeladen haben, wird empfohlen, diese Option zu deaktivieren.",
"fgService": "Vordergrund-Dienst",
"fgServiceTip": "Nach dem Einschalten kann es bei einigen Gerätemodellen zu Abstürzen kommen. Das Ausschalten kann bei einigen Modellen dazu führen, dass SSH-Verbindungen im Hintergrund nicht aufrechterhalten werden können. Bitte erlauben Sie ServerBox in den Systemeinstellungen Benachrichtigungsrechte, Hintergrundausführung und Selbstaktivierung.",
"fileTooLarge": "Datei '{file}' ist zu groß {size}, max {sizeMax}",
"followSystem": "System verfolgen",
"font": "Schriftarten",

View File

@@ -53,6 +53,8 @@
"extraArgs": "Extra arguments",
"fallbackSshDest": "Fallback SSH destination",
"fdroidReleaseTip": "If you downloaded this app from F-Droid, it is recommended to turn off this option.",
"fgService": "Foreground Service",
"fgServiceTip": "After enabling, some device models may crash. Disabling it may cause some models to be unable to maintain SSH connections in the background. Please allow ServerBox notification permissions, background running, and self-wake-up in system settings.",
"fileTooLarge": "File '{file}' too large {size}, max {sizeMax}",
"followSystem": "Follow system",
"font": "Font",

View File

@@ -53,6 +53,8 @@
"extraArgs": "Argumentos extra",
"fallbackSshDest": "Destino SSH alternativo",
"fdroidReleaseTip": "Si descargaste esta aplicación desde F-Droid, se recomienda desactivar esta opción.",
"fgService": "Servicio en primer plano",
"fgServiceTip": "Después de activarlo, algunos modelos de dispositivos pueden bloquearse. Desactivarlo puede hacer que algunos modelos no puedan mantener las conexiones SSH en segundo plano. Por favor, permita los permisos de notificación de ServerBox, la ejecución en segundo plano y el auto-despertar en la configuración del sistema.",
"fileTooLarge": "El archivo '{file}' es demasiado grande '{size}', supera el {sizeMax}",
"followSystem": "Seguir al sistema",
"font": "Fuente",

View File

@@ -53,6 +53,8 @@
"extraArgs": "Arguments supplémentaires",
"fallbackSshDest": "Destino SSH alternativo",
"fdroidReleaseTip": "Si vous avez téléchargé cette application depuis F-Droid, il est recommandé de désactiver cette option.",
"fgService": "Service de premier plan",
"fgServiceTip": "Après l'activation, certains modèles d'appareils peuvent planter. La désactivation peut empêcher certains modèles de maintenir les connexions SSH en arrière-plan. Veuillez autoriser les permissions de notification ServerBox, l'exécution en arrière-plan et l'auto-réveil dans les paramètres système.",
"fileTooLarge": "Fichier '{file}' trop volumineux {size}, max {sizeMax}",
"followSystem": "Suivre le système",
"font": "Police",

View File

@@ -53,6 +53,8 @@
"extraArgs": "Args ekstra",
"fallbackSshDest": "Tujuan SSH mundur",
"fdroidReleaseTip": "Jika Anda mengunduh aplikasi ini dari F-Droid, disarankan untuk mematikan opsi ini.",
"fgService": "Layanan Latar Depan",
"fgServiceTip": "Setelah diaktifkan, beberapa model perangkat mungkin crash. Menonaktifkannya dapat menyebabkan beberapa model tidak dapat mempertahankan koneksi SSH di latar belakang. Harap izinkan perizinan notifikasi ServerBox, menjalankan di latar belakang, dan bangun mandiri di pengaturan sistem.",
"fileTooLarge": "File '{file}' terlalu besar {size}, max {sizeMax}",
"followSystem": "Ikuti sistem",
"font": "Font",

View File

@@ -53,6 +53,8 @@
"extraArgs": "追加引数",
"fallbackSshDest": "フォールバックSSH宛先",
"fdroidReleaseTip": "このアプリをF-Droidからダウンロードした場合、このオプションをオフにすることをお勧めします。",
"fgService": "フォアグラウンドサービス",
"fgServiceTip": "有効にすると、一部の機種でクラッシュする可能性があります。無効にすると、一部の機種でバックグラウンドでのSSH接続を維持できなくなる可能性があります。システム設定でServerBoxの通知権限、バックグラウンド実行、自己起動を許可してください。",
"fileTooLarge": "ファイル '{file}' は大きすぎます '{size}'、{sizeMax} を超えています",
"followSystem": "システムに従う",
"font": "フォント",

View File

@@ -53,6 +53,8 @@
"extraArgs": "Extra argumenten",
"fallbackSshDest": "Fallback SSH-bestemming",
"fdroidReleaseTip": "Als u deze app van F-Droid heeft gedownload, wordt aanbevolen deze optie uit te schakelen.",
"fgService": "Voorgrondservice",
"fgServiceTip": "Na het inschakelen kunnen sommige apparaatmodellen crashen. Uitschakelen kan ertoe leiden dat sommige modellen SSH-verbindingen niet op de achtergrond kunnen behouden. Sta ServerBox notificatierechten, achtergronduitvoering en zelf-ontwaken toe in systeeminstellingen.",
"fileTooLarge": "Bestand '{file}' te groot {size}, max {sizeMax}",
"followSystem": "Volg systeem",
"font": "Lettertype",

View File

@@ -53,6 +53,8 @@
"extraArgs": "Argumentos extras",
"fallbackSshDest": "Destino SSH de fallback",
"fdroidReleaseTip": "Se você baixou este aplicativo do F-Droid, é recomendado desativar esta opção.",
"fgService": "Serviço em primeiro plano",
"fgServiceTip": "Após ativar, alguns modelos de dispositivos podem travar. Desativar pode fazer com que alguns modelos não consigam manter conexões SSH em segundo plano. Por favor, permita as permissões de notificação do ServerBox, execução em segundo plano e auto-despertar nas configurações do sistema.",
"fileTooLarge": "Arquivo '{file}' muito grande '{size}', excedendo {sizeMax}",
"followSystem": "Seguir sistema",
"font": "Fonte",

View File

@@ -53,6 +53,8 @@
"extraArgs": "Дополнительные аргументы",
"fallbackSshDest": "Резервное место назначения SSH",
"fdroidReleaseTip": "Если вы скачали это приложение с F-Droid, рекомендуется отключить эту опцию.",
"fgService": "Сервис переднего плана",
"fgServiceTip": "После включения некоторые модели устройств могут вылетать. Отключение может привести к тому, что некоторые модели не смогут поддерживать SSH-соединения в фоновом режиме. Пожалуйста, разрешите ServerBox права на уведомления, фоновую работу и самопробуждение в системных настройках.",
"fileTooLarge": "Файл '{file}' слишком большой '{size}', превышает {sizeMax}",
"followSystem": "Следовать за системой",
"font": "Шрифт",

View File

@@ -53,6 +53,8 @@
"extraArgs": "Ek argümanlar",
"fallbackSshDest": "Yedek SSH hedefi",
"fdroidReleaseTip": "Bu uygulamayı F-Droid'den indirdiyseniz, bu seçeneği kapatmanız önerilir.",
"fgService": "Ön plan hizmeti",
"fgServiceTip": "Etkinleştirdikten sonra, bazı cihaz modellerinde çökme olabilir. Devre dışı bırakmak, bazı modellerin SSH bağlantılarını arka planda sürdürememesine neden olabilir. Lütfen sistem ayarlarında ServerBox bildirim izinlerine, arka planda çalışmaya ve kendiliğinden uyanmaya izin verin.",
"fileTooLarge": "'{file}' dosyası çok büyük {size}, maksimum {sizeMax}",
"followSystem": "Sistemi takip et",
"font": "Yazı tipi",

View File

@@ -53,6 +53,8 @@
"extraArgs": "Додаткові аргументи",
"fallbackSshDest": "Резервна SSH адреса",
"fdroidReleaseTip": "Якщо ви завантажили цей застосунок з F-Droid, рекомендується відключити цю опцію.",
"fgService": "Служба переднього плану",
"fgServiceTip": "Після увімкнення деякі моделі пристроїв можуть вилітати. Вимкнення може призвести до того, що деякі моделі не зможуть підтримувати SSH-з'єднання у фоновому режимі. Будь ласка, дозвольте ServerBox права на сповіщення, фонову роботу та самопробудження в системних налаштуваннях.",
"fileTooLarge": "Файл '{file}' занадто великий ({size}), макс {sizeMax}",
"followSystem": "Слідувати системі",
"font": "Шрифт",

View File

@@ -53,6 +53,8 @@
"extraArgs": "额外参数",
"fallbackSshDest": "备选 SSH 目标",
"fdroidReleaseTip": "如果你是从 F-Droid 下载的本应用,推荐关闭此选项",
"fgService": "前台服务",
"fgServiceTip": "开启后,可能会导致部分机型闪退。关闭可能导致部分机型无法后台保持 SSH 连接。请在系统设置内允许 ServerBox 通知权限、后台运行、自我唤醒。",
"fileTooLarge": "文件 '{file}' 过大 '{size}',超过了 {sizeMax}",
"followSystem": "跟随系统",
"font": "字体",

View File

@@ -53,6 +53,8 @@
"extraArgs": "額外參數",
"fallbackSshDest": "備選 SSH 目標",
"fdroidReleaseTip": "如果你是從 F-Droid 下載的本應用,推薦關閉此選項",
"fgService": "前台服務",
"fgServiceTip": "開啟後,可能會導致部分機型閃退。關閉可能導致部分機型無法後台保持 SSH 連接。請在系統設置內允許 ServerBox 通知權限、後台運行、自我喚醒。",
"fileTooLarge": "文件 '{file}' 過大 '{size}',超過了 {sizeMax}",
"followSystem": "跟隨系統",
"font": "字型",

View File

@@ -21,6 +21,7 @@ class _AndroidSettingsPageState extends State<AndroidSettingsPage> {
body: ListView(
padding: const EdgeInsets.symmetric(horizontal: 17),
children: [
_buildFgService(),
_buildBgRun(),
_buildAndroidWidgetSharedPreference(),
if (BioAuth.isPlatformSupported)
@@ -115,4 +116,11 @@ class _AndroidSettingsPageState extends State<AndroidSettingsPage> {
// },
// );
// }
Widget _buildFgService() {
return ListTile(
title: TipText(l10n.fgService, l10n.fgServiceTip),
trailing: StoreSwitch(prop: Stores.setting.fgService),
);
}
}

View File

@@ -76,7 +76,6 @@ class SSHPageState extends State<SSHPage>
@override
void dispose() {
super.dispose();
_virtKeyLongPressTimer?.cancel();
_terminalController.dispose();
_discontinuityTimer?.cancel();
@@ -87,6 +86,7 @@ class SSHPageState extends State<SSHPage>
MethodChans.stopService();
}
}
super.dispose();
}
@override
@@ -270,6 +270,161 @@ class SSHPageState extends State<SSHPage>
);
}
@override
bool get wantKeepAlive => true;
void _initStoredCfg() {
final fontFamilly = Stores.setting.fontPath.fetch().getFileName();
final textSize = Stores.setting.termFontSize.fetch();
final textStyle = TextStyle(
fontFamily: fontFamilly,
fontSize: textSize,
);
_terminalStyle = TerminalStyle.fromTextStyle(textStyle);
}
Future<void> _showHelp() async {
if (Stores.setting.sshTermHelpShown.fetch()) return;
return await context.showRoundDialog(
title: libL10n.doc,
child: Text(l10n.sshTermHelp),
actions: [
TextButton(
onPressed: () {
Stores.setting.sshTermHelpShown.put(true);
context.pop();
},
child: Text(l10n.noPromptAgain),
),
],
);
}
@override
FutureOr<void> afterFirstLayout(BuildContext context) async {
await _showHelp();
await _initTerminal();
if (Stores.setting.sshWakeLock.fetch()) WakelockPlus.enable();
}
}
extension _Init on SSHPageState {
Future<void> _initTerminal() async {
_writeLn(l10n.waitConnection);
_client ??= await genClient(
widget.spi,
onStatus: (p0) {
_writeLn(p0.toString());
},
onKeyboardInteractive: _onKeyboardInteractive,
);
_writeLn('${libL10n.execute}: Shell');
final session = await _client?.shell(
pty: SSHPtyConfig(
width: _terminal.viewWidth,
height: _terminal.viewHeight,
),
environment: widget.spi.envs,
);
//_setupDiscontinuityTimer();
if (session == null) {
_writeLn(libL10n.fail);
return;
}
_terminal.buffer.clear();
_terminal.buffer.setCursor(0, 0);
_terminal.onOutput = (data) {
session.write(utf8.encode(data));
};
_terminal.onResize = (width, height, pixelWidth, pixelHeight) {
session.resizeTerminal(width, height);
};
_listen(session.stdout);
_listen(session.stderr);
for (final snippet in SnippetProvider.snippets.value) {
if (snippet.autoRunOn?.contains(widget.spi.id) == true) {
snippet.runInTerm(_terminal, widget.spi);
}
}
if (widget.initCmd != null) {
_terminal.textInput(widget.initCmd!);
_terminal.keyInput(TerminalKey.enter);
}
if (widget.initSnippet != null) {
widget.initSnippet!.runInTerm(_terminal, widget.spi);
}
widget.focusNode?.requestFocus();
await session.done;
if (mounted && widget.notFromTab) {
context.pop();
}
widget.onSessionEnd?.call();
}
void _listen(Stream<Uint8List>? stream) {
if (stream == null) {
return;
}
stream
.cast<List<int>>()
.transform(const Utf8Decoder())
.listen(_terminal.write);
}
void _setupDiscontinuityTimer() {
_discontinuityTimer = Timer.periodic(
const Duration(seconds: 5),
(_) async {
var throwTimeout = true;
Future.delayed(const Duration(seconds: 3), () {
if (throwTimeout) {
_catchTimeout();
}
});
await _client?.ping();
throwTimeout = false;
},
);
}
void _catchTimeout() {
_discontinuityTimer?.cancel();
if (!mounted) return;
_writeLn('\n\nConnection lost\r\n');
context.showRoundDialog(
title: libL10n.attention,
child: Text('${l10n.disconnected}\n${l10n.goBackQ}'),
barrierDismiss: false,
actions: Btn.ok(
onTap: () {
if (mounted) {
context.pop();
}
},
).toList,
);
}
void _writeLn(String p0) {
_terminal.write('$p0\r\n');
}
}
extension _VirtKey on SSHPageState {
void _doVirtualKey(VirtKey item) {
if (item.func != null) {
HapticFeedback.mediumImpact();
@@ -380,10 +535,6 @@ class SSHPageState extends State<SSHPage>
return _terminal.buffer.getText(range);
}
void _writeLn(String p0) {
_terminal.write('$p0\r\n');
}
void _initVirtKeys() {
final virtKeys = VirtKeyX.loadFromStore();
for (int len = 0; len < virtKeys.length; len += 7) {
@@ -398,151 +549,4 @@ class SSHPageState extends State<SSHPage>
FutureOr<List<String>?> _onKeyboardInteractive(SSHUserInfoRequest req) {
return KeybordInteractive.defaultHandle(widget.spi, ctx: context);
}
Future<void> _initTerminal() async {
_writeLn(l10n.waitConnection);
_client ??= await genClient(
widget.spi,
onStatus: (p0) {
_writeLn(p0.toString());
},
onKeyboardInteractive: _onKeyboardInteractive,
);
_writeLn('${libL10n.execute}: Shell');
final session = await _client?.shell(
pty: SSHPtyConfig(
width: _terminal.viewWidth,
height: _terminal.viewHeight,
),
environment: widget.spi.envs,
);
//_setupDiscontinuityTimer();
if (session == null) {
_writeLn(libL10n.fail);
return;
}
_terminal.buffer.clear();
_terminal.buffer.setCursor(0, 0);
_terminal.onOutput = (data) {
session.write(utf8.encode(data));
};
_terminal.onResize = (width, height, pixelWidth, pixelHeight) {
session.resizeTerminal(width, height);
};
_listen(session.stdout);
_listen(session.stderr);
for (final snippet in SnippetProvider.snippets.value) {
if (snippet.autoRunOn?.contains(widget.spi.id) == true) {
snippet.runInTerm(_terminal, widget.spi);
}
}
if (widget.initCmd != null) {
_terminal.textInput(widget.initCmd!);
_terminal.keyInput(TerminalKey.enter);
}
if (widget.initSnippet != null) {
widget.initSnippet!.runInTerm(_terminal, widget.spi);
}
widget.focusNode?.requestFocus();
await session.done;
if (mounted && widget.notFromTab) {
context.pop();
}
widget.onSessionEnd?.call();
}
void _listen(Stream<Uint8List>? stream) {
if (stream == null) {
return;
}
stream
.cast<List<int>>()
.transform(const Utf8Decoder())
.listen(_terminal.write);
}
void _setupDiscontinuityTimer() {
_discontinuityTimer = Timer.periodic(
const Duration(seconds: 5),
(_) async {
var throwTimeout = true;
Future.delayed(const Duration(seconds: 3), () {
if (throwTimeout) {
_catchTimeout();
}
});
await _client?.ping();
throwTimeout = false;
},
);
}
void _catchTimeout() {
_discontinuityTimer?.cancel();
if (!mounted) return;
_writeLn('\n\nConnection lost\r\n');
context.showRoundDialog(
title: libL10n.attention,
child: Text('${l10n.disconnected}\n${l10n.goBackQ}'),
barrierDismiss: false,
actions: Btn.ok(
onTap: () {
if (mounted) {
context.pop();
}
},
).toList,
);
}
@override
bool get wantKeepAlive => true;
void _initStoredCfg() {
final fontFamilly = Stores.setting.fontPath.fetch().getFileName();
final textSize = Stores.setting.termFontSize.fetch();
final textStyle = TextStyle(
fontFamily: fontFamilly,
fontSize: textSize,
);
_terminalStyle = TerminalStyle.fromTextStyle(textStyle);
}
Future<void> _showHelp() async {
if (Stores.setting.sshTermHelpShown.fetch()) return;
return await context.showRoundDialog(
title: libL10n.doc,
child: Text(l10n.sshTermHelp),
actions: [
TextButton(
onPressed: () {
Stores.setting.sshTermHelpShown.put(true);
context.pop();
},
child: Text(l10n.noPromptAgain),
),
],
);
}
@override
FutureOr<void> afterFirstLayout(BuildContext context) async {
await _showHelp();
await _initTerminal();
if (Stores.setting.sshWakeLock.fetch()) WakelockPlus.enable();
}
}

View File

@@ -67,30 +67,6 @@ class _SSHTabPageState extends State<SSHTabPage>
);
}
void _onTapTab(int idx) async {
await _toPage(idx);
}
void _onTapClose(String name) async {
final confirm = await showDialog<bool>(
context: context,
builder: (context) {
return AlertDialog(
title: Text(libL10n.attention),
content: Text('${libL10n.close} SSH ${l10n.conn}($name) ?'),
actions: Btnx.okReds,
);
},
);
Future.delayed(Durations.short1, FocusScope.of(context).unfocus);
if (confirm != true) return;
_tabMap.remove(name);
_tabRN.notify();
_pageCtrl.previousPage(
duration: Durations.medium1, curve: Curves.fastEaseInToSlowEaseOut);
}
Widget _buildBody() {
return ListenBuilder(
listenable: _tabRN,
@@ -109,6 +85,11 @@ class _SSHTabPageState extends State<SSHTabPage>
);
}
@override
bool get wantKeepAlive => true;
}
extension on _SSHTabPageState {
void _onTapInitCard(Spi spi) async {
final name = () {
final reg = RegExp('${spi.name}\\((\\d+)\\)');
@@ -155,8 +136,29 @@ class _SSHTabPageState extends State<SSHTabPage>
}
}
@override
bool get wantKeepAlive => true;
void _onTapTab(int idx) async {
await _toPage(idx);
}
void _onTapClose(String name) async {
final confirm = await showDialog<bool>(
context: context,
builder: (context) {
return AlertDialog(
title: Text(libL10n.attention),
content: Text('${libL10n.close} SSH ${l10n.conn}($name) ?'),
actions: Btnx.okReds,
);
},
);
Future.delayed(Durations.short1, FocusScope.of(context).unfocus);
if (confirm != true) return;
_tabMap.remove(name);
_tabRN.notify();
_pageCtrl.previousPage(
duration: Durations.medium1, curve: Curves.fastEaseInToSlowEaseOut);
}
}
final class _TabBar extends StatelessWidget implements PreferredSizeWidget {

View File

@@ -471,7 +471,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1117;
CURRENT_PROJECT_VERSION = 1124;
DEVELOPMENT_TEAM = BA88US33G6;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Server Box";
@@ -481,7 +481,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.15;
MARKETING_VERSION = 1.0.1117;
MARKETING_VERSION = 1.0.1124;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "Server Box";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -608,7 +608,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1117;
CURRENT_PROJECT_VERSION = 1124;
DEVELOPMENT_TEAM = BA88US33G6;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Server Box";
@@ -618,7 +618,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.15;
MARKETING_VERSION = 1.0.1117;
MARKETING_VERSION = 1.0.1124;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "Server Box";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -638,7 +638,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "3rd Party Mac Developer Application";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1117;
CURRENT_PROJECT_VERSION = 1124;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = BA88US33G6;
INFOPLIST_FILE = Runner/Info.plist;
@@ -649,7 +649,7 @@
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 10.15;
MARKETING_VERSION = 1.0.1117;
MARKETING_VERSION = 1.0.1124;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "Server Box";
PROVISIONING_PROFILE_SPECIFIER = "";

View File

@@ -1,7 +1,7 @@
name: server_box
description: server status & toolbox app.
publish_to: 'none'
version: 1.0.1117+1117
version: 1.0.1124+1124
environment:
sdk: ">=3.0.0"