diff --git a/lib/common/skeleton/dynamic_card.dart b/lib/common/skeleton/dynamic_card.dart index ddbc80260..063e8c468 100644 --- a/lib/common/skeleton/dynamic_card.dart +++ b/lib/common/skeleton/dynamic_card.dart @@ -21,6 +21,7 @@ class DynamicCardSkeleton extends StatelessWidget { ), ), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ @@ -51,44 +52,36 @@ class DynamicCardSkeleton extends StatelessWidget { ), ], ), + const SizedBox(height: 10), Container( + color: color, width: double.infinity, - margin: const EdgeInsets.only(top: 10), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - color: color, - width: double.infinity, - height: 13, - margin: const EdgeInsets.only(bottom: 7), - ), - Container( - color: color, - width: double.infinity, - height: 13, - margin: const EdgeInsets.only(bottom: 7), - ), - Container( - color: color, - width: 300, - height: 13, - margin: const EdgeInsets.only(bottom: 7), - ), - Container( - color: color, - width: 250, - height: 13, - margin: const EdgeInsets.only(bottom: 7), - ), - Container( - color: color, - width: 100, - height: 13, - margin: const EdgeInsets.only(bottom: 7), - ), - ], - ), + height: 13, + margin: const EdgeInsets.only(bottom: 7), + ), + Container( + color: color, + width: double.infinity, + height: 13, + margin: const EdgeInsets.only(bottom: 7), + ), + Container( + color: color, + width: 300, + height: 13, + margin: const EdgeInsets.only(bottom: 7), + ), + Container( + color: color, + width: 250, + height: 13, + margin: const EdgeInsets.only(bottom: 7), + ), + Container( + color: color, + width: 100, + height: 13, + margin: const EdgeInsets.only(bottom: 7), ), if (GlobalData().dynamicsWaterfallFlow) const Spacer(), Row( diff --git a/lib/common/skeleton/video_reply.dart b/lib/common/skeleton/video_reply.dart index e04088c0e..849064bd4 100644 --- a/lib/common/skeleton/video_reply.dart +++ b/lib/common/skeleton/video_reply.dart @@ -30,9 +30,8 @@ class VideoReplySkeleton extends StatelessWidget { ], ), ), - Container( - width: double.infinity, - margin: const EdgeInsets.only( + Padding( + padding: const EdgeInsets.only( top: 4, left: 57, right: 6, diff --git a/lib/common/widgets/custom_sliver_persistent_header_delegate.dart b/lib/common/widgets/custom_sliver_persistent_header_delegate.dart index 993f69b90..c49ec8c33 100644 --- a/lib/common/widgets/custom_sliver_persistent_header_delegate.dart +++ b/lib/common/widgets/custom_sliver_persistent_header_delegate.dart @@ -6,14 +6,14 @@ class CustomSliverPersistentHeaderDelegate required this.child, required this.bgColor, double extent = 45, - this.needRebuild, + this.needRebuild = false, }) : _minExtent = extent, _maxExtent = extent; final double _minExtent; final double _maxExtent; final Widget child; final Color? bgColor; - final bool? needRebuild; + final bool needRebuild; @override Widget build( @@ -26,15 +26,7 @@ class CustomSliverPersistentHeaderDelegate //overlapsContent:SliverPersistentHeader覆盖其他子组件返回true,否则返回false return bgColor != null ? DecoratedBox( - decoration: BoxDecoration( - color: bgColor, - boxShadow: [ - BoxShadow( - color: bgColor!, - offset: const Offset(0, -2), - ), - ], - ), + decoration: BoxDecoration(color: bgColor), child: child, ) : child; @@ -51,6 +43,6 @@ class CustomSliverPersistentHeaderDelegate @override bool shouldRebuild(CustomSliverPersistentHeaderDelegate oldDelegate) { return oldDelegate.bgColor != bgColor || - (needRebuild == true && oldDelegate.child != child); + (needRebuild && oldDelegate.child != child); } } diff --git a/lib/common/widgets/dialog/dialog.dart b/lib/common/widgets/dialog/dialog.dart index 99823fd61..b5325c398 100644 --- a/lib/common/widgets/dialog/dialog.dart +++ b/lib/common/widgets/dialog/dialog.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -void showConfirmDialog({ +Future showConfirmDialog({ required BuildContext context, required String title, dynamic content, required VoidCallback onConfirm, }) { - showDialog( + return showDialog( context: context, builder: (context) { return AlertDialog( diff --git a/lib/common/widgets/dynamic_sliver_appbar_medium.dart b/lib/common/widgets/dynamic_sliver_appbar_medium.dart index be8d03d9f..237b33194 100644 --- a/lib/common/widgets/dynamic_sliver_appbar_medium.dart +++ b/lib/common/widgets/dynamic_sliver_appbar_medium.dart @@ -113,18 +113,14 @@ class _DynamicSliverAppBarMediumState extends State { }); } - Orientation? _orientation; - late Size size; + double? _width; @override void didChangeDependencies() { super.didChangeDependencies(); - size = MediaQuery.sizeOf(context); - final orientation = size.width > size.height - ? Orientation.landscape - : Orientation.portrait; - if (orientation != _orientation) { - _orientation = orientation; + final width = MediaQuery.widthOf(context); + if (_width != width) { + _width = width; _height = 0; _updateHeight(); } @@ -139,7 +135,7 @@ class _DynamicSliverAppBarMediumState extends State { alignment: Alignment.topLeft, child: SizedBox( key: _childKey, - width: size.width, + width: _width, child: widget.flexibleSpace, ), ), diff --git a/lib/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart b/lib/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart index d2da8e7ac..e0188dcf4 100644 --- a/lib/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart +++ b/lib/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart @@ -11,7 +11,6 @@ import 'package:PiliPlus/utils/utils.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:media_kit/media_kit.dart'; import 'package:media_kit_video/media_kit_video.dart'; @@ -48,7 +47,6 @@ class InteractiveviewerGallery extends StatefulWidget { this.minScale = 1.0, this.onPageChanged, this.onDismissed, - this.setStatusBar = true, this.onClose, required this.quality, }); @@ -57,8 +55,6 @@ class InteractiveviewerGallery extends StatefulWidget { final ValueChanged? onClose; - final bool setStatusBar; - /// The sources to show. final List sources; @@ -114,10 +110,6 @@ class _InteractiveviewerGalleryState extends State duration: const Duration(milliseconds: 300), )..addListener(listener); - if (widget.setStatusBar) { - setStatusBar(); - } - var item = widget.sources[currentIndex.value]; if (item.sourceType == SourceType.livePhoto) { _onPlay(item.liveUrl!); @@ -128,18 +120,6 @@ class _InteractiveviewerGalleryState extends State _transformationController!.value = _animation?.value ?? Matrix4.identity(); } - SystemUiMode? mode; - Future setStatusBar() async { - if (Platform.isIOS || Platform.isAndroid) { - SystemChrome.setEnabledSystemUIMode( - SystemUiMode.immersiveSticky, - ); - } - if (Platform.isAndroid && (await Utils.sdkInt < 29)) { - mode = SystemUiMode.manual; - } - } - @override void dispose() { widget.onClose?.call(true); @@ -148,14 +128,6 @@ class _InteractiveviewerGalleryState extends State _animationController ..removeListener(listener) ..dispose(); - if (widget.setStatusBar) { - if (Platform.isIOS || Platform.isAndroid) { - SystemChrome.setEnabledSystemUIMode( - mode ?? SystemUiMode.edgeToEdge, - overlays: SystemUiOverlay.values, - ); - } - } for (var item in widget.sources) { if (item.sourceType == SourceType.networkImage) { CachedNetworkImageProvider(_getActualUrl(item.url)).evict(); diff --git a/lib/common/widgets/list_tile.dart b/lib/common/widgets/list_tile.dart new file mode 100644 index 000000000..31e80a68a --- /dev/null +++ b/lib/common/widgets/list_tile.dart @@ -0,0 +1,1857 @@ +// 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. + +// ignore_for_file: uri_does_not_exist_in_doc_import + +/// @docImport 'card.dart'; +/// @docImport 'checkbox.dart'; +/// @docImport 'checkbox_list_tile.dart'; +/// @docImport 'circle_avatar.dart'; +/// @docImport 'drawer.dart'; +/// @docImport 'expansion_tile.dart'; +/// @docImport 'material.dart'; +/// @docImport 'radio.dart'; +/// @docImport 'radio_list_tile.dart'; +/// @docImport 'scaffold.dart'; +/// @docImport 'switch.dart'; +/// @docImport 'switch_list_tile.dart'; +library; + +import 'dart:math' as math; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +// Examples can assume: +// int _act = 1; + +typedef _Sizes = ({ + double titleY, + BoxConstraints textConstraints, + Size tileSize, +}); +typedef _PositionChild = void Function(RenderBox child, Offset offset); + +/// Defines how [ListTile.leading] and [ListTile.trailing] are +/// vertically aligned relative to the [ListTile]'s titles +/// ([ListTile.title] and [ListTile.subtitle]). +/// +/// See also: +/// +/// * [ListTile.titleAlignment], to configure the title alignment for an +/// individual [ListTile]. +/// * [ListTileThemeData.titleAlignment], to configure the title alignment +/// for all of the [ListTile]s under a [ListTileTheme]. +/// * [ThemeData.listTileTheme], to configure the [ListTileTheme] +/// for an entire app. +extension on ListTileTitleAlignment { + // If isLeading is true the y offset is for the leading widget, otherwise it's + // for the trailing child. + double _yOffsetFor( + double childHeight, + double tileHeight, + _RenderListTile listTile, + bool isLeading, + ) { + return switch (this) { + ListTileTitleAlignment.threeLine => + listTile.isThreeLine + ? ListTileTitleAlignment.top._yOffsetFor( + childHeight, + tileHeight, + listTile, + isLeading, + ) + : ListTileTitleAlignment.center._yOffsetFor( + childHeight, + tileHeight, + listTile, + isLeading, + ), + // This attempts to implement the redlines for the vertical position of the + // leading and trailing icons on the spec page: + // https://m2.material.io/components/lists#specs + // + // For large tiles (> 72dp), both leading and trailing controls should be + // a fixed distance from top. As per guidelines this is set to 16dp. + ListTileTitleAlignment.titleHeight when tileHeight > 72.0 => 16.0, + // For smaller tiles, trailing should always be centered. Leading can be + // centered or closer to the top. It should never be further than 16dp + // to the top. + ListTileTitleAlignment.titleHeight => + isLeading + ? math.min((tileHeight - childHeight) / 2.0, 16.0) + : (tileHeight - childHeight) / 2.0, + ListTileTitleAlignment.top => listTile.minVerticalPadding, + ListTileTitleAlignment.center => (tileHeight - childHeight) / 2.0, + ListTileTitleAlignment.bottom => + tileHeight - childHeight - listTile.minVerticalPadding, + }; + } +} + +/// A single fixed-height row that typically contains some text as well as +/// a leading or trailing icon. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=l8dj0yPBvgQ} +/// +/// A list tile contains one to three lines of text optionally flanked by icons or +/// other widgets, such as check boxes. The icons (or other widgets) for the +/// tile are defined with the [leading] and [trailing] parameters. The first +/// line of text is not optional and is specified with [title]. The value of +/// [subtitle], which _is_ optional, will occupy the space allocated for an +/// additional line of text, or two lines if [isThreeLine] is true. If [dense] +/// is true then the overall height of this tile and the size of the +/// [DefaultTextStyle]s that wrap the [title] and [subtitle] widget are reduced. +/// +/// It is the responsibility of the caller to ensure that [title] does not wrap, +/// and to ensure that [subtitle] doesn't wrap (if [isThreeLine] is false) or +/// wraps to two lines (if it is true). +/// +/// The heights of the [leading] and [trailing] widgets are constrained +/// according to the +/// [Material spec](https://material.io/design/components/lists.html). +/// An exception is made for one-line ListTiles for accessibility. Please +/// see the example below to see how to adhere to both Material spec and +/// accessibility requirements. +/// +/// The [leading] and [trailing] widgets can expand as far as they wish +/// horizontally, so ensure that they are properly constrained. +/// +/// List tiles are typically used in [ListView]s, or arranged in [Column]s in +/// [Drawer]s and [Card]s. +/// +/// This widget requires a [Material] widget ancestor in the tree to paint +/// itself on, which is typically provided by the app's [Scaffold]. +/// The [tileColor], [selectedTileColor], [focusColor], and [hoverColor] +/// are not painted by the [ListTile] itself but by the [Material] widget +/// ancestor. In this case, one can wrap a [Material] widget around the +/// [ListTile], e.g.: +/// +/// {@tool snippet} +/// ```dart +/// const ColoredBox( +/// color: Colors.green, +/// child: Material( +/// child: ListTile( +/// title: Text('ListTile with red background'), +/// tileColor: Colors.red, +/// ), +/// ), +/// ) +/// ``` +/// {@end-tool} +/// +/// ## Performance considerations when wrapping [ListTile] with [Material] +/// +/// Wrapping a large number of [ListTile]s individually with [Material]s +/// is expensive. Consider only wrapping the [ListTile]s that require it +/// or include a common [Material] ancestor where possible. +/// +/// [ListTile] must be wrapped in a [Material] widget to animate [tileColor], +/// [selectedTileColor], [focusColor], and [hoverColor] as these colors +/// are not drawn by the list tile itself but by the material widget ancestor. +/// +/// {@tool dartpad} +/// This example showcases how [ListTile] needs to be wrapped in a [Material] +/// widget to animate colors. +/// +/// ** See code in examples/api/lib/material/list_tile/list_tile.0.dart ** +/// {@end-tool} +/// +/// {@tool dartpad} +/// This example uses a [ListView] to demonstrate different configurations of +/// [ListTile]s in [Card]s. +/// +/// ![Different variations of ListTile](https://flutter.github.io/assets-for-api-docs/assets/material/list_tile.png) +/// +/// ** See code in examples/api/lib/material/list_tile/list_tile.1.dart ** +/// {@end-tool} +/// +/// {@tool dartpad} +/// This sample shows the creation of a [ListTile] using [ThemeData.useMaterial3] flag, +/// as described in: https://m3.material.io/components/lists/overview. +/// +/// ** See code in examples/api/lib/material/list_tile/list_tile.2.dart ** +/// {@end-tool} +/// +/// {@tool dartpad} +/// This sample shows [ListTile]'s [textColor] and [iconColor] can use +/// [WidgetStateColor] color to change the color of the text and icon +/// when the [ListTile] is enabled, selected, or disabled. +/// +/// ** See code in examples/api/lib/material/list_tile/list_tile.3.dart ** +/// {@end-tool} +/// +/// {@tool dartpad} +/// This sample shows [ListTile.titleAlignment] can be used to configure the +/// [leading] and [trailing] widgets alignment relative to the [title] and +/// [subtitle] widgets. +/// +/// ** See code in examples/api/lib/material/list_tile/list_tile.4.dart ** +/// {@end-tool} +/// +/// {@tool snippet} +/// To use a [ListTile] within a [Row], it needs to be wrapped in an +/// [Expanded] widget. [ListTile] requires fixed width constraints, +/// whereas a [Row] does not constrain its children. +/// +/// ```dart +/// const Row( +/// children: [ +/// Expanded( +/// child: ListTile( +/// leading: FlutterLogo(), +/// title: Text('These ListTiles are expanded '), +/// ), +/// ), +/// Expanded( +/// child: ListTile( +/// trailing: FlutterLogo(), +/// title: Text('to fill the available space.'), +/// ), +/// ), +/// ], +/// ) +/// ``` +/// {@end-tool} +/// {@tool snippet} +/// +/// Tiles can be much more elaborate. Here is a tile which can be tapped, but +/// which is disabled when the `_act` variable is not 2. When the tile is +/// tapped, the whole row has an ink splash effect (see [InkWell]). +/// +/// ```dart +/// ListTile( +/// leading: const Icon(Icons.flight_land), +/// title: const Text("Trix's airplane"), +/// subtitle: _act != 2 ? const Text('The airplane is only in Act II.') : null, +/// enabled: _act == 2, +/// onTap: () { /* react to the tile being tapped */ } +/// ) +/// ``` +/// {@end-tool} +/// +/// To be accessible, tappable [leading] and [trailing] widgets have to +/// be at least 48x48 in size. However, to adhere to the Material spec, +/// [trailing] and [leading] widgets in one-line ListTiles should visually be +/// at most 32 ([dense]: true) or 40 ([dense]: false) in height, which may +/// conflict with the accessibility requirement. +/// +/// For this reason, a one-line ListTile allows the height of [leading] +/// and [trailing] widgets to be constrained by the height of the ListTile. +/// This allows for the creation of tappable [leading] and [trailing] widgets +/// that are large enough, but it is up to the developer to ensure that +/// their widgets follow the Material spec. +/// +/// {@tool snippet} +/// +/// Here is an example of a one-line, non-[dense] ListTile with a +/// tappable leading widget that adheres to accessibility requirements and +/// the Material spec. To adjust the use case below for a one-line, [dense] +/// ListTile, adjust the vertical padding to 8.0. +/// +/// ```dart +/// ListTile( +/// leading: GestureDetector( +/// behavior: HitTestBehavior.translucent, +/// onTap: () {}, +/// child: Container( +/// width: 48, +/// height: 48, +/// padding: const EdgeInsets.symmetric(vertical: 4.0), +/// alignment: Alignment.center, +/// child: const CircleAvatar(), +/// ), +/// ), +/// title: const Text('title'), +/// dense: false, +/// ) +/// ``` +/// {@end-tool} +/// +/// ## The ListTile layout isn't exactly what I want +/// +/// If the way ListTile pads and positions its elements isn't quite what +/// you're looking for, it's easy to create custom list items with a +/// combination of other widgets, such as [Row]s and [Column]s. +/// +/// {@tool dartpad} +/// Here is an example of a custom list item that resembles a YouTube-related +/// video list item created with [Expanded] and [Container] widgets. +/// +/// ** See code in examples/api/lib/material/list_tile/custom_list_item.0.dart ** +/// {@end-tool} +/// +/// {@tool dartpad} +/// Here is an example of an article list item with multiline titles and +/// subtitles. It utilizes [Row]s and [Column]s, as well as [Expanded] and +/// [AspectRatio] widgets to organize its layout. +/// +/// ** See code in examples/api/lib/material/list_tile/custom_list_item.1.dart ** +/// {@end-tool} +/// +/// See also: +/// +/// * [ListTileTheme], which defines visual properties for [ListTile]s. +/// * [ListView], which can display an arbitrary number of [ListTile]s +/// in a scrolling list. +/// * [CircleAvatar], which shows an icon representing a person and is often +/// used as the [leading] element of a ListTile. +/// * [Card], which can be used with [Column] to show a few [ListTile]s. +/// * [Divider], which can be used to separate [ListTile]s. +/// * [ListTile.divideTiles], a utility for inserting [Divider]s in between [ListTile]s. +/// * [CheckboxListTile], [RadioListTile], and [SwitchListTile], widgets +/// that combine [ListTile] with other controls. +/// * Material 3 [ListTile] specifications are referenced from +/// and Material 2 [ListTile] specifications are referenced from +/// * Cookbook: [Use lists](https://docs.flutter.dev/cookbook/lists/basic-list) +/// * Cookbook: [Implement swipe to dismiss](https://docs.flutter.dev/cookbook/gestures/dismissible) +class ListTile extends StatelessWidget { + /// Creates a list tile. + /// + /// If [isThreeLine] is true, then [subtitle] must not be null. + /// + /// Requires one of its ancestors to be a [Material] widget. + const ListTile({ + super.key, + this.leading, + this.title, + this.subtitle, + this.trailing, + this.isThreeLine, + this.dense, + this.visualDensity, + this.shape, + this.style, + this.selectedColor, + this.iconColor, + this.textColor, + this.titleTextStyle, + this.subtitleTextStyle, + this.leadingAndTrailingTextStyle, + this.contentPadding, + this.enabled = true, + this.onTap, + this.onLongPress, + this.onFocusChange, + this.mouseCursor, + this.selected = false, + this.focusColor, + this.hoverColor, + this.splashColor, + this.focusNode, + this.autofocus = false, + this.tileColor, + this.selectedTileColor, + this.enableFeedback, + this.horizontalTitleGap, + this.minVerticalPadding, + this.minLeadingWidth, + this.minTileHeight, + this.titleAlignment, + this.internalAddSemanticForOnTap = true, + this.statesController, + }) : assert(isThreeLine != true || subtitle != null); + + /// A widget to display before the title. + /// + /// Typically an [Icon] or a [CircleAvatar] widget. + final Widget? leading; + + /// The primary content of the list tile. + /// + /// Typically a [Text] widget. + /// + /// This should not wrap. To enforce the single line limit, use + /// [Text.maxLines]. + final Widget? title; + + /// Additional content displayed below the title. + /// + /// Typically a [Text] widget. + /// + /// If [isThreeLine] is false, this should not wrap. + /// + /// If [isThreeLine] is true, this should be configured to take a maximum of + /// two lines. For example, you can use [Text.maxLines] to enforce the number + /// of lines. + /// + /// The subtitle's default [TextStyle] depends on [TextTheme.bodyMedium] except + /// [TextStyle.color]. The [TextStyle.color] depends on the value of [enabled] + /// and [selected]. + /// + /// When [enabled] is false, the text color is set to [ThemeData.disabledColor]. + /// + /// When [selected] is false, the text color is set to [ListTileTheme.textColor] + /// if it's not null and to [TextTheme.bodySmall]'s color if [ListTileTheme.textColor] + /// is null. + final Widget? subtitle; + + /// A widget to display after the title. + /// + /// Typically an [Icon] widget. + /// + /// To show right-aligned metadata (assuming left-to-right reading order; + /// left-aligned for right-to-left reading order), consider using a [Row] with + /// [CrossAxisAlignment.baseline] alignment whose first item is [Expanded] and + /// whose second child is the metadata text, instead of using the [trailing] + /// property. + final Widget? trailing; + + /// Whether this list tile is intended to display three lines of text. + /// + /// If true, then [subtitle] must be non-null (since it is expected to give + /// the second and third lines of text). + /// + /// If false, the list tile is treated as having one line if the subtitle is + /// null and treated as having two lines if the subtitle is non-null. + /// + /// When using a [Text] widget for [title] and [subtitle], you can enforce + /// line limits using [Text.maxLines]. + /// + /// See also: + /// + /// * [ListTileTheme.of], which returns the nearest [ListTileTheme]'s + /// [ListTileThemeData]. + final bool? isThreeLine; + + /// {@template flutter.material.ListTile.dense} + /// Whether this list tile is part of a vertically dense list. + /// + /// If this property is null then its value is based on [ListTileTheme.dense]. + /// + /// Dense list tiles default to a smaller height. + /// + /// It is not recommended to set [dense] to true when [ThemeData.useMaterial3] is true. + /// {@endtemplate} + final bool? dense; + + /// Defines how compact the list tile's layout will be. + /// + /// {@macro flutter.material.themedata.visualDensity} + /// + /// See also: + /// + /// * [ThemeData.visualDensity], which specifies the [visualDensity] for all + /// widgets within a [Theme]. + final VisualDensity? visualDensity; + + /// {@template flutter.material.ListTile.shape} + /// Defines the tile's [InkWell.customBorder] and [Ink.decoration] shape. + /// {@endtemplate} + /// + /// If this property is null then [ListTileThemeData.shape] is used. If that + /// is also null then a rectangular [Border] will be used. + /// + /// See also: + /// + /// * [ListTileTheme.of], which returns the nearest [ListTileTheme]'s + /// [ListTileThemeData]. + final ShapeBorder? shape; + + /// Defines the color used for icons and text when the list tile is selected. + /// + /// If this property is null then [ListTileThemeData.selectedColor] + /// is used. If that is also null then [ColorScheme.primary] is used. + /// + /// See also: + /// + /// * [ListTileTheme.of], which returns the nearest [ListTileTheme]'s + /// [ListTileThemeData]. + final Color? selectedColor; + + /// Defines the default color for [leading] and [trailing] icons. + /// + /// If this property is null and [selected] is false then [ListTileThemeData.iconColor] + /// is used. If that is also null and [ThemeData.useMaterial3] is true, [ColorScheme.onSurfaceVariant] + /// is used, otherwise if [ThemeData.brightness] is [Brightness.light], [Colors.black54] is used, + /// and if [ThemeData.brightness] is [Brightness.dark], the value is null. + /// + /// If this property is null and [selected] is true then [ListTileThemeData.selectedColor] + /// is used. If that is also null then [ColorScheme.primary] is used. + /// + /// If this color is a [WidgetStateColor] it will be resolved against + /// [WidgetState.selected] and [WidgetState.disabled] states. + /// + /// See also: + /// + /// * [ListTileTheme.of], which returns the nearest [ListTileTheme]'s + /// [ListTileThemeData]. + final Color? iconColor; + + /// Defines the text color for the [title], [subtitle], [leading], and [trailing]. + /// + /// If this property is null and [selected] is false then [ListTileThemeData.textColor] + /// is used. If that is also null then default text color is used for the [title], [subtitle] + /// [leading], and [trailing]. Except for [subtitle], if [ThemeData.useMaterial3] is false, + /// [TextTheme.bodySmall] is used. + /// + /// If this property is null and [selected] is true then [ListTileThemeData.selectedColor] + /// is used. If that is also null then [ColorScheme.primary] is used. + /// + /// If this color is a [WidgetStateColor] it will be resolved against + /// [WidgetState.selected] and [WidgetState.disabled] states. + /// + /// See also: + /// + /// * [ListTileTheme.of], which returns the nearest [ListTileTheme]'s + /// [ListTileThemeData]. + final Color? textColor; + + /// The text style for ListTile's [title]. + /// + /// If this property is null, then [ListTileThemeData.titleTextStyle] is used. + /// If that is also null and [ThemeData.useMaterial3] is true, [TextTheme.bodyLarge] + /// with [ColorScheme.onSurface] will be used. Otherwise, If ListTile style is + /// [ListTileStyle.list], [TextTheme.titleMedium] will be used and if ListTile style + /// is [ListTileStyle.drawer], [TextTheme.bodyLarge] will be used. + final TextStyle? titleTextStyle; + + /// The text style for ListTile's [subtitle]. + /// + /// If this property is null, then [ListTileThemeData.subtitleTextStyle] is used. + /// If that is also null and [ThemeData.useMaterial3] is true, [TextTheme.bodyMedium] + /// with [ColorScheme.onSurfaceVariant] will be used, otherwise [TextTheme.bodyMedium] + /// with [TextTheme.bodySmall] color will be used. + final TextStyle? subtitleTextStyle; + + /// The text style for ListTile's [leading] and [trailing]. + /// + /// If this property is null, then [ListTileThemeData.leadingAndTrailingTextStyle] is used. + /// If that is also null and [ThemeData.useMaterial3] is true, [TextTheme.labelSmall] + /// with [ColorScheme.onSurfaceVariant] will be used, otherwise [TextTheme.bodyMedium] + /// will be used. + final TextStyle? leadingAndTrailingTextStyle; + + /// Defines the font used for the [title]. + /// + /// If this property is null then [ListTileThemeData.style] is used. If that + /// is also null then [ListTileStyle.list] is used. + /// + /// See also: + /// + /// * [ListTileTheme.of], which returns the nearest [ListTileTheme]'s + /// [ListTileThemeData]. + final ListTileStyle? style; + + /// The tile's internal padding. + /// + /// Insets a [ListTile]'s contents: its [leading], [title], [subtitle], and [trailing] widgets. + /// + /// If this property is null, then [ListTileThemeData.contentPadding] is used. If that is also + /// null and [ThemeData.useMaterial3] is true, then a default value of + /// `EdgeInsetsDirectional.only(start: 16.0, end: 24.0)` will be used. Otherwise, a default value + /// of `EdgeInsets.symmetric(horizontal: 16.0)` will be used. + final EdgeInsetsGeometry? contentPadding; + + /// Whether this list tile is interactive. + /// + /// If false, this list tile is styled with the disabled color from the + /// current [Theme] and the [onTap] and [onLongPress] callbacks are + /// inoperative. + final bool enabled; + + /// Called when the user taps this list tile. + /// + /// Inoperative if [enabled] is false. + final GestureTapCallback? onTap; + + /// Called when the user long-presses on this list tile. + /// + /// Inoperative if [enabled] is false. + final GestureLongPressCallback? onLongPress; + + /// {@macro flutter.material.inkwell.onFocusChange} + final ValueChanged? onFocusChange; + + /// {@template flutter.material.ListTile.mouseCursor} + /// The cursor for a mouse pointer when it enters or is hovering over the + /// widget. + /// + /// If [mouseCursor] is a [WidgetStateMouseCursor], + /// [WidgetStateProperty.resolve] is used for the following [WidgetState]s: + /// + /// * [WidgetState.selected]. + /// * [WidgetState.disabled]. + /// {@endtemplate} + /// + /// If null, then the value of [ListTileThemeData.mouseCursor] is used. If + /// that is also null, then [WidgetStateMouseCursor.clickable] is used. + final MouseCursor? mouseCursor; + + /// If this tile is also [enabled] then icons and text are rendered with the same color. + /// + /// By default the selected color is the theme's primary color. The selected color + /// can be overridden with a [ListTileTheme]. + /// + /// {@tool dartpad} + /// Here is an example of using a [StatefulWidget] to keep track of the + /// selected index, and using that to set the [selected] property on the + /// corresponding [ListTile]. + /// + /// ** See code in examples/api/lib/material/list_tile/list_tile.selected.0.dart ** + /// {@end-tool} + final bool selected; + + /// The color for the tile's [Material] when it has the input focus. + final Color? focusColor; + + /// The color for the tile's [Material] when a pointer is hovering over it. + final Color? hoverColor; + + /// The color of splash for the tile's [Material]. + final Color? splashColor; + + /// {@macro flutter.widgets.Focus.focusNode} + final FocusNode? focusNode; + + /// {@macro flutter.widgets.Focus.autofocus} + final bool autofocus; + + /// {@template flutter.material.ListTile.tileColor} + /// Defines the background color of `ListTile` when [selected] is false. + /// + /// If this property is null and [selected] is false then [ListTileThemeData.tileColor] + /// is used. If that is also null and [selected] is true, [selectedTileColor] is used. + /// When that is also null, the [ListTileTheme.selectedTileColor] is used, otherwise + /// [Colors.transparent] is used. + /// + /// {@endtemplate} + final Color? tileColor; + + /// Defines the background color of `ListTile` when [selected] is true. + /// + /// When the value if null, the [selectedTileColor] is set to [ListTileTheme.selectedTileColor] + /// if it's not null and to [Colors.transparent] if it's null. + final Color? selectedTileColor; + + /// {@template flutter.material.ListTile.enableFeedback} + /// Whether detected gestures should provide acoustic and/or haptic feedback. + /// + /// For example, on Android a tap will produce a clicking sound and a + /// long-press will produce a short vibration, when feedback is enabled. + /// + /// When null, the default value is true. + /// {@endtemplate} + /// + /// See also: + /// + /// * [Feedback] for providing platform-specific feedback to certain actions. + final bool? enableFeedback; + + /// The horizontal gap between the titles and the leading/trailing widgets. + /// + /// If null, then the value of [ListTileTheme.horizontalTitleGap] is used. If + /// that is also null, then a default value of 16 is used. + final double? horizontalTitleGap; + + /// The minimum padding on the top and bottom of the title and subtitle widgets. + /// + /// If null, then the value of [ListTileTheme.minVerticalPadding] is used. If + /// that is also null, then a default value of 4 is used. + final double? minVerticalPadding; + + /// The minimum width allocated for the [ListTile.leading] widget. + /// + /// If null, then the value of [ListTileTheme.minLeadingWidth] is used. If + /// that is also null, then a default value of 40 is used. + final double? minLeadingWidth; + + /// {@template flutter.material.ListTile.minTileHeight} + /// The minimum height allocated for the [ListTile] widget. + /// + /// If this is null, default tile heights are 56.0, 72.0, and 88.0 for one, + /// two, and three lines of text respectively. If `isDense` is true, these + /// defaults are changed to 48.0, 64.0, and 76.0. A visual density value or + /// a large title will also adjust the default tile heights. + /// {@endtemplate} + final double? minTileHeight; + + /// Defines how [ListTile.leading] and [ListTile.trailing] are + /// vertically aligned relative to the [ListTile]'s titles + /// ([ListTile.title] and [ListTile.subtitle]). + /// + /// If this property is null then [ListTileThemeData.titleAlignment] + /// is used. If that is also null then [ListTileTitleAlignment.threeLine] + /// is used. + /// + /// See also: + /// + /// * [ListTileTheme.of], which returns the nearest [ListTileTheme]'s + /// [ListTileThemeData]. + final ListTileTitleAlignment? titleAlignment; + + /// Whether to add button:true to the semantics if onTap is provided. + /// This is a temporary flag to help changing the behavior of ListTile onTap semantics. + /// + // TODO(hangyujin): Remove this flag after fixing related g3 tests and flipping + // the default value to true. + final bool internalAddSemanticForOnTap; + + /// {@macro flutter.material.inkwell.statesController} + final WidgetStatesController? statesController; + + /// Add a one pixel border in between each tile. If color isn't specified the + /// [ThemeData.dividerColor] of the context's [Theme] is used. + /// + /// See also: + /// + /// * [Divider], which you can use to obtain this effect manually. + static Iterable divideTiles({ + BuildContext? context, + required Iterable tiles, + Color? color, + }) { + assert(color != null || context != null); + tiles = tiles.toList(); + + if (tiles.isEmpty || tiles.length == 1) { + return tiles; + } + + Widget wrapTile(Widget tile) { + return DecoratedBox( + position: DecorationPosition.foreground, + decoration: BoxDecoration( + border: Border( + bottom: Divider.createBorderSide(context, color: color), + ), + ), + child: tile, + ); + } + + return [...tiles.take(tiles.length - 1).map(wrapTile), tiles.last]; + } + + bool _isDenseLayout(ThemeData theme, ListTileThemeData tileTheme) { + return dense ?? tileTheme.dense ?? theme.listTileTheme.dense ?? false; + } + + Color _tileBackgroundColor( + ThemeData theme, + ListTileThemeData tileTheme, + ListTileThemeData defaults, + ) { + final Color? color = selected + ? selectedTileColor ?? + tileTheme.selectedTileColor ?? + theme.listTileTheme.selectedTileColor + : tileColor ?? tileTheme.tileColor ?? theme.listTileTheme.tileColor; + return color ?? defaults.tileColor!; + } + + @override + Widget build(BuildContext context) { + assert(debugCheckHasMaterial(context)); + final ThemeData theme = Theme.of(context); + final IconButtonThemeData iconButtonTheme = IconButtonTheme.of(context); + final ListTileThemeData tileTheme = ListTileTheme.of(context); + final ListTileStyle listTileStyle = + style ?? + tileTheme.style ?? + theme.listTileTheme.style ?? + ListTileStyle.list; + final ListTileThemeData defaults = theme.useMaterial3 + ? _LisTileDefaultsM3(context) + : _LisTileDefaultsM2(context, listTileStyle); + final Set states = { + if (!enabled) WidgetState.disabled, + if (selected) WidgetState.selected, + }; + + Color? resolveColor( + Color? explicitColor, + Color? selectedColor, + Color? enabledColor, [ + Color? disabledColor, + ]) { + return _IndividualOverrides( + explicitColor: explicitColor, + selectedColor: selectedColor, + enabledColor: enabledColor, + disabledColor: disabledColor, + ).resolve(states); + } + + Color? effectiveIconColor = + resolveColor(iconColor, selectedColor, iconColor) ?? + resolveColor( + tileTheme.iconColor, + tileTheme.selectedColor, + tileTheme.iconColor, + ) ?? + resolveColor( + theme.listTileTheme.iconColor, + theme.listTileTheme.selectedColor, + theme.listTileTheme.iconColor, + ); + + final Color? defaultEffectiveIconColor = resolveColor( + defaults.iconColor, + defaults.selectedColor, + defaults.iconColor, + theme.disabledColor, + ); + + final Color? effectiveIconButtonColor = + effectiveIconColor ?? + iconButtonTheme.style?.foregroundColor?.resolve(states) ?? + defaultEffectiveIconColor; + + effectiveIconColor ??= defaultEffectiveIconColor; + + final Color? effectiveColor = + resolveColor(textColor, selectedColor, textColor) ?? + resolveColor( + tileTheme.textColor, + tileTheme.selectedColor, + tileTheme.textColor, + ) ?? + resolveColor( + theme.listTileTheme.textColor, + theme.listTileTheme.selectedColor, + theme.listTileTheme.textColor, + ) ?? + resolveColor( + defaults.textColor, + defaults.selectedColor, + defaults.textColor, + theme.disabledColor, + ); + final IconThemeData iconThemeData = IconThemeData( + color: effectiveIconColor, + ); + final IconButtonThemeData iconButtonThemeData = IconButtonThemeData( + style: + IconButtonTheme.of(context).style?.copyWith( + foregroundColor: WidgetStatePropertyAll( + effectiveIconButtonColor, + ), + ) ?? + IconButton.styleFrom(foregroundColor: effectiveIconButtonColor), + ); + + TextStyle? leadingAndTrailingStyle; + if (leading != null || trailing != null) { + leadingAndTrailingStyle = + leadingAndTrailingTextStyle ?? + tileTheme.leadingAndTrailingTextStyle ?? + defaults.leadingAndTrailingTextStyle!; + final Color? leadingAndTrailingTextColor = effectiveColor; + leadingAndTrailingStyle = leadingAndTrailingStyle.copyWith( + color: leadingAndTrailingTextColor, + ); + } + + Widget? leadingIcon; + if (leading != null) { + leadingIcon = AnimatedDefaultTextStyle( + style: leadingAndTrailingStyle!, + duration: kThemeChangeDuration, + child: leading!, + ); + } + + TextStyle titleStyle = + titleTextStyle ?? tileTheme.titleTextStyle ?? defaults.titleTextStyle!; + final Color? titleColor = effectiveColor; + titleStyle = titleStyle.copyWith( + color: titleColor, + fontSize: _isDenseLayout(theme, tileTheme) ? 13.0 : null, + ); + final Widget titleText = AnimatedDefaultTextStyle( + style: titleStyle, + duration: kThemeChangeDuration, + child: title ?? const SizedBox(), + ); + + Widget? subtitleText; + TextStyle? subtitleStyle; + if (subtitle != null) { + subtitleStyle = + subtitleTextStyle ?? + tileTheme.subtitleTextStyle ?? + defaults.subtitleTextStyle!; + final Color? subtitleColor = effectiveColor; + subtitleStyle = subtitleStyle.copyWith( + color: subtitleColor, + fontSize: _isDenseLayout(theme, tileTheme) ? 12.0 : null, + ); + subtitleText = AnimatedDefaultTextStyle( + style: subtitleStyle, + duration: kThemeChangeDuration, + child: subtitle!, + ); + } + + Widget? trailingIcon; + if (trailing != null) { + trailingIcon = AnimatedDefaultTextStyle( + style: leadingAndTrailingStyle!, + duration: kThemeChangeDuration, + child: trailing!, + ); + } + + final TextDirection textDirection = Directionality.of(context); + final EdgeInsets resolvedContentPadding = + contentPadding?.resolve(textDirection) ?? + tileTheme.contentPadding?.resolve(textDirection) ?? + defaults.contentPadding!.resolve(textDirection); + + // Show basic cursor when ListTile isn't enabled or gesture callbacks are null. + final Set mouseStates = { + if (!enabled || (onTap == null && onLongPress == null)) + WidgetState.disabled, + }; + final MouseCursor effectiveMouseCursor = + WidgetStateProperty.resolveAs( + mouseCursor, + mouseStates, + ) ?? + tileTheme.mouseCursor?.resolve(mouseStates) ?? + WidgetStateMouseCursor.clickable.resolve(mouseStates); + + final ListTileTitleAlignment effectiveTitleAlignment = + titleAlignment ?? + tileTheme.titleAlignment ?? + (theme.useMaterial3 + ? ListTileTitleAlignment.threeLine + : ListTileTitleAlignment.titleHeight); + + return InkWell( + customBorder: shape ?? tileTheme.shape, + onTap: enabled ? onTap : null, + onLongPress: enabled ? onLongPress : null, + onFocusChange: onFocusChange, + mouseCursor: effectiveMouseCursor, + canRequestFocus: enabled, + focusNode: focusNode, + focusColor: focusColor, + hoverColor: hoverColor, + splashColor: splashColor, + autofocus: autofocus, + enableFeedback: enableFeedback ?? tileTheme.enableFeedback ?? true, + statesController: statesController, + child: Semantics( + button: + internalAddSemanticForOnTap && + (onTap != null || onLongPress != null), + selected: selected, + enabled: enabled, + child: Ink( + decoration: ShapeDecoration( + shape: shape ?? tileTheme.shape ?? const Border(), + color: _tileBackgroundColor(theme, tileTheme, defaults), + ), + child: Padding( + padding: resolvedContentPadding, + child: IconTheme.merge( + data: iconThemeData, + child: IconButtonTheme( + data: iconButtonThemeData, + child: _ListTile( + leading: leadingIcon, + title: titleText, + subtitle: subtitleText, + trailing: trailingIcon, + isDense: _isDenseLayout(theme, tileTheme), + visualDensity: + visualDensity ?? + tileTheme.visualDensity ?? + theme.visualDensity, + isThreeLine: + isThreeLine ?? + tileTheme.isThreeLine ?? + theme.listTileTheme.isThreeLine ?? + false, + textDirection: textDirection, + titleBaselineType: + titleStyle.textBaseline ?? + defaults.titleTextStyle!.textBaseline!, + subtitleBaselineType: + subtitleStyle?.textBaseline ?? + defaults.subtitleTextStyle!.textBaseline!, + horizontalTitleGap: + horizontalTitleGap ?? tileTheme.horizontalTitleGap ?? 16, + minVerticalPadding: + minVerticalPadding ?? + tileTheme.minVerticalPadding ?? + defaults.minVerticalPadding!, + minLeadingWidth: + minLeadingWidth ?? + tileTheme.minLeadingWidth ?? + defaults.minLeadingWidth!, + minTileHeight: minTileHeight ?? tileTheme.minTileHeight, + titleAlignment: effectiveTitleAlignment, + ), + ), + ), + ), + ), + ), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add( + FlagProperty( + 'isThreeLine', + value: isThreeLine, + ifTrue: 'THREE_LINE', + ifFalse: 'TWO_LINE', + showName: true, + ), + ) + ..add( + FlagProperty( + 'dense', + value: dense, + ifTrue: 'true', + ifFalse: 'false', + showName: true, + ), + ) + ..add( + DiagnosticsProperty( + 'visualDensity', + visualDensity, + defaultValue: null, + ), + ) + ..add( + DiagnosticsProperty('shape', shape, defaultValue: null), + ) + ..add( + DiagnosticsProperty('style', style, defaultValue: null), + ) + ..add( + ColorProperty('selectedColor', selectedColor, defaultValue: null), + ) + ..add(ColorProperty('iconColor', iconColor, defaultValue: null)) + ..add(ColorProperty('textColor', textColor, defaultValue: null)) + ..add( + DiagnosticsProperty( + 'titleTextStyle', + titleTextStyle, + defaultValue: null, + ), + ) + ..add( + DiagnosticsProperty( + 'subtitleTextStyle', + subtitleTextStyle, + defaultValue: null, + ), + ) + ..add( + DiagnosticsProperty( + 'leadingAndTrailingTextStyle', + leadingAndTrailingTextStyle, + defaultValue: null, + ), + ) + ..add( + DiagnosticsProperty( + 'contentPadding', + contentPadding, + defaultValue: null, + ), + ) + ..add( + FlagProperty( + 'enabled', + value: enabled, + ifTrue: 'true', + ifFalse: 'false', + showName: true, + defaultValue: true, + ), + ) + ..add( + DiagnosticsProperty('onTap', onTap, defaultValue: null), + ) + ..add( + DiagnosticsProperty( + 'onLongPress', + onLongPress, + defaultValue: null, + ), + ) + ..add( + DiagnosticsProperty( + 'mouseCursor', + mouseCursor, + defaultValue: null, + ), + ) + ..add( + FlagProperty( + 'selected', + value: selected, + ifTrue: 'true', + ifFalse: 'false', + showName: true, + defaultValue: false, + ), + ) + ..add(ColorProperty('focusColor', focusColor, defaultValue: null)) + ..add(ColorProperty('hoverColor', hoverColor, defaultValue: null)) + ..add( + DiagnosticsProperty( + 'focusNode', + focusNode, + defaultValue: null, + ), + ) + ..add( + FlagProperty( + 'autofocus', + value: autofocus, + ifTrue: 'true', + ifFalse: 'false', + showName: true, + defaultValue: false, + ), + ) + ..add(ColorProperty('tileColor', tileColor, defaultValue: null)) + ..add( + ColorProperty( + 'selectedTileColor', + selectedTileColor, + defaultValue: null, + ), + ) + ..add( + FlagProperty( + 'enableFeedback', + value: enableFeedback, + ifTrue: 'true', + ifFalse: 'false', + showName: true, + ), + ) + ..add( + DoubleProperty( + 'horizontalTitleGap', + horizontalTitleGap, + defaultValue: null, + ), + ) + ..add( + DoubleProperty( + 'minVerticalPadding', + minVerticalPadding, + defaultValue: null, + ), + ) + ..add( + DoubleProperty('minLeadingWidth', minLeadingWidth, defaultValue: null), + ) + ..add( + DiagnosticsProperty( + 'titleAlignment', + titleAlignment, + defaultValue: null, + ), + ); + } +} + +class _IndividualOverrides extends WidgetStateProperty { + _IndividualOverrides({ + this.explicitColor, + this.enabledColor, + this.selectedColor, + this.disabledColor, + }); + + final Color? explicitColor; + final Color? enabledColor; + final Color? selectedColor; + final Color? disabledColor; + + @override + Color? resolve(Set states) { + if (explicitColor is WidgetStateColor) { + return WidgetStateProperty.resolveAs(explicitColor, states); + } + if (states.contains(WidgetState.disabled)) { + return disabledColor; + } + if (states.contains(WidgetState.selected)) { + return selectedColor; + } + return enabledColor; + } +} + +// Identifies the children of a _ListTileElement. +enum _ListTileSlot { leading, title, subtitle, trailing } + +class _ListTile + extends SlottedMultiChildRenderObjectWidget<_ListTileSlot, RenderBox> { + const _ListTile({ + this.leading, + required this.title, + this.subtitle, + this.trailing, + required this.isThreeLine, + required this.isDense, + required this.visualDensity, + required this.textDirection, + required this.titleBaselineType, + required this.horizontalTitleGap, + required this.minVerticalPadding, + required this.minLeadingWidth, + this.minTileHeight, + this.subtitleBaselineType, + required this.titleAlignment, + }); + + final Widget? leading; + final Widget title; + final Widget? subtitle; + final Widget? trailing; + final bool isThreeLine; + final bool isDense; + final VisualDensity visualDensity; + final TextDirection textDirection; + final TextBaseline titleBaselineType; + final TextBaseline? subtitleBaselineType; + final double horizontalTitleGap; + final double minVerticalPadding; + final double minLeadingWidth; + final double? minTileHeight; + final ListTileTitleAlignment titleAlignment; + + @override + Iterable<_ListTileSlot> get slots => _ListTileSlot.values; + + @override + Widget? childForSlot(_ListTileSlot slot) { + return switch (slot) { + _ListTileSlot.leading => leading, + _ListTileSlot.title => title, + _ListTileSlot.subtitle => subtitle, + _ListTileSlot.trailing => trailing, + }; + } + + @override + _RenderListTile createRenderObject(BuildContext context) { + return _RenderListTile( + isThreeLine: isThreeLine, + isDense: isDense, + visualDensity: visualDensity, + textDirection: textDirection, + titleBaselineType: titleBaselineType, + subtitleBaselineType: subtitleBaselineType, + horizontalTitleGap: horizontalTitleGap, + minVerticalPadding: minVerticalPadding, + minLeadingWidth: minLeadingWidth, + minTileHeight: minTileHeight, + titleAlignment: titleAlignment, + ); + } + + @override + void updateRenderObject(BuildContext context, _RenderListTile renderObject) { + renderObject + ..isThreeLine = isThreeLine + ..isDense = isDense + ..visualDensity = visualDensity + ..textDirection = textDirection + ..titleBaselineType = titleBaselineType + ..subtitleBaselineType = subtitleBaselineType + ..horizontalTitleGap = horizontalTitleGap + ..minLeadingWidth = minLeadingWidth + ..minTileHeight = minTileHeight + ..minVerticalPadding = minVerticalPadding + ..titleAlignment = titleAlignment; + } +} + +class _RenderListTile extends RenderBox + with SlottedContainerRenderObjectMixin<_ListTileSlot, RenderBox> { + _RenderListTile({ + required bool isDense, + required VisualDensity visualDensity, + required bool isThreeLine, + required TextDirection textDirection, + required TextBaseline titleBaselineType, + TextBaseline? subtitleBaselineType, + required double horizontalTitleGap, + required double minVerticalPadding, + required double minLeadingWidth, + double? minTileHeight, + required ListTileTitleAlignment titleAlignment, + }) : _isDense = isDense, + _visualDensity = visualDensity, + _isThreeLine = isThreeLine, + _textDirection = textDirection, + _titleBaselineType = titleBaselineType, + _subtitleBaselineType = subtitleBaselineType, + _horizontalTitleGap = horizontalTitleGap, + _minVerticalPadding = minVerticalPadding, + _minLeadingWidth = minLeadingWidth, + _minTileHeight = minTileHeight, + _titleAlignment = titleAlignment; + + RenderBox? get leading => childForSlot(_ListTileSlot.leading); + RenderBox get title => childForSlot(_ListTileSlot.title)!; + RenderBox? get subtitle => childForSlot(_ListTileSlot.subtitle); + RenderBox? get trailing => childForSlot(_ListTileSlot.trailing); + + // The returned list is ordered for hit testing. + @override + Iterable get children { + final RenderBox? title = childForSlot(_ListTileSlot.title); + return [ + if (leading != null) leading!, + if (title != null) title, + if (subtitle != null) subtitle!, + if (trailing != null) trailing!, + ]; + } + + bool get isDense => _isDense; + bool _isDense; + set isDense(bool value) { + if (_isDense == value) { + return; + } + _isDense = value; + markNeedsLayout(); + } + + VisualDensity get visualDensity => _visualDensity; + VisualDensity _visualDensity; + set visualDensity(VisualDensity value) { + if (_visualDensity == value) { + return; + } + _visualDensity = value; + markNeedsLayout(); + } + + bool get isThreeLine => _isThreeLine; + bool _isThreeLine; + set isThreeLine(bool value) { + if (_isThreeLine == value) { + return; + } + _isThreeLine = value; + markNeedsLayout(); + } + + TextDirection get textDirection => _textDirection; + TextDirection _textDirection; + set textDirection(TextDirection value) { + if (_textDirection == value) { + return; + } + _textDirection = value; + markNeedsLayout(); + } + + TextBaseline get titleBaselineType => _titleBaselineType; + TextBaseline _titleBaselineType; + set titleBaselineType(TextBaseline value) { + if (_titleBaselineType == value) { + return; + } + _titleBaselineType = value; + markNeedsLayout(); + } + + TextBaseline? get subtitleBaselineType => _subtitleBaselineType; + TextBaseline? _subtitleBaselineType; + set subtitleBaselineType(TextBaseline? value) { + if (_subtitleBaselineType == value) { + return; + } + _subtitleBaselineType = value; + markNeedsLayout(); + } + + double get horizontalTitleGap => _horizontalTitleGap; + double _horizontalTitleGap; + double get _effectiveHorizontalTitleGap => + _horizontalTitleGap + visualDensity.horizontal * 2.0; + + set horizontalTitleGap(double value) { + if (_horizontalTitleGap == value) { + return; + } + _horizontalTitleGap = value; + markNeedsLayout(); + } + + double get minVerticalPadding => _minVerticalPadding; + double _minVerticalPadding; + + set minVerticalPadding(double value) { + if (_minVerticalPadding == value) { + return; + } + _minVerticalPadding = value; + markNeedsLayout(); + } + + double get minLeadingWidth => _minLeadingWidth; + double _minLeadingWidth; + + set minLeadingWidth(double value) { + if (_minLeadingWidth == value) { + return; + } + _minLeadingWidth = value; + markNeedsLayout(); + } + + double? _minTileHeight; + double? get minTileHeight => _minTileHeight; + set minTileHeight(double? value) { + if (_minTileHeight == value) { + return; + } + _minTileHeight = value; + markNeedsLayout(); + } + + ListTileTitleAlignment get titleAlignment => _titleAlignment; + ListTileTitleAlignment _titleAlignment; + set titleAlignment(ListTileTitleAlignment value) { + if (_titleAlignment == value) { + return; + } + _titleAlignment = value; + markNeedsLayout(); + } + + @override + bool get sizedByParent => false; + + static double _minWidth(RenderBox? box, double height) { + return box == null ? 0.0 : box.getMinIntrinsicWidth(height); + } + + static double _maxWidth(RenderBox? box, double height) { + return box == null ? 0.0 : box.getMaxIntrinsicWidth(height); + } + + @override + double computeMinIntrinsicWidth(double height) { + final double leadingWidth = leading != null + ? math.max(leading!.getMinIntrinsicWidth(height), _minLeadingWidth) + + _effectiveHorizontalTitleGap + : 0.0; + return leadingWidth + + math.max(_minWidth(title, height), _minWidth(subtitle, height)) + + _maxWidth(trailing, height); + } + + @override + double computeMaxIntrinsicWidth(double height) { + final double leadingWidth = leading != null + ? math.max(leading!.getMaxIntrinsicWidth(height), _minLeadingWidth) + + _effectiveHorizontalTitleGap + : 0.0; + return leadingWidth + + math.max(_maxWidth(title, height), _maxWidth(subtitle, height)) + + _maxWidth(trailing, height); + } + + // The target tile height to use if _minTileHeight is not specified. + double get _defaultTileHeight { + final Offset baseDensity = visualDensity.baseSizeAdjustment; + return baseDensity.dy + + switch ((isThreeLine, subtitle != null)) { + (true, _) => isDense ? 76.0 : 88.0, // 3 lines, + (false, true) => isDense ? 64.0 : 72.0, // 2 lines + (false, false) => isDense ? 48.0 : 56.0, // 1 line, + }; + } + + double get _targetTileHeight => _minTileHeight ?? _defaultTileHeight; + + @override + double computeMinIntrinsicHeight(double width) { + return math.max( + _targetTileHeight, + title.getMinIntrinsicHeight(width) + + (subtitle?.getMinIntrinsicHeight(width) ?? 0.0), + ); + } + + @override + double computeMaxIntrinsicHeight(double width) { + return getMinIntrinsicHeight(width); + } + + @override + double? computeDistanceToActualBaseline(TextBaseline baseline) { + final BoxParentData parentData = title.parentData! as BoxParentData; + final BaselineOffset offset = + BaselineOffset(title.getDistanceToActualBaseline(baseline)) + + parentData.offset.dy; + return offset.offset; + } + + BoxConstraints get maxIconHeightConstraint => BoxConstraints( + // One-line trailing and leading widget heights do not follow + // Material specifications, but this sizing is required to adhere + // to accessibility requirements for smallest tappable widget. + // Two- and three-line trailing widget heights are constrained + // properly according to the Material spec. + maxHeight: (isDense ? 48.0 : 56.0) + visualDensity.baseSizeAdjustment.dy, + ); + + static void _positionBox(RenderBox box, Offset offset) { + final BoxParentData parentData = box.parentData! as BoxParentData; + parentData.offset = offset; + } + + // Implements _RenderListTile's layout algorithm. If `positionChild` is not null, + // it will be called on each child with that child's layout offset. + // + // All of the dimensions below were taken from the Material Design spec: + // https://material.io/design/components/lists.html#specs + _Sizes _computeSizes( + ChildBaselineGetter getBaseline, + ChildLayouter getSize, + BoxConstraints constraints, { + _PositionChild? positionChild, + }) { + final BoxConstraints looseConstraints = constraints.loosen(); + final double tileWidth = looseConstraints.maxWidth; + final BoxConstraints iconConstraints = looseConstraints.enforce( + maxIconHeightConstraint, + ); + final RenderBox? leading = this.leading; + final RenderBox? trailing = this.trailing; + + final Size? leadingSize = leading == null + ? null + : getSize(leading, iconConstraints); + final Size? trailingSize = trailing == null + ? null + : getSize(trailing, iconConstraints); + + assert(() { + if (tileWidth == 0.0) { + return true; + } + + String? overflowedWidget; + if (tileWidth == leadingSize?.width) { + overflowedWidget = 'Leading'; + } else if (tileWidth == trailingSize?.width) { + overflowedWidget = 'Trailing'; + } + + if (overflowedWidget == null) { + return true; + } + + throw FlutterError.fromParts([ + ErrorSummary( + '$overflowedWidget widget consumes the entire tile width (including ListTile.contentPadding).', + ), + ErrorDescription( + 'Either resize the tile width so that the ${overflowedWidget.toLowerCase()} widget plus any content padding ' + 'do not exceed the tile width, or use a sized widget, or consider replacing ' + 'ListTile with a custom widget.', + ), + ErrorHint( + 'See also: https://api.flutter.dev/flutter/material/ListTile-class.html#material.ListTile.4', + ), + ]); + }()); + + final double titleStart = leadingSize == null + ? 0.0 + : math.max(_minLeadingWidth, leadingSize.width) + + _effectiveHorizontalTitleGap; + + final double adjustedTrailingWidth = trailingSize == null + ? 0.0 + : math.max(trailingSize.width + _effectiveHorizontalTitleGap, 32.0); + + final BoxConstraints textConstraints = looseConstraints.tighten( + width: tileWidth - titleStart - adjustedTrailingWidth, + ); + + final RenderBox? subtitle = this.subtitle; + final double titleHeight = getSize(title, textConstraints).height; + + final bool isLTR = switch (textDirection) { + TextDirection.ltr => true, + TextDirection.rtl => false, + }; + + final double titleY; + final double tileHeight; + if (subtitle == null) { + tileHeight = math.max( + _targetTileHeight, + titleHeight + 2.0 * _minVerticalPadding, + ); + titleY = (tileHeight - titleHeight) / 2.0; + } else { + final double subtitleHeight = getSize(subtitle, textConstraints).height; + final double titleBaseline = + getBaseline(title, textConstraints, titleBaselineType) ?? titleHeight; + final double subtitleBaseline = + getBaseline(subtitle, textConstraints, subtitleBaselineType!) ?? + subtitleHeight; + + final double targetTitleY = + (isThreeLine ? (isDense ? 22.0 : 28.0) : (isDense ? 28.0 : 32.0)) - + titleBaseline; + final double targetSubtitleY = + (isThreeLine ? (isDense ? 42.0 : 48.0) : (isDense ? 48.0 : 52.0)) + + visualDensity.vertical * 2.0 - + subtitleBaseline; + // Prevent the title and the subtitle from overlapping by moving them away from + // each other by the same distance. + final double halfOverlap = + math.max(targetTitleY + titleHeight - targetSubtitleY, 0) / 2; + final double idealTitleY = targetTitleY - halfOverlap; + final double idealSubtitleY = targetSubtitleY + halfOverlap; + // However if either component can't maintain the minimal padding from the top/bottom edges, the ListTile enters "compat mode". + final bool compact = + idealTitleY < minVerticalPadding || + idealSubtitleY + subtitleHeight + minVerticalPadding > + _targetTileHeight; + + // Position subtitle. + positionChild?.call( + subtitle, + Offset( + isLTR ? titleStart : adjustedTrailingWidth, + compact ? minVerticalPadding + titleHeight : idealSubtitleY, + ), + ); + tileHeight = compact + ? 2 * _minVerticalPadding + titleHeight + subtitleHeight + : _targetTileHeight; + titleY = compact ? minVerticalPadding : idealTitleY; + } + + if (positionChild != null) { + positionChild( + title, + Offset(isLTR ? titleStart : adjustedTrailingWidth, titleY), + ); + + if (leading != null && leadingSize != null) { + positionChild( + leading, + Offset( + isLTR ? 0.0 : tileWidth - leadingSize.width, + titleAlignment._yOffsetFor( + leadingSize.height, + tileHeight, + this, + true, + ), + ), + ); + } + + if (trailing != null && trailingSize != null) { + positionChild( + trailing, + Offset( + isLTR ? tileWidth - trailingSize.width : 0.0, + titleAlignment._yOffsetFor( + trailingSize.height, + tileHeight, + this, + false, + ), + ), + ); + } + } + + return ( + titleY: titleY, + textConstraints: textConstraints, + tileSize: Size(tileWidth, tileHeight), + ); + } + + @override + double? computeDryBaseline( + covariant BoxConstraints constraints, + TextBaseline baseline, + ) { + final _Sizes sizes = _computeSizes( + ChildLayoutHelper.getDryBaseline, + ChildLayoutHelper.dryLayoutChild, + constraints, + ); + final BaselineOffset titleBaseline = + BaselineOffset(title.getDryBaseline(sizes.textConstraints, baseline)) + + sizes.titleY; + return titleBaseline.offset; + } + + @override + Size computeDryLayout(BoxConstraints constraints) { + return constraints.constrain( + _computeSizes( + ChildLayoutHelper.getDryBaseline, + ChildLayoutHelper.dryLayoutChild, + constraints, + ).tileSize, + ); + } + + @override + void performLayout() { + final Size tileSize = _computeSizes( + ChildLayoutHelper.getBaseline, + ChildLayoutHelper.layoutChild, + constraints, + positionChild: _positionBox, + ).tileSize; + + size = constraints.constrain(tileSize); + assert(size.width == constraints.constrainWidth(tileSize.width)); + assert(size.height == constraints.constrainHeight(tileSize.height)); + } + + @override + void paint(PaintingContext context, Offset offset) { + void doPaint(RenderBox? child) { + if (child != null) { + final BoxParentData parentData = child.parentData! as BoxParentData; + context.paintChild(child, parentData.offset + offset); + } + } + + doPaint(leading); + doPaint(title); + doPaint(subtitle); + doPaint(trailing); + } + + @override + bool hitTestSelf(Offset position) => true; + + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + for (final RenderBox child in children) { + final BoxParentData parentData = child.parentData! as BoxParentData; + final bool isHit = result.addWithPaintOffset( + offset: parentData.offset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) { + assert(transformed == position - parentData.offset); + return child.hitTest(result, position: transformed); + }, + ); + if (isHit) { + return true; + } + } + return false; + } +} + +class _LisTileDefaultsM2 extends ListTileThemeData { + _LisTileDefaultsM2(this.context, ListTileStyle style) + : super( + contentPadding: const EdgeInsets.symmetric(horizontal: 16.0), + minLeadingWidth: 40, + minVerticalPadding: 4, + shape: const Border(), + style: style, + ); + + final BuildContext context; + late final ThemeData _theme = Theme.of(context); + late final TextTheme _textTheme = _theme.textTheme; + + @override + Color? get tileColor => Colors.transparent; + + @override + TextStyle? get titleTextStyle => switch (style!) { + ListTileStyle.drawer => _textTheme.bodyLarge, + ListTileStyle.list => _textTheme.titleMedium, + }; + + @override + TextStyle? get subtitleTextStyle => + _textTheme.bodyMedium!.copyWith(color: _textTheme.bodySmall!.color); + + @override + TextStyle? get leadingAndTrailingTextStyle => _textTheme.bodyMedium; + + @override + Color? get selectedColor => _theme.colorScheme.primary; + + @override + Color? get iconColor => switch (_theme.brightness) { + // For the sake of backwards compatibility, the default for unselected + // tiles is Colors.black45 rather than colorScheme.onSurface.withAlpha(0x73). + Brightness.light => Colors.black45, + // null -> use current icon theme color + Brightness.dark => null, + }; +} + +// BEGIN GENERATED TOKEN PROPERTIES - LisTile + +// Do not edit by hand. The code between the "BEGIN GENERATED" and +// "END GENERATED" comments are generated from data in the Material +// Design token database by the script: +// dev/tools/gen_defaults/bin/gen_defaults.dart. + +// dart format off +class _LisTileDefaultsM3 extends ListTileThemeData { + _LisTileDefaultsM3(this.context) + : super( + contentPadding: const EdgeInsetsDirectional.only(start: 16.0, end: 24.0), + minLeadingWidth: 24, + minVerticalPadding: 8, + shape: const RoundedRectangleBorder(), + ); + + final BuildContext context; + late final ThemeData _theme = Theme.of(context); + late final ColorScheme _colors = _theme.colorScheme; + late final TextTheme _textTheme = _theme.textTheme; + + @override + Color? get tileColor => Colors.transparent; + + @override + TextStyle? get titleTextStyle => _textTheme.bodyLarge!.copyWith(color: _colors.onSurface); + + @override + TextStyle? get subtitleTextStyle => _textTheme.bodyMedium!.copyWith(color: _colors.onSurfaceVariant); + + @override + TextStyle? get leadingAndTrailingTextStyle => _textTheme.labelSmall!.copyWith(color: _colors.onSurfaceVariant); + + @override + Color? get selectedColor => _colors.primary; + + @override + Color? get iconColor => _colors.onSurfaceVariant; +} +// dart format on + +// END GENERATED TOKEN PROPERTIES - LisTile diff --git a/lib/common/widgets/loading_widget/http_error.dart b/lib/common/widgets/loading_widget/http_error.dart index 5a7b28fe6..c600f92a2 100644 --- a/lib/common/widgets/loading_widget/http_error.dart +++ b/lib/common/widgets/loading_widget/http_error.dart @@ -19,10 +19,7 @@ class HttpError extends StatelessWidget { Widget build(BuildContext context) { return isSliver ? SliverToBoxAdapter(child: content(context)) - : SizedBox( - width: double.infinity, - child: content(context), - ); + : content(context); } Widget content(BuildContext context) { diff --git a/lib/common/widgets/pendant_avatar.dart b/lib/common/widgets/pendant_avatar.dart index a475ad964..5c0fba0a4 100644 --- a/lib/common/widgets/pendant_avatar.dart +++ b/lib/common/widgets/pendant_avatar.dart @@ -22,13 +22,13 @@ class PendantAvatar extends StatelessWidget { required this.avatar, this.size = 80, double? badgeSize, - bool? isVip, + bool isVip = false, int? officialType, this.garbPendantImage, this.roomId, this.onTap, }) : _badgeType = officialType == null || officialType < 0 - ? isVip == true + ? isVip ? BadgeType.vip : BadgeType.none : officialType == 0 diff --git a/lib/common/widgets/progress_bar/segment_progress_bar.dart b/lib/common/widgets/progress_bar/segment_progress_bar.dart index a8e91f2ff..84bfdc8d2 100644 --- a/lib/common/widgets/progress_bar/segment_progress_bar.dart +++ b/lib/common/widgets/progress_bar/segment_progress_bar.dart @@ -18,6 +18,26 @@ class Segment { this.from, this.to, ]); + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other is Segment) { + return start == other.start && + end == other.end && + color == other.color && + title == other.title && + url == other.url && + from == other.from && + to == other.to; + } + return false; + } + + @override + int get hashCode => Object.hash(start, end, color, title, url, from, to); } class SegmentProgressBar extends CustomPainter { @@ -126,7 +146,7 @@ class SegmentProgressBar extends CustomPainter { } @override - bool shouldRepaint(covariant CustomPainter oldDelegate) { - return false; + bool shouldRepaint(SegmentProgressBar oldDelegate) { + return segmentColors != oldDelegate.segmentColors; } } diff --git a/lib/grpc/reply.dart b/lib/grpc/reply.dart index 9bafc6e24..d58d70647 100644 --- a/lib/grpc/reply.dart +++ b/lib/grpc/reply.dart @@ -103,8 +103,7 @@ class ReplyGrpc { ), DetailListReply.fromBuffer, ); - return res - ..dataOrNull?.root.replies.removeWhere(needRemoveGrpc); + return res..dataOrNull?.root.replies.removeWhere(needRemoveGrpc); } static Future> dialogList({ diff --git a/lib/http/danmaku.dart b/lib/http/danmaku.dart index 7c174d057..6314b1dd2 100644 --- a/lib/http/danmaku.dart +++ b/lib/http/danmaku.dart @@ -18,7 +18,7 @@ class DanmakuHttp { int? fontsize, // 弹幕字号(默认25) int? pool, // 弹幕池选择(0:普通池 1:字幕池 2:特殊池(代码/BAS弹幕)默认普通池,0) //int? rnd,// 当前时间戳*1000000(若无此项,则发送弹幕冷却时间限制为90s;若有此项,则发送弹幕冷却时间限制为5s) - bool? colorful, //60001:专属渐变彩色(需要会员) + bool colorful = false, //60001:专属渐变彩色(需要会员) int? checkboxType, //是否带 UP 身份标识(0:普通;4:带有标识) // String? csrf,//CSRF Token(位于 Cookie) Cookie 方式必要 // String? access_key,// APP 登录 Token APP 方式必要 @@ -35,11 +35,11 @@ class DanmakuHttp { //'aid': aid, 'bvid': bvid, 'progress': progress, - 'color': colorful == true ? 16777215 : color, + 'color': colorful ? 16777215 : color, 'fontsize': fontsize, 'pool': pool, 'rnd': DateTime.now().microsecondsSinceEpoch, - 'colorful': colorful == true ? 60001 : null, + 'colorful': colorful ? 60001 : null, 'checkbox_type': checkboxType, 'csrf': Accounts.main.csrf, // 'access_key': access_key, diff --git a/lib/http/live.dart b/lib/http/live.dart index c23556c59..b41c31e4d 100644 --- a/lib/http/live.dart +++ b/lib/http/live.dart @@ -166,7 +166,7 @@ class LiveHttp { static Future> liveFeedIndex({ required int pn, required bool isLogin, - bool? moduleSelect, + bool moduleSelect = false, }) async { final params = { 'access_key': ?Accounts.main.accessKey, @@ -182,7 +182,7 @@ class LiveHttp { 'fnval': 912, 'disable_rcmd': 0, 'https_url_req': 1, - if (moduleSelect == true) 'module_select': 1, + if (moduleSelect) 'module_select': 1, 'mobi_app': 'android', 'network': 'wifi', 'page': pn, diff --git a/lib/http/reply.dart b/lib/http/reply.dart index 9a6cbc129..0e7a6945e 100644 --- a/lib/http/reply.dart +++ b/lib/http/reply.dart @@ -5,7 +5,6 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/emote/data.dart'; import 'package:PiliPlus/models_new/emote/package.dart'; import 'package:PiliPlus/models_new/reply/data.dart'; -import 'package:PiliPlus/models_new/reply/reply.dart'; import 'package:PiliPlus/models_new/reply2reply/data.dart'; import 'package:PiliPlus/utils/accounts.dart'; import 'package:PiliPlus/utils/accounts/account.dart'; @@ -24,8 +23,6 @@ class ReplyHttp { required int type, required int page, int sort = 1, - required bool antiGoodsReply, - bool? enableFilter, }) async { var res = !isLogin ? await Request().get( @@ -52,99 +49,19 @@ class ReplyHttp { ); if (res.data['code'] == 0) { ReplyData replyData = ReplyData.fromJson(res.data['data']); - // if (enableFilter != false && replyRegExp.pattern.isNotEmpty) { - // // topReplies - // if (replyData.topReplies?.isNotEmpty == true) { - // replyData.topReplies!.removeWhere((item) { - // bool hasMatch = replyRegExp.hasMatch(item.content?.message ?? ''); - // // remove subreplies - // if (!hasMatch) { - // if (item.replies?.isNotEmpty == true) { - // item.replies!.removeWhere((item) => - // replyRegExp.hasMatch(item.content?.message ?? '')); - // } - // } - // return hasMatch; - // }); - // } - - // // replies - // if (replyData.replies?.isNotEmpty == true) { - // replyData.replies!.removeWhere((item) { - // bool hasMatch = replyRegExp.hasMatch(item.content?.message ?? ''); - // // remove subreplies - // if (!hasMatch) { - // if (item.replies?.isNotEmpty == true) { - // item.replies!.removeWhere((item) => - // replyRegExp.hasMatch(item.content?.message ?? '')); - // } - // } - // return hasMatch; - // }); - // } - // } - - // // antiGoodsReply - // if (antiGoodsReply) { - // // topReplies - // if (replyData.topReplies?.isNotEmpty == true) { - // replyData.topReplies!.removeWhere((item) { - // bool hasMatch = needRemove(item); - // // remove subreplies - // if (!hasMatch) { - // if (item.replies?.isNotEmpty == true) { - // item.replies!.removeWhere(needRemove); - // } - // } - // return hasMatch; - // }); - // } - - // // replies - // if (replyData.replies?.isNotEmpty == true) { - // replyData.replies!.removeWhere((item) { - // bool hasMatch = needRemove(item); - // // remove subreplies - // if (!hasMatch) { - // if (item.replies?.isNotEmpty == true) { - // item.replies!.removeWhere(needRemove); - // } - // } - // return hasMatch; - // }); - // } - // } return Success(replyData); } else { return Error(res.data['message']); } } - static bool needRemove(ReplyItemModel reply) { - try { - if ((reply.content?.jumpUrl?.isNotEmpty == true && - reply.content!.jumpUrl!.values.any((url) { - return url['extra'] != null && - (url['extra']['goods_cm_control'] == 1 || - url['extra']['goods_item_id'] != 0 || - url['extra']['goods_prefetched_cache'].isNotEmpty); - })) || - reply.content?.message?.contains(Constants.goodsUrlPrefix) == true) { - return true; - } - } catch (_) {} - return false; - } - static Future> replyReplyList({ required bool isLogin, required int oid, required int root, required int pageNum, required int type, - required bool antiGoodsReply, - bool? isCheck, - bool? filterBanWord, + bool isCheck = false, }) async { var res = await Request().get( Api.replyReplyList, @@ -160,21 +77,10 @@ class ReplyHttp { ); if (res.data['code'] == 0) { ReplyReplyData replyData = ReplyReplyData.fromJson(res.data['data']); - // if (filterBanWord != false && replyRegExp.pattern.isNotEmpty) { - // if (replyData.replies?.isNotEmpty == true) { - // replyData.replies!.removeWhere( - // (item) => replyRegExp.hasMatch(item.content?.message ?? '')); - // } - // } - // if (antiGoodsReply) { - // if (replyData.replies?.isNotEmpty == true) { - // replyData.replies!.removeWhere(needRemove); - // } - // } return Success(replyData); } else { return Error( - isCheck == true + isCheck ? '${res.data['code']}${res.data['message']}' : res.data['message'], ); diff --git a/lib/http/video.dart b/lib/http/video.dart index c92bfaa52..e4647c8a2 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -525,7 +525,7 @@ class VideoHttp { int? root, int? parent, List? pictures, - bool? syncToDynamic, + bool syncToDynamic = false, Map? atNameToMid, }) async { if (message == '') { @@ -540,7 +540,7 @@ class VideoHttp { if (atNameToMid?.isNotEmpty == true) 'at_name_to_mid': jsonEncode(atNameToMid), // {"name":uid} if (pictures != null) 'pictures': jsonEncode(pictures), - if (syncToDynamic == true) 'sync_to_dynamic': 1, + if (syncToDynamic) 'sync_to_dynamic': 1, 'csrf': Accounts.main.csrf, }; var res = await Request().post( diff --git a/lib/models_new/live/live_danmaku/danmaku_msg.dart b/lib/models_new/live/live_danmaku/danmaku_msg.dart index ced02b091..229595aea 100644 --- a/lib/models_new/live/live_danmaku/danmaku_msg.dart +++ b/lib/models_new/live/live_danmaku/danmaku_msg.dart @@ -7,14 +7,22 @@ class DanmakuMsg { Map? emots; BaseEmote? uemote; - DanmakuMsg(); + DanmakuMsg({ + required this.name, + required this.uid, + required this.text, + this.emots, + this.uemote, + }); DanmakuMsg.fromPrefetch(Map obj) { final user = obj['user']; name = user['base']['name']; uid = user['uid']; text = obj['text']; - emots = (obj['emots'] as Map?)?.map((k, v) => MapEntry(k, BaseEmote.fromJson(v))); + emots = (obj['emots'] as Map?)?.map( + (k, v) => MapEntry(k, BaseEmote.fromJson(v)), + ); if ((obj['emoticon']?['emoticon_unique'] as String?)?.isNotEmpty == true) { uemote = BaseEmote.fromJson(obj['emoticon']); } diff --git a/lib/models_new/live/live_danmaku/live_emote.dart b/lib/models_new/live/live_danmaku/live_emote.dart index 17ec8aa3f..583268437 100644 --- a/lib/models_new/live/live_danmaku/live_emote.dart +++ b/lib/models_new/live/live_danmaku/live_emote.dart @@ -3,6 +3,7 @@ class BaseEmote { late String emoticonUnique; late double width; double? height; + late final isUpower = emoticonUnique.startsWith('upower_'); BaseEmote.fromJson(Map json) { url = json['url']; diff --git a/lib/pages/about/view.dart b/lib/pages/about/view.dart index 64c4277ec..6ef67702e 100644 --- a/lib/pages/about/view.dart +++ b/lib/pages/about/view.dart @@ -4,11 +4,13 @@ import 'dart:io'; import 'package:PiliPlus/build_config.dart'; import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; +import 'package:PiliPlus/common/widgets/list_tile.dart'; import 'package:PiliPlus/pages/mine/controller.dart'; import 'package:PiliPlus/services/logger.dart'; import 'package:PiliPlus/utils/accounts.dart'; import 'package:PiliPlus/utils/accounts/account.dart'; import 'package:PiliPlus/utils/cache_manage.dart'; +import 'package:PiliPlus/utils/context_ext.dart'; import 'package:PiliPlus/utils/date_util.dart'; import 'package:PiliPlus/utils/login_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; @@ -17,10 +19,10 @@ import 'package:PiliPlus/utils/update.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:dio/dio.dart' show Headers; import 'package:document_file_save_plus/document_file_save_plus_platform_interface.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide ListTile; import 'package:flutter/services.dart' show Clipboard, ClipboardData; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:get/get.dart'; +import 'package:get/get.dart' hide ContextExtensionss; import 'package:intl/intl.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:package_info_plus/package_info_plus.dart'; @@ -31,9 +33,9 @@ import 'package:re_highlight/styles/github.dart'; import 'package:share_plus/share_plus.dart'; class AboutPage extends StatefulWidget { - const AboutPage({super.key, this.showAppBar}); + const AboutPage({super.key, this.showAppBar = true}); - final bool? showAppBar; + final bool showAppBar; @override State createState() => _AboutPageState(); @@ -72,14 +74,16 @@ class _AboutPageState extends State { const style = TextStyle(fontSize: 15); final outline = theme.colorScheme.outline; final subTitleStyle = TextStyle(fontSize: 13, color: outline); + final showAppBar = widget.showAppBar; + final padding = MediaQuery.viewPaddingOf(context); return Scaffold( - appBar: widget.showAppBar == false - ? null - : AppBar(title: const Text('关于')), + appBar: showAppBar ? AppBar(title: const Text('关于')) : null, resizeToAvoidBottomInset: false, body: ListView( padding: EdgeInsets.only( - bottom: MediaQuery.viewPaddingOf(context).bottom + 100, + left: showAppBar ? padding.left : 0, + right: showAppBar ? padding.right : 0, + bottom: padding.bottom + 100, ), children: [ GestureDetector( @@ -201,23 +205,27 @@ Commit Hash: ${BuildConfig.commitHash}''', trailing: Icon(Icons.arrow_forward, size: 16, color: outline), ), ListTile( - onTap: () => showConfirmDialog( - context: context, - title: '提示', - content: '该操作将清除图片及网络请求缓存数据,确认清除?', - onConfirm: () async { - SmartDialog.showLoading(msg: '正在清除...'); - try { - await CacheManage.clearLibraryCache(); - SmartDialog.showToast('清除成功'); - } catch (err) { - SmartDialog.showToast(err.toString()); - } finally { - SmartDialog.dismiss(); - } - getCacheSize(); - }, - ), + onTap: () { + if (cacheSize.value.isNotEmpty) { + showConfirmDialog( + context: context, + title: '提示', + content: '该操作将清除图片及网络请求缓存数据,确认清除?', + onConfirm: () async { + SmartDialog.showLoading(msg: '正在清除...'); + try { + await CacheManage.clearLibraryCache(); + SmartDialog.showToast('清除成功'); + } catch (err) { + SmartDialog.showToast(err.toString()); + } finally { + SmartDialog.dismiss(); + } + getCacheSize(); + }, + ); + } + }, leading: const Icon(Icons.delete_outline), title: const Text('清除缓存'), subtitle: Obx( diff --git a/lib/pages/article/controller.dart b/lib/pages/article/controller.dart index 560412b5f..907a6a1a5 100644 --- a/lib/pages/article/controller.dart +++ b/lib/pages/article/controller.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/http/dynamics.dart'; import 'package:PiliPlus/http/fav.dart'; +import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/models/dynamics/article_content_model.dart' show ArticleContentModel; @@ -9,6 +10,7 @@ import 'package:PiliPlus/models_new/article/article_info/data.dart'; import 'package:PiliPlus/models_new/article/article_view/data.dart'; import 'package:PiliPlus/pages/common/dyn/common_dyn_controller.dart'; import 'package:PiliPlus/utils/accounts.dart'; +import 'package:PiliPlus/utils/app_scheme.dart'; import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:PiliPlus/utils/url_utils.dart'; @@ -46,8 +48,9 @@ class ArticleController extends CommonDynController { @override void onInit() { super.onInit(); - id = Get.parameters['id']!; - type = Get.parameters['type']!; + final params = Get.parameters; + id = params['id']!; + type = params['type']!; // to opus if (type == 'read') { @@ -55,8 +58,11 @@ class ArticleController extends CommonDynController { url, ) { if (url != null) { - id = url.split('/').last; - type = 'opus'; + final opusId = PiliScheme.uriDigitRegExp.firstMatch(url)?.group(1); + if (opusId != null) { + id = opusId; + type = 'opus'; + } } init(); }); @@ -99,8 +105,10 @@ class ArticleController extends CommonDynController { ..author ??= opusData.modules.moduleAuthor ..title ??= opusData.modules.moduleTag?.text; return true; + } else { + loadingState.value = res as Error; + return false; } - return false; } Future queryRead(int cvid) async { @@ -116,8 +124,10 @@ class ArticleController extends CommonDynController { getArticleInfo(); } return true; + } else { + loadingState.value = res as Error; + return false; } - return false; } // stats @@ -208,6 +218,14 @@ class ArticleController extends CommonDynController { SmartDialog.showToast(res['msg']); } } + + @override + Future onReload() { + if (!isLoaded.value) { + return Future.value(); + } + return super.onReload(); + } } class Summary { diff --git a/lib/pages/article/view.dart b/lib/pages/article/view.dart index 1718b229a..a1d315b95 100644 --- a/lib/pages/article/view.dart +++ b/lib/pages/article/view.dart @@ -66,18 +66,15 @@ class _ArticlePageState extends CommonDynPageState { @override Widget build(BuildContext context) { final theme = Theme.of(context); - final size = MediaQuery.sizeOf(context); - final maxWidth = size.width; - isPortrait = size.height >= maxWidth; return Scaffold( resizeToAvoidBottomInset: false, - appBar: _buildAppBar(isPortrait, maxWidth), + appBar: _buildAppBar(), body: Padding( padding: EdgeInsets.only(left: padding.left, right: padding.right), child: Stack( clipBehavior: Clip.none, children: [ - _buildPage(theme, isPortrait, maxWidth), + _buildPage(theme), _buildBottom(theme), ], ), @@ -85,7 +82,7 @@ class _ArticlePageState extends CommonDynPageState { ); } - Widget _buildPage(ThemeData theme, bool isPortrait, double maxWidth) { + Widget _buildPage(ThemeData theme) { double padding = max(maxWidth / 2 - Grid.smallCardWidth, 0); if (isPortrait) { return Padding( @@ -462,12 +459,12 @@ class _ArticlePageState extends CommonDynPageState { : HttpError(onReload: controller.onReload), Error(:var errMsg) => HttpError( errMsg: errMsg, - onReload: controller.onReload, + onReload: controller.isLoaded.value ? controller.onReload : null, ), }; } - PreferredSizeWidget _buildAppBar(bool isPortrait, double maxWidth) => AppBar( + PreferredSizeWidget _buildAppBar() => AppBar( title: Obx(() { if (controller.isLoaded.value && controller.showTitle.value) { return Text(controller.summary.title ?? ''); diff --git a/lib/pages/article/widgets/opus_content.dart b/lib/pages/article/widgets/opus_content.dart index 939809ac2..c9822f1cc 100644 --- a/lib/pages/article/widgets/opus_content.dart +++ b/lib/pages/article/widgets/opus_content.dart @@ -62,7 +62,6 @@ class OpusContent extends StatelessWidget { @override Widget build(BuildContext context) { // if (kDebugMode) debugPrint('opusContent'); - if (opus.isEmpty) { return const SliverToBoxAdapter(); } diff --git a/lib/pages/article_list/view.dart b/lib/pages/article_list/view.dart index 15bd1e31e..3d76ab3a4 100644 --- a/lib/pages/article_list/view.dart +++ b/lib/pages/article_list/view.dart @@ -101,7 +101,7 @@ class _ArticleListPageState extends State with GridMixin { return SliverAppBar.medium( title: Text(item.name!), pinned: true, - expandedHeight: kToolbarHeight + 130, + expandedHeight: kToolbarHeight + 127, flexibleSpace: FlexibleSpaceBar( background: Container( height: 120, diff --git a/lib/pages/common/dyn/common_dyn_controller.dart b/lib/pages/common/dyn/common_dyn_controller.dart index a913199db..14edd6ac5 100644 --- a/lib/pages/common/dyn/common_dyn_controller.dart +++ b/lib/pages/common/dyn/common_dyn_controller.dart @@ -21,7 +21,7 @@ abstract class CommonDynController extends ReplyController late final horizontalPreview = Pref.horizontalPreview; late final List ratio = Pref.dynamicDetailRatio; - final double offsetDy = 1; + final fabOffset = const Offset(0, 1); @override void onInit() { @@ -31,7 +31,7 @@ abstract class CommonDynController extends ReplyController ); fabAnim = Tween( - begin: Offset(0, offsetDy), + begin: fabOffset, end: Offset.zero, ).animate( CurvedAnimation( diff --git a/lib/pages/common/dyn/common_dyn_page.dart b/lib/pages/common/dyn/common_dyn_page.dart index 463bc6495..a2619508d 100644 --- a/lib/pages/common/dyn/common_dyn_page.dart +++ b/lib/pages/common/dyn/common_dyn_page.dart @@ -3,13 +3,14 @@ import 'dart:math' show pi; import 'package:PiliPlus/common/skeleton/video_reply.dart'; import 'package:PiliPlus/common/widgets/custom_sliver_persistent_header_delegate.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/view_safe_area.dart'; import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' show ReplyInfo; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/pages/common/dyn/common_dyn_controller.dart'; import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart'; import 'package:PiliPlus/pages/video/reply_reply/view.dart'; -import 'package:PiliPlus/utils/context_ext.dart'; +import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/feed_back.dart'; import 'package:PiliPlus/utils/num_util.dart'; import 'package:PiliPlus/utils/page_utils.dart'; @@ -25,18 +26,21 @@ abstract class CommonDynPageState extends State late final scaffoldKey = GlobalKey(); - bool get horizontalPreview => - context.isLandscape && controller.horizontalPreview; + bool get horizontalPreview => !isPortrait && controller.horizontalPreview; Function(List imgList, int index)? imageCallback; dynamic get arguments; late EdgeInsets padding; late bool isPortrait; + late double maxWidth; @override void didChangeDependencies() { super.didChangeDependencies(); + final size = MediaQuery.sizeOf(context); + maxWidth = size.width; + isPortrait = size.isPortrait; imageCallback = horizontalPreview ? (imgList, index) { controller.hideFab(); @@ -193,14 +197,18 @@ abstract class CommonDynPageState extends State ), ), ), - body: VideoReplyReplyPanel( - enableSlide: false, - id: id, - oid: oid, - rpid: rpid, - isVideoDetail: false, - replyType: controller.replyType, - firstFloor: replyItem, + body: ViewSafeArea( + left: showBackBtn, + right: showBackBtn, + child: VideoReplyReplyPanel( + enableSlide: false, + id: id, + oid: oid, + rpid: rpid, + isVideoDetail: false, + replyType: controller.replyType, + firstFloor: replyItem, + ), ), ); if (isPortrait) { @@ -252,14 +260,15 @@ abstract class CommonDynPageState extends State controller.ratio ..[0] = value ..[1] = 100 - value; - GStorage.setting.put( - SettingBoxKey.dynamicDetailRatio, - controller.ratio, - ); + (context as Element).markNeedsBuild(); setState(() {}); } }, + onChangeEnd: (_) => GStorage.setting.put( + SettingBoxKey.dynamicDetailRatio, + controller.ratio, + ), ), ), ), @@ -274,12 +283,14 @@ abstract class CommonDynPageState extends State Widget get replyButton => FloatingActionButton( heroTag: null, onPressed: () { - feedBack(); - controller.onReply( - context, - oid: controller.oid, - replyType: controller.replyType, - ); + try { + feedBack(); + controller.onReply( + context, + oid: controller.oid, + replyType: controller.replyType, + ); + } catch (_) {} }, tooltip: '评论', child: const Icon(Icons.reply), diff --git a/lib/pages/common/slide/common_slide_page.dart b/lib/pages/common/slide/common_slide_page.dart index 96621f69b..f2801bdb4 100644 --- a/lib/pages/common/slide/common_slide_page.dart +++ b/lib/pages/common/slide/common_slide_page.dart @@ -6,9 +6,9 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; abstract class CommonSlidePage extends StatefulWidget { - const CommonSlidePage({super.key, this.enableSlide}); + const CommonSlidePage({super.key, this.enableSlide = true}); - final bool? enableSlide; + final bool enableSlide; } abstract class CommonSlidePageState extends State @@ -26,7 +26,7 @@ abstract class CommonSlidePageState extends State @override void initState() { super.initState(); - enableSlide = widget.enableSlide != false && slideDismissReplyPage; + enableSlide = widget.enableSlide && slideDismissReplyPage; if (enableSlide) { _animController = AnimationController( vsync: this, diff --git a/lib/pages/dynamics/widgets/author_panel.dart b/lib/pages/dynamics/widgets/author_panel.dart index 310be923d..dd8d907e9 100644 --- a/lib/pages/dynamics/widgets/author_panel.dart +++ b/lib/pages/dynamics/widgets/author_panel.dart @@ -48,7 +48,6 @@ class AuthorPanel extends StatelessWidget { Widget avatar = PendantAvatar( avatar: item.modules.moduleAuthor?.face, size: pendant.isNullOrEmpty ? 40 : 34, - isVip: null, officialType: null, // 已被注释 garbPendantImage: pendant, ); diff --git a/lib/pages/dynamics/widgets/dynamic_panel.dart b/lib/pages/dynamics/widgets/dynamic_panel.dart index f6c93b666..5fab2bddf 100644 --- a/lib/pages/dynamics/widgets/dynamic_panel.dart +++ b/lib/pages/dynamics/widgets/dynamic_panel.dart @@ -7,7 +7,6 @@ import 'package:PiliPlus/pages/dynamics/widgets/author_panel.dart'; import 'package:PiliPlus/pages/dynamics/widgets/blocked_item.dart'; import 'package:PiliPlus/pages/dynamics/widgets/content_panel.dart'; import 'package:PiliPlus/pages/dynamics/widgets/module_panel.dart'; -import 'package:PiliPlus/utils/context_ext.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:flutter/material.dart' hide InkWell; @@ -21,6 +20,7 @@ class DynamicPanel extends StatelessWidget { final Function(bool isTop, dynamic dynId)? onSetTop; final VoidCallback? onBlock; final VoidCallback? onUnfold; + final bool isDetailPortraitW; const DynamicPanel({ super.key, @@ -33,6 +33,7 @@ class DynamicPanel extends StatelessWidget { this.onSetTop, this.onBlock, this.onUnfold, + this.isDetailPortraitW = true, }); @override @@ -150,7 +151,7 @@ class DynamicPanel extends StatelessWidget { ), ), ); - if (isSave || (isDetail && context.isLandscape)) { + if (isSave || (isDetail && !isDetailPortraitW)) { return child; } return DecoratedBox( diff --git a/lib/pages/dynamics/widgets/module_panel.dart b/lib/pages/dynamics/widgets/module_panel.dart index 6d9f50ccd..b9bb13ec9 100644 --- a/lib/pages/dynamics/widgets/module_panel.dart +++ b/lib/pages/dynamics/widgets/module_panel.dart @@ -307,7 +307,6 @@ Widget module( return InkWell( onTap: () => PageUtils.handleWebview("https:${music['jump_url']}"), child: Container( - width: double.infinity, padding: const EdgeInsets.only( left: 12, top: 10, diff --git a/lib/pages/dynamics/widgets/vote.dart b/lib/pages/dynamics/widgets/vote.dart index f6d0da015..1215fbb6b 100644 --- a/lib/pages/dynamics/widgets/vote.dart +++ b/lib/pages/dynamics/widgets/vote.dart @@ -4,7 +4,6 @@ import 'package:PiliPlus/common/widgets/dialog/report.dart'; import 'package:PiliPlus/http/dynamics.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/dynamics/vote_model.dart'; -import 'package:PiliPlus/utils/context_ext.dart'; import 'package:PiliPlus/utils/date_util.dart'; import 'package:PiliPlus/utils/num_util.dart'; import 'package:flutter/material.dart'; @@ -45,7 +44,8 @@ class _VotePanelState extends State { @override Widget build(BuildContext context) { final theme = Theme.of(context); - final usePortrait = context.isPortrait || context.isTablet; + final size = MediaQuery.sizeOf(context); + final usePortrait = size.width < 600 || size.shortestSide >= 600; final right = [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/lib/pages/dynamics_create/view.dart b/lib/pages/dynamics_create/view.dart index db6e39096..996ce7f04 100644 --- a/lib/pages/dynamics_create/view.dart +++ b/lib/pages/dynamics_create/view.dart @@ -242,7 +242,6 @@ class _CreateDynPanelState extends CommonRichTextPubPageState { Widget _buildImageList(ThemeData theme) => SizedBox( height: 100, - width: double.infinity, child: SingleChildScrollView( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 16), @@ -779,8 +778,8 @@ class _CreateDynPanelState extends CommonRichTextPubPageState { children: [ GestureDetector( onTap: _onReserve, + behavior: HitTestBehavior.opaque, child: Container( - width: double.infinity, decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(8)), color: theme.colorScheme.onInverseSurface, @@ -789,7 +788,7 @@ class _CreateDynPanelState extends CommonRichTextPubPageState { padding: const EdgeInsets.fromLTRB(12, 12, 30, 12), child: Column( spacing: 3, - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text('直播预约: ${reserveCard.title}'), Text( diff --git a/lib/pages/dynamics_detail/view.dart b/lib/pages/dynamics_detail/view.dart index 3b8a92787..442add7ed 100644 --- a/lib/pages/dynamics_detail/view.dart +++ b/lib/pages/dynamics_detail/view.dart @@ -50,25 +50,22 @@ class _DynamicDetailPageState extends CommonDynPageState { @override Widget build(BuildContext context) { final theme = Theme.of(context); - final size = MediaQuery.sizeOf(context); - final maxWidth = size.width; - isPortrait = size.height >= maxWidth; return Scaffold( resizeToAvoidBottomInset: false, - appBar: _buildAppBar(isPortrait, maxWidth), + appBar: _buildAppBar(), body: Padding( padding: EdgeInsets.only(left: padding.left, right: padding.right), child: isPortrait ? refreshIndicator( onRefresh: controller.onRefresh, - child: _buildBody(theme, isPortrait, maxWidth), + child: _buildBody(theme), ) - : _buildBody(theme, isPortrait, maxWidth), + : _buildBody(theme), ), ); } - PreferredSizeWidget _buildAppBar(bool isPortrait, double maxWidth) => AppBar( + PreferredSizeWidget _buildAppBar() => AppBar( title: Padding( padding: const EdgeInsets.only(right: 12), child: Obx( @@ -96,7 +93,7 @@ class _DynamicDetailPageState extends CommonDynPageState { ], ); - Widget _buildBody(ThemeData theme, bool isPortrait, double maxWidth) { + Widget _buildBody(ThemeData theme) { double padding = max(maxWidth / 2 - Grid.smallCardWidth, 0); Widget child; if (isPortrait) { @@ -112,6 +109,7 @@ class _DynamicDetailPageState extends CommonDynPageState { isDetail: true, callback: imageCallback, maxWidth: maxWidth - this.padding.horizontal - 2 * padding, + isDetailPortraitW: isPortrait, ), ), buildReplyHeader(theme), @@ -145,6 +143,7 @@ class _DynamicDetailPageState extends CommonDynPageState { (maxWidth - this.padding.horizontal) * (flex / (flex + flex1)) - padding, + isDetailPortraitW: isPortrait, ), ), ), diff --git a/lib/pages/dynamics_repost/view.dart b/lib/pages/dynamics_repost/view.dart index 7ee6af3f4..2dda06e5c 100644 --- a/lib/pages/dynamics_repost/view.dart +++ b/lib/pages/dynamics_repost/view.dart @@ -27,7 +27,6 @@ class RepostPanel extends CommonRichTextPubPage { this.pic, this.title, this.uname, - this.isMax, }); // video @@ -36,7 +35,6 @@ class RepostPanel extends CommonRichTextPubPage { final String? pic; final String? title; final String? uname; - final bool? isMax; final DynamicItemModel? item; final String? dynIdStr; @@ -47,8 +45,8 @@ class RepostPanel extends CommonRichTextPubPage { } class _RepostPanelState extends CommonRichTextPubPageState { - late bool _isMax = widget.isMax ?? false; - bool? _isExpanded; + late bool _isMax = false; + late bool _isExpanded = false; late final _key = GlobalKey(); @@ -117,7 +115,7 @@ class _RepostPanelState extends CommonRichTextPubPageState { ) : page(); - return _isExpanded == true + return _isExpanded ? child() : AnimatedSize( alignment: Alignment.topCenter, diff --git a/lib/pages/dynamics_tab/view.dart b/lib/pages/dynamics_tab/view.dart index 429fb7b18..21856ee49 100644 --- a/lib/pages/dynamics_tab/view.dart +++ b/lib/pages/dynamics_tab/view.dart @@ -87,9 +87,7 @@ class _DynamicsTabPageState controller: controller.scrollController, slivers: [ SliverPadding( - padding: EdgeInsets.only( - bottom: MediaQuery.viewPaddingOf(context).bottom + 100, - ), + padding: const EdgeInsets.only(bottom: 100), sliver: buildPage( Obx(() => _buildBody(controller.loadingState.value)), ), diff --git a/lib/pages/episode_panel/view.dart b/lib/pages/episode_panel/view.dart index 27a59038b..629e26d2c 100644 --- a/lib/pages/episode_panel/view.dart +++ b/lib/pages/episode_panel/view.dart @@ -430,9 +430,9 @@ class _EpisodePanelState extends CommonCollapseSlidePageState { top: 6, right: 6, type: switch (episode.badge) { - '会员' => PBadgeType.primary, + '预告' => PBadgeType.gray, '限免' => PBadgeType.free, - _ => PBadgeType.gray, + _ => PBadgeType.primary, }, ), ], diff --git a/lib/pages/fav/note/child_view.dart b/lib/pages/fav/note/child_view.dart index d4a72fc42..14881c2db 100644 --- a/lib/pages/fav/note/child_view.dart +++ b/lib/pages/fav/note/child_view.dart @@ -72,7 +72,6 @@ class _FavNoteChildPageState extends State ), ), ), - width: double.infinity, child: Row( children: [ const SizedBox(width: 16), diff --git a/lib/pages/fav/pgc/child_view.dart b/lib/pages/fav/pgc/child_view.dart index 690823fcc..fdc4f064a 100644 --- a/lib/pages/fav/pgc/child_view.dart +++ b/lib/pages/fav/pgc/child_view.dart @@ -78,7 +78,6 @@ class _FavPgcChildPageState extends State ), ), ), - width: double.infinity, child: Row( children: [ const SizedBox(width: 16), diff --git a/lib/pages/follow_search/view.dart b/lib/pages/follow_search/view.dart index b7c6a966f..5f1644720 100644 --- a/lib/pages/follow_search/view.dart +++ b/lib/pages/follow_search/view.dart @@ -11,11 +11,11 @@ class FollowSearchPage extends CommonSearchPage { const FollowSearchPage({ super.key, this.mid, - this.isFromSelect, + this.isFromSelect = false, }); final int? mid; - final bool? isFromSelect; + final bool isFromSelect; @override State createState() => _FollowSearchPageState(); @@ -40,7 +40,7 @@ class _FollowSearchPageState } return FollowItem( item: list[index], - onSelect: widget.mid != null && widget.isFromSelect != false + onSelect: widget.mid != null && widget.isFromSelect ? (userModel) => Get.back(result: userModel) : null, ); diff --git a/lib/pages/home/view.dart b/lib/pages/home/view.dart index 408d3d568..6215aaf83 100644 --- a/lib/pages/home/view.dart +++ b/lib/pages/home/view.dart @@ -6,7 +6,7 @@ import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/pages/home/controller.dart'; import 'package:PiliPlus/pages/main/controller.dart'; import 'package:PiliPlus/pages/mine/controller.dart'; -import 'package:PiliPlus/utils/context_ext.dart'; +import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/feed_back.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart' hide ContextExtensionss; @@ -33,7 +33,8 @@ class _HomePageState extends State final theme = Theme.of(context); return Column( children: [ - if (!_homeController.useSideBar && context.isPortrait) + if (!_homeController.useSideBar && + MediaQuery.sizeOf(context).isPortrait) customAppBar(theme), if (_homeController.tabs.length > 1) Material( diff --git a/lib/pages/hot/view.dart b/lib/pages/hot/view.dart index b4856b6ec..58084aca2 100644 --- a/lib/pages/hot/view.dart +++ b/lib/pages/hot/view.dart @@ -135,10 +135,7 @@ class _HotPageState extends CommonPageState ), ), SliverPadding( - padding: EdgeInsets.only( - top: 7, - bottom: MediaQuery.viewPaddingOf(context).bottom + 100, - ), + padding: const EdgeInsets.only(top: 7, bottom: 100), sliver: Obx( () => _buildBody(controller.loadingState.value), ), diff --git a/lib/pages/live/view.dart b/lib/pages/live/view.dart index 259bdaaf9..73e02b987 100644 --- a/lib/pages/live/view.dart +++ b/lib/pages/live/view.dart @@ -51,9 +51,9 @@ class _LivePageState extends CommonPageState physics: const AlwaysScrollableScrollPhysics(), slivers: [ SliverPadding( - padding: EdgeInsets.only( + padding: const EdgeInsets.only( top: StyleString.cardSpace, - bottom: MediaQuery.viewPaddingOf(context).bottom + 100, + bottom: 100, ), sliver: SliverMainAxisGroup( slivers: [ diff --git a/lib/pages/live_dm_block/view.dart b/lib/pages/live_dm_block/view.dart index 60300001f..10f4c10aa 100644 --- a/lib/pages/live_dm_block/view.dart +++ b/lib/pages/live_dm_block/view.dart @@ -7,7 +7,7 @@ import 'package:PiliPlus/models/common/live_dm_silent_type.dart'; import 'package:PiliPlus/models_new/live/live_dm_block/shield_user_list.dart'; import 'package:PiliPlus/pages/live_dm_block/controller.dart'; import 'package:PiliPlus/pages/search/widgets/search_text.dart'; -import 'package:PiliPlus/utils/context_ext.dart'; +import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; import 'package:flutter/material.dart'; @@ -31,10 +31,9 @@ class _LiveDmBlockPageState extends State { @override Widget build(BuildContext context) { - isPortrait = context.isPortrait; + isPortrait = MediaQuery.sizeOf(context).isPortrait; padding = MediaQuery.viewPaddingOf(context); final theme = Theme.of(context); - Widget tabBar = TabBar( controller: _controller.tabController, tabs: const [ diff --git a/lib/pages/live_room/send_danmaku/view.dart b/lib/pages/live_room/send_danmaku/view.dart index 359415d8f..335a696c6 100644 --- a/lib/pages/live_room/send_danmaku/view.dart +++ b/lib/pages/live_room/send_danmaku/view.dart @@ -66,7 +66,7 @@ class _ReplyPageState extends CommonRichTextPubPageState { mainAxisSize: MainAxisSize.min, children: [ ...buildInputView(theme), - buildPanelContainer(theme, Colors.transparent), + Flexible(child: buildPanelContainer(theme, Colors.transparent)), ], ), ), diff --git a/lib/pages/live_room/view.dart b/lib/pages/live_room/view.dart index c1b5ab0a4..5f48a94ff 100644 --- a/lib/pages/live_room/view.dart +++ b/lib/pages/live_room/view.dart @@ -177,7 +177,7 @@ class _LiveRoomPageState extends State final size = MediaQuery.sizeOf(context); maxWidth = size.width; maxHeight = size.height; - isPortrait = maxHeight >= maxWidth; + isPortrait = size.isPortrait; if (Platform.isAndroid) { return Floating().isPipMode ? videoPlayerPanel( diff --git a/lib/pages/live_room/widgets/chat_panel.dart b/lib/pages/live_room/widgets/chat_panel.dart index cb6960902..97d63b431 100644 --- a/lib/pages/live_room/widgets/chat_panel.dart +++ b/lib/pages/live_room/widgets/chat_panel.dart @@ -192,13 +192,16 @@ class LiveRoomChatPanel extends StatelessWidget { final uemote = obj.uemote; if (uemote != null) { // "room_{{room_id}}_{{int}}" or "upower_[{{emote}}]" + final isUpower = uemote.isUpower; return WidgetSpan( child: NetworkImgLayer( src: uemote.url, type: ImageType.emote, - width: uemote.width / devicePixelRatio, + width: isUpower ? uemote.width : uemote.width / devicePixelRatio, height: uemote.height == null ? null + : isUpower + ? uemote.height! : uemote.height! / devicePixelRatio, semanticsLabel: obj.text, ), diff --git a/lib/pages/login/view.dart b/lib/pages/login/view.dart index 8569f4d1d..c790fcb45 100644 --- a/lib/pages/login/view.dart +++ b/lib/pages/login/view.dart @@ -2,13 +2,14 @@ import 'dart:ui'; import 'package:PiliPlus/common/widgets/scroll_physics.dart'; import 'package:PiliPlus/pages/login/controller.dart'; +import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/image_util.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:get/get.dart'; +import 'package:get/get.dart' hide ContextExtensionss; import 'package:pretty_qr_code/pretty_qr_code.dart'; import 'package:saver_gallery/saver_gallery.dart'; @@ -506,92 +507,88 @@ class _LoginPageState extends State { padding = MediaQuery.viewPaddingOf(context).copyWith(top: 0) + const EdgeInsets.only(bottom: 25); - return OrientationBuilder( - builder: (context, orientation) { - final isLandscape = orientation == Orientation.landscape; - return Scaffold( - appBar: AppBar( - leading: IconButton( - tooltip: '关闭', - icon: const Icon(Icons.close_outlined), - onPressed: Get.back, - ), - title: Row( - children: [ - const Text('登录'), - if (isLandscape) - Expanded( - child: Align( - alignment: Alignment.centerRight, - child: TabBar( - isScrollable: true, - dividerHeight: 0, - tabs: const [ - Tab( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [Icon(Icons.password), Text(' 密码')], - ), - ), - Tab( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [Icon(Icons.sms_outlined), Text(' 短信')], - ), - ), - Tab( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [Icon(Icons.qr_code), Text(' 扫码')], - ), - ), - Tab( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.cookie_outlined), - Text(' Cookie'), - ], - ), - ), - ], - controller: _loginPageCtr.tabController, - ), - ), - ), - ], - ), - bottom: !isLandscape - ? TabBar( + final isLandscape = !MediaQuery.sizeOf(context).isPortrait; + return Scaffold( + appBar: AppBar( + leading: IconButton( + tooltip: '关闭', + icon: const Icon(Icons.close_outlined), + onPressed: Get.back, + ), + title: Row( + children: [ + const Text('登录'), + if (isLandscape) + Expanded( + child: Align( + alignment: Alignment.centerRight, + child: TabBar( + isScrollable: true, + dividerHeight: 0, tabs: const [ - Tab(icon: Icon(Icons.password), text: '密码'), - Tab(icon: Icon(Icons.sms_outlined), text: '短信'), - Tab(icon: Icon(Icons.qr_code), text: '扫码'), - Tab(icon: Icon(Icons.cookie_outlined), text: 'Cookie'), + Tab( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [Icon(Icons.password), Text(' 密码')], + ), + ), + Tab( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [Icon(Icons.sms_outlined), Text(' 短信')], + ), + ), + Tab( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [Icon(Icons.qr_code), Text(' 扫码')], + ), + ), + Tab( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.cookie_outlined), + Text(' Cookie'), + ], + ), + ), ], controller: _loginPageCtr.tabController, - ) - : null, - ), - body: NotificationListener( - onNotification: (notification) { - if (notification.metrics.axis == Axis.horizontal) { - FocusScope.of(context).unfocus(); - } - return false; - }, - child: tabBarView( - controller: _loginPageCtr.tabController, - children: [ - tabViewOuter(loginByPassword(theme)), - tabViewOuter(loginBySmS(theme)), - tabViewOuter(loginByQRCode(theme)), - tabViewOuter(loginByCookie(theme)), - ], - ), - ), - ); - }, + ), + ), + ), + ], + ), + bottom: !isLandscape + ? TabBar( + tabs: const [ + Tab(icon: Icon(Icons.password), text: '密码'), + Tab(icon: Icon(Icons.sms_outlined), text: '短信'), + Tab(icon: Icon(Icons.qr_code), text: '扫码'), + Tab(icon: Icon(Icons.cookie_outlined), text: 'Cookie'), + ], + controller: _loginPageCtr.tabController, + ) + : null, + ), + body: NotificationListener( + onNotification: (notification) { + if (notification.metrics.axis == Axis.horizontal) { + FocusScope.of(context).unfocus(); + } + return false; + }, + child: tabBarView( + controller: _loginPageCtr.tabController, + children: [ + tabViewOuter(loginByPassword(theme)), + tabViewOuter(loginBySmS(theme)), + tabViewOuter(loginByQRCode(theme)), + tabViewOuter(loginByCookie(theme)), + ], + ), + ), ); } diff --git a/lib/pages/login_devices/view.dart b/lib/pages/login_devices/view.dart index 6d8f369aa..addf99b1d 100644 --- a/lib/pages/login_devices/view.dart +++ b/lib/pages/login_devices/view.dart @@ -1,10 +1,11 @@ +import 'package:PiliPlus/common/widgets/list_tile.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/view_sliver_safe_area.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/login_devices/device.dart'; import 'package:PiliPlus/pages/login_devices/controller.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide ListTile; import 'package:get/get.dart'; class LoginDevicesPage extends StatefulWidget { @@ -30,15 +31,10 @@ class LloginDevicesPageState extends State { child: CustomScrollView( slivers: [ ViewSliverSafeArea( - sliver: MediaQuery.removeViewPadding( - context: context, - removeLeft: true, - removeRight: true, - child: Obx( - () => _buildBody( - colorScheme, - _controller.loadingState.value, - ), + sliver: Obx( + () => _buildBody( + colorScheme, + _controller.loadingState.value, ), ), ), diff --git a/lib/pages/main/view.dart b/lib/pages/main/view.dart index 0fe4aad39..a4d160859 100644 --- a/lib/pages/main/view.dart +++ b/lib/pages/main/view.dart @@ -53,7 +53,7 @@ class _MainAppState extends State _mainController ..checkUnreadDynamic() ..checkDefaultSearch(true) - ..checkUnread(context.isPortrait); + ..checkUnread(useBottomNav); super.didPopNext(); } @@ -69,7 +69,7 @@ class _MainAppState extends State _mainController ..checkUnreadDynamic() ..checkDefaultSearch(true) - ..checkUnread(context.isPortrait); + ..checkUnread(useBottomNav); } } @@ -90,17 +90,20 @@ class _MainAppState extends State } } + late bool useBottomNav; + @override Widget build(BuildContext context) { final theme = Theme.of(context); final padding = MediaQuery.viewPaddingOf(context); - final bool isPortrait = context.isPortrait; - final useBottomNav = isPortrait && !_mainController.useSideBar; + useBottomNav = + !_mainController.useSideBar && MediaQuery.sizeOf(context).isPortrait; Widget? bottomNav = useBottomNav ? _mainController.navigationBars.length > 1 ? _mainController.enableMYBar ? Obx( () => NavigationBar( + maintainBottomViewPadding: true, onDestinationSelected: _mainController.setIndex, selectedIndex: _mainController.selectedIndex.value, destinations: _mainController.navigationBars @@ -167,7 +170,10 @@ class _MainAppState extends State resizeToAvoidBottomInset: false, appBar: AppBar(toolbarHeight: 0), body: Padding( - padding: EdgeInsets.only(right: padding.right), + padding: EdgeInsets.only( + left: useBottomNav ? padding.left : 0.0, + right: padding.right, + ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -261,7 +267,7 @@ class _MainAppState extends State Expanded( child: _mainController.mainTabBarView ? CustomTabBarView( - scrollDirection: isPortrait + scrollDirection: useBottomNav ? Axis.horizontal : Axis.vertical, physics: const NeverScrollableScrollPhysics(), diff --git a/lib/pages/match_info/controller.dart b/lib/pages/match_info/controller.dart index 29fe37a1e..3248a60ab 100644 --- a/lib/pages/match_info/controller.dart +++ b/lib/pages/match_info/controller.dart @@ -1,3 +1,5 @@ +import 'dart:ui' show Offset; + import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/match.dart'; import 'package:PiliPlus/models_new/match/match_info/contest.dart'; @@ -17,7 +19,7 @@ class MatchInfoController extends CommonDynController { LoadingState.loading().obs; @override - double get offsetDy => 2; + Offset get fabOffset => const Offset(0, 2); @override void onInit() { diff --git a/lib/pages/member/widget/user_info_card.dart b/lib/pages/member/widget/user_info_card.dart index 6346bcbd1..19722d00d 100644 --- a/lib/pages/member/widget/user_info_card.dart +++ b/lib/pages/member/widget/user_info_card.dart @@ -44,9 +44,13 @@ class UserInfoCard extends StatelessWidget { Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; final isLight = colorScheme.brightness.isLight; - return context.isPortrait - ? _buildV(context, colorScheme, isLight) - : _buildH(colorScheme, isLight); + final isPortrait = context.width < 600; + return ViewSafeArea( + top: !isPortrait, + child: isPortrait + ? _buildV(context, colorScheme, isLight) + : _buildH(colorScheme, isLight), + ); } Widget _countWidget({ @@ -527,38 +531,35 @@ class UserInfoCard extends StatelessWidget { children: [ // _buildHeader(context), const SizedBox(height: 56), - ViewSafeArea( - top: true, - child: Row( - children: [ - const SizedBox(width: 20), - Padding( - padding: EdgeInsets.only( - top: 10, - bottom: card.prInfo?.content?.isNotEmpty == true ? 0 : 10, - ), - child: _buildAvatar, + Row( + children: [ + const SizedBox(width: 20), + Padding( + padding: EdgeInsets.only( + top: 10, + bottom: card.prInfo?.content?.isNotEmpty == true ? 0 : 10, ), - const SizedBox(width: 10), - Expanded( - flex: 5, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 10), - ..._buildLeft(colorScheme, isLight), - const SizedBox(height: 5), - ], - ), + child: _buildAvatar, + ), + const SizedBox(width: 10), + Expanded( + flex: 5, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 10), + ..._buildLeft(colorScheme, isLight), + const SizedBox(height: 5), + ], ), - Expanded( - flex: 3, - child: _buildRight(colorScheme), - ), - const SizedBox(width: 20), - ], - ), + ), + Expanded( + flex: 3, + child: _buildRight(colorScheme), + ), + const SizedBox(width: 20), + ], ), if (card.prInfo?.content?.isNotEmpty == true) buildPrInfo(colorScheme, isLight, card.prInfo!), diff --git a/lib/pages/member_contribute/view.dart b/lib/pages/member_contribute/view.dart index 2e5219a9b..81f5e809a 100644 --- a/lib/pages/member_contribute/view.dart +++ b/lib/pages/member_contribute/view.dart @@ -45,38 +45,36 @@ class _MemberContributeState extends State final theme = Theme.of(context); return _controller.tabs != null ? Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox( - width: double.infinity, - child: TabBar( - overlayColor: const WidgetStatePropertyAll( - Colors.transparent, - ), - splashFactory: NoSplash.splashFactory, - padding: const EdgeInsets.symmetric(horizontal: 8), - isScrollable: true, - tabs: _controller.tabs!, - tabAlignment: TabAlignment.start, - controller: _controller.tabController, - dividerHeight: 0, - indicatorWeight: 0, - indicatorPadding: const EdgeInsets.symmetric( - horizontal: 3, - vertical: 8, - ), - indicator: BoxDecoration( - color: theme.colorScheme.secondaryContainer, - borderRadius: const BorderRadius.all(Radius.circular(20)), - ), - indicatorSize: TabBarIndicatorSize.tab, - labelStyle: - TabBarTheme.of( - context, - ).labelStyle?.copyWith(fontSize: 14) ?? - const TextStyle(fontSize: 14), - labelColor: theme.colorScheme.onSecondaryContainer, - unselectedLabelColor: theme.colorScheme.outline, + TabBar( + overlayColor: const WidgetStatePropertyAll( + Colors.transparent, ), + splashFactory: NoSplash.splashFactory, + padding: const EdgeInsets.symmetric(horizontal: 8), + isScrollable: true, + tabs: _controller.tabs!, + tabAlignment: TabAlignment.start, + controller: _controller.tabController, + dividerHeight: 0, + indicatorWeight: 0, + indicatorPadding: const EdgeInsets.symmetric( + horizontal: 3, + vertical: 8, + ), + indicator: BoxDecoration( + color: theme.colorScheme.secondaryContainer, + borderRadius: const BorderRadius.all(Radius.circular(20)), + ), + indicatorSize: TabBarIndicatorSize.tab, + labelStyle: + TabBarTheme.of( + context, + ).labelStyle?.copyWith(fontSize: 14) ?? + const TextStyle(fontSize: 14), + labelColor: theme.colorScheme.onSecondaryContainer, + unselectedLabelColor: theme.colorScheme.outline, ), Expanded( child: TabBarView( diff --git a/lib/pages/member_home/view.dart b/lib/pages/member_home/view.dart index ac430320a..9f743cf9a 100644 --- a/lib/pages/member_home/view.dart +++ b/lib/pages/member_home/view.dart @@ -67,7 +67,7 @@ class _MemberHomeState extends State ); Widget _buildBody(LoadingState loadingState) { - final isVertical = context.isPortrait; + final isVertical = context.width < 600; final setting = _ctr.spaceSetting; final isOwner = setting != null; final color = Theme.of(context).colorScheme.outline; diff --git a/lib/pages/member_upower_rank/view.dart b/lib/pages/member_upower_rank/view.dart index ea5a5efb3..3194c9f0e 100644 --- a/lib/pages/member_upower_rank/view.dart +++ b/lib/pages/member_upower_rank/view.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart'; +import 'package:PiliPlus/common/widgets/list_tile.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart'; @@ -8,7 +9,7 @@ import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/models_new/upower_rank/rank_info.dart'; import 'package:PiliPlus/pages/member_upower_rank/controller.dart'; import 'package:PiliPlus/utils/utils.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide ListTile; import 'package:get/get.dart'; class UpowerRankPage extends StatefulWidget { diff --git a/lib/pages/music/video/view.dart b/lib/pages/music/video/view.dart index ea0e0e91a..d0a57e51b 100644 --- a/lib/pages/music/video/view.dart +++ b/lib/pages/music/video/view.dart @@ -2,6 +2,7 @@ import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/models_new/music/bgm_recommend_list.dart'; import 'package:PiliPlus/pages/music/video/controller.dart'; import 'package:PiliPlus/pages/music/widget/music_video_card_h.dart'; @@ -92,6 +93,7 @@ class _MusicRecommandPageState extends State width: 40, height: 40, src: info.mvCover, + type: ImageType.avatar, ), Column( mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/pages/music/view.dart b/lib/pages/music/view.dart index d22da336b..918e1b867 100644 --- a/lib/pages/music/view.dart +++ b/lib/pages/music/view.dart @@ -1,6 +1,5 @@ import 'dart:math'; -import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; import 'package:PiliPlus/common/widgets/custom_icon.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; @@ -48,25 +47,22 @@ class _MusicDetailPageState extends CommonDynPageState { @override Widget build(BuildContext context) { final theme = Theme.of(context); - final size = MediaQuery.sizeOf(context); - final maxWidth = size.width; - isPortrait = size.height >= maxWidth; return Scaffold( resizeToAvoidBottomInset: false, - appBar: _buildAppBar(isPortrait, maxWidth), + appBar: _buildAppBar(), body: Padding( padding: EdgeInsets.only(left: padding.left, right: padding.right), child: isPortrait ? refreshIndicator( onRefresh: controller.onRefresh, - child: _buildBody(theme, isPortrait, maxWidth), + child: _buildBody(theme), ) - : _buildBody(theme, isPortrait, maxWidth), + : _buildBody(theme), ), ); } - PreferredSizeWidget _buildAppBar(bool isPortrait, double maxWidth) => AppBar( + PreferredSizeWidget _buildAppBar() => AppBar( title: Padding( padding: const EdgeInsets.only(right: 12), child: Obx( @@ -104,11 +100,7 @@ class _MusicDetailPageState extends CommonDynPageState { ], ); - Widget _buildBody( - ThemeData theme, - bool isPortrait, - double maxWidth, - ) => Obx(() { + Widget _buildBody(ThemeData theme) => Obx(() { switch (controller.infoState.value) { case Success(:final response): double padding = max(maxWidth / 2 - Grid.smallCardWidth, 0); @@ -358,6 +350,7 @@ class _MusicDetailPageState extends CommonDynPageState { Widget child = Text('${artist.identity}: ${artist.name}', style: style); if (!artist.face.isNullOrEmpty) { child = Row( + spacing: 2, mainAxisSize: MainAxisSize.min, children: [ NetworkImgLayer( @@ -370,8 +363,7 @@ class _MusicDetailPageState extends CommonDynPageState { ], ); } - child = InkWell( - borderRadius: StyleString.mdRadius, + child = GestureDetector( onTap: artist.mid == null || artist.mid == 0 ? () => Utils.copyText(artist.name!) : () => Get.toNamed( @@ -386,22 +378,39 @@ class _MusicDetailPageState extends CommonDynPageState { Widget _buildRank( int? rank, String name, - TextTheme theme, [ + ThemeData theme, [ VoidCallback? onTap, ]) { + final outline = theme.colorScheme.outline; final child = Column( mainAxisSize: MainAxisSize.min, children: [ - Text(NumUtil.numFormat(rank), style: theme.bodyLarge), - Text(name, style: theme.bodySmall), + Text(NumUtil.numFormat(rank)), + Text( + name, + style: theme.textTheme.bodySmall!.copyWith(color: outline), + ), ], ); return onTap == null ? child : InkWell( onTap: onTap, - borderRadius: StyleString.mdRadius, - child: Padding(padding: const EdgeInsets.all(4), child: child), + borderRadius: const BorderRadius.all(Radius.circular(6)), + child: Padding( + padding: const EdgeInsets.all(4), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + child, + Icon( + size: 18, + color: outline, + Icons.keyboard_arrow_right, + ), + ], + ), + ), ); } @@ -414,31 +423,37 @@ class _MusicDetailPageState extends CommonDynPageState { child: Padding( padding: const EdgeInsets.all(16), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( - spacing: 8, + spacing: 10, + crossAxisAlignment: CrossAxisAlignment.start, children: [ GestureDetector( onTap: () => PageUtils.imageView( imgList: [SourceModel(url: item.mvCover!)], ), - child: NetworkImgLayer( - src: item.mvCover, - width: 80, - height: 80, + child: Hero( + tag: item.mvCover!, + child: NetworkImgLayer( + src: item.mvCover, + width: 80, + height: 80, + ), ), ), Expanded( child: Column( spacing: 2, mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - InkWell( - borderRadius: StyleString.mdRadius, + GestureDetector( // TODO: android intent ACTION_MEDIA_SEARCH onTap: () => Utils.copyText( item.musicTitle!, ), + behavior: HitTestBehavior.opaque, child: MarqueeText( item.musicTitle!, maxWidth: maxWidth - 136, // 80 + 16 + 32 + 8 @@ -448,7 +463,6 @@ class _MusicDetailPageState extends CommonDynPageState { Wrap( spacing: 8, runSpacing: 2, - alignment: WrapAlignment.spaceEvenly, children: [ if (!item.artistsList.isNullOrEmpty) for (var artist in item.artistsList!) @@ -456,10 +470,13 @@ class _MusicDetailPageState extends CommonDynPageState { if (!item.musicPublish.isNullOrEmpty) Text( '发行日期:${item.musicPublish}', - style: textTheme.bodySmall, + style: textTheme.bodySmall!.copyWith( + color: theme.colorScheme.outline, + ), ), ], ), + const SizedBox(height: 3), Wrap( spacing: 16, children: [ @@ -471,10 +488,7 @@ class _MusicDetailPageState extends CommonDynPageState { fontSize: 11, ), if (item.mvCid != null && item.mvCid != 0) - InkWell( - borderRadius: const BorderRadius.all( - Radius.circular(4), - ), + GestureDetector( onTap: () => PageUtils.toVideoPage( bvid: item.mvBvid, cid: item.mvCid!, @@ -540,15 +554,14 @@ class _MusicDetailPageState extends CommonDynPageState { const Divider(), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - spacing: 8, children: [ const Text('热歌榜排名'), - _buildRank(item.hotSongHeat?.lastHeat, '热度', textTheme), - _buildRank(item.listenPv, '总播放量', textTheme), + _buildRank(item.hotSongHeat?.lastHeat, '热度', theme), + _buildRank(item.listenPv, '总播放量', theme), _buildRank( item.musicRelation, '使用稿件量', - textTheme, + theme, () => Get.to( const MusicRecommandPage(), arguments: {'id': controller.musicId, 'detail': item}, diff --git a/lib/pages/music/widget/music_video_card_h.dart b/lib/pages/music/widget/music_video_card_h.dart index 998a1ac38..ce8ed46bf 100644 --- a/lib/pages/music/widget/music_video_card_h.dart +++ b/lib/pages/music/widget/music_video_card_h.dart @@ -115,6 +115,7 @@ class MusicVideoCardH extends StatelessWidget { ), ], ), + const SizedBox(height: 3), BounceMarquee( animation: animation, child: Row( diff --git a/lib/pages/pgc/view.dart b/lib/pages/pgc/view.dart index 19cf44c1b..5738e8d41 100644 --- a/lib/pages/pgc/view.dart +++ b/lib/pages/pgc/view.dart @@ -203,10 +203,10 @@ class _PgcPageState extends CommonPageState List _buildRcmd(ThemeData theme) => [ _buildRcmdTitle(theme), SliverPadding( - padding: EdgeInsets.only( + padding: const EdgeInsets.only( left: StyleString.safeSpace, right: StyleString.safeSpace, - bottom: MediaQuery.viewPaddingOf(context).bottom + 100, + bottom: 100, ), sliver: Obx( () => _buildRcmdBody(controller.loadingState.value), diff --git a/lib/pages/pgc_review/post/view.dart b/lib/pages/pgc_review/post/view.dart index 7c003b42a..2ecf59eac 100644 --- a/lib/pages/pgc_review/post/view.dart +++ b/lib/pages/pgc_review/post/view.dart @@ -110,8 +110,7 @@ class _PgcReviewPostPanelState extends State { ), ), ), - SizedBox( - width: double.infinity, + Center( child: Obx( () { final score = _score.value; @@ -124,7 +123,6 @@ class _PgcReviewPostPanelState extends State { 5 => '佳作', _ => '轻触评分', }, - textAlign: TextAlign.center, style: TextStyle( fontSize: 16, color: score == 0 diff --git a/lib/pages/pgc_review/view.dart b/lib/pages/pgc_review/view.dart index 705d34be7..e66ecb496 100644 --- a/lib/pages/pgc_review/view.dart +++ b/lib/pages/pgc_review/view.dart @@ -41,49 +41,47 @@ class _PgcReviewPageState extends State clipBehavior: Clip.none, children: [ Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox( - width: double.infinity, - child: TabBar( - controller: _tabController, - isScrollable: true, - tabAlignment: TabAlignment.start, - dividerHeight: 0, - indicatorWeight: 0, - overlayColor: const WidgetStatePropertyAll(Colors.transparent), - splashFactory: NoSplash.splashFactory, - padding: const EdgeInsets.only(left: 6), - indicatorPadding: const EdgeInsets.symmetric( - horizontal: 3, - vertical: 8, - ), - indicator: BoxDecoration( - color: theme.colorScheme.secondaryContainer, - borderRadius: const BorderRadius.all(Radius.circular(20)), - ), - indicatorSize: TabBarIndicatorSize.tab, - labelColor: theme.colorScheme.onSecondaryContainer, - unselectedLabelColor: theme.colorScheme.outline, - labelStyle: - TabBarTheme.of( - context, - ).labelStyle?.copyWith(fontSize: 13) ?? - const TextStyle(fontSize: 13), - dividerColor: Colors.transparent, - tabs: PgcReviewType.values - .map((e) => Tab(text: e.label)) - .toList(), - onTap: (index) { - try { - if (!_tabController.indexIsChanging) { - final item = PgcReviewType.values[index]; - Get.find( - tag: '${widget.mediaId}${item.name}', - ).scrollController.animToTop(); - } - } catch (_) {} - }, + TabBar( + controller: _tabController, + isScrollable: true, + tabAlignment: TabAlignment.start, + dividerHeight: 0, + indicatorWeight: 0, + overlayColor: const WidgetStatePropertyAll(Colors.transparent), + splashFactory: NoSplash.splashFactory, + padding: const EdgeInsets.only(left: 6), + indicatorPadding: const EdgeInsets.symmetric( + horizontal: 3, + vertical: 8, ), + indicator: BoxDecoration( + color: theme.colorScheme.secondaryContainer, + borderRadius: const BorderRadius.all(Radius.circular(20)), + ), + indicatorSize: TabBarIndicatorSize.tab, + labelColor: theme.colorScheme.onSecondaryContainer, + unselectedLabelColor: theme.colorScheme.outline, + labelStyle: + TabBarTheme.of( + context, + ).labelStyle?.copyWith(fontSize: 13) ?? + const TextStyle(fontSize: 13), + dividerColor: Colors.transparent, + tabs: PgcReviewType.values + .map((e) => Tab(text: e.label)) + .toList(), + onTap: (index) { + try { + if (!_tabController.indexIsChanging) { + final item = PgcReviewType.values[index]; + Get.find( + tag: '${widget.mediaId}${item.name}', + ).scrollController.animToTop(); + } + } catch (_) {} + }, ), Expanded( child: TabBarView( diff --git a/lib/pages/rank/view.dart b/lib/pages/rank/view.dart index 105ae6f80..3c3cc03c9 100644 --- a/lib/pages/rank/view.dart +++ b/lib/pages/rank/view.dart @@ -27,9 +27,7 @@ class _RankPageState extends State SizedBox( width: 64, child: ListView( - padding: EdgeInsets.only( - bottom: MediaQuery.viewPaddingOf(context).bottom + 100, - ), + padding: const EdgeInsets.only(bottom: 100), children: List.generate( RankType.values.length, (index) => IntrinsicHeight( @@ -53,13 +51,14 @@ class _RankPageState extends State child: Row( mainAxisSize: MainAxisSize.min, children: [ - Container( - height: double.infinity, - width: 3, - color: isCurr - ? theme.colorScheme.primary - : Colors.transparent, - ), + if (isCurr) + Container( + height: double.infinity, + width: 3, + color: theme.colorScheme.primary, + ) + else + const SizedBox(width: 3), Expanded( flex: 1, child: Container( diff --git a/lib/pages/rank/zone/view.dart b/lib/pages/rank/zone/view.dart index 9d03e67eb..a33baf0ff 100644 --- a/lib/pages/rank/zone/view.dart +++ b/lib/pages/rank/zone/view.dart @@ -41,10 +41,7 @@ class _ZonePageState extends CommonPageState physics: const AlwaysScrollableScrollPhysics(), slivers: [ SliverPadding( - padding: EdgeInsets.only( - top: 7, - bottom: MediaQuery.viewPaddingOf(context).bottom + 100, - ), + padding: const EdgeInsets.only(top: 7, bottom: 100), sliver: Obx(() => _buildBody(controller.loadingState.value)), ), ], diff --git a/lib/pages/rcmd/view.dart b/lib/pages/rcmd/view.dart index 1acbfb870..8dde8a72e 100644 --- a/lib/pages/rcmd/view.dart +++ b/lib/pages/rcmd/view.dart @@ -39,9 +39,9 @@ class _RcmdPageState extends CommonPageState physics: const AlwaysScrollableScrollPhysics(), slivers: [ SliverPadding( - padding: EdgeInsets.only( + padding: const EdgeInsets.only( top: StyleString.cardSpace, - bottom: MediaQuery.viewPaddingOf(context).bottom + 100, + bottom: 100, ), sliver: Obx(() => _buildBody(controller.loadingState.value)), ), diff --git a/lib/pages/search/view.dart b/lib/pages/search/view.dart index d7dba7844..c51584d7c 100644 --- a/lib/pages/search/view.dart +++ b/lib/pages/search/view.dart @@ -8,8 +8,8 @@ import 'package:PiliPlus/pages/about/view.dart' show showInportExportDialog; import 'package:PiliPlus/pages/search/controller.dart'; import 'package:PiliPlus/pages/search/widgets/hot_keyword.dart'; import 'package:PiliPlus/pages/search/widgets/search_text.dart'; -import 'package:PiliPlus/utils/context_ext.dart'; import 'package:PiliPlus/utils/em.dart' show Em; +import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage_key.dart'; import 'package:PiliPlus/utils/utils.dart'; @@ -34,7 +34,7 @@ class _SearchPageState extends State { @override Widget build(BuildContext context) { final theme = Theme.of(context); - final isPortrait = context.isPortrait; + final isPortrait = MediaQuery.sizeOf(context).isPortrait; return Scaffold( appBar: AppBar( shape: Border( @@ -121,14 +121,13 @@ class _SearchPageState extends State { _searchController.controller.text != '' ? Column( mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, children: _searchController.searchSuggestList .map( (item) => InkWell( borderRadius: const BorderRadius.all(Radius.circular(4)), onTap: () => _searchController.onClickKeyword(item.term!), - child: Container( - width: double.infinity, + child: Padding( padding: const EdgeInsets.only( left: 20, top: 9, diff --git a/lib/pages/search_panel/all/controller.dart b/lib/pages/search_panel/all/controller.dart index e97fd8cf8..09410b6dc 100644 --- a/lib/pages/search_panel/all/controller.dart +++ b/lib/pages/search_panel/all/controller.dart @@ -14,7 +14,7 @@ class SearchAllController required super.tag, }); - bool? hasJump2Video; + late bool hasJump2Video = false; @override void onInit() { @@ -31,7 +31,7 @@ class SearchAllController bool customHandleResponse(bool isRefresh, Success response) { searchResultController?.count[searchType.index] = response.response.numResults ?? 0; - if (searchType == SearchType.video && hasJump2Video != true && isRefresh) { + if (searchType == SearchType.video && !hasJump2Video && isRefresh) { hasJump2Video = true; onPushDetail(response.response.list); } diff --git a/lib/pages/search_panel/article/controller.dart b/lib/pages/search_panel/article/controller.dart index 74858e343..ef59f9077 100644 --- a/lib/pages/search_panel/article/controller.dart +++ b/lib/pages/search_panel/article/controller.dart @@ -54,8 +54,7 @@ class SearchArticleController builder: (context) { final theme = Theme.of(context); return SingleChildScrollView( - child: Container( - width: double.infinity, + child: Padding( padding: EdgeInsets.only( top: 20, left: 16, diff --git a/lib/pages/search_panel/live/widgets/item.dart b/lib/pages/search_panel/live/widgets/item.dart index 7fe612f45..f3b8235f2 100644 --- a/lib/pages/search_panel/live/widgets/item.dart +++ b/lib/pages/search_panel/live/widgets/item.dart @@ -22,6 +22,7 @@ class LiveItem extends StatelessWidget { cover: liveItem.cover, ), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ AspectRatio( aspectRatio: StyleString.aspectRatio, @@ -86,15 +87,12 @@ class LiveItem extends StatelessWidget { .toList(), ), ), - SizedBox( - width: double.infinity, - child: Text( - liveItem.uname!, - maxLines: 1, - style: TextStyle( - fontSize: theme.textTheme.labelMedium!.fontSize, - color: theme.colorScheme.outline, - ), + Text( + liveItem.uname!, + maxLines: 1, + style: TextStyle( + fontSize: theme.textTheme.labelMedium!.fontSize, + color: theme.colorScheme.outline, ), ), ], diff --git a/lib/pages/search_panel/user/controller.dart b/lib/pages/search_panel/user/controller.dart index f44cf91a4..5c650a8e2 100644 --- a/lib/pages/search_panel/user/controller.dart +++ b/lib/pages/search_panel/user/controller.dart @@ -34,8 +34,7 @@ class SearchUserController builder: (context) { final theme = Theme.of(context); return SingleChildScrollView( - child: Container( - width: double.infinity, + child: Padding( padding: EdgeInsets.only( top: 20, left: 16, diff --git a/lib/pages/search_panel/video/controller.dart b/lib/pages/search_panel/video/controller.dart index 05086e076..c422205ff 100644 --- a/lib/pages/search_panel/video/controller.dart +++ b/lib/pages/search_panel/video/controller.dart @@ -22,7 +22,7 @@ class SearchVideoController required super.tag, }); - bool? hasJump2Video; + late bool hasJump2Video = false; @override void onInit() { @@ -45,7 +45,7 @@ class SearchVideoController bool customHandleResponse(bool isRefresh, Success response) { searchResultController?.count[searchType.index] = response.response.numResults ?? 0; - if (searchType == SearchType.video && hasJump2Video != true && isRefresh) { + if (searchType == SearchType.video && !hasJump2Video && isRefresh) { hasJump2Video = true; onPushDetail(response.response.list); } @@ -154,8 +154,7 @@ class SearchVideoController } return SingleChildScrollView( - child: Container( - width: double.infinity, + child: Padding( padding: EdgeInsets.only( top: 20, left: 16, diff --git a/lib/pages/search_result/view.dart b/lib/pages/search_result/view.dart index 61fc1093f..a5b3fff93 100644 --- a/lib/pages/search_result/view.dart +++ b/lib/pages/search_result/view.dart @@ -23,7 +23,7 @@ class _SearchResultPageState extends State late SearchResultController _searchResultController; late TabController _tabController; final String _tag = DateTime.now().millisecondsSinceEpoch.toString(); - final bool? _isFromSearch = Get.arguments?['fromSearch']; + final bool _isFromSearch = Get.arguments?['fromSearch'] ?? false; SSearchController? sSearchController; @override @@ -40,7 +40,7 @@ class _SearchResultPageState extends State length: SearchType.values.length, ); - if (_isFromSearch == true) { + if (_isFromSearch) { try { sSearchController = Get.find( tag: Get.parameters['tag'], @@ -76,7 +76,7 @@ class _SearchResultPageState extends State ), title: GestureDetector( onTap: () { - if (_isFromSearch == true) { + if (_isFromSearch) { Get.back(); } else { Get.offNamed( @@ -98,58 +98,56 @@ class _SearchResultPageState extends State ), body: ViewSafeArea( child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox( - width: double.infinity, - child: TabBar( - overlayColor: const WidgetStatePropertyAll(Colors.transparent), - splashFactory: NoSplash.splashFactory, - padding: const EdgeInsets.only(top: 4, left: 8, right: 8), - controller: _tabController, - tabs: SearchType.values - .map( - (item) => Obx( - () { - int count = _searchResultController.count[item.index]; - return Tab( - text: - '${item.label}${count != -1 ? ' ${count > 99 ? '99+' : count}' : ''}', - ); - }, - ), - ) - .toList(), - isScrollable: true, - indicatorWeight: 0, - indicatorPadding: const EdgeInsets.symmetric( - horizontal: 3, - vertical: 8, - ), - indicator: BoxDecoration( - color: theme.colorScheme.secondaryContainer, - borderRadius: const BorderRadius.all(Radius.circular(20)), - ), - indicatorSize: TabBarIndicatorSize.tab, - labelColor: theme.colorScheme.onSecondaryContainer, - labelStyle: - TabBarTheme.of( - context, - ).labelStyle?.copyWith(fontSize: 13) ?? - const TextStyle(fontSize: 13), - dividerColor: Colors.transparent, - dividerHeight: 0, - unselectedLabelColor: theme.colorScheme.outline, - tabAlignment: TabAlignment.start, - onTap: (index) { - if (!_tabController.indexIsChanging) { - if (_searchResultController.toTopIndex.value == index) { - _searchResultController.toTopIndex.refresh(); - } else { - _searchResultController.toTopIndex.value = index; - } - } - }, + TabBar( + overlayColor: const WidgetStatePropertyAll(Colors.transparent), + splashFactory: NoSplash.splashFactory, + padding: const EdgeInsets.only(top: 4, left: 8, right: 8), + controller: _tabController, + tabs: SearchType.values + .map( + (item) => Obx( + () { + int count = _searchResultController.count[item.index]; + return Tab( + text: + '${item.label}${count != -1 ? ' ${count > 99 ? '99+' : count}' : ''}', + ); + }, + ), + ) + .toList(), + isScrollable: true, + indicatorWeight: 0, + indicatorPadding: const EdgeInsets.symmetric( + horizontal: 3, + vertical: 8, ), + indicator: BoxDecoration( + color: theme.colorScheme.secondaryContainer, + borderRadius: const BorderRadius.all(Radius.circular(20)), + ), + indicatorSize: TabBarIndicatorSize.tab, + labelColor: theme.colorScheme.onSecondaryContainer, + labelStyle: + TabBarTheme.of( + context, + ).labelStyle?.copyWith(fontSize: 13) ?? + const TextStyle(fontSize: 13), + dividerColor: Colors.transparent, + dividerHeight: 0, + unselectedLabelColor: theme.colorScheme.outline, + tabAlignment: TabAlignment.start, + onTap: (index) { + if (!_tabController.indexIsChanging) { + if (_searchResultController.toTopIndex.value == index) { + _searchResultController.toTopIndex.refresh(); + } else { + _searchResultController.toTopIndex.value = index; + } + } + }, ), Expanded( child: tabBarView( diff --git a/lib/pages/search_trending/view.dart b/lib/pages/search_trending/view.dart index 262fe6ad7..13e9e4380 100644 --- a/lib/pages/search_trending/view.dart +++ b/lib/pages/search_trending/view.dart @@ -1,17 +1,20 @@ import 'dart:math'; +import 'package:PiliPlus/common/widgets/list_tile.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/search/search_trending/list.dart'; import 'package:PiliPlus/pages/search_trending/controller.dart'; +import 'package:PiliPlus/utils/context_ext.dart'; +import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/image_util.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide ListTile; import 'package:flutter/services.dart' show SystemUiOverlayStyle; -import 'package:get/get.dart'; +import 'package:get/get.dart' hide ContextExtensionss; class SearchTrendingPage extends StatefulWidget { const SearchTrendingPage({super.key}); @@ -54,96 +57,90 @@ class _SearchTrendingPageState extends State { @override Widget build(BuildContext context) { final theme = Theme.of(context); - return LayoutBuilder( - builder: (context, constraints) { - final maxWidth = constraints.maxWidth; - final width = constraints.maxWidth > constraints.maxHeight - ? min(640.0, maxWidth * 0.6) - : maxWidth; - final height = width * 528 / 1125; - final padding = MediaQuery.viewPaddingOf(context); - _offset = height - 56 - padding.top; - listener(); - final removePadding = maxWidth > width; - return Scaffold( - extendBody: true, - extendBodyBehindAppBar: true, - resizeToAvoidBottomInset: false, - appBar: PreferredSize( - preferredSize: const Size.fromHeight(56), - child: Obx( - () { - final scrollRatio = _scrollRatio.value; - final flag = removePadding || scrollRatio >= 0.5; - return AppBar( - title: Opacity( - opacity: scrollRatio, - child: Text( - 'bilibili热搜', - style: TextStyle( - color: flag ? null : Colors.white, - ), - ), - ), - backgroundColor: theme.colorScheme.surface.withValues( - alpha: scrollRatio, - ), - foregroundColor: flag ? null : Colors.white, - systemOverlayStyle: flag - ? null - : const SystemUiOverlayStyle( - statusBarBrightness: Brightness.dark, - statusBarIconBrightness: Brightness.light, - ), - shape: scrollRatio == 1 - ? Border( - bottom: BorderSide( - color: theme.colorScheme.outline.withValues( - alpha: 0.1, - ), - ), - ) - : null, - ); - }, - ), - ), - body: Center( - child: SizedBox( - width: width, - child: MediaQuery.removePadding( - context: context, - removeLeft: removePadding, - removeRight: removePadding, - child: refreshIndicator( - onRefresh: _controller.onRefresh, - child: CustomScrollView( - controller: _controller.scrollController, - physics: const AlwaysScrollableScrollPhysics(), - slivers: [ - SliverToBoxAdapter( - child: Image.asset( - width: width, - height: height, - 'assets/images/trending_banner.png', - filterQuality: FilterQuality.low, - ), - ), - SliverPadding( - padding: EdgeInsets.only(bottom: padding.bottom + 100), - sliver: Obx( - () => - _buildBody(theme, _controller.loadingState.value), - ), - ), - ], + final padding = MediaQuery.viewPaddingOf(context); + final size = context.mediaQuerySize; + final maxWidth = size.width - padding.horizontal; + final width = size.isPortrait ? maxWidth : min(640.0, maxWidth * 0.6); + final height = width * 528 / 1125; + _offset = height - 56 - padding.top; + listener(); + return Scaffold( + extendBody: true, + extendBodyBehindAppBar: true, + resizeToAvoidBottomInset: false, + appBar: PreferredSize( + preferredSize: const Size.fromHeight(56), + child: Obx( + () { + final scrollRatio = _scrollRatio.value; + final flag = maxWidth > width || scrollRatio >= 0.5; + return AppBar( + title: Opacity( + opacity: scrollRatio, + child: Text( + 'bilibili热搜', + style: TextStyle( + color: flag ? null : Colors.white, ), ), ), + backgroundColor: theme.colorScheme.surface.withValues( + alpha: scrollRatio, + ), + foregroundColor: flag ? null : Colors.white, + systemOverlayStyle: flag + ? null + : const SystemUiOverlayStyle( + statusBarBrightness: Brightness.dark, + statusBarIconBrightness: Brightness.light, + ), + shape: scrollRatio == 1 + ? Border( + bottom: BorderSide( + color: theme.colorScheme.outline.withValues( + alpha: 0.1, + ), + ), + ) + : null, + ); + }, + ), + ), + body: Padding( + padding: EdgeInsets.only( + left: padding.left, + right: padding.right, + ), + child: Center( + child: SizedBox( + width: width, + child: refreshIndicator( + onRefresh: _controller.onRefresh, + child: CustomScrollView( + controller: _controller.scrollController, + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + SliverToBoxAdapter( + child: Image.asset( + width: width, + height: height, + 'assets/images/trending_banner.png', + filterQuality: FilterQuality.low, + ), + ), + SliverPadding( + padding: EdgeInsets.only(bottom: padding.bottom + 100), + sliver: Obx( + () => _buildBody(theme, _controller.loadingState.value), + ), + ), + ], + ), ), ), - ); - }, + ), + ), ); } diff --git a/lib/pages/setting/extra_setting.dart b/lib/pages/setting/extra_setting.dart index 988182d98..1784a233c 100644 --- a/lib/pages/setting/extra_setting.dart +++ b/lib/pages/setting/extra_setting.dart @@ -2,9 +2,9 @@ import 'package:PiliPlus/pages/setting/models/extra_settings.dart'; import 'package:flutter/material.dart'; class ExtraSetting extends StatefulWidget { - const ExtraSetting({super.key, this.showAppBar}); + const ExtraSetting({super.key, this.showAppBar = true}); - final bool? showAppBar; + final bool showAppBar; @override State createState() => _ExtraSettingState(); @@ -15,14 +15,16 @@ class _ExtraSettingState extends State { @override Widget build(BuildContext context) { + final showAppBar = widget.showAppBar; + final padding = MediaQuery.viewPaddingOf(context); return Scaffold( resizeToAvoidBottomInset: false, - appBar: widget.showAppBar == false - ? null - : AppBar(title: const Text('其它设置')), + appBar: showAppBar ? AppBar(title: const Text('其它设置')) : null, body: ListView( padding: EdgeInsets.only( - bottom: MediaQuery.viewPaddingOf(context).bottom + 100, + left: showAppBar ? padding.left : 0, + right: showAppBar ? padding.right : 0, + bottom: padding.bottom + 100, ), children: settings.map((item) => item.widget).toList(), ), diff --git a/lib/pages/setting/models/model.dart b/lib/pages/setting/models/model.dart index 92b199309..0079b3b20 100644 --- a/lib/pages/setting/models/model.dart +++ b/lib/pages/setting/models/model.dart @@ -16,9 +16,9 @@ class SettingsModel { final String? subtitle; final StringGetter? getSubtitle; final String? setKey; - final bool? defaultVal; + final bool defaultVal; final ValueChanged? onChanged; - final bool? needReboot; + final bool needReboot; final Widget? leading; final Widget Function()? getTrailing; final Function? onTap; @@ -32,9 +32,9 @@ class SettingsModel { this.subtitle, this.getSubtitle, this.setKey, - this.defaultVal, + this.defaultVal = false, this.onChanged, - this.needReboot, + this.needReboot = false, this.leading, this.getTrailing, this.onTap, @@ -49,9 +49,6 @@ class SettingsModel { subtitle: subtitle, getSubtitle: getSubtitle, setKey: setKey, - defaultVal: defaultVal, - onChanged: onChanged, - needReboot: needReboot, leading: leading, getTrailing: getTrailing, onTap: onTap, diff --git a/lib/pages/setting/models/style_settings.dart b/lib/pages/setting/models/style_settings.dart index 7d2180323..72925d98f 100644 --- a/lib/pages/setting/models/style_settings.dart +++ b/lib/pages/setting/models/style_settings.dart @@ -449,7 +449,6 @@ List get styleSettings => [ contentPadding: const EdgeInsets.symmetric(vertical: 16), title: const Text('Color Picker'), content: SlideColorPicker( - showResetBtn: false, color: reduceLuxColor ?? Colors.white, callback: (Color? color) { if (color != null && color != reduceLuxColor) { diff --git a/lib/pages/setting/pages/bar_set.dart b/lib/pages/setting/pages/bar_set.dart index 66e46cca6..ee0de3520 100644 --- a/lib/pages/setting/pages/bar_set.dart +++ b/lib/pages/setting/pages/bar_set.dart @@ -68,11 +68,13 @@ class _BarSetPageState extends State { ), body: ReorderableListView( onReorder: onReorder, - footer: SizedBox( - height: MediaQuery.viewPaddingOf(context).bottom + 30, + footer: Padding( + padding: + MediaQuery.viewPaddingOf(context).copyWith(top: 0, left: 0) + + const EdgeInsets.only(right: 34, top: 10), child: const Align( alignment: Alignment.centerRight, - child: Text('*长按拖动排序 '), + child: Text('*长按拖动排序'), ), ), children: defaultBars diff --git a/lib/pages/setting/pages/color_select.dart b/lib/pages/setting/pages/color_select.dart index a278b926d..6a9353211 100644 --- a/lib/pages/setting/pages/color_select.dart +++ b/lib/pages/setting/pages/color_select.dart @@ -53,142 +53,145 @@ class _ColorSelectPageState extends State { TextStyle subTitleStyle = theme.textTheme.labelMedium!.copyWith( color: theme.colorScheme.outline, ); - final size = Get.size; + final size = MediaQuery.sizeOf(context); + final padding = MediaQuery.viewPaddingOf( + context, + ).copyWith(top: 0, bottom: 0); return Scaffold( resizeToAvoidBottomInset: false, appBar: AppBar(title: const Text('选择应用主题')), - body: SafeArea( - bottom: false, - child: ListView( - children: [ - ListTile( - onTap: () async { - ThemeType? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: '主题模式', - value: ctr.themeType.value, - values: ThemeType.values.map((e) => (e, e.desc)).toList(), - ); - }, - ); - if (result != null) { - try { - Get.find().themeType.value = result; - } catch (_) {} - ctr.themeType.value = result; - GStorage.setting.put(SettingBoxKey.themeMode, result.index); - Get.changeThemeMode(result.toThemeMode); - } - }, - leading: Container( - width: 40, - alignment: Alignment.center, - child: const Icon(Icons.flashlight_on_outlined), - ), - title: Text('主题模式', style: titleStyle), - subtitle: Obx( - () => Text( - '当前模式:${ctr.themeType.value.desc}', - style: subTitleStyle, - ), + body: ListView( + children: [ + ListTile( + onTap: () async { + ThemeType? result = await showDialog( + context: context, + builder: (context) { + return SelectDialog( + title: '主题模式', + value: ctr.themeType.value, + values: ThemeType.values.map((e) => (e, e.desc)).toList(), + ); + }, + ); + if (result != null) { + try { + Get.find().themeType.value = result; + } catch (_) {} + ctr.themeType.value = result; + GStorage.setting.put(SettingBoxKey.themeMode, result.index); + Get.changeThemeMode(result.toThemeMode); + } + }, + leading: Container( + width: 40, + alignment: Alignment.center, + child: const Icon(Icons.flashlight_on_outlined), + ), + title: Text('主题模式', style: titleStyle), + subtitle: Obx( + () => Text( + '当前模式:${ctr.themeType.value.desc}', + style: subTitleStyle, ), ), - Obx( - () => ListTile( - enabled: ctr.type.value != 0, - title: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text('调色板风格'), - PopupMenuButton( - enabled: ctr.type.value != 0, - initialValue: _dynamicSchemeVariant, - onSelected: (item) { - _dynamicSchemeVariant = item; - GStorage.setting.put( - SettingBoxKey.schemeVariant, - item.index, - ); - Get.forceAppUpdate(); - }, - itemBuilder: (context) => FlexSchemeVariant.values - .map( - (item) => PopupMenuItem( - value: item, - child: Text(item.variantName), - ), - ) - .toList(), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - _dynamicSchemeVariant.variantName, - style: TextStyle( - height: 1, - fontSize: 13, - color: ctr.type.value == 0 - ? theme.colorScheme.outline.withValues( - alpha: 0.8, - ) - : theme.colorScheme.secondary, - ), - strutStyle: const StrutStyle(leading: 0, height: 1), + ), + Obx( + () => ListTile( + enabled: ctr.type.value != 0, + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text('调色板风格'), + PopupMenuButton( + enabled: ctr.type.value != 0, + initialValue: _dynamicSchemeVariant, + onSelected: (item) { + _dynamicSchemeVariant = item; + GStorage.setting.put( + SettingBoxKey.schemeVariant, + item.index, + ); + Get.forceAppUpdate(); + }, + itemBuilder: (context) => FlexSchemeVariant.values + .map( + (item) => PopupMenuItem( + value: item, + child: Text(item.variantName), ), - Icon( - size: 20, - Icons.keyboard_arrow_right, + ) + .toList(), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + _dynamicSchemeVariant.variantName, + style: TextStyle( + height: 1, + fontSize: 13, color: ctr.type.value == 0 ? theme.colorScheme.outline.withValues( alpha: 0.8, ) : theme.colorScheme.secondary, ), - ], - ), + strutStyle: const StrutStyle(leading: 0, height: 1), + ), + Icon( + size: 20, + Icons.keyboard_arrow_right, + color: ctr.type.value == 0 + ? theme.colorScheme.outline.withValues( + alpha: 0.8, + ) + : theme.colorScheme.secondary, + ), + ], ), - ], - ), - leading: Container( - width: 40, - alignment: Alignment.center, - child: const Icon(Icons.palette_outlined), - ), - subtitle: Text( - _dynamicSchemeVariant.description, - style: const TextStyle(fontSize: 12), - ), + ), + ], + ), + leading: Container( + width: 40, + alignment: Alignment.center, + child: const Icon(Icons.palette_outlined), + ), + subtitle: Text( + _dynamicSchemeVariant.description, + style: const TextStyle(fontSize: 12), ), ), - Obx( - () => RadioListTile( - value: 0, - title: const Text('动态取色'), - groupValue: ctr.type.value, - onChanged: (dynamic val) { - ctr - ..type.value = 0 - ..setting.put(SettingBoxKey.dynamicColor, true); - Get.forceAppUpdate(); - }, - ), + ), + Obx( + () => RadioListTile( + value: 0, + title: const Text('动态取色'), + groupValue: ctr.type.value, + onChanged: (dynamic val) { + ctr + ..type.value = 0 + ..setting.put(SettingBoxKey.dynamicColor, true); + Get.forceAppUpdate(); + }, ), - Obx( - () => RadioListTile( - value: 1, - title: const Text('指定颜色'), - groupValue: ctr.type.value, - onChanged: (dynamic val) { - ctr - ..type.value = 1 - ..setting.put(SettingBoxKey.dynamicColor, false); - Get.forceAppUpdate(); - }, - ), + ), + Obx( + () => RadioListTile( + value: 1, + title: const Text('指定颜色'), + groupValue: ctr.type.value, + onChanged: (dynamic val) { + ctr + ..type.value = 1 + ..setting.put(SettingBoxKey.dynamicColor, false); + Get.forceAppUpdate(); + }, ), - AnimatedSize( + ), + Padding( + padding: padding, + child: AnimatedSize( curve: Curves.easeInOut, alignment: Alignment.topCenter, duration: const Duration(milliseconds: 200), @@ -239,8 +242,11 @@ class _ColorSelectPageState extends State { ), ), ), - ...[ - IgnorePointer( + ), + ...[ + Padding( + padding: padding, + child: IgnorePointer( child: Container( height: size.height / 2, width: size.width, @@ -248,21 +254,21 @@ class _ColorSelectPageState extends State { child: const HomePage(), ), ), - IgnorePointer( - child: NavigationBar( - destinations: NavigationBarType.values - .map( - (item) => NavigationDestination( - icon: item.icon, - label: item.label, - ), - ) - .toList(), - ), + ), + IgnorePointer( + child: NavigationBar( + destinations: NavigationBarType.values + .map( + (item) => NavigationDestination( + icon: item.icon, + label: item.label, + ), + ) + .toList(), ), - ], + ), ], - ), + ], ), ); } diff --git a/lib/pages/setting/pages/display_mode.dart b/lib/pages/setting/pages/display_mode.dart index 8437def8d..236a2416e 100644 --- a/lib/pages/setting/pages/display_mode.dart +++ b/lib/pages/setting/pages/display_mode.dart @@ -62,45 +62,44 @@ class _SetDisplayModeState extends State { return Scaffold( resizeToAvoidBottomInset: false, appBar: AppBar(title: const Text('屏幕帧率设置')), - body: SafeArea( - bottom: false, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only(left: 25, top: 10, bottom: 5), - child: Text( - '没有生效?重启app试试', - style: TextStyle(color: Theme.of(context).colorScheme.outline), - ), + body: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: + MediaQuery.viewPaddingOf(context).copyWith(top: 0, bottom: 0) + + const EdgeInsets.only(left: 25, top: 10, bottom: 5), + child: Text( + '没有生效?重启app试试', + style: TextStyle(color: Theme.of(context).colorScheme.outline), ), - Expanded( - child: ListView.builder( - itemCount: modes.length, - itemBuilder: (context, index) { - final DisplayMode mode = modes[index]; - return RadioListTile( - value: mode, - title: mode == DisplayMode.auto - ? const Text('自动') - : Text('$mode${mode == active ? ' [系统]' : ''}'), - groupValue: preferred, - onChanged: (DisplayMode? newMode) { - FlutterDisplayMode.setPreferredMode( - newMode!, - ).whenComplete( - () => Future.delayed( - const Duration(milliseconds: 100), - fetchAll, - ), - ); - }, - ); - }, - ), + ), + Expanded( + child: ListView.builder( + itemCount: modes.length, + itemBuilder: (context, index) { + final DisplayMode mode = modes[index]; + return RadioListTile( + value: mode, + title: mode == DisplayMode.auto + ? const Text('自动') + : Text('$mode${mode == active ? ' [系统]' : ''}'), + groupValue: preferred, + onChanged: (DisplayMode? newMode) { + FlutterDisplayMode.setPreferredMode( + newMode!, + ).whenComplete( + () => Future.delayed( + const Duration(milliseconds: 100), + fetchAll, + ), + ); + }, + ); + }, ), - ], - ), + ), + ], ), ); } diff --git a/lib/pages/setting/pages/font_size_select.dart b/lib/pages/setting/pages/font_size_select.dart index 79daa932b..5db6d00f0 100644 --- a/lib/pages/setting/pages/font_size_select.dart +++ b/lib/pages/setting/pages/font_size_select.dart @@ -55,7 +55,6 @@ class _FontSizeSelectPageState extends State { ), ), Container( - width: double.infinity, padding: const EdgeInsets.all(20), decoration: BoxDecoration( border: Border( diff --git a/lib/pages/setting/pages/logs.dart b/lib/pages/setting/pages/logs.dart index d9d131ae0..e579e3552 100644 --- a/lib/pages/setting/pages/logs.dart +++ b/lib/pages/setting/pages/logs.dart @@ -181,7 +181,7 @@ class _LogsPageState extends State { return Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, spacing: 5, children: [ @@ -222,8 +222,7 @@ class _LogsPageState extends State { ], ), Card( - child: Container( - width: double.infinity, + child: Padding( padding: const EdgeInsets.all(12.0), child: SelectableText(log.body), ), diff --git a/lib/pages/setting/pages/play_speed_set.dart b/lib/pages/setting/pages/play_speed_set.dart index de0e1f26d..46cbf9f9b 100644 --- a/lib/pages/setting/pages/play_speed_set.dart +++ b/lib/pages/setting/pages/play_speed_set.dart @@ -1,12 +1,13 @@ import 'dart:math'; +import 'package:PiliPlus/common/widgets/list_tile.dart'; import 'package:PiliPlus/common/widgets/view_safe_area.dart'; import 'package:PiliPlus/pages/setting/widgets/switch_item.dart'; import 'package:PiliPlus/utils/context_ext.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage_key.dart'; import 'package:PiliPlus/utils/storage_pref.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide ListTile; import 'package:flutter/services.dart' show FilteringTextInputFormatter; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart' hide ContextExtensionss; diff --git a/lib/pages/setting/play_setting.dart b/lib/pages/setting/play_setting.dart index cccb1d4c7..847118539 100644 --- a/lib/pages/setting/play_setting.dart +++ b/lib/pages/setting/play_setting.dart @@ -2,9 +2,9 @@ import 'package:PiliPlus/pages/setting/models/play_settings.dart'; import 'package:flutter/material.dart'; class PlaySetting extends StatefulWidget { - const PlaySetting({super.key, this.showAppBar}); + const PlaySetting({super.key, this.showAppBar = true}); - final bool? showAppBar; + final bool showAppBar; @override State createState() => _PlaySettingState(); @@ -15,14 +15,16 @@ class _PlaySettingState extends State { @override Widget build(BuildContext context) { + final showAppBar = widget.showAppBar; + final padding = MediaQuery.viewPaddingOf(context); return Scaffold( resizeToAvoidBottomInset: false, - appBar: widget.showAppBar == false - ? null - : AppBar(title: const Text('播放器设置')), + appBar: showAppBar ? AppBar(title: const Text('播放器设置')) : null, body: ListView( padding: EdgeInsets.only( - bottom: MediaQuery.viewPaddingOf(context).bottom + 100, + left: showAppBar ? padding.left : 0, + right: showAppBar ? padding.right : 0, + bottom: padding.bottom + 100, ), children: settings.map((item) => item.widget).toList(), ), diff --git a/lib/pages/setting/privacy_setting.dart b/lib/pages/setting/privacy_setting.dart index 903942a39..f2ff30bb3 100644 --- a/lib/pages/setting/privacy_setting.dart +++ b/lib/pages/setting/privacy_setting.dart @@ -2,9 +2,9 @@ import 'package:PiliPlus/pages/setting/models/privacy_settings.dart'; import 'package:flutter/material.dart'; class PrivacySetting extends StatefulWidget { - const PrivacySetting({super.key, this.showAppBar}); + const PrivacySetting({super.key, this.showAppBar = true}); - final bool? showAppBar; + final bool showAppBar; @override State createState() => _PrivacySettingState(); @@ -15,14 +15,16 @@ class _PrivacySettingState extends State { @override Widget build(BuildContext context) { + final showAppBar = widget.showAppBar; + final padding = MediaQuery.viewPaddingOf(context); return Scaffold( resizeToAvoidBottomInset: false, - appBar: widget.showAppBar == false - ? null - : AppBar(title: const Text('隐私设置')), + appBar: showAppBar ? AppBar(title: const Text('隐私设置')) : null, body: ListView( padding: EdgeInsets.only( - bottom: MediaQuery.viewPaddingOf(context).bottom + 100, + left: showAppBar ? padding.left : 0, + right: showAppBar ? padding.right : 0, + bottom: padding.bottom + 100, ), children: settings.map((item) => item.widget).toList(), ), diff --git a/lib/pages/setting/recommend_setting.dart b/lib/pages/setting/recommend_setting.dart index 19f76c0f8..07fc36a99 100644 --- a/lib/pages/setting/recommend_setting.dart +++ b/lib/pages/setting/recommend_setting.dart @@ -1,11 +1,12 @@ +import 'package:PiliPlus/common/widgets/list_tile.dart'; import 'package:PiliPlus/pages/setting/models/model.dart'; import 'package:PiliPlus/pages/setting/models/recommend_settings.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide ListTile; class RecommendSetting extends StatefulWidget { - const RecommendSetting({super.key, this.showAppBar}); + const RecommendSetting({super.key, this.showAppBar = true}); - final bool? showAppBar; + final bool showAppBar; @override State createState() => _RecommendSettingState(); @@ -24,6 +25,8 @@ class _RecommendSettingState extends State { @override Widget build(BuildContext context) { + final showAppBar = widget.showAppBar; + final padding = MediaQuery.viewPaddingOf(context); final theme = Theme.of(context); return Scaffold( resizeToAvoidBottomInset: false, @@ -32,7 +35,9 @@ class _RecommendSettingState extends State { : AppBar(title: const Text('推荐流设置')), body: ListView( padding: EdgeInsets.only( - bottom: MediaQuery.viewPaddingOf(context).bottom + 100, + left: showAppBar ? padding.left : 0, + right: showAppBar ? padding.right : 0, + bottom: padding.bottom + 100, ), children: [ ...part.map((item) => item.widget), diff --git a/lib/pages/setting/slide_color_picker.dart b/lib/pages/setting/slide_color_picker.dart index a9771bf13..3411b6921 100644 --- a/lib/pages/setting/slide_color_picker.dart +++ b/lib/pages/setting/slide_color_picker.dart @@ -8,12 +8,12 @@ class SlideColorPicker extends StatefulWidget { super.key, required this.color, required this.callback, - this.showResetBtn, + this.showResetBtn = false, }); final Color color; final Function(Color? color) callback; - final bool? showResetBtn; + final bool showResetBtn; @override State createState() => _SlideColorPickerState(); @@ -158,7 +158,7 @@ class _SlideColorPickerState extends State { ), Row( children: [ - if (widget.showResetBtn != false) ...[ + if (widget.showResetBtn) ...[ const SizedBox(width: 16), TextButton( onPressed: () { diff --git a/lib/pages/setting/style_setting.dart b/lib/pages/setting/style_setting.dart index 57f0ca54b..0294f7023 100644 --- a/lib/pages/setting/style_setting.dart +++ b/lib/pages/setting/style_setting.dart @@ -2,9 +2,9 @@ import 'package:PiliPlus/pages/setting/models/style_settings.dart'; import 'package:flutter/material.dart'; class StyleSetting extends StatefulWidget { - const StyleSetting({super.key, this.showAppBar}); + const StyleSetting({super.key, this.showAppBar = true}); - final bool? showAppBar; + final bool showAppBar; @override State createState() => _StyleSettingState(); @@ -15,14 +15,16 @@ class _StyleSettingState extends State { @override Widget build(BuildContext context) { + final showAppBar = widget.showAppBar; + final padding = MediaQuery.viewPaddingOf(context); return Scaffold( resizeToAvoidBottomInset: false, - appBar: widget.showAppBar == false - ? null - : AppBar(title: const Text('外观设置')), + appBar: showAppBar ? AppBar(title: const Text('外观设置')) : null, body: ListView( padding: EdgeInsets.only( - bottom: MediaQuery.viewPaddingOf(context).bottom + 100, + left: showAppBar ? padding.left : 0, + right: showAppBar ? padding.right : 0, + bottom: padding.bottom + 100, ), children: settings.map((item) => item.widget).toList(), ), diff --git a/lib/pages/setting/video_setting.dart b/lib/pages/setting/video_setting.dart index fe58900e8..131899931 100644 --- a/lib/pages/setting/video_setting.dart +++ b/lib/pages/setting/video_setting.dart @@ -2,9 +2,9 @@ import 'package:PiliPlus/pages/setting/models/video_settings.dart'; import 'package:flutter/material.dart'; class VideoSetting extends StatefulWidget { - const VideoSetting({super.key, this.showAppBar}); + const VideoSetting({super.key, this.showAppBar = true}); - final bool? showAppBar; + final bool showAppBar; @override State createState() => _VideoSettingState(); @@ -15,14 +15,16 @@ class _VideoSettingState extends State { @override Widget build(BuildContext context) { + final showAppBar = widget.showAppBar; + final padding = MediaQuery.viewPaddingOf(context); return Scaffold( resizeToAvoidBottomInset: false, - appBar: widget.showAppBar == false - ? null - : AppBar(title: const Text('音视频设置')), + appBar: showAppBar ? AppBar(title: const Text('音视频设置')) : null, body: ListView( padding: EdgeInsets.only( - bottom: MediaQuery.viewPaddingOf(context).bottom + 100, + left: showAppBar ? padding.left : 0, + right: showAppBar ? padding.right : 0, + bottom: padding.bottom + 100, ), children: settings.map((item) => item.widget).toList(), ), diff --git a/lib/pages/setting/view.dart b/lib/pages/setting/view.dart index a6a867e20..b26a8fb8e 100644 --- a/lib/pages/setting/view.dart +++ b/lib/pages/setting/view.dart @@ -1,3 +1,5 @@ +import 'package:PiliPlus/common/widgets/list_tile.dart'; +import 'package:PiliPlus/common/widgets/view_safe_area.dart'; import 'package:PiliPlus/http/login.dart'; import 'package:PiliPlus/models/common/setting_type.dart'; import 'package:PiliPlus/pages/about/view.dart'; @@ -12,9 +14,8 @@ import 'package:PiliPlus/pages/setting/widgets/multi_select_dialog.dart'; import 'package:PiliPlus/pages/webdav/view.dart'; import 'package:PiliPlus/utils/accounts.dart'; import 'package:PiliPlus/utils/accounts/account.dart'; -import 'package:PiliPlus/utils/context_ext.dart'; import 'package:PiliPlus/utils/extension.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide ListTile; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart' hide ContextExtensionss; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; @@ -87,17 +88,16 @@ class _SettingPageState extends State { @override Widget build(BuildContext context) { final theme = Theme.of(context); - _isPortrait = context.isPortrait; + _isPortrait = MediaQuery.sizeOf(context).isPortrait; return Scaffold( resizeToAvoidBottomInset: false, appBar: AppBar( title: _isPortrait ? const Text('设置') : Text(_type.title), ), - body: _isPortrait - ? _buildList(theme) - : SafeArea( - bottom: false, - child: Row( + body: ViewSafeArea( + child: _isPortrait + ? _buildList(theme) + : Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( @@ -137,7 +137,7 @@ class _SettingPageState extends State { ), ], ), - ), + ), ); } @@ -165,14 +165,13 @@ class _SettingPageState extends State { } Widget _buildList(ThemeData theme) { + final padding = MediaQuery.viewPaddingOf(context); TextStyle titleStyle = theme.textTheme.titleMedium!; TextStyle subTitleStyle = theme.textTheme.labelMedium!.copyWith( color: theme.colorScheme.outline, ); return ListView( - padding: EdgeInsets.only( - bottom: MediaQuery.viewPaddingOf(context).bottom + 100, - ), + padding: EdgeInsets.only(bottom: padding.bottom + 100), children: [ _buildSearchItem(theme), ..._items @@ -280,7 +279,11 @@ class _SettingPageState extends State { } Widget _buildSearchItem(ThemeData theme) => Padding( - padding: const EdgeInsets.only(left: 16, right: 16, bottom: 8), + padding: const EdgeInsets.only( + left: 16, + right: 16, + bottom: 8, + ), child: Material( type: MaterialType.transparency, child: InkWell( diff --git a/lib/pages/setting/widgets/normal_item.dart b/lib/pages/setting/widgets/normal_item.dart index 5ea92a31a..4ed528fb4 100644 --- a/lib/pages/setting/widgets/normal_item.dart +++ b/lib/pages/setting/widgets/normal_item.dart @@ -1,4 +1,5 @@ -import 'package:flutter/material.dart'; +import 'package:PiliPlus/common/widgets/list_tile.dart'; +import 'package:flutter/material.dart' hide ListTile; typedef StringGetter = String Function(); @@ -8,9 +9,6 @@ class NormalItem extends StatefulWidget { final String? subtitle; final StringGetter? getSubtitle; final String? setKey; - final bool? defaultVal; - final ValueChanged? onChanged; - final bool? needReboot; final Widget? leading; final Widget Function()? getTrailing; final Function? onTap; @@ -23,9 +21,6 @@ class NormalItem extends StatefulWidget { this.subtitle, this.getSubtitle, this.setKey, - this.defaultVal, - this.onChanged, - this.needReboot, this.leading, this.getTrailing, this.onTap, diff --git a/lib/pages/setting/widgets/switch_item.dart b/lib/pages/setting/widgets/switch_item.dart index acc0eb1b6..1568b41eb 100644 --- a/lib/pages/setting/widgets/switch_item.dart +++ b/lib/pages/setting/widgets/switch_item.dart @@ -1,7 +1,8 @@ +import 'package:PiliPlus/common/widgets/list_tile.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage_key.dart'; import 'package:PiliPlus/utils/storage_pref.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide ListTile; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; @@ -9,9 +10,9 @@ class SetSwitchItem extends StatefulWidget { final String? title; final String? subtitle; final String? setKey; - final bool? defaultVal; + final bool defaultVal; final ValueChanged? onChanged; - final bool? needReboot; + final bool needReboot; final Widget? leading; final Function? onTap; final EdgeInsetsGeometry? contentPadding; @@ -21,9 +22,9 @@ class SetSwitchItem extends StatefulWidget { this.title, this.subtitle, this.setKey, - this.defaultVal, + this.defaultVal = false, this.onChanged, - this.needReboot, + this.needReboot = false, this.leading, this.onTap, this.contentPadding, @@ -44,7 +45,7 @@ class _SetSwitchItemState extends State { } else { val = GStorage.setting.get( widget.setKey, - defaultValue: widget.defaultVal ?? false, + defaultValue: widget.defaultVal, ); } } @@ -109,7 +110,7 @@ class _SetSwitchItemState extends State { } widget.onChanged?.call(val); - if (widget.needReboot == true) { + if (widget.needReboot) { SmartDialog.showToast('重启生效'); } setState(() {}); diff --git a/lib/pages/settings_search/view.dart b/lib/pages/settings_search/view.dart index 941d58eb6..0cc5c8281 100644 --- a/lib/pages/settings_search/view.dart +++ b/lib/pages/settings_search/view.dart @@ -92,24 +92,19 @@ class _SettingsSearchPageState body: CustomScrollView( slivers: [ ViewSliverSafeArea( - sliver: MediaQuery.removeViewPadding( - context: context, - removeLeft: true, - removeRight: true, - child: Obx( - () => _list.isEmpty - ? const HttpError() - : SliverWaterfallFlow( - gridDelegate: - SliverWaterfallFlowDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: Grid.smallCardWidth * 2, - ), - delegate: SliverChildBuilderDelegate( - (_, index) => _list[index].widget, - childCount: _list.length, - ), + sliver: Obx( + () => _list.isEmpty + ? const HttpError() + : SliverWaterfallFlow( + gridDelegate: + SliverWaterfallFlowDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: Grid.smallCardWidth * 2, + ), + delegate: SliverChildBuilderDelegate( + (_, index) => _list[index].widget, + childCount: _list.length, ), - ), + ), ), ), ], diff --git a/lib/pages/space_setting/controller.dart b/lib/pages/space_setting/controller.dart index 841550fed..565f348c0 100644 --- a/lib/pages/space_setting/controller.dart +++ b/lib/pages/space_setting/controller.dart @@ -29,7 +29,7 @@ class SpaceSettingController UserHttp.spaceSetting(); Future onMod() async { - if (hasMod == true && loadingState.value.isSuccess) { + if ((hasMod ?? false) && loadingState.value.isSuccess) { Privacy? data = loadingState.value.data; if (data != null) { var res = await UserHttp.spaceSettingMod( diff --git a/lib/pages/sponsor_block/view.dart b/lib/pages/sponsor_block/view.dart index 2103f54bd..6bd455854 100644 --- a/lib/pages/sponsor_block/view.dart +++ b/lib/pages/sponsor_block/view.dart @@ -417,6 +417,7 @@ class _SponsorBlockPageState extends State { ), content: SlideColorPicker( color: color, + showResetBtn: true, callback: (Color? color) { _blockColor[index] = color ?? item.first.color; setting.put( diff --git a/lib/pages/video/controller.dart b/lib/pages/video/controller.dart index 7992b59af..b9d9204e0 100644 --- a/lib/pages/video/controller.dart +++ b/lib/pages/video/controller.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:math'; import 'dart:ui'; import 'package:PiliPlus/common/widgets/pair.dart'; @@ -44,7 +43,6 @@ import 'package:PiliPlus/plugin/pl_player/models/data_source.dart'; import 'package:PiliPlus/plugin/pl_player/models/heart_beat_type.dart'; import 'package:PiliPlus/plugin/pl_player/models/play_status.dart'; import 'package:PiliPlus/utils/accounts.dart'; -import 'package:PiliPlus/utils/context_ext.dart'; import 'package:PiliPlus/utils/duration_util.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/storage.dart'; @@ -143,16 +141,15 @@ class VideoDetailController extends GetxController ..addListener(scrollListener); late bool isExpanding = false; late bool isCollapsing = false; - late final AnimationController animationController = AnimationController( - vsync: this, - duration: const Duration(milliseconds: 200), - ); - late final double minVideoHeight = Get.mediaQuery.size.shortestSide * 9 / 16; - late final double maxVideoHeight = () { - final size = Get.mediaQuery.size; - return max(size.longestSide * 0.65, size.shortestSide); - }(); - late double videoHeight = minVideoHeight; + AnimationController? _animationController; + AnimationController get animationController => + _animationController ??= AnimationController( + vsync: this, + duration: const Duration(milliseconds: 200), + ); + late double minVideoHeight; + late double maxVideoHeight; + late double videoHeight; void animToTop() { if (scrollKey.currentState?.outerController.hasClients == true) { @@ -423,8 +420,9 @@ class VideoDetailController extends GetxController } } + bool isPortrait = true; bool get horizontalScreen => plPlayerController.horizontalScreen; - bool get showVideoSheet => !horizontalScreen && Get.context!.isLandscape; + bool get showVideoSheet => !horizontalScreen && !isPortrait; int? _lastPos; List? postList; @@ -1147,12 +1145,15 @@ class VideoDetailController extends GetxController bool isQuerying = false; // 视频链接 - Future queryVideoUrl([Duration? defaultST]) async { + Future queryVideoUrl({ + Duration? defaultST, + bool fromReset = false, + }) async { if (isQuerying) { return; } isQuerying = true; - if (plPlayerController.enableSponsorBlock) { + if (plPlayerController.enableSponsorBlock && !fromReset) { _querySponsorBlock(); } if (plPlayerController.cacheVideoQa == null) { @@ -1590,11 +1591,11 @@ class VideoDetailController extends GetxController scrollCtr ..removeListener(scrollListener) ..dispose(); - animationController.dispose(); + _animationController?.dispose(); super.onClose(); } - void onReset([bool? isStein]) { + void onReset({bool isStein = false}) { playedTime = null; videoUrl = null; audioUrl = null; diff --git a/lib/pages/video/introduction/pgc/controller.dart b/lib/pages/video/introduction/pgc/controller.dart index e0f4cf2f0..9b94d6f91 100644 --- a/lib/pages/video/introduction/pgc/controller.dart +++ b/lib/pages/video/introduction/pgc/controller.dart @@ -428,17 +428,16 @@ class PgcIntroController extends CommonIntroController { if (result['status']) { PgcTriple data = result['data']; late final stat = pgcItem.stat!; - if ((data.like == 1) != hasLike.value) { + if (data.like == 1 && !hasLike.value) { stat.like++; hasLike.value = true; } - final hasCoin = data.coin == 1; - if (this.hasCoin != hasCoin) { + if (data.coin == 1 && !hasCoin) { stat.coin += 2; coinNum.value = 2; GlobalData().afterCoin(2); } - if ((data.favorite == 1) != hasFav.value) { + if (data.favorite == 1 && !hasFav.value) { stat.favorite++; hasFav.value = true; } diff --git a/lib/pages/video/introduction/ugc/controller.dart b/lib/pages/video/introduction/ugc/controller.dart index 6a15c0397..98d24641e 100644 --- a/lib/pages/video/introduction/ugc/controller.dart +++ b/lib/pages/video/introduction/ugc/controller.dart @@ -198,21 +198,21 @@ class UgcIntroController extends CommonIntroController with ReloadMixin { if (result['status']) { UgcTriple data = result['data']; late final stat = videoDetail.value.stat!; - if (data.like != hasLike.value) { + if (data.like == true && !hasLike.value) { stat.like++; hasLike.value = true; } - if (data.coin != hasCoin) { + if (data.coin == true && !hasCoin) { stat.coin += 2; coinNum.value = 2; GlobalData().afterCoin(2); } - if (data.fav != hasFav.value) { + if (data.fav == true && !hasFav.value) { stat.favorite++; hasFav.value = true; } hasDislike.value = false; - if (data.coin != true) { + if (!hasCoin) { SmartDialog.showToast('投币失败'); } else { SmartDialog.showToast('三连成功'); @@ -460,7 +460,10 @@ class UgcIntroController extends CommonIntroController with ReloadMixin { } // 修改分P或番剧分集 - Future onChangeEpisode(BaseEpisodeItem episode, {bool? isStein}) async { + Future onChangeEpisode( + BaseEpisodeItem episode, { + bool isStein = false, + }) async { try { final String bvid = episode.bvid ?? this.bvid; final int aid = episode.aid ?? IdUtils.bv2av(bvid); @@ -490,7 +493,7 @@ class UgcIntroController extends CommonIntroController with ReloadMixin { ..plPlayerController.pause() ..makeHeartBeat() ..updateMediaListHistory(aid) - ..onReset(isStein) + ..onReset(isStein: isStein) ..bvid = bvid ..aid = aid ..cid.value = cid diff --git a/lib/pages/video/introduction/ugc/widgets/season.dart b/lib/pages/video/introduction/ugc/widgets/season.dart index 606771975..f137438b2 100644 --- a/lib/pages/video/introduction/ugc/widgets/season.dart +++ b/lib/pages/video/introduction/ugc/widgets/season.dart @@ -14,12 +14,12 @@ class SeasonPanel extends StatefulWidget { super.key, required this.heroTag, required this.showEpisodes, - this.onTap, + this.canTap = true, required this.ugcIntroController, }); final String heroTag; final Function showEpisodes; - final bool? onTap; + final bool canTap; final UgcIntroController ugcIntroController; @override @@ -102,16 +102,16 @@ class _SeasonPanelState extends State { borderRadius: const BorderRadius.all(Radius.circular(6)), child: InkWell( borderRadius: const BorderRadius.all(Radius.circular(6)), - onTap: widget.onTap == false - ? null - : () => widget.showEpisodes( + onTap: widget.canTap + ? () => widget.showEpisodes( _videoDetailController.seasonIndex.value, videoDetail.ugcSeason, null, _videoDetailController.bvid, null, _videoDetailController.seasonCid, - ), + ) + : null, child: Padding( padding: const EdgeInsets.fromLTRB(8, 12, 8, 12), child: Row( diff --git a/lib/pages/video/note/view.dart b/lib/pages/video/note/view.dart index 55d6a5c7a..8051aa274 100644 --- a/lib/pages/video/note/view.dart +++ b/lib/pages/video/note/view.dart @@ -97,6 +97,7 @@ class _NoteListPageState extends CommonSlidePageState { return refreshIndicator( onRefresh: _controller.onRefresh, child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Expanded( child: CustomScrollView( @@ -119,7 +120,6 @@ class _NoteListPageState extends CommonSlidePageState { top: 6, bottom: MediaQuery.viewPaddingOf(context).bottom + 6, ), - width: double.infinity, decoration: BoxDecoration( color: theme.colorScheme.onInverseSurface, border: Border( diff --git a/lib/pages/video/pay_coins/view.dart b/lib/pages/video/pay_coins/view.dart index cce9753bb..f858c9294 100644 --- a/lib/pages/video/pay_coins/view.dart +++ b/lib/pages/video/pay_coins/view.dart @@ -1,12 +1,13 @@ import 'dart:async'; import 'dart:math'; +import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/global_data.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage_key.dart'; import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; +import 'package:get/get.dart' hide ContextExtensionss; import 'package:get/get_navigation/src/dialog/dialog_route.dart'; class PayCoinsPage extends StatefulWidget { @@ -208,7 +209,7 @@ class _PayCoinsPageState extends State @override Widget build(BuildContext context) { - bool isPortrait = context.isPortrait; + bool isPortrait = MediaQuery.sizeOf(context).isPortrait; return isPortrait ? _buildBody(isPortrait) : Row( diff --git a/lib/pages/video/post_panel/view.dart b/lib/pages/video/post_panel/view.dart index 55db1cd5f..6d8be8f84 100644 --- a/lib/pages/video/post_panel/view.dart +++ b/lib/pages/video/post_panel/view.dart @@ -363,7 +363,6 @@ class _PostPanelState extends CommonCollapseSlidePageState { clipBehavior: Clip.none, children: [ Container( - width: double.infinity, margin: const EdgeInsets.symmetric( horizontal: 16, vertical: 5, @@ -375,7 +374,7 @@ class _PostPanelState extends CommonCollapseSlidePageState { ), child: Column( mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (item.actionType != ActionType.full) ...[ Wrap( diff --git a/lib/pages/video/reply/view.dart b/lib/pages/video/reply/view.dart index 0a72a9c76..0142e4841 100644 --- a/lib/pages/video/reply/view.dart +++ b/lib/pages/video/reply/view.dart @@ -21,7 +21,7 @@ class VideoReplyPanel extends StatefulWidget { this.onViewImage, this.onDismissed, this.callback, - this.needController, + required this.needController, }); final int replyLevel; @@ -30,7 +30,7 @@ class VideoReplyPanel extends StatefulWidget { final VoidCallback? onViewImage; final ValueChanged? onDismissed; final Function(List, int)? callback; - final bool? needController; + final bool needController; @override State createState() => _VideoReplyPanelState(); @@ -100,14 +100,14 @@ class _VideoReplyPanelState extends State clipBehavior: Clip.none, children: [ CustomScrollView( - controller: widget.needController == false - ? null - : _videoReplyController.scrollController, - physics: widget.needController == false - ? const AlwaysScrollableScrollPhysics( + controller: widget.needController + ? _videoReplyController.scrollController + : null, + physics: widget.needController + ? const AlwaysScrollableScrollPhysics() + : const AlwaysScrollableScrollPhysics( parent: ClampingScrollPhysics(), - ) - : const AlwaysScrollableScrollPhysics(), + ), key: const PageStorageKey('评论'), slivers: [ SliverPersistentHeader( diff --git a/lib/pages/video/reply/widgets/reply_item_grpc.dart b/lib/pages/video/reply/widgets/reply_item_grpc.dart index fe24c684f..a17b5d00e 100644 --- a/lib/pages/video/reply/widgets/reply_item_grpc.dart +++ b/lib/pages/video/reply/widgets/reply_item_grpc.dart @@ -116,6 +116,7 @@ class ReplyItemGrpc extends StatelessWidget { Widget _buildContent(BuildContext context, ThemeData theme) { return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (PendantAvatar.showDynDecorate && replyItem.member.hasGarbCardImage()) @@ -152,10 +153,7 @@ class ReplyItemGrpc extends StatelessWidget { ], ), ), - SizedBox( - width: double.infinity, - child: _buildAuthorPanel(context, theme), - ), + _buildAuthorPanel(context, theme), ], ) else @@ -439,7 +437,7 @@ class ReplyItemGrpc extends StatelessWidget { clipBehavior: Clip.hardEdge, animationDuration: Duration.zero, child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (replies.isNotEmpty) ...List.generate(replies.length, (index) { @@ -478,8 +476,7 @@ class ReplyItemGrpc extends StatelessWidget { }, ); }, - child: Container( - width: double.infinity, + child: Padding( padding: padding, child: Text.rich( style: TextStyle( @@ -537,8 +534,7 @@ class ReplyItemGrpc extends StatelessWidget { if (extraRow) InkWell( onTap: () => replyReply?.call(replyItem, null), - child: Container( - width: double.infinity, + child: Padding( padding: length == 1 ? const EdgeInsets.fromLTRB(8, 6, 8, 6) : const EdgeInsets.fromLTRB(8, 5, 8, 8), diff --git a/lib/pages/video/reply_new/view.dart b/lib/pages/video/reply_new/view.dart index 4d50c4e05..056505f82 100644 --- a/lib/pages/video/reply_new/view.dart +++ b/lib/pages/video/reply_new/view.dart @@ -90,6 +90,7 @@ class _ReplyPageState extends CommonRichTextPubPageState { ), child: Column( mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ ...buildInputView(), buildImagePreview(), @@ -111,13 +112,11 @@ class _ReplyPageState extends CommonRichTextPubPageState { return Obx( () { if (pathList.isNotEmpty) { - return Container( + return SizedBox( height: 85, - width: double.infinity, - padding: const EdgeInsets.only(bottom: 10), child: SingleChildScrollView( scrollDirection: Axis.horizontal, - padding: const EdgeInsets.symmetric(horizontal: 15), + padding: const EdgeInsets.fromLTRB(15, 0, 15, 10), child: Row( spacing: 10, crossAxisAlignment: CrossAxisAlignment.start, @@ -271,6 +270,7 @@ class _ReplyPageState extends CommonRichTextPubPageState { }) { return GestureDetector( onTap: onTap, + behavior: HitTestBehavior.opaque, child: Column( spacing: 5, mainAxisSize: MainAxisSize.min, diff --git a/lib/pages/video/reply_reply/view.dart b/lib/pages/video/reply_reply/view.dart index a6f0b09ac..6a75d0886 100644 --- a/lib/pages/video/reply_reply/view.dart +++ b/lib/pages/video/reply_reply/view.dart @@ -140,77 +140,74 @@ class _VideoReplyReplyPanelState @override Widget buildList(ThemeData theme) { - return ClipRect( - child: refreshIndicator( - onRefresh: _controller.onRefresh, - child: Obx( - () => Stack( - clipBehavior: Clip.none, - children: [ - ScrollablePositionedList.builder( - key: _listKey, - itemPositionsListener: itemPositionsListener, - itemCount: _itemCount(_controller.loadingState.value), - itemScrollController: _controller.itemScrollCtr, - physics: const AlwaysScrollableScrollPhysics(), - itemBuilder: (context, index) { - if (widget.isDialogue) { + return refreshIndicator( + onRefresh: _controller.onRefresh, + child: Obx( + () => Stack( + clipBehavior: Clip.none, + children: [ + ScrollablePositionedList.builder( + key: _listKey, + itemPositionsListener: itemPositionsListener, + itemCount: _itemCount(_controller.loadingState.value), + itemScrollController: _controller.itemScrollCtr, + physics: const AlwaysScrollableScrollPhysics(), + itemBuilder: (context, index) { + if (widget.isDialogue) { + return _buildBody( + theme, + _controller.loadingState.value, + index, + ); + } else if (firstFloor != null) { + if (index == 0) { + return ReplyItemGrpc( + replyItem: firstFloor!, + replyLevel: 2, + needDivider: false, + onReply: (replyItem) => _controller.onReply( + context, + replyItem: replyItem, + index: -1, + ), + upMid: _controller.upMid, + onViewImage: widget.onViewImage, + onDismissed: widget.onDismissed, + callback: _imageCallback, + onCheckReply: (item) => + _controller.onCheckReply(item, isManual: true), + ); + } else if (index == 1) { + return Divider( + height: 20, + color: theme.dividerColor.withValues(alpha: 0.1), + thickness: 6, + ); + } else if (index == 2) { + return _sortWidget(theme); + } else { return _buildBody( theme, _controller.loadingState.value, - index, + index - 3, ); - } else if (firstFloor != null) { - if (index == 0) { - return ReplyItemGrpc( - replyItem: firstFloor!, - replyLevel: 2, - needDivider: false, - onReply: (replyItem) => _controller.onReply( - context, - replyItem: replyItem, - index: -1, - ), - upMid: _controller.upMid, - onViewImage: widget.onViewImage, - onDismissed: widget.onDismissed, - callback: _imageCallback, - onCheckReply: (item) => - _controller.onCheckReply(item, isManual: true), - ); - } else if (index == 1) { - return Divider( - height: 20, - color: theme.dividerColor.withValues(alpha: 0.1), - thickness: 6, - ); - } else if (index == 2) { - return _sortWidget(theme); - } else { - return _buildBody( - theme, - _controller.loadingState.value, - index - 3, - ); - } - } else { - if (index == 0) { - return _sortWidget(theme); - } else { - return _buildBody( - theme, - _controller.loadingState.value, - index - 1, - ); - } } - }, - ), - if (!widget.isDialogue && - _controller.loadingState.value.isSuccess) - _header(theme), - ], - ), + } else { + if (index == 0) { + return _sortWidget(theme); + } else { + return _buildBody( + theme, + _controller.loadingState.value, + index - 1, + ); + } + } + }, + ), + if (!widget.isDialogue && _controller.loadingState.value.isSuccess) + _header(theme), + ], ), ), ); @@ -241,16 +238,8 @@ class _VideoReplyReplyPanelState Widget _sortWidget(ThemeData theme) => Container( height: 40, + color: theme.colorScheme.surface, padding: const EdgeInsets.fromLTRB(12, 0, 6, 0), - decoration: BoxDecoration( - color: theme.colorScheme.surface, - boxShadow: [ - BoxShadow( - color: theme.colorScheme.surface, - offset: const Offset(0, -2), - ), - ], - ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/pages/video/send_danmaku/view.dart b/lib/pages/video/send_danmaku/view.dart index 8e7c6ca00..d108a6382 100644 --- a/lib/pages/video/send_danmaku/view.dart +++ b/lib/pages/video/send_danmaku/view.dart @@ -433,7 +433,6 @@ class _SendDanmakuPanelState extends CommonTextPubPageState { contentPadding: const EdgeInsets.symmetric(vertical: 16), title: const Text('Color Picker'), content: SlideColorPicker( - showResetBtn: false, color: _color.value, callback: (Color? color) { if (color != null) { diff --git a/lib/pages/video/view.dart b/lib/pages/video/view.dart index 0aa62131f..dcd7cb0b1 100644 --- a/lib/pages/video/view.dart +++ b/lib/pages/video/view.dart @@ -467,10 +467,22 @@ class _VideoDetailPageVState extends State this, ModalRoute.of(context)! as PageRoute, ); + final size = MediaQuery.sizeOf(context); maxWidth = size.width; maxHeight = size.height; - isPortrait = maxHeight >= maxWidth; + + final shortestSide = size.shortestSide; + final minVideoHeight = shortestSide * 9 / 16; + final maxVideoHeight = max(size.longestSide * 0.65, shortestSide); + videoDetailController + ..isPortrait = isPortrait = maxHeight >= maxWidth + ..minVideoHeight = minVideoHeight + ..maxVideoHeight = maxVideoHeight + ..videoHeight = videoDetailController.isVertical.value + ? maxVideoHeight + : minVideoHeight; + themeData = videoDetailController.plPlayerController.darkVideoPage ? MyApp.darkThemeData ?? Theme.of(context) : Theme.of(context); @@ -1552,7 +1564,6 @@ class _VideoDetailPageVState extends State ); return Container( - width: double.infinity, height: 45, decoration: BoxDecoration( border: Border( @@ -1965,7 +1976,7 @@ class _VideoDetailPageVState extends State () => SeasonPanel( key: ValueKey(introController.videoDetail.value), heroTag: heroTag, - onTap: false, + canTap: false, showEpisodes: showEpisodes, ugcIntroController: ugcIntroController, ), @@ -2073,7 +2084,7 @@ class _VideoDetailPageVState extends State videoDetailController.showMediaListPanel(context); return; } - Widget listSheetContent([bool? enableSlide]) => EpisodePanel( + Widget listSheetContent({bool enableSlide = true}) => EpisodePanel( heroTag: heroTag, ugcIntroController: videoDetailController.isUgc ? ugcIntroController @@ -2122,9 +2133,9 @@ class _VideoDetailPageVState extends State child: videoDetailController.plPlayerController.darkVideoPage ? Theme( data: themeData, - child: listSheetContent(false), + child: listSheetContent(enableSlide: false), ) - : listSheetContent(false), + : listSheetContent(enableSlide: false), ); } else { videoDetailController.childKey.currentState?.showBottomSheet( diff --git a/lib/pages/video/widgets/header_control.dart b/lib/pages/video/widgets/header_control.dart index 827541a75..ef9889c0c 100644 --- a/lib/pages/video/widgets/header_control.dart +++ b/lib/pages/video/widgets/header_control.dart @@ -176,7 +176,8 @@ class HeaderControlState extends TripleState { onTap: () { Get.back(); videoDetailCtr.queryVideoUrl( - videoDetailCtr.playedTime, + defaultST: videoDetailCtr.playedTime, + fromReset: true, ); }, leading: const Icon(Icons.refresh_outlined, size: 20), @@ -279,7 +280,8 @@ class HeaderControlState extends TripleState { ); setState(() {}); videoDetailCtr.queryVideoUrl( - videoDetailCtr.playedTime, + defaultST: videoDetailCtr.playedTime, + fromReset: true, ); } }, @@ -957,80 +959,59 @@ class HeaderControlState extends TripleState { thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 6.0), ); - void updateStrokeWidth(double val, {bool isEnd = true}) { + void updateStrokeWidth(double val) { subtitleStrokeWidth = val; plPlayerController ..subtitleStrokeWidth = subtitleStrokeWidth ..updateSubtitleStyle(); - if (isEnd) { - plPlayerController.putSubtitleSettings(); - } setState(() {}); } - void updateOpacity(double val, {bool isEnd = true}) { + void updateOpacity(double val) { subtitleBgOpaticy = val.toPrecision(2); plPlayerController ..subtitleBgOpaticy = subtitleBgOpaticy ..updateSubtitleStyle(); - if (isEnd) { - plPlayerController.putSubtitleSettings(); - } setState(() {}); } - void updateBottomPadding(double val, {bool isEnd = true}) { + void updateBottomPadding(double val) { subtitlePaddingB = val.round(); plPlayerController ..subtitlePaddingB = subtitlePaddingB ..updateSubtitleStyle(); - if (isEnd) { - plPlayerController.putSubtitleSettings(); - } setState(() {}); } - void updateHorizontalPadding(double val, {bool isEnd = true}) { + void updateHorizontalPadding(double val) { subtitlePaddingH = val.round(); plPlayerController ..subtitlePaddingH = subtitlePaddingH ..updateSubtitleStyle(); - if (isEnd) { - plPlayerController.putSubtitleSettings(); - } setState(() {}); } - void updateFontScaleFS(double val, {bool isEnd = true}) { + void updateFontScaleFS(double val) { subtitleFontScaleFS = val; plPlayerController ..subtitleFontScaleFS = subtitleFontScaleFS ..updateSubtitleStyle(); - if (isEnd) { - plPlayerController.putSubtitleSettings(); - } setState(() {}); } - void updateFontScale(double val, {bool isEnd = true}) { + void updateFontScale(double val) { subtitleFontScale = val; plPlayerController ..subtitleFontScale = subtitleFontScale ..updateSubtitleStyle(); - if (isEnd) { - plPlayerController.putSubtitleSettings(); - } setState(() {}); } - void updateFontWeight(double val, {bool isEnd = true}) { + void updateFontWeight(double val) { subtitleFontWeight = val.toInt(); plPlayerController ..subtitleFontWeight = subtitleFontWeight ..updateSubtitleStyle(); - if (isEnd) { - plPlayerController.putSubtitleSettings(); - } setState(() {}); } @@ -1075,8 +1056,9 @@ class HeaderControlState extends TripleState { divisions: 20, label: '${(subtitleFontScale * 100).toStringAsFixed(1)}%', - onChanged: (val) => updateFontScale(val, isEnd: false), - onChangeEnd: updateFontScale, + onChanged: updateFontScale, + onChangeEnd: (_) => + plPlayerController.putSubtitleSettings(), ), ), ), @@ -1105,9 +1087,9 @@ class HeaderControlState extends TripleState { divisions: 20, label: '${(subtitleFontScaleFS * 100).toStringAsFixed(1)}%', - onChanged: (val) => - updateFontScaleFS(val, isEnd: false), - onChangeEnd: updateFontScaleFS, + onChanged: updateFontScaleFS, + onChangeEnd: (_) => + plPlayerController.putSubtitleSettings, ), ), ), @@ -1133,8 +1115,9 @@ class HeaderControlState extends TripleState { value: subtitleFontWeight.toDouble(), divisions: 8, label: '${subtitleFontWeight + 1}', - onChanged: (val) => updateFontWeight(val, isEnd: false), - onChangeEnd: updateFontWeight, + onChanged: updateFontWeight, + onChangeEnd: (_) => + plPlayerController.putSubtitleSettings(), ), ), ), @@ -1160,9 +1143,9 @@ class HeaderControlState extends TripleState { value: subtitleStrokeWidth, divisions: 10, label: '$subtitleStrokeWidth', - onChanged: (val) => - updateStrokeWidth(val, isEnd: false), - onChangeEnd: updateStrokeWidth, + onChanged: updateStrokeWidth, + onChangeEnd: (_) => + plPlayerController.putSubtitleSettings(), ), ), ), @@ -1188,9 +1171,9 @@ class HeaderControlState extends TripleState { value: subtitlePaddingH.toDouble(), divisions: 100, label: '$subtitlePaddingH', - onChanged: (val) => - updateHorizontalPadding(val, isEnd: false), - onChangeEnd: updateHorizontalPadding, + onChanged: updateHorizontalPadding, + onChangeEnd: (_) => + plPlayerController.putSubtitleSettings(), ), ), ), @@ -1216,9 +1199,9 @@ class HeaderControlState extends TripleState { value: subtitlePaddingB.toDouble(), divisions: 200, label: '$subtitlePaddingB', - onChanged: (val) => - updateBottomPadding(val, isEnd: false), - onChangeEnd: updateBottomPadding, + onChanged: updateBottomPadding, + onChangeEnd: (_) => + plPlayerController.putSubtitleSettings(), ), ), ), @@ -1242,8 +1225,9 @@ class HeaderControlState extends TripleState { min: 0, max: 1, value: subtitleBgOpaticy, - onChanged: (val) => updateOpacity(val, isEnd: false), - onChangeEnd: updateOpacity, + onChanged: updateOpacity, + onChangeEnd: (_) => + plPlayerController.putSubtitleSettings(), ), ), ), @@ -1315,13 +1299,8 @@ class HeaderControlState extends TripleState { thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 6.0), ); - void updateLineHeight(double val, {bool isEnd = true}) { + void updateLineHeight(double val) { danmakuLineHeight = val.toPrecision(1); - if (isEnd) { - plPlayerController - ..danmakuLineHeight = danmakuLineHeight - ..putDanmakuSettings(); - } setState(() {}); try { danmakuController?.updateOption( @@ -1332,13 +1311,8 @@ class HeaderControlState extends TripleState { } catch (_) {} } - void updateDuration(double val, {bool isEnd = true}) { + void updateDuration(double val) { danmakuDuration = val.toPrecision(1); - if (isEnd) { - plPlayerController - ..danmakuDuration = danmakuDuration - ..putDanmakuSettings(); - } setState(() {}); try { danmakuController?.updateOption( @@ -1349,13 +1323,8 @@ class HeaderControlState extends TripleState { } catch (_) {} } - void updateStaticDuration(double val, {bool isEnd = true}) { + void updateStaticDuration(double val) { danmakuStaticDuration = val.toPrecision(1); - if (isEnd) { - plPlayerController - ..danmakuStaticDuration = danmakuStaticDuration - ..putDanmakuSettings(); - } setState(() {}); try { danmakuController?.updateOption( @@ -1367,13 +1336,8 @@ class HeaderControlState extends TripleState { } catch (_) {} } - void updateFontSizeFS(double val, {bool isEnd = true}) { + void updateFontSizeFS(double val) { fontSizeFS = val; - if (isEnd) { - plPlayerController - ..danmakuFontScaleFS = fontSizeFS - ..putDanmakuSettings(); - } setState(() {}); if (isFullScreen) { try { @@ -1386,13 +1350,8 @@ class HeaderControlState extends TripleState { } } - void updateFontSize(double val, {bool isEnd = true}) { + void updateFontSize(double val) { fontSize = val; - if (isEnd) { - plPlayerController - ..danmakuFontScale = fontSize - ..putDanmakuSettings(); - } setState(() {}); if (!isFullScreen) { try { @@ -1405,13 +1364,8 @@ class HeaderControlState extends TripleState { } } - void updateStrokeWidth(double val, {bool isEnd = true}) { + void updateStrokeWidth(double val) { strokeWidth = val; - if (isEnd) { - plPlayerController - ..strokeWidth = val - ..putDanmakuSettings(); - } setState(() {}); try { danmakuController?.updateOption( @@ -1420,13 +1374,8 @@ class HeaderControlState extends TripleState { } catch (_) {} } - void updateFontWeight(double val, {bool isEnd = true}) { + void updateFontWeight(double val) { fontWeight = val.toInt(); - if (isEnd) { - plPlayerController - ..fontWeight = fontWeight - ..putDanmakuSettings(); - } setState(() {}); try { danmakuController?.updateOption( @@ -1435,13 +1384,8 @@ class HeaderControlState extends TripleState { } catch (_) {} } - void updateOpacity(double val, {bool isEnd = true}) { + void updateOpacity(double val) { opacity = val; - if (isEnd) { - plPlayerController - ..danmakuOpacity = opacity - ..putDanmakuSettings(); - } setState(() {}); try { danmakuController?.updateOption( @@ -1450,13 +1394,8 @@ class HeaderControlState extends TripleState { } catch (_) {} } - void updateShowArea(double val, {bool isEnd = true}) { + void updateShowArea(double val) { showArea = val.toPrecision(1); - if (isEnd) { - plPlayerController - ..showArea = showArea - ..putDanmakuSettings(); - } setState(() {}); try { danmakuController?.updateOption( @@ -1465,13 +1404,8 @@ class HeaderControlState extends TripleState { } catch (_) {} } - void updateDanmakuWeight(double val, {bool isEnd = true}) { + void updateDanmakuWeight(double val) { danmakuWeight = val.toInt(); - if (isEnd) { - plPlayerController - ..danmakuWeight = danmakuWeight - ..putDanmakuSettings(); - } setState(() {}); } @@ -1528,9 +1462,10 @@ class HeaderControlState extends TripleState { value: danmakuWeight.toDouble(), divisions: 10, label: '$danmakuWeight', - onChanged: (val) => - updateDanmakuWeight(val, isEnd: false), - onChangeEnd: updateDanmakuWeight, + onChanged: updateDanmakuWeight, + onChangeEnd: (_) => plPlayerController + ..danmakuWeight = danmakuWeight + ..putDanmakuSettings(), ), ), ), @@ -1610,8 +1545,10 @@ class HeaderControlState extends TripleState { value: showArea, divisions: 9, label: '${showArea * 100}%', - onChanged: (val) => updateShowArea(val, isEnd: false), - onChangeEnd: updateShowArea, + onChanged: updateShowArea, + onChangeEnd: (_) => plPlayerController + ..showArea = showArea + ..putDanmakuSettings(), ), ), ), @@ -1637,8 +1574,10 @@ class HeaderControlState extends TripleState { value: opacity, divisions: 10, label: '${opacity * 100}%', - onChanged: (val) => updateOpacity(val, isEnd: false), - onChangeEnd: updateOpacity, + onChanged: updateOpacity, + onChangeEnd: (_) => plPlayerController + ..danmakuOpacity = opacity + ..putDanmakuSettings(), ), ), ), @@ -1664,8 +1603,10 @@ class HeaderControlState extends TripleState { value: fontWeight.toDouble(), divisions: 8, label: '${fontWeight + 1}', - onChanged: (val) => updateFontWeight(val, isEnd: false), - onChangeEnd: updateFontWeight, + onChanged: updateFontWeight, + onChangeEnd: (_) => plPlayerController + ..fontWeight = fontWeight + ..putDanmakuSettings(), ), ), ), @@ -1691,9 +1632,10 @@ class HeaderControlState extends TripleState { value: strokeWidth, divisions: 6, label: '$strokeWidth', - onChanged: (val) => - updateStrokeWidth(val, isEnd: false), - onChangeEnd: updateStrokeWidth, + onChanged: updateStrokeWidth, + onChangeEnd: (_) => plPlayerController + ..strokeWidth = strokeWidth + ..putDanmakuSettings(), ), ), ), @@ -1719,8 +1661,10 @@ class HeaderControlState extends TripleState { value: fontSize, divisions: 20, label: '${(fontSize * 100).toStringAsFixed(1)}%', - onChanged: (val) => updateFontSize(val, isEnd: false), - onChangeEnd: updateFontSize, + onChanged: updateFontSize, + onChangeEnd: (_) => plPlayerController + ..danmakuFontScale = fontSize + ..putDanmakuSettings(), ), ), ), @@ -1746,8 +1690,10 @@ class HeaderControlState extends TripleState { value: fontSizeFS, divisions: 20, label: '${(fontSizeFS * 100).toStringAsFixed(1)}%', - onChanged: (val) => updateFontSizeFS(val, isEnd: false), - onChangeEnd: updateFontSizeFS, + onChanged: updateFontSizeFS, + onChangeEnd: (_) => plPlayerController + ..danmakuFontScaleFS = fontSizeFS + ..putDanmakuSettings(), ), ), ), @@ -1773,8 +1719,10 @@ class HeaderControlState extends TripleState { value: danmakuDuration, divisions: 49, label: danmakuDuration.toString(), - onChanged: (val) => updateDuration(val, isEnd: false), - onChangeEnd: updateDuration, + onChanged: updateDuration, + onChangeEnd: (_) => plPlayerController + ..danmakuDuration = danmakuDuration + ..putDanmakuSettings(), ), ), ), @@ -1800,9 +1748,10 @@ class HeaderControlState extends TripleState { value: danmakuStaticDuration, divisions: 49, label: danmakuStaticDuration.toString(), - onChanged: (val) => - updateStaticDuration(val, isEnd: false), - onChangeEnd: updateStaticDuration, + onChanged: updateStaticDuration, + onChangeEnd: (_) => plPlayerController + ..danmakuStaticDuration = danmakuStaticDuration + ..putDanmakuSettings(), ), ), ), @@ -1826,8 +1775,10 @@ class HeaderControlState extends TripleState { min: 1.0, max: 3.0, value: danmakuLineHeight, - onChanged: (val) => updateLineHeight(val, isEnd: false), - onChangeEnd: updateLineHeight, + onChanged: updateLineHeight, + onChangeEnd: (_) => plPlayerController + ..danmakuLineHeight = danmakuLineHeight + ..putDanmakuSettings(), ), ), ), diff --git a/lib/pages/webdav/view.dart b/lib/pages/webdav/view.dart index a1233e83e..e06f7beea 100644 --- a/lib/pages/webdav/view.dart +++ b/lib/pages/webdav/view.dart @@ -36,113 +36,122 @@ class _WebDavSettingPageState extends State { @override Widget build(BuildContext context) { - EdgeInsets padding = MediaQuery.viewPaddingOf(context); + final showAppBar = widget.showAppBar; + final padding = MediaQuery.viewPaddingOf(context); return Scaffold( - appBar: !widget.showAppBar - ? null - : AppBar(title: const Text('WebDAV 设置')), - body: ListView( - padding: padding.copyWith( - top: 20, - left: padding.left + 20, - right: padding.right + 20, - bottom: padding.bottom + 100, - ), + appBar: showAppBar ? AppBar(title: const Text('WebDAV 设置')) : null, + body: Stack( + clipBehavior: Clip.none, children: [ - TextField( - controller: _uriCtr, - decoration: const InputDecoration( - labelText: '地址', - border: OutlineInputBorder(), + ListView( + padding: padding.copyWith( + top: 20, + left: 20 + (showAppBar ? padding.left : 0), + right: 20 + (showAppBar ? padding.right : 0), + bottom: padding.bottom + 100, ), - ), - const SizedBox(height: 20), - TextField( - controller: _usernameCtr, - decoration: const InputDecoration( - labelText: '用户', - border: OutlineInputBorder(), - ), - ), - const SizedBox(height: 20), - TextField( - controller: _passwordCtr, - autofillHints: const [AutofillHints.password], - decoration: InputDecoration( - labelText: '密码', - border: const OutlineInputBorder(), - suffixIcon: IconButton( - onPressed: () => setState(() => _obscureText = !_obscureText), - icon: _obscureText - ? const Icon(Icons.visibility) - : const Icon(Icons.visibility_off), - ), - ), - obscureText: _obscureText, - ), - const SizedBox(height: 20), - TextField( - controller: _directoryCtr, - decoration: const InputDecoration( - labelText: '路径', - border: OutlineInputBorder(), - ), - ), - const SizedBox(height: 20), - Row( children: [ - Expanded( - child: FilledButton.tonal( - style: FilledButton.styleFrom( - shape: const RoundedRectangleBorder( - borderRadius: StyleString.mdRadius, - ), - ), - onPressed: WebDav().backup, - child: const Text('备份设置'), + TextField( + controller: _uriCtr, + decoration: const InputDecoration( + labelText: '地址', + border: OutlineInputBorder(), ), ), - const SizedBox(width: 20), - Expanded( - child: FilledButton.tonal( - style: FilledButton.styleFrom( - shape: const RoundedRectangleBorder( - borderRadius: StyleString.mdRadius, + const SizedBox(height: 20), + TextField( + controller: _usernameCtr, + decoration: const InputDecoration( + labelText: '用户', + border: OutlineInputBorder(), + ), + ), + const SizedBox(height: 20), + TextField( + controller: _passwordCtr, + autofillHints: const [AutofillHints.password], + decoration: InputDecoration( + labelText: '密码', + border: const OutlineInputBorder(), + suffixIcon: IconButton( + onPressed: () => + setState(() => _obscureText = !_obscureText), + icon: _obscureText + ? const Icon(Icons.visibility) + : const Icon(Icons.visibility_off), + ), + ), + obscureText: _obscureText, + ), + const SizedBox(height: 20), + TextField( + controller: _directoryCtr, + decoration: const InputDecoration( + labelText: '路径', + border: OutlineInputBorder(), + ), + ), + const SizedBox(height: 20), + Row( + children: [ + Expanded( + child: FilledButton.tonal( + style: FilledButton.styleFrom( + shape: const RoundedRectangleBorder( + borderRadius: StyleString.mdRadius, + ), + ), + onPressed: WebDav().backup, + child: const Text('备份设置'), ), ), - onPressed: WebDav().restore, - child: const Text('恢复设置'), - ), + const SizedBox(width: 20), + Expanded( + child: FilledButton.tonal( + style: FilledButton.styleFrom( + shape: const RoundedRectangleBorder( + borderRadius: StyleString.mdRadius, + ), + ), + onPressed: WebDav().restore, + child: const Text('恢复设置'), + ), + ), + ], ), ], ), + Positioned( + right: 16 + (showAppBar ? padding.right : 0), + bottom: 16 + padding.bottom, + child: FloatingActionButton( + child: const Icon(Icons.save), + onPressed: () async { + await GStorage.setting.putAll({ + SettingBoxKey.webdavUri: _uriCtr.text, + SettingBoxKey.webdavUsername: _usernameCtr.text, + SettingBoxKey.webdavPassword: _passwordCtr.text, + SettingBoxKey.webdavDirectory: _directoryCtr.text, + }); + if (_uriCtr.text.isEmpty) { + return; + } + try { + final res = await WebDav().init(); + if (res.first) { + SmartDialog.showToast('配置成功'); + } else { + SmartDialog.showToast('配置失败: ${res.second}'); + } + } catch (e) { + SmartDialog.showToast('配置失败: ${e.toString()}'); + return; + } + }, + ), + ), ], ), - floatingActionButton: FloatingActionButton( - child: const Icon(Icons.save), - onPressed: () async { - await GStorage.setting.putAll({ - SettingBoxKey.webdavUri: _uriCtr.text, - SettingBoxKey.webdavUsername: _usernameCtr.text, - SettingBoxKey.webdavPassword: _passwordCtr.text, - SettingBoxKey.webdavDirectory: _directoryCtr.text, - }); - if (_uriCtr.text.isEmpty) { - return; - } - try { - final res = await WebDav().init(); - if (res.first) { - SmartDialog.showToast('配置成功'); - } else { - SmartDialog.showToast('配置失败: ${res.second}'); - } - } catch (e) { - SmartDialog.showToast('配置失败: ${e.toString()}'); - return; - } - }, - ), ); } } diff --git a/lib/pages/whisper_detail/view.dart b/lib/pages/whisper_detail/view.dart index 62fb15c28..cf9da110e 100644 --- a/lib/pages/whisper_detail/view.dart +++ b/lib/pages/whisper_detail/view.dart @@ -135,8 +135,12 @@ class _WhisperDetailPageState hidePanel(); }, behavior: HitTestBehavior.opaque, - child: Obx( - () => _buildBody(_whisperDetailController.loadingState.value), + child: Align( + alignment: Alignment.topCenter, + child: Obx( + () => + _buildBody(_whisperDetailController.loadingState.value), + ), ), ), ), diff --git a/lib/pages/whisper_detail/widget/chat_item.dart b/lib/pages/whisper_detail/widget/chat_item.dart index d6d8fddb3..3419ba766 100644 --- a/lib/pages/whisper_detail/widget/chat_item.dart +++ b/lib/pages/whisper_detail/widget/chat_item.dart @@ -688,7 +688,7 @@ class ChatItem extends StatelessWidget { ), padding: const EdgeInsets.all(12), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ SelectableText( content['title'], @@ -728,10 +728,7 @@ class ChatItem extends StatelessWidget { GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => PiliScheme.routePushFromUrl(content['jump_uri']), - child: SizedBox( - width: double.infinity, - child: Text(content['jump_text']), - ), + child: Text(content['jump_text']), ), ], if ((content['jump_text_2'] as String?)?.isNotEmpty == true && @@ -740,10 +737,7 @@ class ChatItem extends StatelessWidget { GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => PiliScheme.routePushFromUrl(content['jump_uri_2']), - child: SizedBox( - width: double.infinity, - child: Text(content['jump_text_2']), - ), + child: Text(content['jump_text_2']), ), ], if ((content['jump_text_3'] as String?)?.isNotEmpty == true && @@ -752,10 +746,7 @@ class ChatItem extends StatelessWidget { GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => PiliScheme.routePushFromUrl(content['jump_uri_3']), - child: SizedBox( - width: double.infinity, - child: Text(content['jump_text_3']), - ), + child: Text(content['jump_text_3']), ), ], ], diff --git a/lib/plugin/pl_player/widgets/app_bar_ani.dart b/lib/plugin/pl_player/widgets/app_bar_ani.dart index 0505ba25f..799471882 100644 --- a/lib/plugin/pl_player/widgets/app_bar_ani.dart +++ b/lib/plugin/pl_player/widgets/app_bar_ani.dart @@ -20,7 +20,7 @@ class AppBarAni extends StatelessWidget { return SlideTransition( position: Tween( - begin: isTop ? const Offset(0, -1) : const Offset(0, 1.1), + begin: isTop ? const Offset(0, -1) : const Offset(0, 1.2), end: Offset.zero, ).animate( CurvedAnimation( diff --git a/lib/plugin/pl_player/widgets/bottom_control.dart b/lib/plugin/pl_player/widgets/bottom_control.dart index 02906c495..7d15b78e3 100644 --- a/lib/plugin/pl_player/widgets/bottom_control.dart +++ b/lib/plugin/pl_player/widgets/bottom_control.dart @@ -124,7 +124,8 @@ class BottomControl extends StatelessWidget { ), ), if (controller.viewPointList.isNotEmpty && - controller.showVP.value) ...[ + controller.showVP.value) + // ...[ Positioned( left: 0, right: 0, @@ -141,16 +142,16 @@ class BottomControl extends StatelessWidget { ), ), ), - buildViewPointWidget( - controller, - 8.75, - maxWidth - - 40 - - (isFullScreen - ? MediaQuery.viewPaddingOf(context).horizontal - : 0), - ), - ], + // buildViewPointWidget( + // controller, + // 8.75, + // maxWidth - + // 40 - + // (isFullScreen + // ? MediaQuery.viewPaddingOf(context).horizontal + // : 0), + // ), + // ], if (controller.dmTrend.isNotEmpty && controller.showDmTreandChart.value) buildDmChart(theme, controller, 4.5), diff --git a/lib/utils/app_scheme.dart b/lib/utils/app_scheme.dart index 837f9aa2c..db76d40ee 100644 --- a/lib/utils/app_scheme.dart +++ b/lib/utils/app_scheme.dart @@ -481,7 +481,7 @@ class PiliScheme { try { index = FavTabType.values.byName(tab).index; } catch (e) { - if (kDebugMode) print('favorite jump: $e'); + if (kDebugMode) debugPrint('favorite jump: $e'); } } Get.toNamed('/fav', arguments: index); diff --git a/lib/utils/duration_util.dart b/lib/utils/duration_util.dart index aa2719913..065eaeb76 100644 --- a/lib/utils/duration_util.dart +++ b/lib/utils/duration_util.dart @@ -21,11 +21,7 @@ class DurationUtil { if (data == null || data.isEmpty) { return 0; } - List split = data - .split(':') - .reversed - .map(int.parse) - .toList(); + List split = data.split(':').reversed.map(int.parse).toList(); int duration = 0; for (int i = 0; i < split.length; i++) { duration += split[i] * pow(60, i).toInt(); diff --git a/lib/utils/extension.dart b/lib/utils/extension.dart index fdcb8e7e6..a3fef920e 100644 --- a/lib/utils/extension.dart +++ b/lib/utils/extension.dart @@ -209,3 +209,7 @@ extension ThreeDotItemTypeExt on ThreeDotItemType { } } } + +extension SizeExt on Size { + bool get isPortrait => width < 600 || height >= width; +} diff --git a/lib/utils/page_utils.dart b/lib/utils/page_utils.dart index 07f64c091..52ecf5738 100644 --- a/lib/utils/page_utils.dart +++ b/lib/utils/page_utils.dart @@ -44,17 +44,12 @@ class PageUtils { ValueChanged? onDismissed, int? quality, }) { - final currentRoute = Get.currentRoute; - bool noneSet = - currentRoute.startsWith('/videoV') || - currentRoute.startsWith('/member?'); return Navigator.of(Get.context!).push( HeroDialogRoute( builder: (context) => InteractiveviewerGallery( sources: imgList, initIndex: initialPage, onDismissed: onDismissed, - setStatusBar: !noneSet, quality: quality ?? GlobalData().imgQuality, ), ), @@ -562,13 +557,13 @@ class PageUtils { duration: const Duration(milliseconds: 200), )..forward(); key.currentState?.showBottomSheet( + constraints: const BoxConstraints(), (context) { return FadeTransition( opacity: Tween(begin: 0, end: 1).animate(ctr), child: InteractiveviewerGallery( sources: imgList.map((url) => SourceModel(url: url)).toList(), initIndex: index, - setStatusBar: false, onClose: (value) async { if (!value) { try { diff --git a/lib/utils/reply_utils.dart b/lib/utils/reply_utils.dart index 6b7b8b175..cbf98ee04 100644 --- a/lib/utils/reply_utils.dart +++ b/lib/utils/reply_utils.dart @@ -144,8 +144,6 @@ class ReplyUtils { type: type, sort: ReplySortType.time.index, page: 1, - enableFilter: false, - antiGoodsReply: false, ); if (res is Error) { @@ -168,8 +166,6 @@ class ReplyUtils { root: id, pageNum: 1, type: type, - filterBanWord: false, - antiGoodsReply: false, ); if (res1 is Error) { @@ -185,9 +181,7 @@ class ReplyUtils { root: id, pageNum: 1, type: type, - filterBanWord: false, isCheck: true, - antiGoodsReply: false, ); if (res2 is Error) { @@ -222,9 +216,7 @@ https://api.bilibili.com/x/v2/reply/reply?oid=$oid&pn=1&ps=20&root=$id&type=$typ root: root, pageNum: i, type: type, - filterBanWord: false, isCheck: true, - antiGoodsReply: false, ); if (res3 is Error) { break; @@ -251,9 +243,7 @@ https://api.bilibili.com/x/v2/reply/reply?oid=$oid&pn=1&ps=20&root=$id&type=$typ root: root, pageNum: i, type: type, - filterBanWord: false, isCheck: true, - antiGoodsReply: false, ); if (res4 is Error) { break; diff --git a/lib/utils/storage_pref.dart b/lib/utils/storage_pref.dart index c7a5e7254..04127905e 100644 --- a/lib/utils/storage_pref.dart +++ b/lib/utils/storage_pref.dart @@ -1,5 +1,5 @@ import 'dart:io'; -import 'dart:math' show pow, min, sqrt; +import 'dart:math' show pow, sqrt; import 'package:PiliPlus/common/widgets/pair.dart'; import 'package:PiliPlus/http/constants.dart'; @@ -534,9 +534,8 @@ class Pref { static bool get optTabletNav => _setting.get(SettingBoxKey.optTabletNav, defaultValue: true); - static bool get horizontalScreen { - return _setting.get(SettingBoxKey.horizontalScreen) ?? isTablet; - } + static bool get horizontalScreen => + _setting.get(SettingBoxKey.horizontalScreen) ?? isTablet; static bool get isTablet { bool isTablet; @@ -545,9 +544,9 @@ class Pref { } else { final view = WidgetsBinding.instance.platformDispatcher.views.first; final screenSize = view.physicalSize / view.devicePixelRatio; - final shortestSide = min(screenSize.width.abs(), screenSize.height.abs()); - isTablet = shortestSide >= 600; + isTablet = screenSize.shortestSide >= 600; } + _setting.put(SettingBoxKey.horizontalScreen, isTablet); return isTablet; }