Compare commits

...

12 Commits

Author SHA1 Message Date
lollipopkit🏳️‍⚧️
fd2bf08f78 bump: v1253 2025-09-09 13:32:17 +08:00
lollipopkit🏳️‍⚧️
98e13c39cf fix: android channel invoke 2025-09-09 13:30:37 +08:00
lollipopkit🏳️‍⚧️
e70abeef04 bump: v1251 2025-09-09 13:14:01 +08:00
lollipopkit🏳️‍⚧️
194774d6fb opt.: system detect logic to avoid creating useless file (#905) 2025-09-09 13:10:40 +08:00
lollipopkit🏳️‍⚧️
640d61bab9 fix: holding Backspace doesnt work on desktop (#903) 2025-09-08 14:06:35 +08:00
lollipopkit🏳️‍⚧️
7f4cf22cc9 fix: rm camera perm on mac 2025-09-08 12:37:30 +08:00
lollipopkit🏳️‍⚧️
05a927753f feat: stop all servers in noti center (#901) 2025-09-06 14:04:53 +08:00
lollipopkit🏳️‍⚧️
0c7b72fb2c bump: v1246 2025-09-05 12:31:33 +08:00
lollipopkit🏳️‍⚧️
a869b97502 fix: server stat l10n 2025-09-05 00:24:18 +08:00
lollipopkit🏳️‍⚧️
eadd343205 readd: home drawer
Fixes #900
2025-09-05 00:12:41 +08:00
lollipopkit🏳️‍⚧️
1bac986fe0 bug: single server providers should be keepalived (#899) 2025-09-04 23:50:00 +08:00
lollipopkit🏳️‍⚧️
a94be6c2c3 fix: macOS appstore rejection (#893) 2025-09-03 22:19:04 +08:00
45 changed files with 355 additions and 180 deletions

View File

@@ -17,17 +17,28 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with:
fetch-depth: 1
- uses: subosito/flutter-action@v2 - uses: subosito/flutter-action@v2
with: with:
channel: 'stable' # or: 'beta', 'dev' or 'master' channel: 'stable'
cache: true
cache-key: 'flutter-:os:-:channel:-:version:-:arch:-:hash:'
- name: Cache pub dependencies
uses: actions/cache@v4
with:
path: |
${{ env.PUB_CACHE }}
~/.pub-cache
key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.lock') }}
restore-keys: |
${{ runner.os }}-pub-
- name: Install dependencies - name: Install dependencies
run: flutter pub get run: flutter pub get
# Uncomment this step to verify the use of 'dart format' on each commit.
- name: Verify formatting
run: dart format --output=none .
# Consider passing '--fatal-infos' for slightly stricter analysis. # Consider passing '--fatal-infos' for slightly stricter analysis.
- name: Analyze project source - name: Analyze project source
run: dart analyze run: dart analyze

View File

@@ -9,6 +9,11 @@ on:
permissions: permissions:
contents: write contents: write
# Set by fl_build
# env:
# APP_NAME: ServerBox
# BUILD_NUMBER: ${{ github.ref_name }}
jobs: jobs:
releaseAndroid: releaseAndroid:
name: Release android name: Release android
@@ -20,7 +25,7 @@ jobs:
uses: subosito/flutter-action@v2 uses: subosito/flutter-action@v2
with: with:
channel: "stable" channel: "stable"
flutter-version: "3.35.1" flutter-version: "3.35.3"
- uses: actions/setup-java@v4 - uses: actions/setup-java@v4
with: with:
distribution: "zulu" distribution: "zulu"
@@ -98,16 +103,12 @@ jobs:
# uses: actions/checkout@v4 # uses: actions/checkout@v4
# - name: Install Flutter # - name: Install Flutter
# uses: subosito/flutter-action@v2 # uses: subosito/flutter-action@v2
# with:
# channel: 'stable'
# flutter-version: '3.32.1'
# - name: Build # - name: Build
# run: dart run fl_build -p ios,mac # run: dart run fl_build -p ios
# - name: Create Release # - name: Create Release
# uses: softprops/action-gh-release@v2 # uses: softprops/action-gh-release@v2
# with: # with:
# files: | # files: |
# ${{ env.APP_NAME }}_universal_macos.zip
# ${{ env.APP_NAME }}_universal.ipa # ${{ env.APP_NAME }}_universal.ipa
# env: # env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -16,8 +16,7 @@ class ForegroundService : Service() {
var isRunning: Boolean = false var isRunning: Boolean = false
} }
private val chanId = "ForegroundServiceChannel" private val chanId = "ForegroundServiceChannel"
private val GROUP_KEY = "ssh_sessions_group" private val NOTIFICATION_ID = 1000
private val SUMMARY_ID = 1000
private val ACTION_STOP_FOREGROUND = "ACTION_STOP_FOREGROUND" private val ACTION_STOP_FOREGROUND = "ACTION_STOP_FOREGROUND"
private val ACTION_UPDATE_SESSIONS = "tech.lolli.toolbox.ACTION_UPDATE_SESSIONS" private val ACTION_UPDATE_SESSIONS = "tech.lolli.toolbox.ACTION_UPDATE_SESSIONS"
private val ACTION_DISCONNECT_SESSION = "tech.lolli.toolbox.ACTION_DISCONNECT_SESSION" private val ACTION_DISCONNECT_SESSION = "tech.lolli.toolbox.ACTION_DISCONNECT_SESSION"
@@ -70,6 +69,9 @@ class ForegroundService : Service() {
return when (action) { return when (action) {
ACTION_STOP_FOREGROUND -> { ACTION_STOP_FOREGROUND -> {
// Notify Flutter to stop all connections before stopping service
val stopAllIntent = Intent("tech.lolli.toolbox.STOP_ALL_CONNECTIONS")
sendBroadcast(stopAllIntent)
clearAll() clearAll()
stopForegroundService() stopForegroundService()
START_NOT_STICKY START_NOT_STICKY
@@ -81,7 +83,7 @@ class ForegroundService : Service() {
} }
else -> { else -> {
// Default bring up foreground with placeholder // Default bring up foreground with placeholder
ensureForeground(createSummaryNotification(0, emptyList())) ensureForeground(createMergedNotification(0, emptyList(), emptyList()))
START_STICKY START_STICKY
} }
} }
@@ -118,18 +120,19 @@ class ForegroundService : Service() {
private fun ensureForeground(notification: Notification) { private fun ensureForeground(notification: Notification) {
try { try {
if (!isFgStarted) { if (!isFgStarted) {
startForeground(SUMMARY_ID, notification) startForeground(NOTIFICATION_ID, notification)
isFgStarted = true isFgStarted = true
} else { } else {
val nm = getSystemService(NotificationManager::class.java) val nm = getSystemService(NotificationManager::class.java)
nm?.notify(SUMMARY_ID, notification) nm?.notify(NOTIFICATION_ID, notification)
} }
} catch (e: Exception) { } catch (e: Exception) {
logError("Failed to start/update foreground", e) logError("Failed to start/update foreground", e)
} }
} }
private fun createSummaryNotification(count: Int, lines: List<String>): Notification {
private fun createMergedNotification(count: Int, lines: List<String>, sessions: List<SessionItem>): Notification {
val notificationIntent = Intent(this, MainActivity::class.java) val notificationIntent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity( val pendingIntent = PendingIntent.getActivity(
this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE
@@ -143,21 +146,56 @@ class ForegroundService : Service() {
Notification.Builder(this) Notification.Builder(this)
} }
val inbox = Notification.InboxStyle() // Use the earliest session's start time for chronometer
lines.forEach { inbox.addLine(it) } val earliestStartTime = sessions.minOfOrNull { it.startWhen } ?: System.currentTimeMillis()
return builder val title = when {
.setContentTitle("SSH sessions: $count active") count == 0 -> "Server Box"
.setContentText(if (lines.isNotEmpty()) lines.first() else "Running") count == 1 -> sessions.first().title
else -> "SSH sessions: $count active"
}
val contentText = when {
count == 0 -> "Ready for connections"
count == 1 -> {
val session = sessions.first()
"${session.subtitle} · ${session.status}"
}
else -> "Multiple SSH connections active"
}
// For multiple sessions, show details in expanded view
val style = if (count > 1) {
val inbox = Notification.InboxStyle()
val maxLines = 5
val displayLines = if (lines.size > maxLines) {
lines.take(maxLines) + "...and ${lines.size - maxLines} more"
} else {
lines
}
displayLines.forEach { inbox.addLine(it) }
inbox.setBigContentTitle(title)
inbox
} else {
null
}
val notification = builder
.setContentTitle(title)
.setContentText(contentText)
.setSmallIcon(R.mipmap.ic_launcher) .setSmallIcon(R.mipmap.ic_launcher)
.setStyle(inbox) .setWhen(earliestStartTime)
.setUsesChronometer(true)
.setOngoing(true) .setOngoing(true)
.setOnlyAlertOnce(true) .setOnlyAlertOnce(true)
.setGroup(GROUP_KEY)
.setGroupSummary(true)
.setContentIntent(pendingIntent) .setContentIntent(pendingIntent)
.addAction(android.R.drawable.ic_delete, "Stop", stopPending) .addAction(android.R.drawable.ic_delete, "Stop All", stopPending)
.build()
if (style != null) {
notification.setStyle(style)
}
return notification.build()
} }
private fun handleUpdateSessions(payload: String) { private fun handleUpdateSessions(payload: String) {
@@ -192,71 +230,21 @@ class ForegroundService : Service() {
return return
} }
// Build per-session notifications // Cancel any existing individual notifications (we only show merged notification now)
val currentIds = mutableSetOf<Int>() val toCancel = postedIds.toSet()
val summaryLines = mutableListOf<String>()
sessions.forEach { s ->
// Assign a stable, collision-resistant id per session for this service lifecycle
val nid = notificationIdMap.getOrPut(s.id) { nextNotificationId.getAndIncrement() }
currentIds.add(nid)
summaryLines.add("${s.title}: ${s.status}")
val disconnectIntent = Intent(this, MainActivity::class.java).apply {
action = ACTION_DISCONNECT_SESSION
putExtra("session_id", s.id)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
}
val disconnectPending = PendingIntent.getActivity(
this, nid, disconnectIntent, PendingIntent.FLAG_IMMUTABLE
)
val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Notification.Builder(this, chanId)
} else {
Notification.Builder(this)
}
val noti = builder
.setContentTitle(s.title)
.setContentText("${s.subtitle} · ${s.status}")
.setSmallIcon(R.mipmap.ic_launcher)
.setWhen(s.startWhen)
.setUsesChronometer(true)
.setOngoing(true)
.setOnlyAlertOnce(true)
.setGroup(GROUP_KEY)
.addAction(android.R.drawable.ic_media_pause, "Disconnect", disconnectPending)
.build()
nm.notify(nid, noti)
}
// Cancel stale ones
val toCancel = postedIds - currentIds
toCancel.forEach { nm.cancel(it) } toCancel.forEach { nm.cancel(it) }
// Clean up id mappings for canceled notifications to prevent growth
if (toCancel.isNotEmpty()) {
val keysToRemove = notificationIdMap.filterValues { it in toCancel }.keys
keysToRemove.forEach { notificationIdMap.remove(it) }
}
postedIds.clear() postedIds.clear()
postedIds.addAll(currentIds) notificationIdMap.clear()
// Post/update summary and ensure foreground // Create merged notification content
val maxSummaryLines = 5 val summaryLines = sessions.map { "${it.title}: ${it.status}" }
val truncated = summaryLines.size > maxSummaryLines val mergedNotification = createMergedNotification(sessions.size, summaryLines, sessions)
val displaySummaryLines = if (truncated) { ensureForeground(mergedNotification)
summaryLines.take(maxSummaryLines) + "...and ${summaryLines.size - maxSummaryLines} more"
} else {
summaryLines
}
val summary = createSummaryNotification(sessions.size, displaySummaryLines)
ensureForeground(summary)
} }
private fun clearAll() { private fun clearAll() {
val nm = getSystemService(NotificationManager::class.java) val nm = getSystemService(NotificationManager::class.java)
nm?.cancel(SUMMARY_ID) nm?.cancel(NOTIFICATION_ID)
postedIds.forEach { id -> nm?.cancel(id) } postedIds.forEach { id -> nm?.cancel(id) }
postedIds.clear() postedIds.clear()
isFgStarted = false isFgStarted = false

View File

@@ -4,6 +4,9 @@ import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build import android.os.Build
import android.Manifest import android.Manifest
import android.content.BroadcastReceiver
import android.content.Context
import android.content.IntentFilter
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.android.FlutterFragmentActivity
@@ -16,6 +19,8 @@ class MainActivity: FlutterFragmentActivity() {
private lateinit var channel: MethodChannel private lateinit var channel: MethodChannel
private val ACTION_UPDATE_SESSIONS = "tech.lolli.toolbox.ACTION_UPDATE_SESSIONS" private val ACTION_UPDATE_SESSIONS = "tech.lolli.toolbox.ACTION_UPDATE_SESSIONS"
private val ACTION_DISCONNECT_SESSION = "tech.lolli.toolbox.ACTION_DISCONNECT_SESSION" private val ACTION_DISCONNECT_SESSION = "tech.lolli.toolbox.ACTION_DISCONNECT_SESSION"
private val ACTION_STOP_ALL_CONNECTIONS = "tech.lolli.toolbox.STOP_ALL_CONNECTIONS"
private var stopAllReceiver: BroadcastReceiver? = null
override fun configureFlutterEngine(flutterEngine: FlutterEngine) { override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine) super.configureFlutterEngine(flutterEngine)
@@ -92,6 +97,9 @@ class MainActivity: FlutterFragmentActivity() {
// Handle intent if launched via notification action // Handle intent if launched via notification action
handleActionIntent(intent) handleActionIntent(intent)
// Register broadcast receiver for stop all connections
setupStopAllReceiver()
} }
private fun reqPerm() { private fun reqPerm() {
@@ -141,4 +149,28 @@ class MainActivity: FlutterFragmentActivity() {
} }
} }
} }
private fun setupStopAllReceiver() {
stopAllReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action == ACTION_STOP_ALL_CONNECTIONS && ::channel.isInitialized) {
try {
channel.invokeMethod("stopAllConnections", null)
} catch (e: Exception) {
android.util.Log.e("MainActivity", "Failed to invoke stopAllConnections: ${e.message}")
}
}
}
}
val filter = IntentFilter(ACTION_STOP_ALL_CONNECTIONS)
registerReceiver(stopAllReceiver, filter)
}
override fun onDestroy() {
super.onDestroy()
stopAllReceiver?.let {
unregisterReceiver(it)
stopAllReceiver = null
}
}
} }

