chore: screenshots

This commit is contained in:
lollipopkit
2023-12-03 13:16:51 +08:00
parent 440dabfca8
commit 66d344c910
21 changed files with 171 additions and 181 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 144 KiB

View File

@@ -6,4 +6,12 @@ extension ListX<T> on List<T> {
} }
return list; return list;
} }
List<T> combine(List<T> other, [bool self = true]) {
final list = self ? this : List<T>.from(this);
for (var i = 0; i < length; i++) {
list[i] = other[i];
}
return list;
}
} }

View File

@@ -1,9 +1,8 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart';
import 'package:toolbox/data/res/path.dart'; import 'package:toolbox/core/utils/misc.dart';
// abstract final class SecureStore { // abstract final class SecureStore {
// static const _secureStorage = FlutterSecureStorage(); // static const _secureStorage = FlutterSecureStorage();
@@ -30,8 +29,8 @@ import 'package:toolbox/data/res/path.dart';
// } // }
// } // }
class PersistentStore<E> { class PersistentStore {
late final Box<E> box; late final Box box;
final String boxName; final String boxName;
@@ -41,35 +40,48 @@ class PersistentStore<E> {
boxName, boxName,
//encryptionCipher: SecureStore._cipher, //encryptionCipher: SecureStore._cipher,
); );
}
/// Get all db filenames. extension BoxX on Box {
/// static const _internalPreffix = '_sbi_';
/// - [suffixs] defaults to ['.hive']
/// /// Last modified timestamp
/// - If [hideSetting] is true, hide 'setting.hive' static const String lastModifiedKey = '${_internalPreffix}lastModified';
static Future<List<String>> getFileNames({ int? get lastModified {
bool hideSetting = false, final val = get(lastModifiedKey);
List<String>? suffixs, if (val == null || val is! int) {
}) async { final time = timeStamp;
final docPath = await Paths.doc; put(lastModifiedKey, time);
final dir = Directory(docPath); return time;
final files = await dir.list().toList();
if (suffixs != null) {
files.removeWhere((e) => !suffixs.contains(e.path.split('.').last));
} else {
// filter out non-hive(db) files
files.removeWhere((e) => !e.path.endsWith('.hive'));
} }
if (hideSetting) { return val;
files.removeWhere((e) => e.path.endsWith('setting.hive'));
}
final paths =
files.map((e) => e.path.replaceFirst('$docPath/', '')).toList();
return paths;
} }
Future<void> updateLastModified() => put(lastModifiedKey, timeStamp);
/// Convert db to json /// Convert db to json
Map<String, dynamic> toJson() => {for (var e in box.keys) e: box.get(e)}; Map<String, dynamic> toJson({bool includeInternal = true}) {
final json = <String, dynamic>{};
for (final key in keys) {
if (key is String &&
key.startsWith(_internalPreffix) &&
!includeInternal) {
continue;
}
json[key] = get(key);
}
return json;
}
}
extension StoreX on PersistentStore {
_StoreProperty<T> property<T>(String key, T defaultValue) {
return _StoreProperty<T>(box, key, defaultValue);
}
_StoreListProperty<T> listProperty<T>(String key, List<T> defaultValue) {
return _StoreListProperty<T>(box, key, defaultValue);
}
} }
abstract class StorePropertyBase<T> { abstract class StorePropertyBase<T> {
@@ -79,8 +91,8 @@ abstract class StorePropertyBase<T> {
Future<void> delete(); Future<void> delete();
} }
class StoreProperty<T> implements StorePropertyBase<T> { class _StoreProperty<T> implements StorePropertyBase<T> {
StoreProperty(this._box, this._key, this.defaultValue); _StoreProperty(this._box, this._key, this.defaultValue);
final Box _box; final Box _box;
final String _key; final String _key;
@@ -102,6 +114,7 @@ class StoreProperty<T> implements StorePropertyBase<T> {
@override @override
Future<void> put(T value) { Future<void> put(T value) {
_box.updateLastModified();
return _box.put(_key, value); return _box.put(_key, value);
} }
@@ -111,8 +124,8 @@ class StoreProperty<T> implements StorePropertyBase<T> {
} }
} }
class StoreListProperty<T> implements StorePropertyBase<List<T>> { class _StoreListProperty<T> implements StorePropertyBase<List<T>> {
StoreListProperty(this._box, this._key, this.defaultValue); _StoreListProperty(this._box, this._key, this.defaultValue);
final Box _box; final Box _box;
final String _key; final String _key;

View File

@@ -2,29 +2,13 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:icloud_storage/icloud_storage.dart'; import 'package:icloud_storage/icloud_storage.dart';
import 'package:toolbox/data/model/app/sync.dart';
import 'package:toolbox/data/res/logger.dart'; import 'package:toolbox/data/res/logger.dart';
import '../../data/model/app/error.dart'; import '../../data/model/app/error.dart';
import '../../data/model/app/json.dart'; import '../../data/model/app/json.dart';
import '../../data/res/path.dart'; import '../../data/res/path.dart';
class SyncResult<T, E> {
final List<T> up;
final List<T> down;
final Map<T, E> err;
const SyncResult({
required this.up,
required this.down,
required this.err,
});
@override
String toString() {
return 'SyncResult{up: $up, down: $down, err: $err}';
}
}
abstract final class ICloud { abstract final class ICloud {
static const _containerId = 'iCloud.tech.lolli.serverbox'; static const _containerId = 'iCloud.tech.lolli.serverbox';
@@ -111,7 +95,7 @@ abstract final class ICloud {
/// Return `null` if upload success, `ICloudErr` otherwise /// Return `null` if upload success, `ICloudErr` otherwise
/// ///
/// TODO: consider merge strategy, use [SyncAble] and [JsonSerializable] /// TODO: consider merge strategy, use [SyncAble] and [JsonSerializable]
static Future<SyncResult<String, ICloudErr>> sync({ static Future<SyncResult<String, ICloudErr>> syncFiles({
required Iterable<String> relativePaths, required Iterable<String> relativePaths,
String? bakPrefix, String? bakPrefix,
}) async { }) async {
@@ -196,4 +180,5 @@ abstract final class ICloud {
Loggers.app.info('iCloud sync, up: $uploadFiles, down: $downloadFiles'); Loggers.app.info('iCloud sync, up: $uploadFiles, down: $downloadFiles');
} }
} }
} }

View File

@@ -37,3 +37,5 @@ String pathJoin(String path1, String path2) {
/// Check if a url is a file url (ends with a file extension) /// Check if a url is a file url (ends with a file extension)
bool isFileUrl(String url) => url.split('/').last.contains('.'); bool isFileUrl(String url) => url.split('/').last.contains('.');
int get timeStamp => DateTime.now().millisecondsSinceEpoch;

View File

@@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:toolbox/core/persistant_store.dart';
import 'package:toolbox/data/model/server/private_key_info.dart'; import 'package:toolbox/data/model/server/private_key_info.dart';
import 'package:toolbox/data/model/server/server_private_info.dart'; import 'package:toolbox/data/model/server/server_private_info.dart';
import 'package:toolbox/data/model/server/snippet.dart'; import 'package:toolbox/data/model/server/snippet.dart';
@@ -60,8 +61,8 @@ class Backup {
spis = Stores.server.fetch(), spis = Stores.server.fetch(),
snippets = Stores.snippet.fetch(), snippets = Stores.snippet.fetch(),
keys = Stores.key.fetch(), keys = Stores.key.fetch(),
dockerHosts = Stores.docker.toJson(), dockerHosts = Stores.docker.box.toJson(),
settings = Stores.setting.toJson(); settings = Stores.setting.box.toJson();
static Future<String> backup() async { static Future<String> backup() async {
final result = _diyEncrypt(json.encode(Backup.loadFromStore())); final result = _diyEncrypt(json.encode(Backup.loadFromStore()));

View File

@@ -0,0 +1,24 @@
import 'dart:async';
class SyncResult<T, E> {
final List<T> up;
final List<T> down;
final Map<T, E> err;
const SyncResult({
required this.up,
required this.down,
required this.err,
});
@override
String toString() {
return 'SyncResult{up: $up, down: $down, err: $err}';
}
}
abstract class SyncIface<T> {
/// Merge [other] into [this], return [this] after merge.
/// Data in [other] has higher priority than [this].
FutureOr<void> sync(T other);
}

View File

@@ -1,9 +0,0 @@
abstract class SyncAble<T> {
/// If [other] is newer than [this] then return true,
/// else return false
bool needSync(T other);
/// Merge [other] into [this],
/// return [this] after merge
T merge(T other);
}

View File

@@ -1,6 +1,6 @@
import '../../core/persistant_store.dart'; import '../../core/persistant_store.dart';
class DockerStore extends PersistentStore<String> { class DockerStore extends PersistentStore {
DockerStore() : super('docker'); DockerStore() : super('docker');
String? fetch(String? id) { String? fetch(String? id) {

View File

@@ -1,7 +1,7 @@
import '../../core/persistant_store.dart'; import '../../core/persistant_store.dart';
import '../model/server/private_key_info.dart'; import '../model/server/private_key_info.dart';
class PrivateKeyStore extends PersistentStore<PrivateKeyInfo> { class PrivateKeyStore extends PersistentStore {
PrivateKeyStore() : super('key'); PrivateKeyStore() : super('key');
void put(PrivateKeyInfo info) { void put(PrivateKeyInfo info) {

View File

@@ -1,7 +1,7 @@
import '../../core/persistant_store.dart'; import '../../core/persistant_store.dart';
import '../model/server/server_private_info.dart'; import '../model/server/server_private_info.dart';
class ServerStore extends PersistentStore<ServerPrivateInfo> { class ServerStore extends PersistentStore {
ServerStore() : super('server'); ServerStore() : super('server');
void put(ServerPrivateInfo info) { void put(ServerPrivateInfo info) {

View File

@@ -15,22 +15,19 @@ class SettingStore extends PersistentStore {
// item in the drawer of the home page) // item in the drawer of the home page)
/// Discussion #146 /// Discussion #146
late final serverTabUseOldUI = StoreProperty( late final serverTabUseOldUI = property(
box,
'serverTabUseOldUI', 'serverTabUseOldUI',
false, false,
); );
/// Time out for server connect and more... /// Time out for server connect and more...
late final timeout = StoreProperty( late final timeout = property(
box,
'timeOut', 'timeOut',
5, 5,
); );
/// Record history of SFTP path and etc. /// Record history of SFTP path and etc.
late final recordHistory = StoreProperty( late final recordHistory = property(
box,
'recordHistory', 'recordHistory',
true, true,
); );
@@ -38,142 +35,125 @@ class SettingStore extends PersistentStore {
/// Bigger for bigger font size /// Bigger for bigger font size
/// 1.0 means 100% /// 1.0 means 100%
/// Warning: This may cause some UI issues /// Warning: This may cause some UI issues
late final textFactor = StoreProperty( late final textFactor = property(
box,
'textFactor', 'textFactor',
1.0, 1.0,
); );
/// Lanch page idx /// Lanch page idx
late final launchPage = StoreProperty( late final launchPage = property(
box,
'launchPage', 'launchPage',
Defaults.launchPageIdx, Defaults.launchPageIdx,
); );
/// Server detail disk ignore path /// Server detail disk ignore path
late final diskIgnorePath = late final diskIgnorePath =
StoreListProperty(box, 'diskIgnorePath', Defaults.diskIgnorePath); property('diskIgnorePath', Defaults.diskIgnorePath);
/// Use double column servers page on Desktop /// Use double column servers page on Desktop
late final doubleColumnServersPage = StoreProperty( late final doubleColumnServersPage = property(
box,
'doubleColumnServersPage', 'doubleColumnServersPage',
isDesktop, isDesktop,
); );
/// Disk view: amount / IO /// Disk view: amount / IO
late final serverTabPreferDiskAmount = StoreProperty( late final serverTabPreferDiskAmount = property(
box,
'serverTabPreferDiskAmount', 'serverTabPreferDiskAmount',
false, false,
); );
// ------END------ // ------END------
late final primaryColor = StoreProperty( late final primaryColor = property(
box,
'primaryColor', 'primaryColor',
4287106639, 4287106639,
); );
late final serverStatusUpdateInterval = StoreProperty( late final serverStatusUpdateInterval = property(
box,
'serverStatusUpdateInterval', 'serverStatusUpdateInterval',
Defaults.updateInterval, Defaults.updateInterval,
); );
// Max retry count when connect to server // Max retry count when connect to server
late final maxRetryCount = StoreProperty(box, 'maxRetryCount', 2); late final maxRetryCount = property('maxRetryCount', 2);
// Night mode: 0 -> auto, 1 -> light, 2 -> dark, 3 -> AMOLED, 4 -> AUTO-AMOLED // Night mode: 0 -> auto, 1 -> light, 2 -> dark, 3 -> AMOLED, 4 -> AUTO-AMOLED
late final themeMode = StoreProperty(box, 'themeMode', 0); late final themeMode = property('themeMode', 0);
// Font file path // Font file path
late final fontPath = StoreProperty(box, 'fontPath', ''); late final fontPath = property('fontPath', '');
// Backgroud running (Android) // Backgroud running (Android)
late final bgRun = StoreProperty(box, 'bgRun', isAndroid); late final bgRun = property('bgRun', isAndroid);
// Server order // Server order
late final serverOrder = StoreListProperty<String>(box, 'serverOrder', []); late final serverOrder = listProperty<String>('serverOrder', []);
late final snippetOrder = StoreListProperty<String>(box, 'snippetOrder', []); late final snippetOrder = listProperty<String>('snippetOrder', []);
// Server details page cards order // Server details page cards order
late final detailCardOrder = late final detailCardOrder =
StoreListProperty(box, 'detailCardPrder', Defaults.detailCardOrder); listProperty('detailCardPrder', Defaults.detailCardOrder);
// SSH term font size // SSH term font size
late final termFontSize = StoreProperty(box, 'termFontSize', 13.0); late final termFontSize = property('termFontSize', 13.0);
// Locale // Locale
late final locale = StoreProperty<String>(box, 'locale', ''); late final locale = property<String>('locale', '');
// SSH virtual key (ctrl | alt) auto turn off // SSH virtual key (ctrl | alt) auto turn off
late final sshVirtualKeyAutoOff = late final sshVirtualKeyAutoOff = property('sshVirtualKeyAutoOff', true);
StoreProperty(box, 'sshVirtualKeyAutoOff', true);
late final editorFontSize = StoreProperty(box, 'editorFontSize', 13.0); late final editorFontSize = property('editorFontSize', 13.0);
// Editor theme // Editor theme
late final editorTheme = StoreProperty( late final editorTheme = property(
box,
'editorTheme', 'editorTheme',
Defaults.editorTheme, Defaults.editorTheme,
); );
late final editorDarkTheme = StoreProperty( late final editorDarkTheme = property(
box,
'editorDarkTheme', 'editorDarkTheme',
Defaults.editorDarkTheme, Defaults.editorDarkTheme,
); );
late final fullScreen = StoreProperty( late final fullScreen = property(
box,
'fullScreen', 'fullScreen',
false, false,
); );
late final fullScreenJitter = StoreProperty( late final fullScreenJitter = property(
box,
'fullScreenJitter', 'fullScreenJitter',
true, true,
); );
late final fullScreenRotateQuarter = StoreProperty( late final fullScreenRotateQuarter = property(
box,
'fullScreenRotateQuarter', 'fullScreenRotateQuarter',
1, 1,
); );
late final keyboardType = StoreProperty( late final keyboardType = property(
box,
'keyboardType', 'keyboardType',
TextInputType.text.index, TextInputType.text.index,
); );
late final sshVirtKeys = StoreListProperty( late final sshVirtKeys = listProperty(
box,
'sshVirtKeys', 'sshVirtKeys',
Defaults.sshVirtKeys, Defaults.sshVirtKeys,
); );
late final netViewType = StoreProperty( late final netViewType = property(
box,
'netViewType', 'netViewType',
NetViewType.speed, NetViewType.speed,
); );
// Only valid on iOS // Only valid on iOS
late final autoUpdateHomeWidget = StoreProperty( late final autoUpdateHomeWidget = property(
box,
'autoUpdateHomeWidget', 'autoUpdateHomeWidget',
isIOS, isIOS,
); );
late final autoCheckAppUpdate = StoreProperty( late final autoCheckAppUpdate = property(
box,
'autoCheckAppUpdate', 'autoCheckAppUpdate',
true, true,
); );
@@ -181,59 +161,54 @@ class SettingStore extends PersistentStore {
/// Display server tab function buttons on the bottom of each server card if [true] /// Display server tab function buttons on the bottom of each server card if [true]
/// ///
/// Otherwise, display them on the top of server detail page /// Otherwise, display them on the top of server detail page
late final moveOutServerTabFuncBtns = StoreProperty( late final moveOutServerTabFuncBtns = property(
box,
'moveOutServerTabFuncBtns', 'moveOutServerTabFuncBtns',
true, true,
); );
/// Whether use `rm -r` to delete directory on SFTP /// Whether use `rm -r` to delete directory on SFTP
late final sftpRmrDir = StoreProperty( late final sftpRmrDir = property(
box,
'sftpRmrDir', 'sftpRmrDir',
false, false,
); );
/// Whether use system's primary color as the app's primary color /// Whether use system's primary color as the app's primary color
late final useSystemPrimaryColor = StoreProperty( late final useSystemPrimaryColor = property(
box,
'useSystemPrimaryColor', 'useSystemPrimaryColor',
false, false,
); );
/// Only valid on iOS and macOS /// Only valid on iOS and macOS
late final icloudSync = StoreProperty( late final icloudSync = property(
box,
'icloudSync', 'icloudSync',
false, false,
); );
/// Only valid on iOS / Android / Windows /// Only valid on iOS / Android / Windows
late final useBioAuth = StoreProperty( late final useBioAuth = property(
box,
'useBioAuth', 'useBioAuth',
false, false,
); );
/// The performance of highlight is bad /// The performance of highlight is bad
late final editorHighlight = StoreProperty(box, 'editorHighlight', true); late final editorHighlight = property('editorHighlight', true);
/// Open SFTP with last viewed path /// Open SFTP with last viewed path
late final sftpOpenLastPath = StoreProperty(box, 'sftpOpenLastPath', true); late final sftpOpenLastPath = property('sftpOpenLastPath', true);
/// Show tip of suspend /// Show tip of suspend
late final showSuspendTip = StoreProperty(box, 'showSuspendTip', true); late final showSuspendTip = property('showSuspendTip', true);
/// Server func btns display name /// Server func btns display name
late final serverFuncBtnsDisplayName = late final serverFuncBtnsDisplayName =
StoreProperty(box, 'serverFuncBtnsDisplayName', false); property('serverFuncBtnsDisplayName', false);
// Never show these settings for users // Never show these settings for users
// //
// ------BEGIN------ // ------BEGIN------
/// Version of store db /// Version of store db
late final storeVersion = StoreProperty(box, 'storeVersion', 0); late final storeVersion = property('storeVersion', 0);
// ------END------ // ------END------
} }

View File

@@ -1,7 +1,7 @@
import '../../core/persistant_store.dart'; import '../../core/persistant_store.dart';
import '../model/server/snippet.dart'; import '../model/server/snippet.dart';
class SnippetStore extends PersistentStore<Snippet> { class SnippetStore extends PersistentStore {
SnippetStore() : super('snippet'); SnippetStore() : super('snippet');
void put(Snippet snippet) { void put(Snippet snippet) {

View File

@@ -95,8 +95,6 @@ class BackupPage extends StatelessWidget {
prop: Stores.setting.icloudSync, prop: Stores.setting.icloudSync,
func: (val) async { func: (val) async {
if (val) { if (val) {
final relativePaths = await PersistentStore.getFileNames();
await ICloud.sync(relativePaths: relativePaths);
} }
}, },
), ),
@@ -109,17 +107,12 @@ class BackupPage extends StatelessWidget {
if (icloudLoading.value) { if (icloudLoading.value) {
return UIs.centerSizedLoadingSmall; return UIs.centerSizedLoadingSmall;
} }
return ConstrainedBox( return Row(
constraints: const BoxConstraints(maxWidth: 137), mainAxisSize: MainAxisSize.min,
child: Row(
children: [ children: [
TextButton( TextButton(
onPressed: () async { onPressed: () async {
icloudLoading.value = true; icloudLoading.value = true;
final files = await PersistentStore.getFileNames();
for (final file in files) {
await ICloud.download(relativePath: file);
}
icloudLoading.value = false; icloudLoading.value = false;
}, },
child: Text(l10n.download), child: Text(l10n.download),
@@ -128,16 +121,11 @@ class BackupPage extends StatelessWidget {
TextButton( TextButton(
onPressed: () async { onPressed: () async {
icloudLoading.value = true; icloudLoading.value = true;
final files = await PersistentStore.getFileNames();
for (final file in files) {
await ICloud.upload(relativePath: file);
}
icloudLoading.value = false; icloudLoading.value = false;
}, },
child: Text(l10n.upload), child: Text(l10n.upload),
), ),
], ],
),
); );
}, },
), ),

View File

@@ -8,6 +8,7 @@ import 'package:toolbox/core/channel/bg_run.dart';
import 'package:toolbox/core/channel/home_widget.dart'; import 'package:toolbox/core/channel/home_widget.dart';
import 'package:toolbox/core/extension/context/dialog.dart'; import 'package:toolbox/core/extension/context/dialog.dart';
import 'package:toolbox/core/extension/context/locale.dart'; import 'package:toolbox/core/extension/context/locale.dart';
import 'package:toolbox/core/persistant_store.dart';
import 'package:toolbox/core/utils/platform/auth.dart'; import 'package:toolbox/core/utils/platform/auth.dart';
import 'package:toolbox/core/utils/platform/base.dart'; import 'package:toolbox/core/utils/platform/base.dart';
import 'package:toolbox/data/res/github_id.dart'; import 'package:toolbox/data/res/github_id.dart';
@@ -340,7 +341,7 @@ class _HomePageState extends State<HomePage>
} }
Future<void> _onLongPressSetting() async { Future<void> _onLongPressSetting() async {
final map = Stores.setting.toJson(); final map = Stores.setting.box.toJson(includeInternal: false);
final keys = map.keys; final keys = map.keys;
/// Encode [map] to String with indent `\t` /// Encode [map] to String with indent `\t`

View File

@@ -219,29 +219,31 @@ class _ServerPageState extends State<ServerPage>
final cardStatus = getCardNoti(id); final cardStatus = getCardNoti(id);
final title = _buildServerCardTitle(srv.status, srv.state, srv.spi); final title = _buildServerCardTitle(srv.status, srv.state, srv.spi);
return ValueBuilder(
listenable: cardStatus,
build: () {
late final List<Widget> children;
if (srv.state == ServerState.finished) {
if (cardStatus.value.flip) {
children = [title, ..._buildFlipedCard(srv)];
} else {
children = [title, ..._buildNormalCard(srv.status, srv.spi)];
}
} else {
children = [title];
}
return AnimatedContainer( return AnimatedContainer(
duration: const Duration(milliseconds: 377), duration: const Duration(milliseconds: 377),
curve: Curves.fastEaseInToSlowEaseOut, curve: Curves.fastEaseInToSlowEaseOut,
height: _calcCardHeight(srv.state, cardStatus.value.flip), height: _calcCardHeight(srv.state, cardStatus.value.flip),
child: ValueBuilder( child: Column(
listenable: cardStatus,
build: () {
final List<Widget> children = [title];
if (srv.state == ServerState.finished) {
if (cardStatus.value.flip) {
children.addAll(_buildFlipedCard(srv));
} else {
children.addAll(_buildNormalCard(srv.status, srv.spi));
}
}
return Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: children, children: children,
),
); );
}, },
),
); );
} }