mirror of
https://github.com/GopeedLab/gopeed.git
synced 2026-11-07 17:10:29 +00:00
Merge latest PR #1518 into 2.0.0-beta.2
This commit is contained in:
commit
c6dd58848d
8 files changed
+257
-186
No files matched your search
@@ -7,6 +7,7 @@ import '../../../../core/utils/breakpoints.dart';
|
||||
import '../../../../shared/theme/app_design_tokens.dart';
|
||||
import '../../../../shared/theme/app_palette.dart';
|
||||
import '../../../../shared/widgets/app_path_picker_field.dart';
|
||||
import '../../../../shared/widgets/app_path_placeholder_button.dart';
|
||||
import '../../../../shared/widgets/app_primary_button.dart';
|
||||
import '../../../../shared/widgets/app_text_field.dart';
|
||||
import '../../../../l10n/l10n.dart';
|
||||
@@ -180,6 +181,14 @@ Future<DownloadCategoryDraft?> showDownloadCategoryDialog(
|
||||
desktopWidth: AppDesignTokens.settingsFormControlWidth,
|
||||
allowAndroidEditing: true,
|
||||
),
|
||||
const SizedBox(height: AppDesignTokens.space4),
|
||||
Align(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: AppPathPlaceholderButton(
|
||||
key: const ValueKey('download-category-placeholders'),
|
||||
controller: pathController,
|
||||
),
|
||||
),
|
||||
if (validationMessage != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(validationMessage!, style: TextStyle(color: palette.error, fontSize: 12)),
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import '../../l10n/l10n.dart';
|
||||
import '../theme/app_design_tokens.dart';
|
||||
import '../theme/app_palette.dart';
|
||||
|
||||
/// A single editable name/value pair in a narrow headers form.
|
||||
class AppHttpHeaderGroup extends StatelessWidget {
|
||||
const AppHttpHeaderGroup({super.key, required this.name, required this.value, required this.remove});
|
||||
|
||||
final Widget name;
|
||||
final Widget value;
|
||||
final Widget remove;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final palette = AppPalette.of(context);
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(AppDesignTokens.space12),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: palette.border),
|
||||
borderRadius: BorderRadius.circular(AppDesignTokens.controlRadius),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: Text(context.l10n.httpHeaderName)),
|
||||
remove,
|
||||
],
|
||||
),
|
||||
const SizedBox(height: AppDesignTokens.space4),
|
||||
name,
|
||||
const SizedBox(height: AppDesignTokens.space12),
|
||||
Text(context.l10n.httpHeaderValue),
|
||||
const SizedBox(height: AppDesignTokens.space8),
|
||||
value,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import '../theme/app_design_tokens.dart';
|
||||
import '../theme/app_palette.dart';
|
||||
import '../../l10n/l10n.dart';
|
||||
import 'app_text_field.dart';
|
||||
import 'app_http_header_group.dart';
|
||||
import 'app_form_row.dart';
|
||||
|
||||
class AppHttpHeadersController extends ChangeNotifier {
|
||||
AppHttpHeadersController({Map<String, String>? headers, Iterable<String> defaultNames = const []}) {
|
||||
@@ -84,132 +84,60 @@ class AppHttpHeadersEditor extends StatelessWidget {
|
||||
final bool stacked;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final palette = AppPalette.of(context);
|
||||
if (stacked) {
|
||||
return AnimatedBuilder(
|
||||
key: ValueKey('$keyPrefix-editor'),
|
||||
animation: controller,
|
||||
builder: (context, _) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Widget build(BuildContext context) => AppFormRow(
|
||||
key: ValueKey('$keyPrefix-editor'),
|
||||
label: label,
|
||||
direction: stacked ? Axis.vertical : Axis.horizontal,
|
||||
child: AnimatedBuilder(
|
||||
animation: controller,
|
||||
builder: (context, _) => Column(
|
||||
children: [
|
||||
for (var index = 0; index < controller._rows.length; index++) ...[
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(color: palette.textPrimary, fontSize: 13, fontWeight: FontWeight.w500),
|
||||
flex: 382,
|
||||
child: _HeaderTextField(
|
||||
key: ValueKey('$keyPrefix-name-$index'),
|
||||
controller: controller._rows[index].name,
|
||||
hintText: context.l10n.httpHeaderName,
|
||||
),
|
||||
),
|
||||
shad.OutlineButton(
|
||||
key: ValueKey('$keyPrefix-add'),
|
||||
onPressed: controller.add,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.add, size: 18),
|
||||
const SizedBox(width: AppDesignTokens.space4),
|
||||
Text(context.l10n.add),
|
||||
],
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
flex: 618,
|
||||
child: _HeaderTextField(
|
||||
key: ValueKey('$keyPrefix-value-$index'),
|
||||
controller: controller._rows[index].value,
|
||||
hintText: context.l10n.httpHeaderValue,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: AppDesignTokens.space8),
|
||||
for (var index = 0; index < controller._rows.length; index++) ...[
|
||||
AppHttpHeaderGroup(
|
||||
key: ObjectKey(controller._rows[index]),
|
||||
name: _HeaderTextField(
|
||||
key: ValueKey('$keyPrefix-name-$index'),
|
||||
controller: controller._rows[index].name,
|
||||
hintText: context.l10n.httpHeaderName,
|
||||
),
|
||||
value: _HeaderTextField(
|
||||
key: ValueKey('$keyPrefix-value-$index'),
|
||||
controller: controller._rows[index].value,
|
||||
hintText: context.l10n.httpHeaderValue,
|
||||
),
|
||||
remove: _HeaderAction(
|
||||
const SizedBox(width: 4),
|
||||
_HeaderAction(
|
||||
key: ValueKey('$keyPrefix-remove-$index'),
|
||||
icon: Icons.delete_outline,
|
||||
icon: Icons.remove,
|
||||
onPressed: controller._rows.length == 1 ? null : () => controller.removeAt(index),
|
||||
),
|
||||
),
|
||||
if (index != controller._rows.length - 1) const SizedBox(height: AppDesignTokens.space12),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
return Row(
|
||||
key: ValueKey('$keyPrefix-editor'),
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 104,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(color: palette.textPrimary, fontSize: 13, fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: AnimatedBuilder(
|
||||
animation: controller,
|
||||
builder: (context, _) => Column(
|
||||
children: [
|
||||
for (var index = 0; index < controller._rows.length; index++) ...[
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
flex: 382,
|
||||
child: _HeaderTextField(
|
||||
key: ValueKey('$keyPrefix-name-$index'),
|
||||
controller: controller._rows[index].name,
|
||||
hintText: context.l10n.httpHeaderName,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
flex: 618,
|
||||
child: _HeaderTextField(
|
||||
key: ValueKey('$keyPrefix-value-$index'),
|
||||
controller: controller._rows[index].value,
|
||||
hintText: context.l10n.httpHeaderValue,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
_HeaderAction(
|
||||
key: ValueKey('$keyPrefix-remove-$index'),
|
||||
icon: Icons.remove,
|
||||
onPressed: controller._rows.length == 1 ? null : () => controller.removeAt(index),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Visibility(
|
||||
visible: index == controller._rows.length - 1,
|
||||
maintainAnimation: true,
|
||||
maintainSize: true,
|
||||
maintainState: true,
|
||||
child: _HeaderAction(
|
||||
key: index == controller._rows.length - 1 ? ValueKey('$keyPrefix-add') : null,
|
||||
icon: Icons.add,
|
||||
onPressed: controller.add,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(width: 4),
|
||||
Visibility(
|
||||
visible: index == controller._rows.length - 1,
|
||||
maintainAnimation: true,
|
||||
maintainSize: true,
|
||||
maintainState: true,
|
||||
child: _HeaderAction(
|
||||
key: index == controller._rows.length - 1 ? ValueKey('$keyPrefix-add') : null,
|
||||
icon: Icons.add,
|
||||
onPressed: controller.add,
|
||||
),
|
||||
if (index != controller._rows.length - 1) const SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
if (index != controller._rows.length - 1) const SizedBox(height: 8),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class _HttpHeaderRowControllers {
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||
|
||||
import '../../l10n/l10n.dart';
|
||||
import '../theme/app_design_tokens.dart';
|
||||
import '../theme/app_palette.dart';
|
||||
|
||||
/// Explains supported date variables and inserts one at the path's selection.
|
||||
class AppPathPlaceholderButton extends StatelessWidget {
|
||||
const AppPathPlaceholderButton({super.key, required this.controller});
|
||||
|
||||
final TextEditingController controller;
|
||||
|
||||
void _showPlaceholders(BuildContext context) {
|
||||
final selection = controller.selection;
|
||||
final now = DateTime.now();
|
||||
final year = now.year.toString();
|
||||
final month = now.month.toString().padLeft(2, '0');
|
||||
final day = now.day.toString().padLeft(2, '0');
|
||||
final l10n = context.l10n;
|
||||
final placeholders = [
|
||||
('%year%', l10n.placeholderYear, year),
|
||||
('%month%', l10n.placeholderMonth, month),
|
||||
('%day%', l10n.placeholderDay, day),
|
||||
('%date%', l10n.placeholderDate, '$year-$month-$day'),
|
||||
];
|
||||
const PopoverOverlayHandler().show<void>(
|
||||
context: context,
|
||||
alignment: Alignment.topLeft,
|
||||
anchorAlignment: Alignment.bottomLeft,
|
||||
offset: const Offset(0, AppDesignTokens.space4),
|
||||
builder: (popoverContext) => Card(
|
||||
padding: const EdgeInsets.all(AppDesignTokens.space8),
|
||||
child: SizedBox(
|
||||
width: (MediaQuery.sizeOf(context).width - 80).clamp(180.0, 320.0),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 280),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
for (final (variable, description, example) in placeholders)
|
||||
GhostButton(
|
||||
key: ValueKey('path-placeholder-$variable'),
|
||||
onPressed: () {
|
||||
final text = controller.text;
|
||||
final valid = selection.isValid && selection.end <= text.length;
|
||||
final start = valid ? selection.start : text.length;
|
||||
final end = valid ? selection.end : text.length;
|
||||
controller.value = TextEditingValue(
|
||||
text: text.replaceRange(start, end, variable),
|
||||
selection: TextSelection.collapsed(offset: start + variable.length),
|
||||
);
|
||||
closeOverlay(popoverContext);
|
||||
},
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('$variable — $description', style: const TextStyle(fontSize: 12)),
|
||||
Text(
|
||||
l10n.example.replaceAll('@value', example),
|
||||
style: TextStyle(color: AppPalette.of(context).textMuted, fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Builder(
|
||||
builder: (anchorContext) =>
|
||||
GhostButton(onPressed: () => _showPlaceholders(anchorContext), child: Text(context.l10n.insertPlaceholder)),
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/gestures.dart' show Drag;
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart' hide Column, Expanded, Row;
|
||||
|
||||
@@ -99,6 +100,7 @@ class _FileTreeViewState<T> extends State<FileTreeView<T>> with SingleTickerProv
|
||||
late final Animation<double> _reveal;
|
||||
final _scrollController = ScrollController();
|
||||
final _horizontalScrollController = ScrollController();
|
||||
Drag? _horizontalDrag;
|
||||
final Map<String, double> _labelWidthCache = {};
|
||||
TextStyle? _labelMeasurementStyle;
|
||||
TextDirection? _labelMeasurementDirection;
|
||||
@@ -138,6 +140,7 @@ class _FileTreeViewState<T> extends State<FileTreeView<T>> with SingleTickerProv
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_horizontalDrag?.cancel();
|
||||
_transitionController.removeStatusListener(_onTransitionStatus);
|
||||
_transitionController.dispose();
|
||||
_scrollController.dispose();
|
||||
@@ -290,26 +293,36 @@ class _FileTreeViewState<T> extends State<FileTreeView<T>> with SingleTickerProv
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Expanded(
|
||||
child: ClipRect(
|
||||
child: AnimatedBuilder(
|
||||
animation: _horizontalScrollController,
|
||||
builder: (context, child) {
|
||||
final offset = horizontalScrollEnabled && _horizontalScrollController.hasClients
|
||||
? _horizontalScrollController.offset
|
||||
: 0.0;
|
||||
return Transform.translate(offset: Offset(-offset, 0), child: child);
|
||||
},
|
||||
child: OverflowBox(
|
||||
alignment: Alignment.centerLeft,
|
||||
minWidth: 0,
|
||||
maxWidth: double.infinity,
|
||||
child: SizedBox(
|
||||
width: nameContentWidth,
|
||||
child: Text(
|
||||
data.label,
|
||||
maxLines: 1,
|
||||
softWrap: false,
|
||||
style: contentTextStyle.copyWith(color: palette.textPrimary),
|
||||
child: GestureDetector(
|
||||
key: ValueKey('${widget.keyPrefix}-name-scroll-${data.key}'),
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onHorizontalDragStart: horizontalScrollEnabled ? _startNameDrag : null,
|
||||
onHorizontalDragUpdate: horizontalScrollEnabled
|
||||
? (details) => _horizontalDrag?.update(details)
|
||||
: null,
|
||||
onHorizontalDragEnd: horizontalScrollEnabled ? (details) => _horizontalDrag?.end(details) : null,
|
||||
onHorizontalDragCancel: horizontalScrollEnabled ? () => _horizontalDrag?.cancel() : null,
|
||||
child: ClipRect(
|
||||
child: AnimatedBuilder(
|
||||
animation: _horizontalScrollController,
|
||||
builder: (context, child) {
|
||||
final offset = horizontalScrollEnabled && _horizontalScrollController.hasClients
|
||||
? _horizontalScrollController.offset
|
||||
: 0.0;
|
||||
return Transform.translate(offset: Offset(-offset, 0), child: child);
|
||||
},
|
||||
child: OverflowBox(
|
||||
alignment: Alignment.centerLeft,
|
||||
minWidth: 0,
|
||||
maxWidth: double.infinity,
|
||||
child: SizedBox(
|
||||
width: nameContentWidth,
|
||||
child: Text(
|
||||
data.label,
|
||||
maxLines: 1,
|
||||
softWrap: false,
|
||||
style: contentTextStyle.copyWith(color: palette.textPrimary),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -331,6 +344,12 @@ class _FileTreeViewState<T> extends State<FileTreeView<T>> with SingleTickerProv
|
||||
);
|
||||
}
|
||||
|
||||
void _startNameDrag(DragStartDetails details) {
|
||||
_horizontalDrag?.cancel();
|
||||
if (!_horizontalScrollController.hasClients) return;
|
||||
_horizontalDrag = _horizontalScrollController.position.drag(details, () => _horizontalDrag = null);
|
||||
}
|
||||
|
||||
Widget _buildTreeTransition(BuildContext context, VirtualTreeEntry<FileTreeNode<T>> entry, Widget row) {
|
||||
if (_transitioningNodeKeys.contains(entry.node.data.key)) {
|
||||
return SizeTransition(
|
||||
|
||||
@@ -302,6 +302,11 @@
|
||||
<modifierMask key="keyEquivalentModifierMask"/>
|
||||
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
|
||||
<items>
|
||||
<menuItem title="Close" keyEquivalent="w" id="clo-seW-inD">
|
||||
<connections>
|
||||
<action selector="performClose:" target="-1" id="clo-seA-ctN"/>
|
||||
</connections>
|
||||
</menuItem>
|
||||
<menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
|
||||
<connections>
|
||||
<action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:shadcn_flutter/shadcn_flutter.dart' as shad;
|
||||
import 'package:gopeed/shared/theme/app_theme.dart';
|
||||
import 'package:gopeed/shared/widgets/app_path_placeholder_button.dart';
|
||||
|
||||
void main() {
|
||||
for (final width in [390.0, 1024.0]) {
|
||||
testWidgets('path placeholders explain and insert variables at width $width', (tester) async {
|
||||
tester.view.physicalSize = Size(width, 800);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
final controller = TextEditingController(text: '/downloads/replace');
|
||||
addTearDown(controller.dispose);
|
||||
controller.selection = const TextSelection(baseOffset: 11, extentOffset: 18);
|
||||
await tester.pumpWidget(
|
||||
shad.ShadcnApp(
|
||||
theme: AppTheme.light(),
|
||||
materialTheme: AppTheme.materialLight(),
|
||||
home: Center(child: AppPathPlaceholderButton(controller: controller)),
|
||||
),
|
||||
);
|
||||
await tester.tap(find.text('Insert Placeholder'));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 300));
|
||||
expect(find.text('%year% — Current year'), findsOneWidget);
|
||||
expect(find.text('%month% — Current month (01-12)'), findsOneWidget);
|
||||
expect(find.text('%day% — Current day (01-31)'), findsOneWidget);
|
||||
expect(find.text('%date% — Full date (YYYY-MM-DD)'), findsOneWidget);
|
||||
expect(find.text('e.g. ${DateTime.now().year}'), findsOneWidget);
|
||||
await tester.tap(find.byKey(const ValueKey('path-placeholder-%year%')));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.text, '/downloads/%year%');
|
||||
expect(controller.selection.baseOffset, controller.text.length);
|
||||
expect(find.text('%year% — Current year'), findsNothing);
|
||||
for (final variable in ['%month%', '%day%', '%date%']) {
|
||||
controller.text = '/downloads/';
|
||||
await tester.tap(find.text('Insert Placeholder'));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 300));
|
||||
await tester.tap(find.byKey(ValueKey('path-placeholder-$variable')));
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.text, '/downloads/$variable');
|
||||
}
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2474,11 +2474,17 @@ void main() {
|
||||
expect(find.byType(DownloadCategoriesControl), findsOneWidget);
|
||||
await tester.enterText(find.byKey(const ValueKey('download-category-name')), '归档');
|
||||
await tester.enterText(find.byKey(const ValueKey('download-category-path')), '/tmp/archive');
|
||||
await tester.tap(find.text('Insert Placeholder'));
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 300));
|
||||
expect(find.text('%date% — Full date (YYYY-MM-DD)'), findsOneWidget);
|
||||
await tester.tap(find.byKey(const ValueKey('path-placeholder-%date%')));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Confirm'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('归档'), findsOneWidget);
|
||||
expect(find.text('/tmp/archive'), findsOneWidget);
|
||||
expect(find.text('/tmp/archive%date%'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byKey(const ValueKey('edit-download-category-资料')));
|
||||
await tester.pumpAndSettle();
|
||||
@@ -2963,8 +2969,9 @@ void main() {
|
||||
expect(tester.getRect(proxy).top, greaterThan(tester.getRect(find.text('Proxy')).bottom));
|
||||
final name = find.byKey(const ValueKey('create-task-http-header-name-0'));
|
||||
final value = find.byKey(const ValueKey('create-task-http-header-value-0'));
|
||||
expect(tester.getRect(value).top, greaterThan(tester.getRect(name).bottom));
|
||||
expect(tester.getSize(value).width, tester.getSize(name).width);
|
||||
expect(tester.getRect(value).top, closeTo(tester.getRect(name).top, 0.01));
|
||||
expect(tester.getRect(value).left, greaterThan(tester.getRect(name).right));
|
||||
expect(tester.getSize(value).width / tester.getSize(name).width, closeTo(1.618, 0.01));
|
||||
await tester.ensureVisible(value);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.enterText(find.descendant(of: value, matching: find.byType(EditableText)), 'mobile-agent');
|
||||
@@ -2974,6 +2981,9 @@ void main() {
|
||||
final editor = tester.widget<AppHttpHeadersEditor>(find.byType(AppHttpHeadersEditor));
|
||||
expect(editor.controller.toMap()['User-Agent'], 'mobile-agent');
|
||||
final add = find.byKey(const ValueKey('create-task-http-header-add'));
|
||||
final lastRemove = find.byKey(const ValueKey('create-task-http-header-remove-2'));
|
||||
expect(tester.getCenter(add).dy, closeTo(tester.getCenter(lastRemove).dy, 0.01));
|
||||
expect(tester.getRect(add).left, greaterThan(tester.getRect(lastRemove).right));
|
||||
await tester.ensureVisible(add);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(add);
|
||||
@@ -2990,12 +3000,8 @@ void main() {
|
||||
for (final width in [720.0, 1024.0, 390.0]) {
|
||||
tester.view.physicalSize = Size(width, 844);
|
||||
await tester.pumpAndSettle();
|
||||
if (width >= 720) {
|
||||
expect(tester.getRect(name).top, closeTo(tester.getRect(value).top, 0.01));
|
||||
expect(tester.getRect(value).left, greaterThan(tester.getRect(name).right));
|
||||
} else {
|
||||
expect(tester.getRect(value).top, greaterThan(tester.getRect(name).bottom));
|
||||
}
|
||||
expect(tester.getRect(name).top, closeTo(tester.getRect(value).top, 0.01));
|
||||
expect(tester.getRect(value).left, greaterThan(tester.getRect(name).right));
|
||||
expect(editor.controller.toMap()['User-Agent'], 'mobile-agent');
|
||||
expect(tester.takeException(), isNull);
|
||||
}
|
||||
@@ -3449,6 +3455,20 @@ void main() {
|
||||
expect(position.pixels, greaterThan(0));
|
||||
expect(tester.getRect(find.text(longName)).left, lessThan(nameLeftBefore));
|
||||
expect(tester.getRect(trailing).left, closeTo(trailingLeftBefore, 0.01));
|
||||
|
||||
position.jumpTo(0);
|
||||
await tester.pumpAndSettle();
|
||||
final nameArea = find.byKey(const ValueKey('resolve-tree-name-scroll-file:0'));
|
||||
await tester.drag(nameArea, const Offset(-100, 0));
|
||||
await tester.pumpAndSettle();
|
||||
expect(position.pixels, greaterThan(0));
|
||||
expect(tester.getRect(find.text(longName)).left, lessThan(nameLeftBefore));
|
||||
expect(tester.getRect(trailing).left, closeTo(trailingLeftBefore, 0.01));
|
||||
final previousOffset = position.pixels;
|
||||
await tester.drag(nameArea, const Offset(80, 0));
|
||||
await tester.pumpAndSettle();
|
||||
expect(position.pixels, lessThan(previousOffset));
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
testWidgets('file name scrollbar stays hidden when measured labels fit', (WidgetTester tester) async {
|
||||
|
||||
Reference in new issue
Block a user