Submodule flutter_server_box.wiki added at f440010313

View File

@@ -748,7 +748,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 1241; CURRENT_PROJECT_VERSION = 1253;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
@@ -758,7 +758,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.1241; MARKETING_VERSION = 1.0.1253;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -884,7 +884,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 1241; CURRENT_PROJECT_VERSION = 1253;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
@@ -894,7 +894,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.1241; MARKETING_VERSION = 1.0.1253;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -912,7 +912,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = 1241; CURRENT_PROJECT_VERSION = 1253;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
@@ -922,7 +922,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.1241; MARKETING_VERSION = 1.0.1253;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -943,7 +943,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1241; CURRENT_PROJECT_VERSION = 1253;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -956,7 +956,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.0.1241; MARKETING_VERSION = 1.0.1253;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
@@ -982,7 +982,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1241; CURRENT_PROJECT_VERSION = 1253;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -995,7 +995,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.0.1241; MARKETING_VERSION = 1.0.1253;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1018,7 +1018,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1241; CURRENT_PROJECT_VERSION = 1253;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -1031,7 +1031,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
MARKETING_VERSION = 1.0.1241; MARKETING_VERSION = 1.0.1253;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1054,7 +1054,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1241; CURRENT_PROJECT_VERSION = 1253;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -1066,7 +1066,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.1241; MARKETING_VERSION = 1.0.1253;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
@@ -1095,7 +1095,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1241; CURRENT_PROJECT_VERSION = 1253;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -1107,7 +1107,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.1241; MARKETING_VERSION = 1.0.1253;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
PRODUCT_NAME = ServerBox; PRODUCT_NAME = ServerBox;
@@ -1133,7 +1133,7 @@
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1241; CURRENT_PROJECT_VERSION = 1253;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -1145,7 +1145,7 @@
"$(inherited)", "$(inherited)",
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
MARKETING_VERSION = 1.0.1241; MARKETING_VERSION = 1.0.1253;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
PRODUCT_NAME = ServerBox; PRODUCT_NAME = ServerBox;

