diff --git a/lib/common/widgets/flutter/time_picker.dart b/lib/common/widgets/flutter/time_picker.dart new file mode 100644 index 000000000..84fdbec22 --- /dev/null +++ b/lib/common/widgets/flutter/time_picker.dart @@ -0,0 +1,63 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; + +Future showTimePicker({ + required BuildContext context, + required TimeOfDay initialTime, + TransitionBuilder? builder, + bool barrierDismissible = true, + Color? barrierColor, + String? barrierLabel, + bool useRootNavigator = true, + TimePickerEntryMode initialEntryMode = TimePickerEntryMode.dial, + String? cancelText, + String? confirmText, + String? helpText, + String? errorInvalidText, + String? hourLabelText, + String? minuteLabelText, + RouteSettings? routeSettings, + EntryModeChangeCallback? onEntryModeChanged, + Offset? anchorPoint, + Orientation? orientation, + Icon? switchToInputEntryModeIcon, + Icon? switchToTimerEntryModeIcon, + bool emptyInitialInput = false, + BoxConstraints? constraints, +}) { + assert(debugCheckHasMaterialLocalizations(context)); + + final Widget dialog = DialogTheme( + data: const DialogThemeData(constraints: BoxConstraints(minWidth: 280.0)), + child: TimePickerDialog( + initialTime: initialTime, + initialEntryMode: initialEntryMode, + cancelText: cancelText, + confirmText: confirmText, + helpText: helpText, + errorInvalidText: errorInvalidText, + hourLabelText: hourLabelText, + minuteLabelText: minuteLabelText, + orientation: orientation, + onEntryModeChanged: onEntryModeChanged, + switchToInputEntryModeIcon: switchToInputEntryModeIcon, + switchToTimerEntryModeIcon: switchToTimerEntryModeIcon, + emptyInitialInput: emptyInitialInput, + ), + ); + return showDialog( + context: context, + barrierDismissible: barrierDismissible, + barrierColor: barrierColor, + barrierLabel: barrierLabel, + useRootNavigator: useRootNavigator, + builder: (BuildContext context) { + return builder == null ? dialog : builder(context, dialog); + }, + routeSettings: routeSettings, + anchorPoint: anchorPoint, + ); +} diff --git a/lib/common/widgets/image/custom_grid_view.dart b/lib/common/widgets/image/custom_grid_view.dart index 12dad66fb..1ecad26f0 100644 --- a/lib/common/widgets/image/custom_grid_view.dart +++ b/lib/common/widgets/image/custom_grid_view.dart @@ -327,7 +327,12 @@ class _CustomGridViewDelegate extends MultiChildLayoutDelegate { @override void performLayout(Size size) { - final constraints = BoxConstraints.expand(width: width, height: height); + final constraints = BoxConstraints( + minWidth: width, + maxWidth: width, + minHeight: height, + maxHeight: height, + ); for (int i = 0; i < itemCount; i++) { layoutChild(i, constraints); positionChild( diff --git a/lib/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart b/lib/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart index f83f74a7b..d13696f0a 100644 --- a/lib/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart +++ b/lib/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart @@ -124,9 +124,11 @@ class _InteractiveviewerGalleryState extends State ..removeListener(listener) ..dispose(); _transformationController.dispose(); - for (final item in widget.sources) { - if (item.sourceType == SourceType.networkImage) { - CachedNetworkImageProvider(_getActualUrl(item.url)).evict(); + if (widget.quality != _quality) { + for (final item in widget.sources) { + if (item.sourceType == SourceType.networkImage) { + CachedNetworkImageProvider(_getActualUrl(item.url)).evict(); + } } } super.dispose(); diff --git a/lib/pages/dynamics/widgets/action_panel.dart b/lib/pages/dynamics/widgets/action_panel.dart index d0df95245..655b6af94 100644 --- a/lib/pages/dynamics/widgets/action_panel.dart +++ b/lib/pages/dynamics/widgets/action_panel.dart @@ -98,7 +98,7 @@ class ActionPanel extends StatelessWidget { }, child: Text( like.count != null ? NumUtils.numFormat(like.count) : '点赞', - key: ValueKey(like.count?.toString() ?? '点赞'), + key: ValueKey(like.count), style: TextStyle(color: like.status! ? primary : outline), ), ), diff --git a/lib/pages/dynamics_create/view.dart b/lib/pages/dynamics_create/view.dart index bc5ec31ee..c92df8516 100644 --- a/lib/pages/dynamics_create/view.dart +++ b/lib/pages/dynamics_create/view.dart @@ -8,6 +8,7 @@ import 'package:PiliPlus/common/widgets/flutter/draggable_sheet/draggable_scroll as dyn_sheet; import 'package:PiliPlus/common/widgets/flutter/text_field/controller.dart'; import 'package:PiliPlus/common/widgets/flutter/text_field/text_field.dart'; +import 'package:PiliPlus/common/widgets/flutter/time_picker.dart'; import 'package:PiliPlus/common/widgets/pair.dart'; import 'package:PiliPlus/http/dynamics.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -31,7 +32,8 @@ import 'package:PiliPlus/utils/extension/context_ext.dart'; import 'package:PiliPlus/utils/extension/iterable_ext.dart'; import 'package:PiliPlus/utils/grid.dart'; import 'package:PiliPlus/utils/request_utils.dart'; -import 'package:flutter/material.dart' hide DraggableScrollableSheet; +import 'package:flutter/material.dart' + hide DraggableScrollableSheet, showTimePicker; import 'package:flutter/services.dart' show LengthLimitingTextInputFormatter; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; @@ -280,46 +282,45 @@ class _CreateDynPanelState extends CommonRichTextPubPageState { Widget _buildImageList(ThemeData theme) => SizedBox( height: 100, - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Obx( - () => Row( - spacing: 10, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ...List.generate( - imageList.length, - (index) => buildImage(index, 100), - ), - if (imageList.length != limit) - Builder( - builder: (context) { - const borderRadius = StyleString.mdRadius; - return Material( - borderRadius: borderRadius, - child: InkWell( - borderRadius: borderRadius, - onTap: () => onPickImage(() { - if (imageList.isNotEmpty && !enablePublish.value) { - enablePublish.value = true; - } - }), - child: Ink( - width: 100, - height: 100, - decoration: BoxDecoration( - borderRadius: borderRadius, - color: theme.colorScheme.secondaryContainer, - ), - child: const Center(child: Icon(Icons.add, size: 35)), - ), - ), - ); - }, + child: Obx( + () => CustomScrollView( + scrollDirection: Axis.horizontal, + slivers: [ + const SliverToBoxAdapter(child: SizedBox(width: 16)), + if (imageList.isNotEmpty) + SliverPadding( + padding: const .only(right: 10), + sliver: SliverList.separated( + itemCount: imageList.length, + itemBuilder: (context, index) => buildImage(index, 100), + separatorBuilder: (_, _) => const SizedBox(width: 10), ), - ], - ), + ), + if (imageList.length != limit) + SliverToBoxAdapter( + child: Material( + borderRadius: StyleString.mdRadius, + child: InkWell( + borderRadius: StyleString.mdRadius, + onTap: () => onPickImage(() { + if (imageList.isNotEmpty && !enablePublish.value) { + enablePublish.value = true; + } + }), + child: Ink( + width: 100, + height: 100, + decoration: BoxDecoration( + borderRadius: StyleString.mdRadius, + color: theme.colorScheme.secondaryContainer, + ), + child: const Center(child: Icon(Icons.add, size: 35)), + ), + ), + ), + ), + const SliverToBoxAdapter(child: SizedBox(width: 16)), + ], ), ), ); @@ -629,18 +630,18 @@ class _CreateDynPanelState extends CommonRichTextPubPageState { } final color = theme.colorScheme.onSurfaceVariant; - late final gridDelegate = SliverGridDelegateWithExtentAndRatio( - maxCrossAxisExtent: 65, - mainAxisSpacing: 12, - crossAxisSpacing: 12, - mainAxisExtent: 25, - ); return SizedBox( height: height, child: GridView( + physics: const ClampingScrollPhysics(), padding: const EdgeInsets.only(left: 12, bottom: 12, right: 12), - gridDelegate: gridDelegate, + gridDelegate: SliverGridDelegateWithExtentAndRatio( + maxCrossAxisExtent: 65, + mainAxisSpacing: 12, + crossAxisSpacing: 12, + mainAxisExtent: 25, + ), children: [ item( onTap: _onReserve, diff --git a/lib/pages/dynamics_create_reserve/view.dart b/lib/pages/dynamics_create_reserve/view.dart index 169a74fa0..fb77fe480 100644 --- a/lib/pages/dynamics_create_reserve/view.dart +++ b/lib/pages/dynamics_create_reserve/view.dart @@ -1,7 +1,8 @@ +import 'package:PiliPlus/common/widgets/flutter/time_picker.dart'; import 'package:PiliPlus/pages/dynamics_create_reserve/controller.dart'; import 'package:PiliPlus/utils/date_utils.dart'; import 'package:PiliPlus/utils/utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide showTimePicker; import 'package:flutter/services.dart' show TextInputFormatter, LengthLimitingTextInputFormatter; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; diff --git a/lib/pages/dynamics_create_vote/view.dart b/lib/pages/dynamics_create_vote/view.dart index c6a9966ec..e911814cd 100644 --- a/lib/pages/dynamics_create_vote/view.dart +++ b/lib/pages/dynamics_create_vote/view.dart @@ -1,6 +1,7 @@ import 'dart:io' show File; import 'package:PiliPlus/common/widgets/button/icon_button.dart'; +import 'package:PiliPlus/common/widgets/flutter/time_picker.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/models/dynamics/vote_model.dart'; import 'package:PiliPlus/pages/dynamics_create_vote/controller.dart'; @@ -9,7 +10,7 @@ import 'package:PiliPlus/utils/extension/file_ext.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:easy_debounce/easy_throttle.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide showTimePicker; import 'package:flutter/services.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; diff --git a/lib/pages/dynamics_detail/controller.dart b/lib/pages/dynamics_detail/controller.dart index 0d5dc6673..1d5a31f7c 100644 --- a/lib/pages/dynamics_detail/controller.dart +++ b/lib/pages/dynamics_detail/controller.dart @@ -69,7 +69,7 @@ class DynamicDetailController extends CommonDynController { action: action, ); if (res.isSuccess) { - await Future.delayed(const Duration(milliseconds: 500), () { + Future.delayed(const Duration(milliseconds: 500), () { if (!isClosed) { onReload(); } diff --git a/lib/pages/emote/view.dart b/lib/pages/emote/view.dart index 6f5d168bb..1ea9d1813 100644 --- a/lib/pages/emote/view.dart +++ b/lib/pages/emote/view.dart @@ -68,6 +68,7 @@ class _EmotePanelState extends State final size = flag ? 40.0 : 60.0; final isTextEmote = e.type == 4; return GridView.builder( + physics: const ClampingScrollPhysics(), padding: const EdgeInsets.only( left: 12, right: 12, diff --git a/lib/pages/fav_create/view.dart b/lib/pages/fav_create/view.dart index 7e5abe933..a2951cdc4 100644 --- a/lib/pages/fav_create/view.dart +++ b/lib/pages/fav_create/view.dart @@ -174,9 +174,11 @@ class _CreateFavPageState extends State { final leadingStyle = const TextStyle(fontSize: 14); Widget _buildBody(ThemeData theme) => SingleChildScrollView( + padding: .only(bottom: MediaQuery.viewPaddingOf(context).bottom + 25), child: Column( + spacing: 12, children: [ - if (_attr == null || !FavUtils.isDefaultFav(_attr!)) ...[ + if (_attr == null || !FavUtils.isDefaultFav(_attr!)) Builder( builder: (context) { return ListTile( @@ -260,8 +262,6 @@ class _CreateFavPageState extends State { ); }, ), - const SizedBox(height: 16), - ], ListTile( tileColor: theme.colorScheme.onInverseSurface, title: Row( @@ -318,8 +318,7 @@ class _CreateFavPageState extends State { ], ), ), - const SizedBox(height: 16), - if (_attr == null || !FavUtils.isDefaultFav(_attr!)) ...[ + if (_attr == null || !FavUtils.isDefaultFav(_attr!)) ListTile( tileColor: theme.colorScheme.onInverseSurface, title: Row( @@ -362,8 +361,6 @@ class _CreateFavPageState extends State { ], ), ), - const SizedBox(height: 16), - ], Builder( builder: (context) { void onTap() { @@ -389,7 +386,6 @@ class _CreateFavPageState extends State { ); }, ), - const SizedBox(height: 16), ], ), ); diff --git a/lib/pages/live_emote/view.dart b/lib/pages/live_emote/view.dart index b8dacb80f..a4dbe119a 100644 --- a/lib/pages/live_emote/view.dart +++ b/lib/pages/live_emote/view.dart @@ -75,6 +75,7 @@ class _LiveEmotePanelState extends State final width = widthFac * 38; final height = heightFac * 38; return GridView.builder( + physics: const ClampingScrollPhysics(), padding: const EdgeInsets.only( left: 12, right: 12, diff --git a/lib/pages/member_dynamics/controller.dart b/lib/pages/member_dynamics/controller.dart index 15d54817b..7e867bae0 100644 --- a/lib/pages/member_dynamics/controller.dart +++ b/lib/pages/member_dynamics/controller.dart @@ -66,13 +66,17 @@ class MemberDynamicsController : DynamicsHttp.setTop(dynamicId: dynamicId)); if (res.isSuccess) { List list = loadingState.value.data!; - list[0].modules.moduleTag = null; + list[0].modules + ..moduleTag = null + ..moduleAuthor?.isTop = false; if (isTop) { loadingState.refresh(); SmartDialog.showToast('取消置顶成功'); } else { final item = list.firstWhere((item) => item.idStr == dynamicId); - item.modules.moduleTag = ModuleTag(text: '置顶'); + item.modules + ..moduleTag = ModuleTag(text: '置顶') + ..moduleAuthor?.isTop = true; list ..remove(item) ..insert(0, item); diff --git a/lib/pages/setting/extra_setting.dart b/lib/pages/setting/extra_setting.dart index 1784a233c..0c6343e0c 100644 --- a/lib/pages/setting/extra_setting.dart +++ b/lib/pages/setting/extra_setting.dart @@ -20,13 +20,14 @@ class _ExtraSettingState extends State { return Scaffold( resizeToAvoidBottomInset: false, appBar: showAppBar ? AppBar(title: const Text('其它设置')) : null, - body: ListView( + body: ListView.builder( padding: EdgeInsets.only( left: showAppBar ? padding.left : 0, right: showAppBar ? padding.right : 0, bottom: padding.bottom + 100, ), - children: settings.map((item) => item.widget).toList(), + itemCount: settings.length, + itemBuilder: (context, index) => settings[index].widget, ), ); } diff --git a/lib/pages/setting/play_setting.dart b/lib/pages/setting/play_setting.dart index 847118539..6c97855a5 100644 --- a/lib/pages/setting/play_setting.dart +++ b/lib/pages/setting/play_setting.dart @@ -20,13 +20,14 @@ class _PlaySettingState extends State { return Scaffold( resizeToAvoidBottomInset: false, appBar: showAppBar ? AppBar(title: const Text('播放器设置')) : null, - body: ListView( + body: ListView.builder( padding: EdgeInsets.only( left: showAppBar ? padding.left : 0, right: showAppBar ? padding.right : 0, bottom: padding.bottom + 100, ), - children: settings.map((item) => item.widget).toList(), + itemCount: settings.length, + itemBuilder: (context, index) => settings[index].widget, ), ); } diff --git a/lib/pages/setting/style_setting.dart b/lib/pages/setting/style_setting.dart index 0294f7023..1b13350d7 100644 --- a/lib/pages/setting/style_setting.dart +++ b/lib/pages/setting/style_setting.dart @@ -20,13 +20,14 @@ class _StyleSettingState extends State { return Scaffold( resizeToAvoidBottomInset: false, appBar: showAppBar ? AppBar(title: const Text('外观设置')) : null, - body: ListView( + body: ListView.builder( padding: EdgeInsets.only( left: showAppBar ? padding.left : 0, right: showAppBar ? padding.right : 0, bottom: padding.bottom + 100, ), - children: settings.map((item) => item.widget).toList(), + itemCount: settings.length, + itemBuilder: (context, index) => settings[index].widget, ), ); } diff --git a/lib/pages/setting/video_setting.dart b/lib/pages/setting/video_setting.dart index 131899931..495720aa6 100644 --- a/lib/pages/setting/video_setting.dart +++ b/lib/pages/setting/video_setting.dart @@ -20,13 +20,14 @@ class _VideoSettingState extends State { return Scaffold( resizeToAvoidBottomInset: false, appBar: showAppBar ? AppBar(title: const Text('音视频设置')) : null, - body: ListView( + body: ListView.builder( padding: EdgeInsets.only( left: showAppBar ? padding.left : 0, right: showAppBar ? padding.right : 0, bottom: padding.bottom + 100, ), - children: settings.map((item) => item.widget).toList(), + itemCount: settings.length, + itemBuilder: (context, index) => settings[index].widget, ), ); } diff --git a/lib/pages/setting/widgets/select_dialog.dart b/lib/pages/setting/widgets/select_dialog.dart index c532f58c4..b0b27d3be 100644 --- a/lib/pages/setting/widgets/select_dialog.dart +++ b/lib/pages/setting/widgets/select_dialog.dart @@ -247,7 +247,7 @@ class _CdnSelectDialogState extends State { valueListenable: item, builder: (context, value, _) { return Text( - item.value ?? '---', + value ?? '---', style: const TextStyle(fontSize: 13), maxLines: 1, overflow: TextOverflow.ellipsis, diff --git a/lib/pages/settings_search/view.dart b/lib/pages/settings_search/view.dart index 5b399c991..ba69b26b5 100644 --- a/lib/pages/settings_search/view.dart +++ b/lib/pages/settings_search/view.dart @@ -66,7 +66,7 @@ class _SettingsSearchPageState onPressed: () { if (_textEditingController.text.isNotEmpty) { _textEditingController.clear(); - _list.value = []; + _list.clear(); } else { Get.back(); } diff --git a/lib/pages/video/pay_coins/view.dart b/lib/pages/video/pay_coins/view.dart index 1b5314ac1..4f815a2bc 100644 --- a/lib/pages/video/pay_coins/view.dart +++ b/lib/pages/video/pay_coins/view.dart @@ -82,7 +82,7 @@ class _PayCoinsPageState extends State Timer? _timer; late final RxInt _thunderIndex = (-1).obs; - late final List _thunderImages = const [ + static const List _thunderImages = [ 'assets/images/paycoins/ic_thunder_1.png', 'assets/images/paycoins/ic_thunder_2.png', 'assets/images/paycoins/ic_thunder_3.png', diff --git a/lib/pages/video/reply_new/view.dart b/lib/pages/video/reply_new/view.dart index cd227be92..88851906d 100644 --- a/lib/pages/video/reply_new/view.dart +++ b/lib/pages/video/reply_new/view.dart @@ -115,17 +115,12 @@ class _ReplyPageState extends CommonRichTextPubPageState { if (imageList.isNotEmpty) { return SizedBox( height: 85, - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - padding: const EdgeInsets.fromLTRB(15, 0, 15, 10), - child: Row( - spacing: 10, - crossAxisAlignment: CrossAxisAlignment.start, - children: List.generate( - imageList.length, - (index) => buildImage(index, 75), - ), - ), + child: ListView.separated( + scrollDirection: .horizontal, + padding: const .fromLTRB(15, 0, 15, 10), + itemCount: imageList.length, + itemBuilder: (_, index) => buildImage(index, 75), + separatorBuilder: (_, _) => const SizedBox(width: 10), ), ); } else { @@ -314,6 +309,7 @@ class _ReplyPageState extends CommonRichTextPubPageState { return SizedBox( height: height, child: GridView( + physics: const ClampingScrollPhysics(), padding: const EdgeInsets.only(left: 12, bottom: 12, right: 12), gridDelegate: gridDelegate, children: [ diff --git a/lib/pages/video/send_danmaku/view.dart b/lib/pages/video/send_danmaku/view.dart index 4bc9090f9..68018e228 100644 --- a/lib/pages/video/send_danmaku/view.dart +++ b/lib/pages/video/send_danmaku/view.dart @@ -179,62 +179,62 @@ class _SendDanmakuPanelState extends CommonTextPubPageState { ), ), ), - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 12), - Row( - children: [ - Text( - '弹幕字号', - style: TextStyle( - fontSize: 15, - color: themeData.colorScheme.onSurface, - ), - ), - const SizedBox(width: 16), - _buildFontSizeItem(18, '小'), - const SizedBox(width: 5), - _buildFontSizeItem(25, '标准'), - ], - ), - const SizedBox(height: 12), - Row( - children: [ - Text( - '弹幕样式', - style: TextStyle( - fontSize: 15, - color: themeData.colorScheme.onSurface, - ), - ), - const SizedBox(width: 16), - _buildPositionItem(1, '滚动'), - const SizedBox(width: 5), - _buildPositionItem(5, '顶部'), - const SizedBox(width: 5), - _buildPositionItem(4, '底部'), - ], - ), - const SizedBox(height: 12), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '弹幕颜色', - style: TextStyle( - fontSize: 15, - color: themeData.colorScheme.onSurface, - ), - ), - const SizedBox(width: 16), - _buildColorPanel, - ], - ), - SizedBox(height: 12 + MediaQuery.viewPaddingOf(context).bottom), - ], + child: ListView( + physics: const ClampingScrollPhysics(), + padding: .only( + top: 12, + bottom: 12 + MediaQuery.viewPaddingOf(context).bottom, ), + children: [ + Row( + children: [ + Text( + '弹幕字号', + style: TextStyle( + fontSize: 15, + color: themeData.colorScheme.onSurface, + ), + ), + const SizedBox(width: 16), + _buildFontSizeItem(18, '小'), + const SizedBox(width: 5), + _buildFontSizeItem(25, '标准'), + ], + ), + const SizedBox(height: 12), + Row( + children: [ + Text( + '弹幕样式', + style: TextStyle( + fontSize: 15, + color: themeData.colorScheme.onSurface, + ), + ), + const SizedBox(width: 16), + _buildPositionItem(1, '滚动'), + const SizedBox(width: 5), + _buildPositionItem(5, '顶部'), + const SizedBox(width: 5), + _buildPositionItem(4, '底部'), + ], + ), + const SizedBox(height: 12), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '弹幕颜色', + style: TextStyle( + fontSize: 15, + color: themeData.colorScheme.onSurface, + ), + ), + const SizedBox(width: 16), + _buildColorPanel, + ], + ), + ], ), );