SHA256
init
This commit is contained in:
@@ -1,13 +1,6 @@
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
val newBuildDir: Directory =
|
||||
rootProject.layout.buildDirectory
|
||||
.dir("../../build")
|
||||
.dir("../../flutter_build_android")
|
||||
.get()
|
||||
rootProject.layout.buildDirectory.value(newBuildDir)
|
||||
|
||||
|
||||
@@ -11,12 +11,29 @@ pluginManagement {
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
|
||||
repositories {
|
||||
maven("https://maven.aliyun.com/repository/gradle-plugin")
|
||||
maven("https://maven.aliyun.com/repository/public")
|
||||
maven("https://maven.aliyun.com/repository/google")
|
||||
maven("https://maven.aliyun.com/repository/central")
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
|
||||
repositories {
|
||||
maven("https://maven.aliyun.com/repository/public")
|
||||
maven("https://maven.aliyun.com/repository/google")
|
||||
maven("https://maven.aliyun.com/repository/central")
|
||||
google()
|
||||
mavenCentral()
|
||||
val storageUrl: String = System.getenv("FLUTTER_STORAGE_BASE_URL") ?: "https://storage.googleapis.com"
|
||||
maven("$storageUrl/download.flutter.io")
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||
id("com.android.application") version "8.11.1" apply false
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_highlight/themes/atom-one-dark.dart';
|
||||
import 'package:flutter_highlight/themes/atom-one-light.dart';
|
||||
import 'package:code_text_field/code_text_field.dart';
|
||||
import 'package:highlight/languages/json.dart' as json_highlight;
|
||||
import 'package:highlight/languages/sql.dart' as sql_highlight;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'backend/studio_backend_client.dart';
|
||||
import 'platform/studio_download_saver.dart';
|
||||
|
||||
@@ -87,10 +87,15 @@ class StudioLoginView extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _StudioLoginViewState extends State<StudioLoginView> {
|
||||
static const String _prefsBackendUrlKey = 'studio_login_backend_url';
|
||||
static const String _prefsWorkspaceKey = 'studio_login_workspace';
|
||||
static const String _prefsUsernameKey = 'studio_login_username';
|
||||
|
||||
late final TextEditingController _backendUrlController;
|
||||
late final TextEditingController _workspaceController;
|
||||
late final TextEditingController _usernameController;
|
||||
late final TextEditingController _passwordController;
|
||||
Timer? _rememberTimer;
|
||||
bool _loading = false;
|
||||
String? _errorMessage;
|
||||
Map<String, dynamic> _loginConfig = <String, dynamic>{};
|
||||
@@ -104,11 +109,15 @@ class _StudioLoginViewState extends State<StudioLoginView> {
|
||||
_workspaceController = TextEditingController(text: _defaultWorkspace);
|
||||
_usernameController = TextEditingController();
|
||||
_passwordController = TextEditingController();
|
||||
_loadLoginConfig();
|
||||
_backendUrlController.addListener(_queueRememberInputs);
|
||||
_workspaceController.addListener(_queueRememberInputs);
|
||||
_usernameController.addListener(_queueRememberInputs);
|
||||
unawaited(_initializeLoginForm());
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_rememberTimer?.cancel();
|
||||
_backendUrlController.dispose();
|
||||
_workspaceController.dispose();
|
||||
_usernameController.dispose();
|
||||
@@ -116,6 +125,55 @@ class _StudioLoginViewState extends State<StudioLoginView> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _restoreRememberedInputs() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
final backendUrl = prefs.getString(_prefsBackendUrlKey)?.trim() ?? '';
|
||||
final workspace = prefs.getString(_prefsWorkspaceKey)?.trim() ?? '';
|
||||
final username = prefs.getString(_prefsUsernameKey)?.trim() ?? '';
|
||||
|
||||
if (backendUrl.isNotEmpty) {
|
||||
_backendUrlController.text = backendUrl;
|
||||
}
|
||||
if (workspace.isNotEmpty) {
|
||||
_workspaceController.text = _normalizeWorkspace(workspace);
|
||||
}
|
||||
if (username.isNotEmpty) {
|
||||
_usernameController.text = username;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _initializeLoginForm() async {
|
||||
await _restoreRememberedInputs();
|
||||
await _loadLoginConfig();
|
||||
}
|
||||
|
||||
void _queueRememberInputs() {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
_rememberTimer?.cancel();
|
||||
_rememberTimer = Timer(const Duration(milliseconds: 250), () {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
unawaited(_rememberInputs());
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _rememberInputs() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(_prefsBackendUrlKey, _backendUrlController.text.trim());
|
||||
await prefs.setString(
|
||||
_prefsWorkspaceKey,
|
||||
_normalizeWorkspace(_workspaceController.text),
|
||||
);
|
||||
await prefs.setString(_prefsUsernameKey, _usernameController.text.trim());
|
||||
}
|
||||
|
||||
Future<void> _loadLoginConfig() async {
|
||||
try {
|
||||
final client = StudioBackendClient(
|
||||
@@ -206,6 +264,7 @@ class _StudioLoginViewState extends State<StudioLoginView> {
|
||||
return;
|
||||
}
|
||||
|
||||
unawaited(_rememberInputs());
|
||||
widget.onLogin(
|
||||
StudioRuntime(
|
||||
backendUrl: backendUrl,
|
||||
|
||||
+53
-84
@@ -18,22 +18,20 @@ class StudioDashboardPage extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final quickNodes = _allLeaves(rootNodes).take(6).toList();
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 72,
|
||||
height: 72,
|
||||
width: 56,
|
||||
height: 56,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
@@ -45,35 +43,35 @@ class StudioDashboardPage extends StatelessWidget {
|
||||
).colorScheme.tertiary.withOpacity(0.20),
|
||||
],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.space_dashboard_rounded,
|
||||
size: 38,
|
||||
size: 30,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 18),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'工作台',
|
||||
style: Theme.of(context).textTheme.headlineSmall
|
||||
style: Theme.of(context).textTheme.titleLarge
|
||||
?.copyWith(fontWeight: FontWeight.w700),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'左侧菜单已映射自 studio.html 的核心入口,点击即可打开多标签页。',
|
||||
style: Theme.of(context).textTheme.bodyMedium
|
||||
?.copyWith(color: Colors.white70),
|
||||
style: Theme.of(context).textTheme.bodySmall
|
||||
?.copyWith(color: _mutedColor(context, 0.72)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Wrap(
|
||||
spacing: 10,
|
||||
runSpacing: 10,
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
_MiniBadge(text: user.workspace),
|
||||
_MiniBadge(text: user.languageCode.toUpperCase()),
|
||||
@@ -83,7 +81,7 @@ class StudioDashboardPage extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
const SizedBox(height: 8),
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final cardWidth = constraints.maxWidth >= 1100
|
||||
@@ -93,8 +91,8 @@ class StudioDashboardPage extends StatelessWidget {
|
||||
: constraints.maxWidth;
|
||||
|
||||
return Wrap(
|
||||
spacing: 16,
|
||||
runSpacing: 16,
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: cardWidth,
|
||||
@@ -127,38 +125,7 @@ class StudioDashboardPage extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(18),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'快捷入口',
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
children: quickNodes
|
||||
.map(
|
||||
(node) => ActionChip(
|
||||
avatar: Icon(node.icon, size: 18),
|
||||
label: Text(node.title),
|
||||
onPressed: () => onOpenNode(node),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
const SizedBox(height: 10),
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final leftWidth = constraints.maxWidth >= 980
|
||||
@@ -176,7 +143,7 @@ class StudioDashboardPage extends StatelessWidget {
|
||||
width: leftWidth,
|
||||
child: _ActivityPanel(activities: activities),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
const SizedBox(width: 12),
|
||||
SizedBox(
|
||||
width: rightWidth,
|
||||
child: _FeaturePanel(onOpenNode: onOpenNode),
|
||||
@@ -188,7 +155,7 @@ class StudioDashboardPage extends StatelessWidget {
|
||||
return Column(
|
||||
children: [
|
||||
_ActivityPanel(activities: activities),
|
||||
const SizedBox(height: 16),
|
||||
const SizedBox(height: 10),
|
||||
_FeaturePanel(onOpenNode: onOpenNode),
|
||||
],
|
||||
);
|
||||
@@ -230,19 +197,19 @@ class _InfoPanel extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(18),
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 50,
|
||||
height: 50,
|
||||
width: 40,
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Icon(icon),
|
||||
child: Icon(icon, size: 20),
|
||||
),
|
||||
const SizedBox(width: 14),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -250,19 +217,19 @@ class _InfoPanel extends StatelessWidget {
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.bodySmall
|
||||
?.copyWith(color: Colors.white60),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
value,
|
||||
style: Theme.of(context).textTheme.titleLarge
|
||||
?.copyWith(fontWeight: FontWeight.w700),
|
||||
?.copyWith(color: _mutedColor(context, 0.68)),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
value,
|
||||
style: Theme.of(context).textTheme.titleMedium
|
||||
?.copyWith(fontWeight: FontWeight.w700),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
subtitle,
|
||||
style: Theme.of(context).textTheme.bodyMedium
|
||||
?.copyWith(color: Colors.white70),
|
||||
style: Theme.of(context).textTheme.bodySmall
|
||||
?.copyWith(color: _mutedColor(context, 0.72)),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -283,38 +250,38 @@ class _ActivityPanel extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(18),
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'最近活动',
|
||||
style: Theme.of(context).textTheme.titleLarge
|
||||
style: Theme.of(context).textTheme.titleMedium
|
||||
?.copyWith(fontWeight: FontWeight.w700),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const SizedBox(height: 8),
|
||||
...activities.take(8).map(
|
||||
(item) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Icon(Icons.bolt_rounded, size: 18),
|
||||
const SizedBox(width: 12),
|
||||
const Icon(Icons.bolt_rounded, size: 16),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
item.title,
|
||||
style: Theme.of(context).textTheme.bodyLarge
|
||||
style: Theme.of(context).textTheme.bodyMedium
|
||||
?.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
item.detail,
|
||||
style: Theme.of(context).textTheme.bodyMedium
|
||||
?.copyWith(color: Colors.white70),
|
||||
style: Theme.of(context).textTheme.bodySmall
|
||||
?.copyWith(color: _mutedColor(context, 0.70)),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -341,23 +308,25 @@ class _FeaturePanel extends StatelessWidget {
|
||||
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(18),
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'功能覆盖',
|
||||
style: Theme.of(context).textTheme.titleLarge
|
||||
style: Theme.of(context).textTheme.titleMedium
|
||||
?.copyWith(fontWeight: FontWeight.w700),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const Text(
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'当前 Flutter 桌面端实现了 Studio 页面的核心交互:登录、导航、标签页、JSON/SQL 编辑、日志和运维任务。',
|
||||
style: Theme.of(context).textTheme.bodySmall
|
||||
?.copyWith(color: _mutedColor(context, 0.72)),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
const SizedBox(height: 10),
|
||||
Wrap(
|
||||
spacing: 10,
|
||||
runSpacing: 10,
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: nodes
|
||||
.take(5)
|
||||
.map(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -90,7 +90,7 @@ class _StudioHistoryPageState extends State<StudioHistoryPage> {
|
||||
return SingleChildScrollView(
|
||||
child: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(18),
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
@@ -102,13 +102,13 @@ class _StudioHistoryPageState extends State<StudioHistoryPage> {
|
||||
children: [
|
||||
Text(
|
||||
widget.tab.title,
|
||||
style: Theme.of(context).textTheme.headlineSmall
|
||||
style: Theme.of(context).textTheme.titleMedium
|
||||
?.copyWith(fontWeight: FontWeight.w700),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'从后端读取发布与修改历史。',
|
||||
style: Theme.of(context).textTheme.bodyMedium
|
||||
style: Theme.of(context).textTheme.bodySmall
|
||||
?.copyWith(color: _mutedColor(context)),
|
||||
),
|
||||
],
|
||||
@@ -127,7 +127,7 @@ class _StudioHistoryPageState extends State<StudioHistoryPage> {
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
const SizedBox(height: 8),
|
||||
if (_errorMessage != null) ...[
|
||||
SelectableText(
|
||||
_errorMessage!,
|
||||
@@ -139,7 +139,7 @@ class _StudioHistoryPageState extends State<StudioHistoryPage> {
|
||||
],
|
||||
if (_loading)
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 28),
|
||||
padding: EdgeInsets.symmetric(vertical: 16),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
)
|
||||
else
|
||||
|
||||
+161
-78
@@ -17,7 +17,7 @@ class StudioSqlPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _StudioSqlPageState extends State<StudioSqlPage> {
|
||||
late final TextEditingController _sqlController;
|
||||
late final CodeController _sqlController;
|
||||
late final TextEditingController _limitController;
|
||||
late final TextEditingController _countSqlController;
|
||||
late final TextEditingController _summarySqlController;
|
||||
@@ -38,9 +38,10 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_sqlController = TextEditingController(
|
||||
_sqlController = CodeController(
|
||||
text:
|
||||
'select id, code, name from ttx_demo_table order by id desc limit 20;',
|
||||
'select id, code, name from item order by id desc limit 20',
|
||||
language: sql_highlight.sql,
|
||||
);
|
||||
_limitController = TextEditingController(text: '100');
|
||||
_countSqlController = TextEditingController();
|
||||
@@ -347,6 +348,67 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
||||
});
|
||||
}
|
||||
|
||||
Map<String, TextStyle> _sqlCodeTheme(BuildContext context) {
|
||||
final isDark = _isDarkTheme(context);
|
||||
return isDark ? atomOneDarkTheme : atomOneLightTheme;
|
||||
}
|
||||
|
||||
Widget _buildStatusGrid(BuildContext context) {
|
||||
final rows = <DataRow>[
|
||||
DataRow(
|
||||
cells: [
|
||||
const DataCell(Text('状态')),
|
||||
DataCell(SelectableText(_status)),
|
||||
],
|
||||
),
|
||||
DataRow(
|
||||
cells: [
|
||||
const DataCell(Text('详情')),
|
||||
DataCell(
|
||||
SelectableText(
|
||||
_detail.isEmpty ? '-' : _detail,
|
||||
style: const TextStyle(fontFamily: 'monospace', height: 1.45),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
DataRow(
|
||||
cells: [
|
||||
const DataCell(Text('结果')),
|
||||
DataCell(
|
||||
SelectableText(
|
||||
_resultRows.isEmpty
|
||||
? '无结果'
|
||||
: '共 ${_resultRows.length} 行,${_resultColumns.length} 列',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
];
|
||||
|
||||
if (_isTemplateMode) {
|
||||
rows.add(
|
||||
DataRow(
|
||||
cells: [
|
||||
const DataCell(Text('模板')),
|
||||
DataCell(SelectableText('${_selectedTid()} / ${_selectedOid()}')),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: DataTable(
|
||||
columns: const [
|
||||
DataColumn(label: Text('字段')),
|
||||
DataColumn(label: Text('内容')),
|
||||
],
|
||||
rows: rows,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTemplateBody(BuildContext context) {
|
||||
final tids = _templateIndex.keys.toList();
|
||||
final selectedTid = _selectedTid();
|
||||
@@ -357,12 +419,11 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 260,
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final wide = constraints.maxWidth >= 860;
|
||||
final tidField = SizedBox(
|
||||
width: wide ? 260 : double.infinity,
|
||||
child: DropdownButtonFormField<String>(
|
||||
value: tids.contains(selectedTid) ? selectedTid : null,
|
||||
decoration: const InputDecoration(labelText: 'tid'),
|
||||
@@ -381,9 +442,9 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 260,
|
||||
);
|
||||
final oidField = SizedBox(
|
||||
width: wide ? 260 : double.infinity,
|
||||
child: DropdownButtonFormField<String>(
|
||||
value: oids.contains(_selectedOid()) ? _selectedOid() : null,
|
||||
decoration: const InputDecoration(labelText: 'oid'),
|
||||
@@ -399,18 +460,48 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: _loading ? null : _loadSelectedTemplate,
|
||||
icon: const Icon(Icons.folder_open_rounded),
|
||||
label: const Text('加载模板'),
|
||||
),
|
||||
FilledButton.icon(
|
||||
onPressed: _loading ? null : _saveTemplate,
|
||||
icon: const Icon(Icons.save_rounded),
|
||||
label: Text(_templateLoaded ? '更新模板' : '创建模板'),
|
||||
),
|
||||
],
|
||||
);
|
||||
final buttons = Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
children: [
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: _loading ? null : _loadSelectedTemplate,
|
||||
icon: const Icon(Icons.folder_open_rounded),
|
||||
label: const Text('加载模板'),
|
||||
),
|
||||
FilledButton.icon(
|
||||
onPressed: _loading ? null : _saveTemplate,
|
||||
icon: const Icon(Icons.save_rounded),
|
||||
label: Text(_templateLoaded ? '更新模板' : '创建模板'),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
if (!wide) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
tidField,
|
||||
const SizedBox(height: 12),
|
||||
oidField,
|
||||
const SizedBox(height: 12),
|
||||
buttons,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(child: tidField),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(child: oidField),
|
||||
const SizedBox(width: 12),
|
||||
buttons,
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
_buildSqlEditors(context, includeTemplateFields: true),
|
||||
@@ -429,41 +520,19 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(18),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.tab.title,
|
||||
style: Theme.of(context).textTheme.headlineSmall
|
||||
?.copyWith(fontWeight: FontWeight.w700),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
_isTemplateMode ? '从后端加载 SQL 模板并支持创建、更新。' : '直接执行 SQL 并查看结果。',
|
||||
style: Theme.of(context).textTheme.bodyMedium
|
||||
?.copyWith(color: _mutedColor(context)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
if (_loading)
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 24),
|
||||
padding: EdgeInsets.symmetric(vertical: 16),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
)
|
||||
else if (_isTemplateMode)
|
||||
_buildTemplateBody(context)
|
||||
else
|
||||
_buildSqlEditors(context, includeTemplateFields: false),
|
||||
const SizedBox(height: 16),
|
||||
const SizedBox(height: 12),
|
||||
Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(18),
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -498,15 +567,8 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
||||
],
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
SelectableText(_status),
|
||||
if (_detail.isNotEmpty) ...[
|
||||
const SizedBox(height: 10),
|
||||
SelectableText(
|
||||
_detail,
|
||||
style: const TextStyle(fontFamily: 'monospace', height: 1.45),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 8),
|
||||
_buildStatusGrid(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -559,17 +621,17 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
||||
Widget _buildSqlEditors(BuildContext context, {required bool includeTemplateFields}) {
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(18),
|
||||
padding: const EdgeInsets.all(14),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (includeTemplateFields) ...[
|
||||
Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
spacing: 10,
|
||||
runSpacing: 10,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 220,
|
||||
width: 200,
|
||||
child: TextField(
|
||||
controller: _countSqlController,
|
||||
decoration: const InputDecoration(labelText: 'count SQL'),
|
||||
@@ -577,7 +639,7 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 220,
|
||||
width: 200,
|
||||
child: TextField(
|
||||
controller: _summarySqlController,
|
||||
decoration: const InputDecoration(labelText: 'summary SQL'),
|
||||
@@ -607,34 +669,55 @@ class _StudioSqlPageState extends State<StudioSqlPage> {
|
||||
const SizedBox(height: 14),
|
||||
],
|
||||
SizedBox(
|
||||
height: 280,
|
||||
child: TextField(
|
||||
controller: _sqlController,
|
||||
expands: true,
|
||||
maxLines: null,
|
||||
minLines: null,
|
||||
keyboardType: TextInputType.multiline,
|
||||
textAlignVertical: TextAlignVertical.top,
|
||||
style: const TextStyle(fontFamily: 'monospace'),
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'SQL 内容',
|
||||
alignLabelWithHint: true,
|
||||
height: 240,
|
||||
child: CodeTheme(
|
||||
data: CodeThemeData(
|
||||
styles: _sqlCodeTheme(context),
|
||||
),
|
||||
child: CodeField(
|
||||
controller: _sqlController,
|
||||
expands: true,
|
||||
wrap: true,
|
||||
lineNumbers: false,
|
||||
isDense: true,
|
||||
keyboardType: TextInputType.multiline,
|
||||
textStyle: const TextStyle(
|
||||
fontFamily: 'monospace',
|
||||
fontSize: 13.5,
|
||||
height: 1.45,
|
||||
),
|
||||
cursorColor: Theme.of(context).colorScheme.primary,
|
||||
background: Colors.transparent,
|
||||
decoration: BoxDecoration(
|
||||
color: _softPanelColor(context),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.outlineVariant,
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.all(12),
|
||||
textSelectionTheme: TextSelectionThemeData(
|
||||
cursorColor: Theme.of(context).colorScheme.primary,
|
||||
selectionColor: Theme.of(
|
||||
context,
|
||||
).colorScheme.primary.withOpacity(0.20),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!includeTemplateFields) ...[
|
||||
const SizedBox(height: 12),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 160,
|
||||
width: 150,
|
||||
child: TextField(
|
||||
controller: _limitController,
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: const InputDecoration(labelText: 'limit'),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
const SizedBox(width: 10),
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: _loading ? null : _formatSql,
|
||||
icon: const Icon(Icons.format_align_left_rounded),
|
||||
|
||||
@@ -127,13 +127,14 @@ class _StudioShellState extends State<StudioShell> {
|
||||
widget.runtime.allowedMenuIds,
|
||||
);
|
||||
final leaves = _allLeaves(visibleRoots);
|
||||
final filteredLeaves = _searchQuery.trim().isEmpty
|
||||
final query = _searchQuery.trim().toLowerCase();
|
||||
final filteredLeaves = query.isEmpty
|
||||
? leaves
|
||||
: leaves
|
||||
.where(
|
||||
(node) =>
|
||||
node.title.contains(_searchQuery) ||
|
||||
node.summary.contains(_searchQuery),
|
||||
node.title.toLowerCase().contains(query) ||
|
||||
node.summary.toLowerCase().contains(query),
|
||||
)
|
||||
.toList();
|
||||
|
||||
|
||||
@@ -77,11 +77,6 @@ class StudioSidebar extends StatelessWidget {
|
||||
child: Text('没有匹配的菜单项'),
|
||||
),
|
||||
] else ...[
|
||||
_QuickLaunchStrip(
|
||||
nodes: _allLeaves(rootNodes).take(4).toList(),
|
||||
onOpenNode: onOpenNode,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
...rootNodes.map(
|
||||
(node) => _TreeNode(node: node, onLeafTap: onOpenNode),
|
||||
),
|
||||
@@ -94,17 +89,6 @@ class StudioSidebar extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
List<StudioNavNode> _allLeaves(List<StudioNavNode> nodes) {
|
||||
final result = <StudioNavNode>[];
|
||||
for (final node in nodes) {
|
||||
if (node.children.isEmpty) {
|
||||
result.add(node);
|
||||
} else {
|
||||
result.addAll(_allLeaves(node.children));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class _SidebarHero extends StatelessWidget {
|
||||
@@ -215,36 +199,6 @@ class _SidebarHero extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _QuickLaunchStrip extends StatelessWidget {
|
||||
const _QuickLaunchStrip({required this.nodes, required this.onOpenNode});
|
||||
|
||||
final List<StudioNavNode> nodes;
|
||||
final ValueChanged<StudioNavNode> onOpenNode;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||
child: Text(
|
||||
'快速入口',
|
||||
style: Theme.of(context).textTheme.titleSmall
|
||||
?.copyWith(color: _mutedColor(context)),
|
||||
),
|
||||
),
|
||||
...nodes.map(
|
||||
(node) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: _LeafNavTile(node: node, onTap: () => onOpenNode(node)),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TreeNode extends StatelessWidget {
|
||||
const _TreeNode({required this.node, required this.onLeafTap});
|
||||
|
||||
|
||||
@@ -39,29 +39,29 @@ class StudioWorkspaceView extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(18),
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_WorkspaceHeader(
|
||||
user: user,
|
||||
selectedTab: selectedTab,
|
||||
onRefresh: onRefresh,
|
||||
onLogout: onLogout,
|
||||
themeMode: themeMode,
|
||||
onToggleTheme: onToggleTheme,
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
_TabStrip(
|
||||
tabs: tabs,
|
||||
selectedTabId: selectedTab.id,
|
||||
onSelectTab: onSelectTab,
|
||||
onCloseTab: onCloseTab,
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
const SizedBox(height: 8),
|
||||
Expanded(
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 260),
|
||||
layoutBuilder: (currentChild, previousChildren) {
|
||||
return Stack(
|
||||
alignment: Alignment.topCenter,
|
||||
children: [
|
||||
...previousChildren,
|
||||
if (currentChild != null) currentChild,
|
||||
],
|
||||
);
|
||||
},
|
||||
child: _buildPage(
|
||||
key: ValueKey('${selectedTab.id}:$refreshToken'),
|
||||
tab: selectedTab,
|
||||
@@ -73,7 +73,7 @@ class StudioWorkspaceView extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
const SizedBox(height: 8),
|
||||
_StudioStatusBar(
|
||||
user: user,
|
||||
selectedTab: selectedTab,
|
||||
@@ -145,92 +145,6 @@ class StudioWorkspaceView extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _WorkspaceHeader extends StatelessWidget {
|
||||
const _WorkspaceHeader({
|
||||
required this.user,
|
||||
required this.selectedTab,
|
||||
required this.onRefresh,
|
||||
required this.onLogout,
|
||||
required this.themeMode,
|
||||
required this.onToggleTheme,
|
||||
});
|
||||
|
||||
final StudioUser user;
|
||||
final StudioTab selectedTab;
|
||||
final VoidCallback onRefresh;
|
||||
final VoidCallback onLogout;
|
||||
final ThemeMode themeMode;
|
||||
final VoidCallback onToggleTheme;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(18),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
selectedTab.title,
|
||||
style: Theme.of(context).textTheme.headlineSmall
|
||||
?.copyWith(fontWeight: FontWeight.w700),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
selectedTab.summary,
|
||||
style: Theme.of(context).textTheme.bodyMedium
|
||||
?.copyWith(color: _mutedColor(context)),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Wrap(
|
||||
spacing: 10,
|
||||
runSpacing: 10,
|
||||
children: [
|
||||
_MiniBadge(text: user.workspace),
|
||||
_MiniBadge(text: user.displayName),
|
||||
if (user.backendAppName.isNotEmpty)
|
||||
_MiniBadge(text: user.backendAppName),
|
||||
if (user.organizationName.isNotEmpty)
|
||||
_MiniBadge(text: user.organizationName),
|
||||
_MiniBadge(text: selectedTab.pageType.name),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Wrap(
|
||||
spacing: 10,
|
||||
runSpacing: 10,
|
||||
alignment: WrapAlignment.end,
|
||||
children: [
|
||||
_ThemeToggleButton(
|
||||
themeMode: themeMode,
|
||||
onPressed: onToggleTheme,
|
||||
),
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: onRefresh,
|
||||
icon: const Icon(Icons.refresh_rounded),
|
||||
label: const Text('刷新'),
|
||||
),
|
||||
FilledButton.tonalIcon(
|
||||
onPressed: onLogout,
|
||||
icon: const Icon(Icons.logout_rounded),
|
||||
label: const Text('退出'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TabStrip extends StatelessWidget {
|
||||
const _TabStrip({
|
||||
required this.tabs,
|
||||
@@ -247,11 +161,11 @@ class _TabStrip extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 58,
|
||||
height: 42,
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: tabs.length,
|
||||
separatorBuilder: (_, __) => const SizedBox(width: 10),
|
||||
separatorBuilder: (_, __) => const SizedBox(width: 4),
|
||||
itemBuilder: (context, index) {
|
||||
final tab = tabs[index];
|
||||
final selected = tab.id == selectedTabId;
|
||||
@@ -261,8 +175,8 @@ class _TabStrip extends StatelessWidget {
|
||||
onTap: () => onSelectTab(tab.id),
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 180),
|
||||
width: 210,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
|
||||
width: 172,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: selected
|
||||
? scheme.primary.withOpacity(0.18)
|
||||
@@ -275,18 +189,21 @@ class _TabStrip extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(tab.icon, size: 18),
|
||||
const SizedBox(width: 10),
|
||||
Icon(tab.icon, size: 14),
|
||||
const SizedBox(width: 5),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
tab.title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
Text(
|
||||
tab.pageType.name,
|
||||
@@ -299,10 +216,19 @@ class _TabStrip extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
if (tab.id != StudioTab.dashboardId)
|
||||
IconButton(
|
||||
tooltip: '关闭',
|
||||
onPressed: () => onCloseTab(tab.id),
|
||||
icon: const Icon(Icons.close_rounded, size: 16),
|
||||
SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: IconButton(
|
||||
tooltip: '关闭',
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
onPressed: () => onCloseTab(tab.id),
|
||||
icon: const Icon(Icons.close_rounded, size: 12),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -331,10 +257,10 @@ class _StudioStatusBar extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
child: Wrap(
|
||||
spacing: 16,
|
||||
runSpacing: 10,
|
||||
spacing: 10,
|
||||
runSpacing: 8,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
_MiniBadge(text: '用户 ${user.displayName}'),
|
||||
|
||||
@@ -55,11 +55,14 @@ ThemeData buildStudioTheme(Brightness brightness) {
|
||||
surfaceTintColor: colorScheme.surfaceTint,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
|
||||
),
|
||||
visualDensity: VisualDensity.compact,
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
drawerTheme: DrawerThemeData(
|
||||
backgroundColor: isDark ? const Color(0xFF07111A) : Colors.white,
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
isDense: true,
|
||||
fillColor: inputFill,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
@@ -73,6 +76,25 @@ ThemeData buildStudioTheme(Brightness brightness) {
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
borderSide: BorderSide(color: colorScheme.primary),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
prefixIconConstraints: const BoxConstraints.tightFor(width: 34, height: 34),
|
||||
suffixIconConstraints: const BoxConstraints.tightFor(width: 34, height: 34),
|
||||
),
|
||||
filledButtonTheme: FilledButtonThemeData(
|
||||
style: FilledButton.styleFrom(
|
||||
minimumSize: const Size(0, 36),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
visualDensity: VisualDensity.compact,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
),
|
||||
iconButtonTheme: IconButtonThemeData(
|
||||
style: IconButton.styleFrom(
|
||||
minimumSize: const Size(28, 28),
|
||||
padding: EdgeInsets.zero,
|
||||
visualDensity: VisualDensity.compact,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -86,7 +108,7 @@ class _MiniBadge extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: _isDarkTheme(context)
|
||||
? Colors.white.withOpacity(0.06)
|
||||
|
||||
+2
-1
@@ -37,8 +37,9 @@ dependencies:
|
||||
crypto: ^3.0.6
|
||||
dio: ^5.8.0
|
||||
file_selector: ^1.0.3
|
||||
pointycastle: ^3.9.1
|
||||
pointycastle: ^4.0.0
|
||||
shared_preferences: ^2.3.2
|
||||
code_text_field: ^1.1.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user