View File

@@ -77,8 +77,10 @@ abstract final class MethodChans {
} }
/// Register a handler for native -> Flutter callbacks. /// Register a handler for native -> Flutter callbacks.
/// Currently handles: `disconnectSession` with argument map {id: string} /// Currently handles:
static void registerHandler(Future<void> Function(String id) onDisconnect) { /// - `disconnectSession` with argument map {id: string}
/// - `stopAllConnections` with no arguments
static void registerHandler(Future<void> Function(String id) onDisconnect, [VoidCallback? onStopAll]) {
_channel.setMethodCallHandler((call) async { _channel.setMethodCallHandler((call) async {
switch (call.method) { switch (call.method) {
case 'disconnectSession': case 'disconnectSession':
@@ -88,6 +90,9 @@ abstract final class MethodChans {
await onDisconnect(id); await onDisconnect(id);
} }
return; return;
case 'stopAllConnections':
onStopAll?.call();
return;
default: default:
return; return;
} }

View File

@@ -9,8 +9,8 @@ class SystemDetector {
/// ///
/// First checks if a custom system type is configured in [spi]. /// First checks if a custom system type is configured in [spi].
/// If not, attempts to detect the system by running commands: /// If not, attempts to detect the system by running commands:
/// 1. 'ver' command to detect Windows /// 1. 'uname -a' command to detect Linux/BSD/Darwin
/// 2. 'uname -a' command to detect Linux/BSD/Darwin /// 2. 'ver' command to detect Windows (if uname fails)
/// ///
/// Returns [SystemType.linux] as default if detection fails. /// Returns [SystemType.linux] as default if detection fails.
static Future<SystemType> detect(SSHClient client, Spi spi) async { static Future<SystemType> detect(SSHClient client, Spi spi) async {
@@ -22,17 +22,8 @@ class SystemDetector {
} }
try { try {
// Try to detect Windows systems first (more reliable detection) // Try to detect Unix/Linux/BSD systems first (more reliable and doesn't create files)
final powershellResult = await client.run('ver 2>nul').string; final unixResult = await client.run('uname -a 2>/dev/null').string;
if (powershellResult.isNotEmpty &&
(powershellResult.contains('Windows') || powershellResult.contains('NT'))) {
detectedSystemType = SystemType.windows;
dprint('Detected Windows system type for ${spi.oldId}');
return detectedSystemType;
}
// Try to detect Unix/Linux/BSD systems
final unixResult = await client.run('uname -a').string;
if (unixResult.contains('Linux')) { if (unixResult.contains('Linux')) {
detectedSystemType = SystemType.linux; detectedSystemType = SystemType.linux;
dprint('Detected Linux system type for ${spi.oldId}'); dprint('Detected Linux system type for ${spi.oldId}');
@@ -42,6 +33,15 @@ class SystemDetector {
dprint('Detected BSD system type for ${spi.oldId}'); dprint('Detected BSD system type for ${spi.oldId}');
return detectedSystemType; return detectedSystemType;
} }
// If uname fails, try to detect Windows systems
final powershellResult = await client.run('ver 2>nul').string;
if (powershellResult.isNotEmpty &&
(powershellResult.contains('Windows') || powershellResult.contains('NT'))) {
detectedSystemType = SystemType.windows;
dprint('Detected Windows system type for ${spi.oldId}');
return detectedSystemType;
}
} catch (e) { } catch (e) {
Loggers.app.warning('System detection failed for ${spi.oldId}: $e'); Loggers.app.warning('System detection failed for ${spi.oldId}: $e');
} }

View File

@@ -41,7 +41,7 @@ abstract class ServerState with _$ServerState {
} }
// Individual server state management // Individual server state management
@riverpod @Riverpod(keepAlive: true)
class ServerNotifier extends _$ServerNotifier { class ServerNotifier extends _$ServerNotifier {
@override @override
ServerState build(String serverId) { ServerState build(String serverId) {

View File

@@ -3,6 +3,6 @@
abstract class BuildData { abstract class BuildData {
static const String name = "ServerBox"; static const String name = "ServerBox";
static const int build = 1241; static const int build = 1253;
static const int script = 69; static const int script = 69;
} }

View File

@@ -51,12 +51,31 @@ abstract final class TermSessionManager {
static void init() { static void init() {
if (isAndroid) { if (isAndroid) {
MethodChans.registerHandler((id) async { MethodChans.registerHandler(
_entries[id]?.disconnect?.call(); (id) async {
}); _entries[id]?.disconnect?.call();
},
() {
// Stop all connections when notification "Stop All" is pressed
stopAllConnections();
},
);
} }
} }
/// Called when Android notification "Stop All" button is pressed
static void stopAllConnections() {
// Disconnect all sessions
final disconnectCallbacks = _entries.values.map((e) => e.disconnect).where((cb) => cb != null).toList();
for (final disconnect in disconnectCallbacks) {
disconnect!();
}
// Clear all entries
_entries.clear();
_activeId = null;
_sync();
}
/// Add a session record and push update to Android. /// Add a session record and push update to Android.
static void add({ static void add({
required String id, required String id,

View File

@@ -1628,6 +1628,12 @@ abstract class AppLocalizations {
/// **'Connection Statistics'** /// **'Connection Statistics'**
String get connectionStats; String get connectionStats;
/// No description provided for @connectionStatsDesc.
///
/// In en, this message translates to:
/// **'View server connection success rate and history'**
String get connectionStatsDesc;
/// No description provided for @noConnectionStatsData. /// No description provided for @noConnectionStatsData.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -1729,6 +1735,12 @@ abstract class AppLocalizations {
/// In en, this message translates to: /// In en, this message translates to:
/// **'At least one tab must be selected'** /// **'At least one tab must be selected'**
String get atLeastOneTab; String get atLeastOneTab;
/// No description provided for @serverTabRequired.
///
/// In en, this message translates to:
/// **'Server tab cannot be removed'**
String get serverTabRequired;
} }
class _AppLocalizationsDelegate class _AppLocalizationsDelegate

View File

@@ -855,6 +855,10 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get connectionStats => 'Verbindungsstatistiken'; String get connectionStats => 'Verbindungsstatistiken';
@override
String get connectionStatsDesc =>
'Server-Verbindungserfolgsrate und Verlauf anzeigen';
@override @override
String get noConnectionStatsData => 'Keine Verbindungsstatistikdaten'; String get noConnectionStatsData => 'Keine Verbindungsstatistikdaten';
@@ -911,4 +915,7 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get atLeastOneTab => 'Mindestens ein Tab muss ausgewählt sein'; String get atLeastOneTab => 'Mindestens ein Tab muss ausgewählt sein';
@override
String get serverTabRequired => 'Server-Tab kann nicht entfernt werden';
} }

View File

@@ -847,6 +847,10 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get connectionStats => 'Connection Statistics'; String get connectionStats => 'Connection Statistics';
@override
String get connectionStatsDesc =>
'View server connection success rate and history';
@override @override
String get noConnectionStatsData => 'No connection statistics data'; String get noConnectionStatsData => 'No connection statistics data';
@@ -903,4 +907,7 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get atLeastOneTab => 'At least one tab must be selected'; String get atLeastOneTab => 'At least one tab must be selected';
@override
String get serverTabRequired => 'Server tab cannot be removed';
} }

View File

@@ -856,6 +856,10 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get connectionStats => 'Estadísticas de conexión'; String get connectionStats => 'Estadísticas de conexión';
@override
String get connectionStatsDesc =>
'Ver la tasa de éxito de conexión del servidor e historial';
@override @override
String get noConnectionStatsData => String get noConnectionStatsData =>
'No hay datos de estadísticas de conexión'; 'No hay datos de estadísticas de conexión';
@@ -913,4 +917,7 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get atLeastOneTab => 'Al menos una pestaña debe estar seleccionada'; String get atLeastOneTab => 'Al menos una pestaña debe estar seleccionada';
@override
String get serverTabRequired => 'Server tab cannot be removed';
} }

View File

@@ -859,6 +859,10 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get connectionStats => 'Statistiques de connexion'; String get connectionStats => 'Statistiques de connexion';
@override
String get connectionStatsDesc =>
'Voir le taux de réussite de connexion du serveur et l\'historique';
@override @override
String get noConnectionStatsData => String get noConnectionStatsData =>
'Aucune donnée de statistiques de connexion'; 'Aucune donnée de statistiques de connexion';
@@ -916,4 +920,7 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get atLeastOneTab => 'Au moins un onglet doit être sélectionné'; String get atLeastOneTab => 'Au moins un onglet doit être sélectionné';
@override
String get serverTabRequired => 'Server tab cannot be removed';
} }

View File

@@ -847,6 +847,10 @@ class AppLocalizationsId extends AppLocalizations {
@override @override
String get connectionStats => 'Statistik Koneksi'; String get connectionStats => 'Statistik Koneksi';
@override
String get connectionStatsDesc =>
'Lihat tingkat keberhasilan koneksi server dan riwayat';
@override @override
String get noConnectionStatsData => 'Tidak ada data statistik koneksi'; String get noConnectionStatsData => 'Tidak ada data statistik koneksi';
@@ -903,4 +907,7 @@ class AppLocalizationsId extends AppLocalizations {
@override @override
String get atLeastOneTab => 'Setidaknya satu tab harus dipilih'; String get atLeastOneTab => 'Setidaknya satu tab harus dipilih';
@override
String get serverTabRequired => 'Server tab cannot be removed';
} }

View File

@@ -822,6 +822,9 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get connectionStats => '接続統計'; String get connectionStats => '接続統計';
@override
String get connectionStatsDesc => 'サーバー接続成功率と履歴を表示';
@override @override
String get noConnectionStatsData => '接続統計データがありません'; String get noConnectionStatsData => '接続統計データがありません';
@@ -876,4 +879,7 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get atLeastOneTab => '少なくとも1つのタブを選択する必要があります'; String get atLeastOneTab => '少なくとも1つのタブを選択する必要があります';
@override
String get serverTabRequired => 'サーバータブは削除できません';
} }

View File

@@ -853,6 +853,10 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get connectionStats => 'Verbindingsstatistieken'; String get connectionStats => 'Verbindingsstatistieken';
@override
String get connectionStatsDesc =>
'Bekijk server verbindingssucces ratio en geschiedenis';
@override @override
String get noConnectionStatsData => 'Geen verbindingsstatistiekgegevens'; String get noConnectionStatsData => 'Geen verbindingsstatistiekgegevens';
@@ -910,4 +914,7 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get atLeastOneTab => String get atLeastOneTab =>
'Er moet minimaal één tabblad worden geselecteerd'; 'Er moet minimaal één tabblad worden geselecteerd';
@override
String get serverTabRequired => 'Server tab cannot be removed';
} }

View File

@@ -850,6 +850,10 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get connectionStats => 'Estatísticas de conexão'; String get connectionStats => 'Estatísticas de conexão';
@override
String get connectionStatsDesc =>
'Ver taxa de sucesso de conexão do servidor e histórico';
@override @override
String get noConnectionStatsData => 'Não há dados de estatísticas de conexão'; String get noConnectionStatsData => 'Não há dados de estatísticas de conexão';
@@ -906,4 +910,7 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get atLeastOneTab => 'Pelo menos uma aba deve ser selecionada'; String get atLeastOneTab => 'Pelo menos uma aba deve ser selecionada';
@override
String get serverTabRequired => 'Server tab cannot be removed';
} }

View File

@@ -852,6 +852,10 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get connectionStats => 'Статистика соединений'; String get connectionStats => 'Статистика соединений';
@override
String get connectionStatsDesc =>
'Просмотр коэффициента успешности подключения к серверу и истории';
@override @override
String get noConnectionStatsData => 'Нет данных статистики соединений'; String get noConnectionStatsData => 'Нет данных статистики соединений';
@@ -908,4 +912,7 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get atLeastOneTab => 'Должна быть выбрана хотя бы одна вкладка'; String get atLeastOneTab => 'Должна быть выбрана хотя бы одна вкладка';
@override
String get serverTabRequired => 'Server tab cannot be removed';
} }

