opt.: migrate fl_lib

This commit is contained in:
lollipopkit
2024-05-14 22:29:37 +08:00
parent 248430e5b0
commit 04dfede535
136 changed files with 686 additions and 3896 deletions

View File

@@ -1,50 +0,0 @@
import 'package:flutter/material.dart';
const _interactiveStates = <MaterialState>{
MaterialState.pressed,
MaterialState.hovered,
MaterialState.focused,
MaterialState.selected
};
extension ColorX on Color {
String get toHex {
final redStr = red.toRadixString(16).padLeft(2, '0');
final greenStr = green.toRadixString(16).padLeft(2, '0');
final blueStr = blue.toRadixString(16).padLeft(2, '0');
return '#$redStr$greenStr$blueStr';
}
bool get isBrightColor {
return getBrightnessFromColor == Brightness.light;
}
Brightness get getBrightnessFromColor {
return ThemeData.estimateBrightnessForColor(this);
}
MaterialStateProperty<Color?> get materialStateColor {
return MaterialStateProperty.resolveWith((states) {
if (states.any(_interactiveStates.contains)) {
return this;
}
return null;
});
}
MaterialColor get materialColor => MaterialColor(
value,
{
50: withOpacity(0.05),
100: withOpacity(0.1),
200: withOpacity(0.2),
300: withOpacity(0.3),
400: withOpacity(0.4),
500: withOpacity(0.5),
600: withOpacity(0.6),
700: withOpacity(0.7),
800: withOpacity(0.8),
900: withOpacity(0.9),
},
);
}

View File

@@ -1,11 +0,0 @@
import 'package:flutter/material.dart';
extension ContextX on BuildContext {
void pop<T extends Object?>([T? result]) {
Navigator.of(this).pop<T>(result);
}
bool get canPop => Navigator.of(this).canPop();
bool get isDark => Theme.of(this).brightness == Brightness.dark;
}

View File