View File

@@ -847,6 +847,10 @@ class AppLocalizationsTr extends AppLocalizations {
@override @override
String get connectionStats => 'Bağlantı İstatistikleri'; String get connectionStats => 'Bağlantı İstatistikleri';
@override
String get connectionStatsDesc =>
'Sunucu bağlantı başarı oranını ve geçmişi görüntüle';
@override @override
String get noConnectionStatsData => 'Bağlantı istatistik verisi yok'; String get noConnectionStatsData => 'Bağlantı istatistik verisi yok';
@@ -903,4 +907,7 @@ class AppLocalizationsTr extends AppLocalizations {
@override @override
String get atLeastOneTab => 'En az bir sekme seçilmelidir'; String get atLeastOneTab => 'En az bir sekme seçilmelidir';
@override
String get serverTabRequired => 'Server tab cannot be removed';
} }

View File

@@ -853,6 +853,10 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get connectionStats => 'Статистика з\'єднань'; String get connectionStats => 'Статистика з\'єднань';
@override
String get connectionStatsDesc =>
'Переглянути коефіцієнт успішності підключення до сервера та історію';
@override @override
String get noConnectionStatsData => 'Немає даних статистики з\'єднань'; String get noConnectionStatsData => 'Немає даних статистики з\'єднань';
@@ -909,4 +913,7 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get atLeastOneTab => 'Потрібно вибрати принаймні одну вкладку'; String get atLeastOneTab => 'Потрібно вибрати принаймні одну вкладку';
@override
String get serverTabRequired => 'Server tab cannot be removed';
} }

View File

@@ -807,6 +807,9 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get connectionStats => '连接统计'; String get connectionStats => '连接统计';
@override
String get connectionStatsDesc => '查看服务器连接成功率和历史记录';
@override @override
String get noConnectionStatsData => '暂无连接统计数据'; String get noConnectionStatsData => '暂无连接统计数据';
@@ -861,6 +864,9 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get atLeastOneTab => '至少需要选择一个标签'; String get atLeastOneTab => '至少需要选择一个标签';
@override
String get serverTabRequired => '服务器标签不能被移除';
} }
/// The translations for Chinese, as used in Taiwan (`zh_TW`). /// The translations for Chinese, as used in Taiwan (`zh_TW`).
@@ -1666,6 +1672,9 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
@override @override
String get connectionStats => '連線統計'; String get connectionStats => '連線統計';
@override
String get connectionStatsDesc => '檢視伺服器連線成功率和歷史記錄';
@override @override
String get noConnectionStatsData => '暫無連線統計資料'; String get noConnectionStatsData => '暫無連線統計資料';
@@ -1720,4 +1729,7 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
@override @override
String get atLeastOneTab => '至少需要選擇一個標籤'; String get atLeastOneTab => '至少需要選擇一個標籤';
@override
String get serverTabRequired => '服務器標籤不能被移除';
} }

View File

@@ -251,6 +251,7 @@
"writeScriptFailTip": "Das Schreiben des Skripts ist fehlgeschlagen, möglicherweise aufgrund fehlender Berechtigungen oder das Verzeichnis existiert nicht.", "writeScriptFailTip": "Das Schreiben des Skripts ist fehlgeschlagen, möglicherweise aufgrund fehlender Berechtigungen oder das Verzeichnis existiert nicht.",
"writeScriptTip": "Nach der Verbindung mit dem Server wird ein Skript in `~/.config/server_box` \n | `/tmp/server_box` geschrieben, um den Systemstatus zu überwachen. Sie können den Skriptinhalt überprüfen.", "writeScriptTip": "Nach der Verbindung mit dem Server wird ein Skript in `~/.config/server_box` \n | `/tmp/server_box` geschrieben, um den Systemstatus zu überwachen. Sie können den Skriptinhalt überprüfen.",
"connectionStats": "Verbindungsstatistiken", "connectionStats": "Verbindungsstatistiken",
"connectionStatsDesc": "Server-Verbindungserfolgsrate und Verlauf anzeigen",
"noConnectionStatsData": "Keine Verbindungsstatistikdaten", "noConnectionStatsData": "Keine Verbindungsstatistikdaten",
"totalAttempts": "Gesamt", "totalAttempts": "Gesamt",
"lastSuccess": "Letzter Erfolg", "lastSuccess": "Letzter Erfolg",
@@ -281,5 +282,6 @@
"homeTabsCustomizeDesc": "Passen Sie an, welche Tabs auf der Startseite angezeigt werden und ihre Reihenfolge", "homeTabsCustomizeDesc": "Passen Sie an, welche Tabs auf der Startseite angezeigt werden und ihre Reihenfolge",
"reset": "Zurücksetzen", "reset": "Zurücksetzen",
"availableTabs": "Verfügbare Tabs", "availableTabs": "Verfügbare Tabs",
"atLeastOneTab": "Mindestens ein Tab muss ausgewählt sein" "atLeastOneTab": "Mindestens ein Tab muss ausgewählt sein",
} "serverTabRequired": "Server-Tab kann nicht entfernt werden"
}

View File

@@ -251,6 +251,7 @@
"writeScriptFailTip": "Writing to the script failed, possibly due to lack of permissions or the directory does not exist.", "writeScriptFailTip": "Writing to the script failed, possibly due to lack of permissions or the directory does not exist.",
"writeScriptTip": "After connecting to the server, a script will be written to `~/.config/server_box` \n | `/tmp/server_box` to monitor the system status. You can review the script content.", "writeScriptTip": "After connecting to the server, a script will be written to `~/.config/server_box` \n | `/tmp/server_box` to monitor the system status. You can review the script content.",
"connectionStats": "Connection Statistics", "connectionStats": "Connection Statistics",
"connectionStatsDesc": "View server connection success rate and history",
"noConnectionStatsData": "No connection statistics data", "noConnectionStatsData": "No connection statistics data",
"totalAttempts": "Total", "totalAttempts": "Total",
"lastSuccess": "Last Success", "lastSuccess": "Last Success",
@@ -281,5 +282,6 @@
"homeTabsCustomizeDesc": "Customize which tabs appear on the home page and their order", "homeTabsCustomizeDesc": "Customize which tabs appear on the home page and their order",
"reset": "Reset", "reset": "Reset",
"availableTabs": "Available Tabs", "availableTabs": "Available Tabs",
"atLeastOneTab": "At least one tab must be selected" "atLeastOneTab": "At least one tab must be selected",
"serverTabRequired": "Server tab cannot be removed"
} }

View File

@@ -251,6 +251,7 @@
"writeScriptFailTip": "La escritura en el script falló, posiblemente por falta de permisos o porque el directorio no existe.", "writeScriptFailTip": "La escritura en el script falló, posiblemente por falta de permisos o porque el directorio no existe.",
"writeScriptTip": "Después de conectarse al servidor, se escribirá un script en `~/.config/server_box` \n | `/tmp/server_box` para monitorear el estado del sistema. Puedes revisar el contenido del script.", "writeScriptTip": "Después de conectarse al servidor, se escribirá un script en `~/.config/server_box` \n | `/tmp/server_box` para monitorear el estado del sistema. Puedes revisar el contenido del script.",
"connectionStats": "Estadísticas de conexión", "connectionStats": "Estadísticas de conexión",
"connectionStatsDesc": "Ver la tasa de éxito de conexión del servidor e historial",
"noConnectionStatsData": "No hay datos de estadísticas de conexión", "noConnectionStatsData": "No hay datos de estadísticas de conexión",
"totalAttempts": "Total", "totalAttempts": "Total",
"lastSuccess": "Último éxito", "lastSuccess": "Último éxito",
@@ -281,5 +282,6 @@
"homeTabsCustomizeDesc": "Personaliza qué pestañas aparecen en la página de inicio y su orden", "homeTabsCustomizeDesc": "Personaliza qué pestañas aparecen en la página de inicio y su orden",
"reset": "Restablecer", "reset": "Restablecer",
"availableTabs": "Pestañas disponibles", "availableTabs": "Pestañas disponibles",
"atLeastOneTab": "Al menos una pestaña debe estar seleccionada" "atLeastOneTab": "Al menos una pestaña debe estar seleccionada",
} "serverTabRequired": "Server tab cannot be removed"
}

View File

@@ -251,6 +251,7 @@
"writeScriptFailTip": "Échec de l'écriture dans le script, probablement en raison d'un manque de permissions ou que le répertoire n'existe pas.", "writeScriptFailTip": "Échec de l'écriture dans le script, probablement en raison d'un manque de permissions ou que le répertoire n'existe pas.",
"writeScriptTip": "Après la connexion au serveur, un script sera écrit dans `~/.config/server_box` \n | `/tmp/server_box` pour surveiller l'état du système. Vous pouvez examiner le contenu du script.", "writeScriptTip": "Après la connexion au serveur, un script sera écrit dans `~/.config/server_box` \n | `/tmp/server_box` pour surveiller l'état du système. Vous pouvez examiner le contenu du script.",
"connectionStats": "Statistiques de connexion", "connectionStats": "Statistiques de connexion",
"connectionStatsDesc": "Voir le taux de réussite de connexion du serveur et l'historique",
"noConnectionStatsData": "Aucune donnée de statistiques de connexion", "noConnectionStatsData": "Aucune donnée de statistiques de connexion",
"totalAttempts": "Total", "totalAttempts": "Total",
"lastSuccess": "Dernier succès", "lastSuccess": "Dernier succès",
@@ -281,5 +282,6 @@
"homeTabsCustomizeDesc": "Personnalisez les onglets qui apparaissent sur la page d'accueil et leur ordre", "homeTabsCustomizeDesc": "Personnalisez les onglets qui apparaissent sur la page d'accueil et leur ordre",
"reset": "Réinitialiser", "reset": "Réinitialiser",
"availableTabs": "Onglets disponibles", "availableTabs": "Onglets disponibles",
"atLeastOneTab": "Au moins un onglet doit être sélectionné" "atLeastOneTab": "Au moins un onglet doit être sélectionné",
} "serverTabRequired": "Server tab cannot be removed"
}

View File