@@ -1,232 +0,0 @@
import 'dart:async';
import 'package:choice/choice.dart';
import 'package:flutter/material.dart';
import 'package:toolbox/core/extension/context/common.dart';
import 'package:toolbox/core/extension/context/locale.dart';
import 'package:toolbox/data/res/store.dart';
import 'package:toolbox/view/widget/choice_chip.dart';
import 'package:toolbox/view/widget/tag.dart';
import 'package:toolbox/view/widget/val_builder.dart';
import '../../../data/res/ui.dart';
import '../../../view/widget/input_field.dart';
extension DialogX on BuildContext {
Future<T?> showRoundDialog<T>({
Widget? child,
List<Widget>? actions,
Widget? title,
bool barrierDismiss = true,
void Function(BuildContext)? onContext,
}) async {
return await showDialog<T>(
context: this,
barrierDismissible: barrierDismiss,
builder: (ctx) {
onContext?.call(ctx);
return AlertDialog(
title: title,
content: child,
actions: actions,
actionsPadding: const EdgeInsets.all(17),
);
},
);
}
Future<T> showLoadingDialog<T>({
required Future<T> Function() fn,
bool barrierDismiss = false,
}) async {
BuildContext? ctx;
showRoundDialog(
child: UIs.centerSizedLoading,
barrierDismiss: barrierDismiss,
onContext: (c) => ctx = c,
);
try {
return await fn();
} catch (e) {
rethrow;
} finally {
/// Wait for context to be unmounted
await Future.delayed(const Duration(milliseconds: 100));
if (ctx?.mounted == true) {
ctx?.pop();
}
}
}
static final _recoredPwd = <String, String>{};
/// Show a dialog to input password
///
/// [hostId] set it to null to skip remembering the password
Future<String?> showPwdDialog({
String? hostId,
String? title,
String? label,
}) async {
if (!mounted) return null;
return await showRoundDialog<String>(
title: Text(title ?? hostId ?? l10n.pwd),
child: Input(
controller: TextEditingController(text: _recoredPwd[hostId]),
autoFocus: true,
type: TextInputType.visiblePassword,
obscureText: true,
onSubmitted: (val) {
pop(val);
if (hostId != null && Stores.setting.rememberPwdInMem.fetch()) {
_recoredPwd[hostId] = val;
}
},
label: label ?? l10n.pwd,
),
);
}
Future<List<T>?> showPickDialog<T>({
required List<T?> items,
String Function(T)? name,
bool multi = true,
List<T>? initial,
bool clearable = false,
List<Widget>? actions,
}) async {
var vals = initial ?? <T>[];
final sure = await showRoundDialog<bool>(
title: Text(l10n.choose),
child: SingleChildScrollView(
child: Choice<T>(
onChanged: (value) => vals = value,
multiple: multi,
clearable: clearable,
value: vals,
builder: (state, _) {
return Wrap(
children: List<Widget>.generate(
items.length,
(index) {
final item = items[index];
if (item == null) return UIs.placeholder;
return ChoiceChipX<T>(
label: name?.call(item) ?? item.toString(),
state: state,
value: item,
);
},
),
);
},
),
),
actions: [
if (actions != null) ...actions,
TextButton(
onPressed: () => pop(true),
child: Text(l10n.ok),
),
],
);
if (sure == true && vals.isNotEmpty) {
return vals;
}
return null;
}
Future<T?> showPickSingleDialog<T>({
required List<T?> items,
String Function(T)? name,
T? initial,
bool clearable = false,
List<Widget>? actions,
}) async {
final vals = await showPickDialog<T>(
items: items,
name: name,
multi: false,
initial: initial == null ? null : [initial],
actions: actions,
);
if (vals != null && vals.isNotEmpty) {
return vals.first;
}
return null;
}
Future<List<T>?> showPickWithTagDialog<T>({
required List<T?> Function(String? tag) itemsBuilder,
required ValueNotifier<List<String>> tags,
String Function(T)? name,
List<T>? initial,
bool clearable = false,
bool multi = false,
List<Widget>? actions,
}) async {
var vals = initial ?? <T>[];
final tag = ValueNotifier<String?>(null);
final sure = await showRoundDialog<bool>(
title: Text(l10n.choose),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListenableBuilder(
listenable: tag,
builder: (_, __) => TagSwitcher(
tags: tags,
width: 300,
initTag: tag.value,
onTagChanged: (e) => tag.value = e,
),
),
const Divider(),
SingleChildScrollView(
child: ValBuilder(
listenable: tag,
builder: (val) {
final items = itemsBuilder(val);
return Choice<T>(
onChanged: (value) => vals = value,
multiple: multi,
clearable: clearable,
value: vals,
builder: (state, _) {
return Wrap(
children: List<Widget>.generate(
items.length,
(index) {
final item = items[index];
if (item == null) return UIs.placeholder;
return ChoiceChipX<T>(
label: name?.call(item) ?? item.toString(),
state: state,
value: item,
);
},
),
);
},
);
},
),
)
],
),
actions: [
if (actions != null) ...actions,
TextButton(
onPressed: () => pop(true),
child: Text(l10n.ok),
),
],
);
if (sure == true && vals.isNotEmpty) {
return vals;
}
return null;
}
}

View File

@@ -1,24 +0,0 @@
import 'package:flutter/material.dart';
extension SnackBarX on BuildContext {
void showSnackBar(String text) =>
ScaffoldMessenger.of(this).showSnackBar(SnackBar(
content: Text(text),
behavior: SnackBarBehavior.floating,
));
void showSnackBarWithAction(
String content,
String action,
GestureTapCallback onTap,
) {
ScaffoldMessenger.of(this).showSnackBar(SnackBar(
content: Text(content),
behavior: SnackBarBehavior.floating,
action: SnackBarAction(
label: action,
onPressed: onTap,
),
));
}
}

View File

@@ -1,15 +0,0 @@
extension DateTimeX on DateTime {
String get hourMinute {
return '${hour.toString().padLeft(2, '0')}:${minute.toString().padLeft(2, '0')}';
}
/// Format: 2021-01-01-0000
String get numStr {
final year = this.year.toString();
final month = this.month.toString().padLeft(2, '0');
final day = this.day.toString().padLeft(2, '0');
final hour = this.hour.toString().padLeft(2, '0');
final minute = this.minute.toString().padLeft(2, '0');
return '$year-$month-$day-$hour$minute';
}
}