@@ -251,6 +251,7 @@
"writeScriptFailTip": "Penulisan ke skrip gagal, mungkin karena tidak ada izin atau direktori tidak ada.", "writeScriptFailTip": "Penulisan ke skrip gagal, mungkin karena tidak ada izin atau direktori tidak ada.",
"writeScriptTip": "Setelah terhubung ke server, sebuah skrip akan ditulis ke `~/.config/server_box` \n | `/tmp/server_box` untuk memantau status sistem. Anda dapat meninjau konten skrip tersebut.", "writeScriptTip": "Setelah terhubung ke server, sebuah skrip akan ditulis ke `~/.config/server_box` \n | `/tmp/server_box` untuk memantau status sistem. Anda dapat meninjau konten skrip tersebut.",
"connectionStats": "Statistik Koneksi", "connectionStats": "Statistik Koneksi",
"connectionStatsDesc": "Lihat tingkat keberhasilan koneksi server dan riwayat",
"noConnectionStatsData": "Tidak ada data statistik koneksi", "noConnectionStatsData": "Tidak ada data statistik koneksi",
"totalAttempts": "Total", "totalAttempts": "Total",
"lastSuccess": "Sukses Terakhir", "lastSuccess": "Sukses Terakhir",
@@ -281,5 +282,6 @@
"homeTabsCustomizeDesc": "Sesuaikan tab mana yang muncul di halaman beranda dan urutannya", "homeTabsCustomizeDesc": "Sesuaikan tab mana yang muncul di halaman beranda dan urutannya",
"reset": "Reset", "reset": "Reset",
"availableTabs": "Tab Tersedia", "availableTabs": "Tab Tersedia",
"atLeastOneTab": "Setidaknya satu tab harus dipilih" "atLeastOneTab": "Setidaknya satu tab harus dipilih",
} "serverTabRequired": "Server tab cannot be removed"
}

View File

@@ -251,6 +251,7 @@
"writeScriptFailTip": "スクリプトの書き込みに失敗しました。権限がないかディレクトリが存在しない可能性があります。", "writeScriptFailTip": "スクリプトの書き込みに失敗しました。権限がないかディレクトリが存在しない可能性があります。",
"writeScriptTip": "サーバーに接続すると、システムの状態を監視するためのスクリプトが `~/.config/server_box` \n | `/tmp/server_box` に書き込まれます。スクリプトの内容を確認できます。", "writeScriptTip": "サーバーに接続すると、システムの状態を監視するためのスクリプトが `~/.config/server_box` \n | `/tmp/server_box` に書き込まれます。スクリプトの内容を確認できます。",
"connectionStats": "接続統計", "connectionStats": "接続統計",
"connectionStatsDesc": "サーバー接続成功率と履歴を表示",
"noConnectionStatsData": "接続統計データがありません", "noConnectionStatsData": "接続統計データがありません",
"totalAttempts": "総計", "totalAttempts": "総計",
"lastSuccess": "最後の成功", "lastSuccess": "最後の成功",
@@ -281,5 +282,6 @@
"homeTabsCustomizeDesc": "ホームページに表示するタブとその順序をカスタマイズします", "homeTabsCustomizeDesc": "ホームページに表示するタブとその順序をカスタマイズします",
"reset": "リセット", "reset": "リセット",
"availableTabs": "利用可能なタブ", "availableTabs": "利用可能なタブ",
"atLeastOneTab": "少なくとも1つのタブを選択する必要があります" "atLeastOneTab": "少なくとも1つのタブを選択する必要があります",
} "serverTabRequired": "サーバータブは削除できません"
}

View File

@@ -251,6 +251,7 @@
"writeScriptFailTip": "Het schrijven naar het script is mislukt, mogelijk door gebrek aan rechten of omdat de map niet bestaat.", "writeScriptFailTip": "Het schrijven naar het script is mislukt, mogelijk door gebrek aan rechten of omdat de map niet bestaat.",
"writeScriptTip": "Na het verbinden met de server wordt een script geschreven naar `~/.config/server_box` \n | `/tmp/server_box` om de systeemstatus te monitoren. U kunt de inhoud van het script controleren.", "writeScriptTip": "Na het verbinden met de server wordt een script geschreven naar `~/.config/server_box` \n | `/tmp/server_box` om de systeemstatus te monitoren. U kunt de inhoud van het script controleren.",
"connectionStats": "Verbindingsstatistieken", "connectionStats": "Verbindingsstatistieken",
"connectionStatsDesc": "Bekijk server verbindingssucces ratio en geschiedenis",
"noConnectionStatsData": "Geen verbindingsstatistiekgegevens", "noConnectionStatsData": "Geen verbindingsstatistiekgegevens",
"totalAttempts": "Totaal", "totalAttempts": "Totaal",
"lastSuccess": "Laatst succesvol", "lastSuccess": "Laatst succesvol",
@@ -281,5 +282,6 @@
"homeTabsCustomizeDesc": "Pas aan welke tabbladen op de startpagina worden weergegeven en hun volgorde", "homeTabsCustomizeDesc": "Pas aan welke tabbladen op de startpagina worden weergegeven en hun volgorde",
"reset": "Resetten", "reset": "Resetten",
"availableTabs": "Beschikbare tabbladen", "availableTabs": "Beschikbare tabbladen",
"atLeastOneTab": "Er moet minimaal één tabblad worden geselecteerd" "atLeastOneTab": "Er moet minimaal één tabblad worden geselecteerd",
} "serverTabRequired": "Server tab cannot be removed"
}

View File

@@ -251,6 +251,7 @@
"writeScriptFailTip": "Falha ao escrever no script, possivelmente devido à falta de permissões ou o diretório não existe.", "writeScriptFailTip": "Falha ao escrever no script, possivelmente devido à falta de permissões ou o diretório não existe.",
"writeScriptTip": "Após conectar ao servidor, um script será escrito em `~/.config/server_box` \n | `/tmp/server_box` para monitorar o status do sistema. Você pode revisar o conteúdo do script.", "writeScriptTip": "Após conectar ao servidor, um script será escrito em `~/.config/server_box` \n | `/tmp/server_box` para monitorar o status do sistema. Você pode revisar o conteúdo do script.",
"connectionStats": "Estatísticas de conexão", "connectionStats": "Estatísticas de conexão",
"connectionStatsDesc": "Ver taxa de sucesso de conexão do servidor e histórico",
"noConnectionStatsData": "Não há dados de estatísticas de conexão", "noConnectionStatsData": "Não há dados de estatísticas de conexão",
"totalAttempts": "Total", "totalAttempts": "Total",
"lastSuccess": "Último sucesso", "lastSuccess": "Último sucesso",
@@ -281,5 +282,6 @@
"homeTabsCustomizeDesc": "Personalize quais abas aparecem na página inicial e sua ordem", "homeTabsCustomizeDesc": "Personalize quais abas aparecem na página inicial e sua ordem",
"reset": "Redefinir", "reset": "Redefinir",
"availableTabs": "Abas disponíveis", "availableTabs": "Abas disponíveis",
"atLeastOneTab": "Pelo menos uma aba deve ser selecionada" "atLeastOneTab": "Pelo menos uma aba deve ser selecionada",
} "serverTabRequired": "Server tab cannot be removed"
}

View File

@@ -251,6 +251,7 @@
"writeScriptFailTip": "Запись скрипта не удалась, возможно, из-за отсутствия прав или потому что, директории не существует.", "writeScriptFailTip": "Запись скрипта не удалась, возможно, из-за отсутствия прав или потому что, директории не существует.",
"writeScriptTip": "После подключения к серверу скрипт будет записан в `~/.config/server_box` \n | `/tmp/server_box` для мониторинга состояния системы. Вы можете проверить содержимое скрипта.", "writeScriptTip": "После подключения к серверу скрипт будет записан в `~/.config/server_box` \n | `/tmp/server_box` для мониторинга состояния системы. Вы можете проверить содержимое скрипта.",
"connectionStats": "Статистика соединений", "connectionStats": "Статистика соединений",
"connectionStatsDesc": "Просмотр коэффициента успешности подключения к серверу и истории",
"noConnectionStatsData": "Нет данных статистики соединений", "noConnectionStatsData": "Нет данных статистики соединений",
"totalAttempts": "Общее", "totalAttempts": "Общее",
"lastSuccess": "Последний успех", "lastSuccess": "Последний успех",
@@ -281,5 +282,6 @@
"homeTabsCustomizeDesc": "Настройте, какие вкладки появляются на главной странице и их порядок", "homeTabsCustomizeDesc": "Настройте, какие вкладки появляются на главной странице и их порядок",
"reset": "Сброс", "reset": "Сброс",
"availableTabs": "Доступные вкладки", "availableTabs": "Доступные вкладки",
"atLeastOneTab": "Должна быть выбрана хотя бы одна вкладка" "atLeastOneTab": "Должна быть выбрана хотя бы одна вкладка",
} "serverTabRequired": "Server tab cannot be removed"
}

View File

@@ -251,6 +251,7 @@
"writeScriptFailTip": "Betik yazma başarısız oldu, muhtemelen izin eksikliği veya dizin mevcut değil.", "writeScriptFailTip": "Betik yazma başarısız oldu, muhtemelen izin eksikliği veya dizin mevcut değil.",
"writeScriptTip": "Sunucuya bağlandıktan sonra, sistem durumunu izlemek için `~/.config/server_box` \n | `/tmp/server_box` dizinine bir betik yazılacak. Betik içeriğini inceleyebilirsiniz.", "writeScriptTip": "Sunucuya bağlandıktan sonra, sistem durumunu izlemek için `~/.config/server_box` \n | `/tmp/server_box` dizinine bir betik yazılacak. Betik içeriğini inceleyebilirsiniz.",
"connectionStats": "Bağlantı İstatistikleri", "connectionStats": "Bağlantı İstatistikleri",
"connectionStatsDesc": "Sunucu bağlantı başarı oranını ve geçmişi görüntüle",
"noConnectionStatsData": "Bağlantı istatistik verisi yok", "noConnectionStatsData": "Bağlantı istatistik verisi yok",
"totalAttempts": "Toplam", "totalAttempts": "Toplam",
"lastSuccess": "Son Başarı", "lastSuccess": "Son Başarı",
@@ -281,5 +282,6 @@
"homeTabsCustomizeDesc": "Ana sayfada görünecek sekmeleri ve sıralarını özelleştirin", "homeTabsCustomizeDesc": "Ana sayfada görünecek sekmeleri ve sıralarını özelleştirin",
"reset": "Sıfırla", "reset": "Sıfırla",
"availableTabs": "Mevcut Sekmeler", "availableTabs": "Mevcut Sekmeler",
"atLeastOneTab": "En az bir sekme seçilmelidir" "atLeastOneTab": "En az bir sekme seçilmelidir",
} "serverTabRequired": "Server tab cannot be removed"
}

View File

@@ -251,6 +251,7 @@
"writeScriptFailTip": "Запис у скрипт не вдався, можливо, через брак дозволів або каталог не існує.", "writeScriptFailTip": "Запис у скрипт не вдався, можливо, через брак дозволів або каталог не існує.",
"writeScriptTip": "Після підключення до сервера скрипт буде записано у `~/.config/server_box` \n | `/tmp/server_box` для моніторингу стану системи. Ви можете переглянути вміст скрипта.", "writeScriptTip": "Після підключення до сервера скрипт буде записано у `~/.config/server_box` \n | `/tmp/server_box` для моніторингу стану системи. Ви можете переглянути вміст скрипта.",
"connectionStats": "Статистика з'єднань", "connectionStats": "Статистика з'єднань",
"connectionStatsDesc": "Переглянути коефіцієнт успішності підключення до сервера та історію",
"noConnectionStatsData": "Немає даних статистики з'єднань", "noConnectionStatsData": "Немає даних статистики з'єднань",
"totalAttempts": "Загальна кількість", "totalAttempts": "Загальна кількість",
"lastSuccess": "Останній успіх", "lastSuccess": "Останній успіх",
@@ -281,5 +282,6 @@
"homeTabsCustomizeDesc": "Налаштуйте, які вкладки відображаються на головній сторінці та їх порядок", "homeTabsCustomizeDesc": "Налаштуйте, які вкладки відображаються на головній сторінці та їх порядок",
"reset": "Скинути", "reset": "Скинути",
"availableTabs": "Доступні вкладки", "availableTabs": "Доступні вкладки",
"atLeastOneTab": "Потрібно вибрати принаймні одну вкладку" "atLeastOneTab": "Потрібно вибрати принаймні одну вкладку",
} "serverTabRequired": "Server tab cannot be removed"
}

View File

@@ -251,6 +251,7 @@
"writeScriptFailTip": "写入脚本失败,可能是没有权限/目录不存在等", "writeScriptFailTip": "写入脚本失败,可能是没有权限/目录不存在等",
"writeScriptTip": "在连接服务器后,会向 `~/.config/server_box` \n | `/tmp/server_box` 写入脚本来监测系统状态,你可以审查脚本内容。", "writeScriptTip": "在连接服务器后,会向 `~/.config/server_box` \n | `/tmp/server_box` 写入脚本来监测系统状态,你可以审查脚本内容。",
"connectionStats": "连接统计", "connectionStats": "连接统计",
"connectionStatsDesc": "查看服务器连接成功率和历史记录",
"noConnectionStatsData": "暂无连接统计数据", "noConnectionStatsData": "暂无连接统计数据",
"totalAttempts": "总次数", "totalAttempts": "总次数",
"lastSuccess": "最后成功", "lastSuccess": "最后成功",
@@ -281,5 +282,6 @@
"homeTabsCustomizeDesc": "自定义主页上显示的标签及其顺序", "homeTabsCustomizeDesc": "自定义主页上显示的标签及其顺序",
"reset": "重置", "reset": "重置",
"availableTabs": "可用标签", "availableTabs": "可用标签",
"atLeastOneTab": "至少需要选择一个标签" "atLeastOneTab": "至少需要选择一个标签",
"serverTabRequired": "服务器标签不能被移除"
} }

View File

@@ -251,6 +251,7 @@
"writeScriptFailTip": "寫入腳本失敗,可能是沒有權限/目錄不存在等。", "writeScriptFailTip": "寫入腳本失敗,可能是沒有權限/目錄不存在等。",
"writeScriptTip": "連線到伺服器後,將會在 `~/.config/server_box` \n | `/tmp/server_box` 中寫入一個腳本來監測系統狀態。你可以審查腳本內容。", "writeScriptTip": "連線到伺服器後,將會在 `~/.config/server_box` \n | `/tmp/server_box` 中寫入一個腳本來監測系統狀態。你可以審查腳本內容。",
"connectionStats": "連線統計", "connectionStats": "連線統計",
"connectionStatsDesc": "檢視伺服器連線成功率和歷史記錄",
"noConnectionStatsData": "暫無連線統計資料", "noConnectionStatsData": "暫無連線統計資料",
"totalAttempts": "總次數", "totalAttempts": "總次數",
"lastSuccess": "最後成功", "lastSuccess": "最後成功",
@@ -281,5 +282,6 @@
"homeTabsCustomizeDesc": "自訂主頁上顯示的標籤及其順序", "homeTabsCustomizeDesc": "自訂主頁上顯示的標籤及其順序",
"reset": "重置", "reset": "重置",
"availableTabs": "可用標籤", "availableTabs": "可用標籤",
"atLeastOneTab": "至少需要選擇一個標籤" "atLeastOneTab": "至少需要選擇一個標籤",
} "serverTabRequired": "服務器標籤不能被移除"
}

View File