View File

@@ -1,20 +0,0 @@
import 'package:toolbox/core/extension/context/locale.dart';
extension DurationX on Duration {
String get toStr {
final days = inDays;
if (days > 0) {
return '$days ${l10n.day}';
}
final hours = inHours % 24;
if (hours > 0) {
return '$hours ${l10n.hour}';
}
final minutes = inMinutes % 60;
if (minutes > 0) {
return '$minutes ${l10n.minute}';
}
final seconds = inSeconds % 60;
return '$seconds ${l10n.second}';
}
}

View File

@@ -1,12 +0,0 @@
extension EnumListX<T> on List<T> {
T fromIndex(int index, [T? defaultValue]) {
try {
return this[index];
} catch (e) {
if (defaultValue != null) {
return defaultValue;
}
throw Exception('Invalid index: $index');
}
}
}

View File

@@ -1,37 +0,0 @@
extension ListX<T> on List<T> {
List<T> joinWith(T item, [bool self = true]) {
final list = self ? this : List<T>.from(this);
for (var i = length - 1; i > 0; i--) {
list.insert(i, item);
}
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;
}
T? get firstOrNull => isEmpty ? null : first;
T? get lastOrNull => isEmpty ? null : last;
T? firstWhereOrNull(bool Function(T element) test) {
try {
return firstWhere(test);
} catch (_) {
return null;
}
}
T? lastWhereOrNull(bool Function(T element) test) {
try {
return lastWhere(test);
} catch (_) {
return null;
}
}
}

View File

@@ -1,24 +0,0 @@
import 'package:flutter/material.dart';
extension LocaleX on Locale {
String get code {
if (countryCode == null) {
return languageCode;
}
return '${languageCode}_$countryCode';
}
}
extension String2Locale on String {
Locale? get toLocale {
// Issue #151
if (isEmpty) {
return null;
}
final parts = split('_');
if (parts.length == 1) {
return Locale(parts[0]);
}
return Locale(parts[0], parts[1]);
}
}

View File

@@ -1,5 +0,0 @@
import 'package:flutter/widgets.dart';
extension MideaQueryX on MediaQueryData {
bool get useDoubleColumn => size.width > 639;
}

View File

@@ -1,40 +0,0 @@
extension NumX on num {
String get bytes2Str {
const suffix = ['B', 'KB', 'MB', 'GB', 'TB'];
double value = toDouble();
int squareTimes = 0;
for (; value / 1024 > 1 && squareTimes < suffix.length - 1; squareTimes++) {
value /= 1024;
}
var finalValue = value.toStringAsFixed(1);
if (finalValue.endsWith('.0')) {
finalValue = finalValue.replaceFirst('.0', '');
}
return '$finalValue ${suffix[squareTimes]}';
}
String get kb2Str => (this * 1024).bytes2Str;
}
extension BigIntX on BigInt {
String get bytes2Str {
const suffix = ['B', 'KB', 'MB', 'GB', 'TB'];
double value = toDouble();
int squareTimes = 0;
for (; value / 1024 > 1 && squareTimes < suffix.length - 1; squareTimes++) {
value /= 1024;
}
var finalValue = value.toStringAsFixed(1);
if (finalValue.endsWith('.0')) {
finalValue = finalValue.replaceFirst('.0', '');
}
return '$finalValue ${suffix[squareTimes]}';
}
String get kb2Str => (this * BigInt.from(1024)).bytes2Str;
}
extension IntX on int {
Duration secondsToDuration() => Duration(seconds: this);
DateTime get tsToDateTime => DateTime.fromMillisecondsSinceEpoch(this * 1000);
}

View File

@@ -1,97 +0,0 @@
import 'package:toolbox/core/extension/listx.dart';
import 'package:toolbox/core/persistant_store.dart';
typedef _OnMove<T> = void Function(List<T>);
extension OrderX<T> on List<T> {
void move(
int oldIndex,
int newIndex, {
StorePropertyBase<List<T>>? property,
_OnMove<T>? onMove,
}) {
if (oldIndex == newIndex) return;
if (oldIndex < newIndex) {
newIndex -= 1;
}
final item = this[oldIndex];
removeAt(oldIndex);
insert(newIndex, item);
property?.put(this);
onMove?.call(this);
}
void update(T id, T newId) {
final index = indexOf(id);
if (index == -1) return;
this[index] = newId;
}
int index(T id) {
return indexOf(id);
}
void moveByItem(
int o,
int n, {
/// The list after filtering.
///
/// It's used to find the index of the item.
List<T>? filtered,
StorePropertyBase<List<T>>? property,
_OnMove<T>? onMove,
}) {
if (o == n) return;
if (o < n) {
n -= 1;
}
final index = indexOf((filtered ?? this)[o]);
if (index == -1) return;
var newIndex = indexOf((filtered ?? this)[n]);
if (newIndex == -1) return;
if (o < n) {
newIndex += 1;
}
move(index, newIndex, property: property, onMove: onMove);
}
/// order: ['d', 'b', 'e']
/// this: ['a', 'b', 'c', 'd']\
/// result: ['d', 'b', 'a', 'c']\
/// return: ['e']
List<String> reorder({
required List<String> order,
required bool Function(T, String) finder,
}) {
final newOrder = <T>[];
final missed = <T>[];
final surplus = <String>[];
for (final id in order.toSet()) {
final item = firstWhereOrNull((element) => finder(element, id));
if (item == null) {
surplus.add(id);
} else {
newOrder.add(item);
}
}
for (final item in this) {
if (!newOrder.contains(item)) {
missed.add(item);
}
}
clear();
addAll(newOrder);
addAll(missed);
return surplus;
}
/// Dart uses memory address to compare objects by default.
/// This method compares the values of the objects.
bool equals(List<T> other) {
if (length != other.length) return false;
for (var i = 0; i < length; i++) {
if (this[i] != other[i]) return false;
}
return true;
}
}

View File

@@ -2,10 +2,8 @@ import 'dart:async';
import 'dart:typed_data';
import 'package:dartssh2/dartssh2.dart';
import 'package:fl_lib/fl_lib.dart';
import 'package:flutter/widgets.dart';
import 'package:toolbox/core/extension/context/dialog.dart';
import 'package:toolbox/core/extension/stringx.dart';
import 'package:toolbox/core/extension/uint8list.dart';
import '../../data/res/misc.dart';
@@ -81,7 +79,7 @@ extension SSHClientX on SSHClient {
isRequestingPwd = true;
final user = Miscs.pwdRequestWithUserReg.firstMatch(data)?.group(1);
if (context == null) return;
final pwd = await context.showPwdDialog(title: user, hostId: id);
final pwd = await context.showPwdDialog(title: user, id: id);
if (pwd == null || pwd.isEmpty) {
session.kill(SSHSignal.TERM);
} else {

View File

@@ -1,36 +0,0 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
extension StringX on String {
/// Format: `#8b2252` or `8b2252`
Color? get hexToColor {
final hexCode = replaceAll('#', '');
final val = int.tryParse('FF$hexCode', radix: 16);
if (val == null) {
return null;
}
return Color(val);
}
Uint8List get uint8List => Uint8List.fromList(utf8.encode(this));
/// Upper the first letter.
String get upperFirst {
if (isEmpty) {
return this;
}
final runes = codeUnits;
if (runes[0] >= 97 && runes[0] <= 122) {
final origin = String.fromCharCode(runes[0]);
final upper = origin.toUpperCase();
return replaceFirst(origin, upper);
}
return this;
}
}
extension StringXX on String? {
String? get selfIfNotNullEmpty => this?.isEmpty == true ? null : this;
}

View File

@@ -1,11 +0,0 @@
import 'dart:convert';
import 'dart:typed_data';
extension FutureUint8ListX on Future<Uint8List> {
Future<String> get string async => utf8.decode(await this);
Future<ByteData> get byteData async => (await this).buffer.asByteData();
}
extension Uint8ListX on Uint8List {
String get string => utf8.decode(this);
}

View File

@@ -1,8 +0,0 @@
import 'package:flutter/material.dart';
import 'package:toolbox/view/widget/cardx.dart';
extension WidgetX on Widget {
Widget get card {
return CardX(child: this);
}
}