@@ -98,7 +98,7 @@ class _ConnectionStatsPageState extends State<ConnectionStatsPage> {
), ),
), ),
Text( Text(
'${libL10n.success}: $successRate%', '${libL10n.success}: $successRate',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
color: stats.successRate >= 0.8 color: stats.successRate >= 0.8

View File

@@ -68,7 +68,7 @@ class _HomeTabsConfigPageState extends ConsumerState<HomeTabsConfigPage> {
} }
Widget _buildTabItem(AppTab tab, int index, bool isSelected) { Widget _buildTabItem(AppTab tab, int index, bool isSelected) {
final canRemove = _selectedTabs.length > 1; final canRemove = _selectedTabs.length > 1 && tab != AppTab.server;
final child = ListTile( final child = ListTile(
leading: tab.navDestination.icon, leading: tab.navDestination.icon,
title: Text(tab.navDestination.label), title: Text(tab.navDestination.label),
@@ -77,7 +77,7 @@ class _HomeTabsConfigPageState extends ConsumerState<HomeTabsConfigPage> {
icon: const Icon(Icons.delete), icon: const Icon(Icons.delete),
onPressed: canRemove ? () => _removeTab(tab) : null, onPressed: canRemove ? () => _removeTab(tab) : null,
color: canRemove ? null : Theme.of(context).disabledColor, color: canRemove ? null : Theme.of(context).disabledColor,
tooltip: canRemove ? libL10n.delete : l10n.atLeastOneTab, tooltip: canRemove ? libL10n.delete : (tab == AppTab.server ? l10n.serverTabRequired : l10n.atLeastOneTab),
) )
: IconButton(icon: const Icon(Icons.add), onPressed: () => _addTab(tab)), : IconButton(icon: const Icon(Icons.add), onPressed: () => _addTab(tab)),
onTap: isSelected && canRemove ? () => _removeTab(tab) : null, onTap: isSelected && canRemove ? () => _removeTab(tab) : null,
@@ -111,6 +111,10 @@ class _HomeTabsConfigPageState extends ConsumerState<HomeTabsConfigPage> {
context.showSnackBar(l10n.atLeastOneTab); context.showSnackBar(l10n.atLeastOneTab);
return; return;
} }
if (tab == AppTab.server) {
context.showSnackBar(l10n.serverTabRequired);
return;
}
setState(() { setState(() {
_selectedTabs.remove(tab); _selectedTabs.remove(tab);
}); });

View File

@@ -42,8 +42,8 @@ extension _Server on _AppSettingsPageState {
Widget _buildConnectionStats() { Widget _buildConnectionStats() {
return ListTile( return ListTile(
leading: const Icon(Icons.analytics, size: _kIconSize), leading: const Icon(Icons.analytics, size: _kIconSize),
title: const Text('连接统计'), title: Text(l10n.connectionStats),
subtitle: const Text('查看服务器连接成功率和历史记录'), subtitle: Text(l10n.connectionStatsDesc),
trailing: const Icon(Icons.keyboard_arrow_right), trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () { onTap: () {
Navigator.of(context).push( Navigator.of(context).push(

View File

@@ -471,7 +471,7 @@
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1241; CURRENT_PROJECT_VERSION = 1253;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Server Box"; INFOPLIST_KEY_CFBundleDisplayName = "Server Box";
@@ -481,7 +481,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 10.15; MACOSX_DEPLOYMENT_TARGET = 10.15;
MARKETING_VERSION = 1.0.1241; MARKETING_VERSION = 1.0.1253;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "Server Box"; PRODUCT_NAME = "Server Box";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@@ -608,7 +608,7 @@
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1241; CURRENT_PROJECT_VERSION = 1253;
DEVELOPMENT_TEAM = BA88US33G6; DEVELOPMENT_TEAM = BA88US33G6;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "Server Box"; INFOPLIST_KEY_CFBundleDisplayName = "Server Box";
@@ -618,7 +618,7 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 10.15; MACOSX_DEPLOYMENT_TARGET = 10.15;
MARKETING_VERSION = 1.0.1241; MARKETING_VERSION = 1.0.1253;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "Server Box"; PRODUCT_NAME = "Server Box";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@@ -638,7 +638,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "3rd Party Mac Developer Application"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "3rd Party Mac Developer Application";
CODE_SIGN_STYLE = Manual; CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1241; CURRENT_PROJECT_VERSION = 1253;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=macosx*]" = BA88US33G6; "DEVELOPMENT_TEAM[sdk=macosx*]" = BA88US33G6;
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
@@ -649,11 +649,11 @@
"@executable_path/../Frameworks", "@executable_path/../Frameworks",
); );
MACOSX_DEPLOYMENT_TARGET = 10.15; MACOSX_DEPLOYMENT_TARGET = 10.15;
MARKETING_VERSION = 1.0.1241; MARKETING_VERSION = 1.0.1253;
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
PRODUCT_NAME = "Server Box"; PRODUCT_NAME = "Server Box";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
"PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = serverbox_lkmm; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = hj_macmini_for_serverbox;
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.0;
}; };
name = Release; name = Release;

View File

@@ -18,18 +18,12 @@
<true/> <true/>
<key>com.apple.security.cs.allow-jit</key> <key>com.apple.security.cs.allow-jit</key>
<true/> <true/>
<key>com.apple.security.device.camera</key> <key>com.apple.security.files.user-selected.read-write</key>
<true/> <true/>
<key>com.apple.security.network.client</key> <key>com.apple.security.network.client</key>
<true/> <true/>
<key>com.apple.security.network.server</key> <key>com.apple.security.network.server</key>
<true/> <true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.temporary-exception.files.home-relative-path.read-only</key>
<array>
<string>.ssh/</string>
</array>
<key>keychain-access-groups</key> <key>keychain-access-groups</key>
<array/> <array/>
</dict> </dict>

View File

@@ -16,18 +16,12 @@
</array> </array>
<key>com.apple.security.app-sandbox</key> <key>com.apple.security.app-sandbox</key>
<true/> <true/>
<key>com.apple.security.device.camera</key> <key>com.apple.security.files.user-selected.read-write</key>
<true/> <true/>
<key>com.apple.security.network.client</key> <key>com.apple.security.network.client</key>
<true/> <true/>
<key>com.apple.security.network.server</key> <key>com.apple.security.network.server</key>
<true/> <true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.temporary-exception.files.home-relative-path.read-only</key>
<array>
<string>.ssh/</string>
</array>
<key>keychain-access-groups</key> <key>keychain-access-groups</key>
<array/> <array/>
</dict> </dict>

View File

@@ -480,8 +480,8 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
path: "." path: "."
ref: "v1.0.51" ref: "v1.0.52"
resolved-ref: "430672b7c7608b68ceda785533ec11e1ac3e9ca7" resolved-ref: "38e7d41ccd71bf44e286d86b4ad656f05c5c2548"
url: "https://github.com/lppcg/fl_build.git" url: "https://github.com/lppcg/fl_build.git"
source: git source: git
version: "1.0.0" version: "1.0.0"
@@ -1838,8 +1838,8 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: "v4.0.3" ref: "v4.0.4"
resolved-ref: c64183346b924173eb7251800001a64771911185 resolved-ref: "5747837cdb7b113ef733ce0104e4f2bfa1eb4a36"
url: "https://github.com/lollipopkit/xterm.dart" url: "https://github.com/lollipopkit/xterm.dart"
source: git source: git
version: "4.0.0" version: "4.0.0"

View File

@@ -1,7 +1,7 @@
name: server_box name: server_box
description: server status & toolbox app. description: server status & toolbox app.
publish_to: "none" publish_to: "none"
version: 1.0.1241+1241 version: 1.0.1253+1253
environment: environment:
sdk: ">=3.9.0" sdk: ">=3.9.0"
@@ -47,7 +47,7 @@ dependencies:
xterm: xterm:
git: git:
url: https://github.com/lollipopkit/xterm.dart url: https://github.com/lollipopkit/xterm.dart
ref: v4.0.3 ref: v4.0.4
computer: computer:
git: git:
url: https://github.com/lollipopkit/dart_computer url: https://github.com/lollipopkit/dart_computer
@@ -102,7 +102,7 @@ dev_dependencies:
fl_build: fl_build:
git: git:
url: https://github.com/lppcg/fl_build.git url: https://github.com/lppcg/fl_build.git
ref: v1.0.51 ref: v1.0.52
flutter: flutter:
generate: true generate: true