From e892411092772951e8aa63b7d0e235080648a70a Mon Sep 17 00:00:00 2001 From: dom Date: Thu, 30 Jul 2026 12:49:30 +0800 Subject: [PATCH] opt ui Signed-off-by: dom --- lib/common/skeleton/space_opus.dart | 60 ++- lib/common/skeleton/video_card_h.dart | 19 +- lib/common/skeleton/video_card_v.dart | 78 ++-- .../widgets/floating_navigation_bar.dart | 110 +++--- lib/common/widgets/flutter/tabs.dart | 370 ------------------ lib/common/widgets/main_layout.dart | 121 ++++++ lib/common/widgets/simple_app_bar.dart | 36 ++ .../widgets/video_card/video_card_v.dart | 4 +- lib/pages/dynamics/widgets/vote.dart | 10 +- lib/pages/dynamics_detail/view.dart | 7 +- lib/pages/home/view.dart | 40 +- lib/pages/live/widgets/live_item_app.dart | 4 +- .../live_follow/widgets/live_item_follow.dart | 4 +- .../live_search/widgets/live_search_room.dart | 4 +- lib/pages/main/view.dart | 209 +++++----- lib/pages/member_coin_arc/widgets/item.dart | 4 +- .../widgets/video_card_v_member_home.dart | 4 +- .../member_opus/widgets/space_opus_item.dart | 10 +- lib/pages/member_shop/widgets/item.dart | 8 +- lib/pages/save_panel/view.dart | 4 +- lib/pages/search_panel/live/widgets/item.dart | 4 +- .../video/reply/widgets/reply_item_grpc.dart | 36 +- lib/pages/video/view.dart | 193 ++++----- .../whisper_detail/widget/chat_item.dart | 6 +- lib/scripts/navigation_drawer.patch | 24 +- lib/scripts/patch.ps1 | 4 +- lib/scripts/tabs.patch | 24 ++ 27 files changed, 617 insertions(+), 780 deletions(-) delete mode 100644 lib/common/widgets/flutter/tabs.dart create mode 100644 lib/common/widgets/main_layout.dart create mode 100644 lib/common/widgets/simple_app_bar.dart create mode 100644 lib/scripts/tabs.patch diff --git a/lib/common/skeleton/space_opus.dart b/lib/common/skeleton/space_opus.dart index e62e15c42..85e5e558a 100644 --- a/lib/common/skeleton/space_opus.dart +++ b/lib/common/skeleton/space_opus.dart @@ -7,43 +7,37 @@ class SpaceOpusSkeleton extends StatelessWidget { @override Widget build(BuildContext context) { - final surface = Theme.of(context).colorScheme.onInverseSurface; + final surface = ColorScheme.of(context).onInverseSurface; return Skeleton( child: Card( - clipBehavior: Clip.hardEdge, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(6)), - ), - child: LayoutBuilder( - builder: (context, constraints) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - height: - (0.68 + 0.82 * Utils.random.nextDouble()) * - constraints.maxWidth, + shape: const RoundedRectangleBorder(borderRadius: .all(.circular(6))), + child: Column( + crossAxisAlignment: .start, + children: [ + AspectRatio( + aspectRatio: 1 / (0.68 + 0.82 * Utils.random.nextDouble()), + child: DecoratedBox( + decoration: BoxDecoration( color: surface, + borderRadius: const .vertical(top: .circular(6)), ), - Container( - height: 10, - color: surface, - margin: const EdgeInsets.all(10), - width: constraints.maxWidth * 0.7, - ), - Container( - height: 10, - color: surface, - margin: const EdgeInsets.only( - left: 10, - right: 10, - bottom: 10, - ), - width: constraints.maxWidth, - ), - ], - ); - }, + ), + ), + FractionallySizedBox( + widthFactor: 0.7, + alignment: .topLeft, + child: Container( + height: 10, + color: surface, + margin: const .all(10), + ), + ), + Container( + height: 10, + color: surface, + margin: const .only(left: 10, right: 10, bottom: 10), + ), + ], ), ), ); diff --git a/lib/common/skeleton/video_card_h.dart b/lib/common/skeleton/video_card_h.dart index e328a6cd1..a8172f683 100644 --- a/lib/common/skeleton/video_card_h.dart +++ b/lib/common/skeleton/video_card_h.dart @@ -7,15 +7,12 @@ class VideoCardHSkeleton extends StatelessWidget { @override Widget build(BuildContext context) { - final color = Theme.of(context).colorScheme.onInverseSurface; + final color = ColorScheme.of(context).onInverseSurface; return Skeleton( child: Padding( - padding: const .symmetric( - horizontal: Style.safeSpace, - vertical: 5, - ), + padding: const .symmetric(horizontal: Style.safeSpace, vertical: 5), child: Row( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: .start, children: [ AspectRatio( aspectRatio: Style.aspectRatio, @@ -30,13 +27,13 @@ class VideoCardHSkeleton extends StatelessWidget { child: Padding( padding: const .fromLTRB(10, 4, 6, 4), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: .start, children: [ Container( color: color, width: 200, - height: 11, - margin: const EdgeInsets.only(bottom: 5), + height: 13, + margin: const .only(bottom: 5), ), Container( color: color, @@ -48,15 +45,15 @@ class VideoCardHSkeleton extends StatelessWidget { color: color, width: 100, height: 13, - margin: const EdgeInsets.only(bottom: 5), + margin: const .only(bottom: 5), ), Row( + spacing: 8, children: [ Container( color: color, width: 40, height: 13, - margin: const EdgeInsets.only(right: 8), ), Container( color: color, diff --git a/lib/common/skeleton/video_card_v.dart b/lib/common/skeleton/video_card_v.dart index 7a36f4d6c..798a8f3aa 100644 --- a/lib/common/skeleton/video_card_v.dart +++ b/lib/common/skeleton/video_card_v.dart @@ -7,54 +7,58 @@ class VideoCardVSkeleton extends StatelessWidget { @override Widget build(BuildContext context) { - final color = Theme.of(context).colorScheme.onInverseSurface; + final color = ColorScheme.of(context).onInverseSurface; return Skeleton( child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: .start, children: [ AspectRatio( aspectRatio: Style.aspectRatio, child: DecoratedBox( decoration: BoxDecoration( color: color, - borderRadius: Style.mdRadius, + borderRadius: const .vertical(top: Style.imgRadius), ), ), ), - Padding( - // 多列 - padding: const EdgeInsets.fromLTRB(4, 5, 6, 6), - // 单列 - // padding: const EdgeInsets.fromLTRB(14, 10, 4, 8), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - // const SizedBox(height: 6), - Container( - width: 200, - height: 13, - margin: const EdgeInsets.only(bottom: 5), - color: color, - ), - Container( - width: 150, - height: 13, - margin: const EdgeInsets.only(bottom: 12), - color: color, - ), - Container( - width: 110, - height: 13, - margin: const EdgeInsets.only(bottom: 5), - color: color, - ), - Container( - width: 75, - height: 13, - color: color, - ), - ], + Expanded( + child: Padding( + padding: const .fromLTRB(4, 5, 6, 6), + child: Column( + crossAxisAlignment: .start, + mainAxisAlignment: .spaceBetween, + children: [ + Container(width: 200, height: 13, color: color), + Container( + height: 13, + width: 140, + color: color, + margin: const .only(top: 5), + ), + const Spacer(), + Row( + spacing: 8, + children: [ + Container( + height: 11, + color: color, + width: 40, + ), + Container( + height: 11, + color: color, + width: 40, + ), + ], + ), + Container( + height: 11, + width: 75, + color: color, + margin: const .only(top: 5), + ), + ], + ), ), ), ], diff --git a/lib/common/widgets/floating_navigation_bar.dart b/lib/common/widgets/floating_navigation_bar.dart index 46838ca14..2f62ac305 100644 --- a/lib/common/widgets/floating_navigation_bar.dart +++ b/lib/common/widgets/floating_navigation_bar.dart @@ -74,65 +74,61 @@ class FloatingNavigationBar extends StatelessWidget { final padding = MediaQuery.viewPaddingOf(context); - return UnconstrainedBox( - child: Padding( - padding: .fromLTRB( - padding.left, - 0, - padding.right, - bottomPadding + padding.bottom, - ), - child: SizedBox( - height: _kNavigationHeight, - width: destinations.length * _kIndicatorWidth, - child: DecoratedBox( - decoration: ShapeDecoration( - color: ElevationOverlay.applySurfaceTint( - backgroundColor ?? - navigationBarTheme.backgroundColor ?? - defaults.backgroundColor!, - surfaceTintColor ?? - navigationBarTheme.surfaceTintColor ?? - defaults.surfaceTintColor, - elevation ?? - navigationBarTheme.elevation ?? - defaults.elevation!, - ), - shape: RoundedSuperellipseBorder( - side: defaults.borderSide, - borderRadius: _kBorderRadius, - ), + return Padding( + padding: .fromLTRB( + padding.left, + 0, + padding.right, + bottomPadding + padding.bottom, + ), + child: SizedBox( + height: _kNavigationHeight, + width: destinations.length * _kIndicatorWidth, + child: DecoratedBox( + decoration: ShapeDecoration( + color: ElevationOverlay.applySurfaceTint( + backgroundColor ?? + navigationBarTheme.backgroundColor ?? + defaults.backgroundColor!, + surfaceTintColor ?? + navigationBarTheme.surfaceTintColor ?? + defaults.surfaceTintColor, + elevation ?? navigationBarTheme.elevation ?? defaults.elevation!, ), - child: Padding( - padding: _kIndicatorPadding, - child: Row( - crossAxisAlignment: .stretch, - children: [ - for (int i = 0; i < destinations.length; i++) - Expanded( - child: _SelectableAnimatedBuilder( - duration: animationDuration, - isSelected: i == selectedIndex, - builder: (context, animation) { - return _NavigationDestinationInfo( - index: i, - selectedIndex: selectedIndex, - totalNumberOfDestinations: destinations.length, - selectedAnimation: animation, - labelBehavior: effectiveLabelBehavior, - indicatorColor: indicatorColor, - indicatorShape: indicatorShape, - overlayColor: overlayColor, - onTap: _handleTap(i), - labelTextStyle: labelTextStyle, - labelPadding: labelPadding, - child: destinations[i], - ); - }, - ), + shape: RoundedSuperellipseBorder( + side: defaults.borderSide, + borderRadius: _kBorderRadius, + ), + ), + child: Padding( + padding: _kIndicatorPadding, + child: Row( + crossAxisAlignment: .stretch, + children: [ + for (int i = 0; i < destinations.length; i++) + Expanded( + child: _SelectableAnimatedBuilder( + duration: animationDuration, + isSelected: i == selectedIndex, + builder: (context, animation) { + return _NavigationDestinationInfo( + index: i, + selectedIndex: selectedIndex, + totalNumberOfDestinations: destinations.length, + selectedAnimation: animation, + labelBehavior: effectiveLabelBehavior, + indicatorColor: indicatorColor, + indicatorShape: indicatorShape, + overlayColor: overlayColor, + onTap: _handleTap(i), + labelTextStyle: labelTextStyle, + labelPadding: labelPadding, + child: destinations[i], + ); + }, ), - ], - ), + ), + ], ), ), ), diff --git a/lib/common/widgets/flutter/tabs.dart b/lib/common/widgets/flutter/tabs.dart deleted file mode 100644 index cf3807362..000000000 --- a/lib/common/widgets/flutter/tabs.dart +++ /dev/null @@ -1,370 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:ui' show SemanticsRole; - -import 'package:flutter/foundation.dart' show clampDouble; -import 'package:flutter/gestures.dart' show DragStartBehavior; -import 'package:flutter/material.dart' hide TabBarView; - -/// A page view that displays the widget which corresponds to the currently -/// selected tab. -/// -/// This widget is typically used in conjunction with a [TabBar]. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=POtoEH-5l40} -/// -/// If a [TabController] is not provided, then there must be a [DefaultTabController] -/// ancestor. -/// -/// The tab controller's [TabController.length] must equal the length of the -/// [children] list and the length of the [TabBar.tabs] list. -/// -/// To see a sample implementation, visit the [TabController] documentation. -class CustomTabBarView extends StatefulWidget { - /// Creates a page view with one child per tab. - /// - /// The length of [children] must be the same as the [controller]'s length. - const CustomTabBarView({ - super.key, - required this.children, - this.controller, - this.physics, - this.dragStartBehavior = DragStartBehavior.start, - this.viewportFraction = 1.0, - this.clipBehavior = Clip.hardEdge, - this.scrollDirection = Axis.horizontal, - }); - - /// This widget's selection and animation state. - /// - /// If [TabController] is not provided, then the value of [DefaultTabController.of] - /// will be used. - final TabController? controller; - - /// One widget per tab. - /// - /// Its length must match the length of the [TabBar.tabs] - /// list, as well as the [controller]'s [TabController.length]. - final List children; - - /// How the page view should respond to user input. - /// - /// For example, determines how the page view continues to animate after the - /// user stops dragging the page view. - /// - /// The physics are modified to snap to page boundaries using - /// [PageScrollPhysics] prior to being used. - /// - /// Defaults to matching platform conventions. - final ScrollPhysics? physics; - - /// {@macro flutter.widgets.scrollable.dragStartBehavior} - final DragStartBehavior dragStartBehavior; - - /// {@macro flutter.widgets.pageview.viewportFraction} - final double viewportFraction; - - /// {@macro flutter.material.Material.clipBehavior} - /// - /// Defaults to [Clip.hardEdge]. - final Clip clipBehavior; - - final Axis scrollDirection; - - @override - State createState() => _CustomTabBarViewState(); -} - -class _CustomTabBarViewState extends State { - TabController? _controller; - PageController? _pageController; - late List _childrenWithKey; - int? _currentIndex; - int _warpUnderwayCount = 0; - int _scrollUnderwayCount = 0; - bool _debugHasScheduledValidChildrenCountCheck = false; - - // If the TabBarView is rebuilt with a new tab controller, the caller should - // dispose the old one. In that case the old controller's animation will be - // null and should not be accessed. - bool get _controllerIsValid => _controller?.animation != null; - - void _updateTabController() { - final TabController? newController = - widget.controller ?? DefaultTabController.maybeOf(context); - assert(() { - if (newController == null) { - throw FlutterError( - 'No TabController for ${widget.runtimeType}.\n' - 'When creating a ${widget.runtimeType}, you must either provide an explicit ' - 'TabController using the "controller" property, or you must ensure that there ' - 'is a DefaultTabController above the ${widget.runtimeType}.\n' - 'In this case, there was neither an explicit controller nor a default controller.', - ); - } - return true; - }()); - - if (newController == _controller) { - return; - } - - if (_controllerIsValid) { - _controller!.animation!.removeListener(_handleTabControllerAnimationTick); - } - _controller = newController; - if (_controller != null) { - _controller!.animation!.addListener(_handleTabControllerAnimationTick); - } - } - - void _jumpToPage(int page) { - _warpUnderwayCount += 1; - _pageController!.jumpToPage(page); - _warpUnderwayCount -= 1; - } - - Future _animateToPage( - int page, { - required Duration duration, - required Curve curve, - }) async { - _warpUnderwayCount += 1; - await _pageController!.animateToPage( - page, - duration: duration, - curve: curve, - ); - _warpUnderwayCount -= 1; - } - - @override - void initState() { - super.initState(); - _updateChildren(); - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - _updateTabController(); - _currentIndex = _controller!.index; - if (_pageController == null) { - _pageController = PageController( - initialPage: _currentIndex!, - viewportFraction: widget.viewportFraction, - ); - } else { - _pageController!.jumpToPage(_currentIndex!); - } - } - - @override - void didUpdateWidget(CustomTabBarView oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.controller != oldWidget.controller) { - _updateTabController(); - _currentIndex = _controller!.index; - _jumpToPage(_currentIndex!); - } - if (widget.viewportFraction != oldWidget.viewportFraction) { - _pageController?.dispose(); - _pageController = PageController( - initialPage: _currentIndex!, - viewportFraction: widget.viewportFraction, - ); - } - // While a warp is under way, we stop updating the tab page contents. - // This is tracked in https://github.com/flutter/flutter/issues/31269. - if (widget.children != oldWidget.children && _warpUnderwayCount == 0) { - _updateChildren(); - } - } - - @override - void dispose() { - if (_controllerIsValid) { - _controller!.animation!.removeListener(_handleTabControllerAnimationTick); - } - _controller = null; - _pageController?.dispose(); - // We don't own the _controller Animation, so it's not disposed here. - super.dispose(); - } - - void _updateChildren() { - _childrenWithKey = KeyedSubtree.ensureUniqueKeysForList( - widget.children.map((Widget child) { - return Semantics(role: SemanticsRole.tabPanel, child: child); - }).toList(), - ); - } - - void _handleTabControllerAnimationTick() { - if (_scrollUnderwayCount > 0 || !_controller!.indexIsChanging) { - return; - } // This widget is driving the controller's animation. - - if (_controller!.index != _currentIndex) { - _currentIndex = _controller!.index; - _warpToCurrentIndex(); - } - } - - void _warpToCurrentIndex() { - if (!mounted || _pageController!.page == _currentIndex!.toDouble()) { - return; - } - - final bool adjacentDestination = - (_currentIndex! - _controller!.previousIndex).abs() == 1; - if (adjacentDestination) { - _warpToAdjacentTab(_controller!.animationDuration); - } else { - _warpToNonAdjacentTab(_controller!.animationDuration); - } - } - - Future _warpToAdjacentTab(Duration duration) async { - if (duration == Duration.zero) { - _jumpToPage(_currentIndex!); - } else { - await _animateToPage( - _currentIndex!, - duration: duration, - curve: Curves.ease, - ); - } - if (mounted) { - setState(_updateChildren); - } - return Future.value(); - } - - Future _warpToNonAdjacentTab(Duration duration) async { - final int previousIndex = _controller!.previousIndex; - assert((_currentIndex! - previousIndex).abs() > 1); - - // initialPage defines which page is shown when starting the animation. - // This page is adjacent to the destination page. - final int initialPage = _currentIndex! > previousIndex - ? _currentIndex! - 1 - : _currentIndex! + 1; - - setState(() { - // Needed for `RenderSliverMultiBoxAdaptor.move` and kept alive children. - // For motivation, see https://github.com/flutter/flutter/pull/29188 and - // https://github.com/flutter/flutter/issues/27010#issuecomment-486475152. - _childrenWithKey = List.of(_childrenWithKey, growable: false); - final Widget temp = _childrenWithKey[initialPage]; - _childrenWithKey[initialPage] = _childrenWithKey[previousIndex]; - _childrenWithKey[previousIndex] = temp; - }); - - // Make a first jump to the adjacent page. - _jumpToPage(initialPage); - - // Jump or animate to the destination page. - if (duration == Duration.zero) { - _jumpToPage(_currentIndex!); - } else { - await _animateToPage( - _currentIndex!, - duration: duration, - curve: Curves.ease, - ); - } - - if (mounted) { - setState(_updateChildren); - } - } - - void _syncControllerOffset() { - _controller!.offset = clampDouble( - _pageController!.page! - _controller!.index, - -1.0, - 1.0, - ); - } - - // Called when the PageView scrolls - bool _handleScrollNotification(ScrollNotification notification) { - if (_warpUnderwayCount > 0 || _scrollUnderwayCount > 0) { - return false; - } - - if (notification.depth != 0) { - return false; - } - - if (!_controllerIsValid) { - return false; - } - - _scrollUnderwayCount += 1; - final double page = _pageController!.page!; - if (notification is ScrollUpdateNotification && - !_controller!.indexIsChanging) { - final bool pageChanged = (page - _controller!.index).abs() > 1.0; - if (pageChanged) { - _controller!.index = page.round(); - _currentIndex = _controller!.index; - } - _syncControllerOffset(); - } else if (notification is ScrollEndNotification) { - _controller!.index = page.round(); - _currentIndex = _controller!.index; - if (!_controller!.indexIsChanging) { - _syncControllerOffset(); - } - } - _scrollUnderwayCount -= 1; - - return false; - } - - bool _debugScheduleCheckHasValidChildrenCount() { - if (_debugHasScheduledValidChildrenCountCheck) { - return true; - } - WidgetsBinding.instance.addPostFrameCallback((Duration duration) { - _debugHasScheduledValidChildrenCountCheck = false; - if (!mounted) { - return; - } - assert(() { - if (_controller!.length != widget.children.length) { - throw FlutterError( - "Controller's length property (${_controller!.length}) does not match the " - "number of children (${widget.children.length}) present in TabBarView's children property.", - ); - } - return true; - }()); - }, debugLabel: 'TabBarView.validChildrenCountCheck'); - _debugHasScheduledValidChildrenCountCheck = true; - return true; - } - - @override - Widget build(BuildContext context) { - assert(_debugScheduleCheckHasValidChildrenCount()); - - return NotificationListener( - onNotification: _handleScrollNotification, - child: PageView( - scrollDirection: widget.scrollDirection, - dragStartBehavior: widget.dragStartBehavior, - clipBehavior: widget.clipBehavior, - controller: _pageController, - physics: widget.physics == null - ? const PageScrollPhysics().applyTo(const ClampingScrollPhysics()) - : const PageScrollPhysics().applyTo(widget.physics), - children: _childrenWithKey, - ), - ); - } -} diff --git a/lib/common/widgets/main_layout.dart b/lib/common/widgets/main_layout.dart new file mode 100644 index 000000000..fda45f1b7 --- /dev/null +++ b/lib/common/widgets/main_layout.dart @@ -0,0 +1,121 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart' + show BoxParentData, BoxHitTestResult, ChildLayoutHelper; + +enum MainType { sideBar, body, bottomNav } + +class MainLayout + extends SlottedMultiChildRenderObjectWidget { + const MainLayout({ + super.key, + required this.sideBar, + required this.bottomNav, + required this.body, + }); + + final Widget? sideBar; + final Widget? bottomNav; + final Widget body; + + @override + Iterable get slots => MainType.values; + + @override + Widget? childForSlot(slot) => switch (slot) { + .sideBar => sideBar, + .bottomNav => bottomNav, + .body => body, + }; + + @override + SlottedContainerRenderObjectMixin createRenderObject( + BuildContext context, + ) { + return _RenderMainLayout(); + } +} + +class _RenderMainLayout extends RenderBox + with SlottedContainerRenderObjectMixin { + Offset _getOffset(RenderBox child) { + return (child.parentData as BoxParentData).offset; + } + + void _setOffset(RenderBox child, Offset offset) { + (child.parentData as BoxParentData).offset = offset; + } + + @override + void performLayout() { + final constraints = this.constraints; + size = constraints.biggest; + + final Offset bodyOffset; + final BoxConstraints bodyConstraints; + + final sideBar = childForSlot(.sideBar); + if (sideBar != null) { + final sideBarWidth = ChildLayoutHelper.layoutChild( + sideBar, + BoxConstraints.tightFor(height: constraints.maxHeight), + ).width; + _setOffset(sideBar, .zero); + + bodyOffset = Offset(sideBarWidth, 0); + bodyConstraints = BoxConstraints.tightFor( + width: constraints.maxWidth - sideBarWidth, + height: constraints.maxHeight, + ); + } else { + final bottomNav = childForSlot(.bottomNav); + if (bottomNav != null) { + final bottomNavSize = ChildLayoutHelper.layoutChild( + bottomNav, + constraints.loosen(), + ); + _setOffset( + bottomNav, + Offset( + (constraints.maxWidth - bottomNavSize.width) / 2, + constraints.maxHeight - bottomNavSize.height, + ), + ); + } + + bodyOffset = .zero; + bodyConstraints = BoxConstraints.tightFor( + width: constraints.maxWidth, + height: constraints.maxHeight, + ); + } + + final body = childForSlot(.body)!..layout(bodyConstraints); + _setOffset(body, bodyOffset); + } + + @override + void paint(PaintingContext context, Offset offset) { + for (final child in children) { + context.paintChild(child, _getOffset(child) + offset); + } + } + + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + for (final type in MainType.values.reversed) { + final child = childForSlot(type); + if (child == null) continue; + final bool isHit = result.addWithPaintOffset( + offset: _getOffset(child), + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) { + return child.hitTest(result, position: transformed); + }, + ); + if (isHit) { + return true; + } + } + return false; + } +} diff --git a/lib/common/widgets/simple_app_bar.dart b/lib/common/widgets/simple_app_bar.dart new file mode 100644 index 000000000..b9698f9b6 --- /dev/null +++ b/lib/common/widgets/simple_app_bar.dart @@ -0,0 +1,36 @@ +import 'package:PiliPlus/utils/extension/theme_ext.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart' show SystemUiOverlayStyle; + +class SimpleAppBar extends StatelessWidget { + const SimpleAppBar({ + super.key, + required this.height, + required this.brightness, + Brightness? statusBarIconBrightness, + this.backgroundColor = Colors.black, + }) : statusBarIconBrightness = statusBarIconBrightness ?? .light; + + final double height; + final Brightness brightness; + final Brightness statusBarIconBrightness; + final Color backgroundColor; + + @override + Widget build(BuildContext context) { + return AnnotatedRegion( + value: SystemUiOverlayStyle( + statusBarBrightness: .light, + statusBarColor: Colors.transparent, + statusBarIconBrightness: statusBarIconBrightness, + systemStatusBarContrastEnforced: false, + systemNavigationBarColor: Colors.transparent, + systemNavigationBarIconBrightness: brightness.reverse, + ), + child: ColoredBox( + color: backgroundColor, + child: SizedBox(height: height, width: .infinity), + ), + ); + } +} diff --git a/lib/common/widgets/video_card/video_card_v.dart b/lib/common/widgets/video_card/video_card_v.dart index 09cda2eb0..929aa1afc 100644 --- a/lib/common/widgets/video_card/video_card_v.dart +++ b/lib/common/widgets/video_card/video_card_v.dart @@ -90,11 +90,11 @@ class VideoCardV extends StatelessWidget { clipBehavior: Clip.none, children: [ Card( - clipBehavior: Clip.hardEdge, child: InkWell( onTap: onPushDetail, onLongPress: onLongPress, onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, + borderRadius: const .all(.circular(12)), child: Column( crossAxisAlignment: .start, children: [ @@ -111,7 +111,7 @@ class VideoCardV extends StatelessWidget { src: videoItem.cover, width: maxWidth, height: maxHeight, - type: .emote, + borderRadius: const .vertical(top: .circular(12)), ), if (videoItem.duration > 0) PBadge( diff --git a/lib/pages/dynamics/widgets/vote.dart b/lib/pages/dynamics/widgets/vote.dart index 0c15ac67d..abce46b09 100644 --- a/lib/pages/dynamics/widgets/vote.dart +++ b/lib/pages/dynamics/widgets/vote.dart @@ -317,10 +317,7 @@ class _VotePanelState extends State { .toList(), ); return Card( - clipBehavior: Clip.hardEdge, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(6)), - ), + shape: const RoundedRectangleBorder(borderRadius: .all(.circular(6))), child: Builder( builder: (context) { final opt = _voteInfo.options[index]; @@ -331,9 +328,10 @@ class _VotePanelState extends State { : () => _onSelected(context, !selected, opt.optIdx!), onLongPress: PlatformUtils.isMobile ? onLongPress : null, onSecondaryTap: PlatformUtils.isDesktop ? onLongPress : null, + borderRadius: const .all(.circular(6)), child: Column( spacing: 5, - crossAxisAlignment: CrossAxisAlignment.stretch, + crossAxisAlignment: .stretch, children: [ Stack( clipBehavior: Clip.none, @@ -345,7 +343,7 @@ class _VotePanelState extends State { src: opt.imgUrl, width: constraints.maxWidth, height: constraints.maxHeight, - type: .emote, + borderRadius: const .vertical(top: .circular(6)), ), ), ), diff --git a/lib/pages/dynamics_detail/view.dart b/lib/pages/dynamics_detail/view.dart index 2e9a4ef54..cf4754fa9 100644 --- a/lib/pages/dynamics_detail/view.dart +++ b/lib/pages/dynamics_detail/view.dart @@ -122,12 +122,7 @@ class _DynamicDetailPageState appBar: _buildAppBar(), body: Padding( padding: EdgeInsets.only(left: padding.left, right: padding.right), - child: isPortrait - ? refreshIndicator( - onRefresh: controller.onRefresh, - child: _buildBody(), - ) - : _buildBody(), + child: _buildBody(), ), floatingActionButtonLocation: floatingActionButtonLocation, floatingActionButton: SlideTransition( diff --git a/lib/pages/home/view.dart b/lib/pages/home/view.dart index bc2862e03..68faa8a90 100644 --- a/lib/pages/home/view.dart +++ b/lib/pages/home/view.dart @@ -22,6 +22,7 @@ class HomePage extends StatefulWidget { class _HomePageState extends CommonPageState with AutomaticKeepAliveClientMixin { + late ColorScheme _colorScheme; final _homeController = Get.putOrFind(HomeController.new); final _mainController = Get.find(); @@ -31,10 +32,15 @@ class _HomePageState extends CommonPageState @override bool get wantKeepAlive => true; + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _colorScheme = ColorScheme.of(context); + } + @override Widget build(BuildContext context) { super.build(context); - final theme = Theme.of(context); Widget tabBar; if (_homeController.tabs.length > 1) { tabBar = Padding( @@ -62,7 +68,7 @@ class _HomePageState extends CommonPageState if (_homeController.hideTopBar && _mainController.barHideType == .instant) { tabBar = Material( - color: theme.colorScheme.surface, + color: _colorScheme.surface, child: tabBar, ); } @@ -73,7 +79,7 @@ class _HomePageState extends CommonPageState children: [ if (!_mainController.useSideBar && MediaQuery.sizeOf(context).isPortrait) - customAppBar(theme), + customAppBar(), tabBar, Expanded( child: onBuild( @@ -87,15 +93,15 @@ class _HomePageState extends CommonPageState ); } - Widget customAppBar(ThemeData theme) { + Widget customAppBar() { const padding = EdgeInsets.fromLTRB(14, 6, 14, 0); final child = Row( children: [ - searchBar(theme), + searchBar(), const SizedBox(width: 4), msgBadge(_mainController), const SizedBox(width: 8), - userAvatar(theme: theme, mainController: _mainController), + userAvatar(colorScheme: _colorScheme, mainController: _mainController), ], ); if (_homeController.hideTopBar) { @@ -138,17 +144,17 @@ class _HomePageState extends CommonPageState ); } - Widget searchBar(ThemeData theme) { + Widget searchBar() { const borderRadius = BorderRadius.all(Radius.circular(25)); return Expanded( child: SizedBox( height: 44, child: Material( borderRadius: borderRadius, - color: theme.colorScheme.onSecondaryContainer.withValues(alpha: 0.05), + color: _colorScheme.onSecondaryContainer.withValues(alpha: 0.05), child: InkWell( borderRadius: borderRadius, - splashColor: theme.colorScheme.primaryContainer.withValues( + splashColor: _colorScheme.primaryContainer.withValues( alpha: 0.3, ), onTap: () => Get.toNamed( @@ -162,7 +168,7 @@ class _HomePageState extends CommonPageState const SizedBox(width: 14), Icon( Icons.search_outlined, - color: theme.colorScheme.onSecondaryContainer, + color: _colorScheme.onSecondaryContainer, semanticLabel: '搜索', ), const SizedBox(width: 10), @@ -172,7 +178,7 @@ class _HomePageState extends CommonPageState _homeController.defaultSearch.value, maxLines: 1, overflow: TextOverflow.ellipsis, - style: TextStyle(color: theme.colorScheme.outline), + style: TextStyle(color: _colorScheme.outline), ), ), ), @@ -187,7 +193,7 @@ class _HomePageState extends CommonPageState } Widget userAvatar({ - required ThemeData theme, + required ColorScheme colorScheme, required MainController mainController, }) { return Semantics( @@ -209,7 +215,7 @@ Widget userAvatar({ type: .transparency, child: InkWell( onTap: mainController.toMinePage, - splashColor: theme.colorScheme.primaryContainer.withValues( + splashColor: colorScheme.primaryContainer.withValues( alpha: 0.3, ), customBorder: const CircleBorder(), @@ -226,12 +232,12 @@ Widget userAvatar({ padding: const .all(2), decoration: BoxDecoration( shape: .circle, - color: theme.colorScheme.secondaryContainer, + color: colorScheme.secondaryContainer, ), child: Icon( size: 14, MdiIcons.incognito, - color: theme.colorScheme.onSecondaryContainer, + color: colorScheme.onSecondaryContainer, ), ), ) @@ -248,13 +254,13 @@ Widget userAvatar({ tooltip: '点击登录', style: IconButton.styleFrom( padding: .zero, - backgroundColor: theme.colorScheme.onInverseSurface, + backgroundColor: colorScheme.onInverseSurface, ), onPressed: mainController.toMinePage, icon: Icon( Icons.person_rounded, size: 22, - color: theme.colorScheme.primary, + color: colorScheme.primary, ), ), ); diff --git a/lib/pages/live/widgets/live_item_app.dart b/lib/pages/live/widgets/live_item_app.dart index 3835a97fd..a98e97151 100644 --- a/lib/pages/live/widgets/live_item_app.dart +++ b/lib/pages/live/widgets/live_item_app.dart @@ -33,11 +33,11 @@ class LiveCardVApp extends StatelessWidget { return Stack( children: [ Card( - clipBehavior: Clip.hardEdge, child: InkWell( onTap: () => PageUtils.toLiveRoom(item.roomid), onLongPress: onLongPress, onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, + borderRadius: const .all(.circular(12)), child: Column( crossAxisAlignment: .start, children: [ @@ -51,7 +51,7 @@ class LiveCardVApp extends StatelessWidget { src: showFirstFrame ? item.systemCover : item.cover, width: boxConstraints.maxWidth, height: boxConstraints.maxHeight, - type: .emote, + borderRadius: const .vertical(top: .circular(12)), ), Positioned( left: 0, diff --git a/lib/pages/live_follow/widgets/live_item_follow.dart b/lib/pages/live_follow/widgets/live_item_follow.dart index d5ac60bc0..cd6043ced 100644 --- a/lib/pages/live_follow/widgets/live_item_follow.dart +++ b/lib/pages/live_follow/widgets/live_item_follow.dart @@ -22,11 +22,11 @@ class LiveCardVFollow extends StatelessWidget { cover: liveItem.roomCover, ); return Card( - clipBehavior: Clip.hardEdge, child: InkWell( onTap: () => PageUtils.toLiveRoom(liveItem.roomid), onLongPress: onLongPress, onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, + borderRadius: const .all(.circular(12)), child: Column( crossAxisAlignment: .start, children: [ @@ -43,7 +43,7 @@ class LiveCardVFollow extends StatelessWidget { src: liveItem.roomCover!, width: maxWidth, height: maxHeight, - type: .emote, + borderRadius: const .vertical(top: .circular(12)), ), Positioned( left: 0, diff --git a/lib/pages/live_search/widgets/live_search_room.dart b/lib/pages/live_search/widgets/live_search_room.dart index 818a3cca9..ecf0c1df2 100644 --- a/lib/pages/live_search/widgets/live_search_room.dart +++ b/lib/pages/live_search/widgets/live_search_room.dart @@ -22,11 +22,11 @@ class LiveCardVSearch extends StatelessWidget { cover: item.cover, ); return Card( - clipBehavior: Clip.hardEdge, child: InkWell( onTap: () => PageUtils.toLiveRoom(item.roomid), onLongPress: onLongPress, onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, + borderRadius: const .all(.circular(12)), child: Column( crossAxisAlignment: .start, children: [ @@ -43,7 +43,7 @@ class LiveCardVSearch extends StatelessWidget { src: item.cover!, width: maxWidth, height: maxHeight, - type: .emote, + borderRadius: const .vertical(top: .circular(12)), ), Positioned( left: 0, diff --git a/lib/pages/main/view.dart b/lib/pages/main/view.dart index 71e073fe2..c0d961c53 100644 --- a/lib/pages/main/view.dart +++ b/lib/pages/main/view.dart @@ -5,8 +5,8 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/floating_navigation_bar.dart'; import 'package:PiliPlus/common/widgets/flutter/pop_scope.dart'; -import 'package:PiliPlus/common/widgets/flutter/tabs.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; +import 'package:PiliPlus/common/widgets/main_layout.dart'; import 'package:PiliPlus/common/widgets/route_aware_mixin.dart'; import 'package:PiliPlus/models/common/nav_bar_config.dart'; import 'package:PiliPlus/pages/home/view.dart'; @@ -46,7 +46,7 @@ class _MainAppState extends PopScopeState final _mainController = Get.put(MainController()); late final _setting = GStorage.setting; late EdgeInsets _padding; - late ThemeData theme; + late ColorScheme _colorScheme; Brightness? _brightness; @override @@ -74,8 +74,8 @@ class _MainAppState extends PopScopeState void didChangeDependencies() { super.didChangeDependencies(); _padding = MediaQuery.viewPaddingOf(context); - theme = Theme.of(context); - final brightness = theme.brightness; + _colorScheme = ColorScheme.of(context); + final brightness = _colorScheme.brightness; NetworkImgLayer.reduce = NetworkImgLayer.reduceLuxColor != null && brightness.isDark; if (PlatformUtils.isDesktop) { @@ -368,128 +368,141 @@ class _MainAppState extends PopScopeState return bottomNav; } - Widget _sideBar(ThemeData theme) { - return _mainController.navigationBars.length > 1 - ? context.isTablet && _mainController.optTabletNav - ? Column( - children: [ - const SizedBox(height: 25), - userAndSearchVertical(theme), - const Spacer(flex: 2), - Expanded( - flex: 5, - child: SizedBox( - width: 130, - child: Obx( - () => NavigationDrawer( - backgroundColor: Colors.transparent, - tilePadding: const .symmetric( - vertical: 5, - horizontal: 12, - ), - indicatorShape: const RoundedRectangleBorder( - borderRadius: .all(.circular(16)), - ), - onDestinationSelected: _mainController.setIndex, - selectedIndex: _mainController.selectedIndex.value, - children: _mainController.navigationBars - .map( - (e) => NavigationDrawerDestination( - label: Text(e.label), - icon: _buildIcon(type: e), - selectedIcon: _buildIcon( - type: e, - selected: true, - ), - ), - ) - .toList(), + Widget _sideBar() { + if (_mainController.navigationBars.length > 1) { + if (context.isTablet && _mainController.optTabletNav) { + return Padding( + padding: const .only(top: 25), + child: MediaQuery.removePadding( + context: context, + removeRight: true, + child: DrawerTheme( + data: DrawerThemeData(width: 130 + _padding.left), + child: Obx( + () => NavigationDrawer( + /// apply `lib/scripts/navigation_drawer.patch` + flex: 5, + backgroundColor: Colors.transparent, + onDestinationSelected: _mainController.setIndex, + selectedIndex: _mainController.selectedIndex.value, + header: Expanded(flex: 4, child: userAndSearchVertical()), + tilePadding: const .symmetric(vertical: 5, horizontal: 12), + indicatorShape: const RoundedRectangleBorder( + borderRadius: .all(.circular(16)), + ), + children: _mainController.navigationBars + .map( + (e) => NavigationDrawerDestination( + label: Text(e.label), + icon: _buildIcon(type: e), + selectedIcon: _buildIcon( + type: e, + selected: true, ), ), - ), - ), - ], - ) - : Obx( - () => NavigationRail( - groupAlignment: 0.5, - selectedIndex: _mainController.selectedIndex.value, - onDestinationSelected: _mainController.setIndex, - labelType: .selected, - leading: userAndSearchVertical(theme), - destinations: _mainController.navigationBars - .map( - (e) => NavigationRailDestination( - label: Text(e.label), - icon: _buildIcon(type: e), - selectedIcon: _buildIcon(type: e, selected: true), - ), - ) - .toList(), - ), - ) - : Container( - width: 80, - padding: const .only(top: 10), - child: userAndSearchVertical(theme), - ); + ) + .toList(), + ), + ), + ), + ), + ); + } + return Obx( + () => NavigationRail( + groupAlignment: 0.5, + labelType: .selected, + leading: userAndSearchVertical(), + backgroundColor: Colors.transparent, + onDestinationSelected: _mainController.setIndex, + selectedIndex: _mainController.selectedIndex.value, + destinations: _mainController.navigationBars + .map( + (e) => NavigationRailDestination( + label: Text(e.label), + icon: _buildIcon(type: e), + selectedIcon: _buildIcon(type: e, selected: true), + ), + ) + .toList(), + ), + ); + } + return Container( + width: 80, + margin: .only(top: 12 + _padding.top, left: _padding.left), + child: userAndSearchVertical(), + ); } @override Widget build(BuildContext context) { Widget child; if (_mainController.mainTabBarView) { - child = CustomTabBarView( - scrollDirection: _mainController.useBottomNav ? .horizontal : .vertical, - physics: const NeverScrollableScrollPhysics(), + child = TabBarView( controller: _mainController.controller, + physics: const NeverScrollableScrollPhysics(), + + /// apply `lib/scripts/tabs.patch` + scrollDirection: _mainController.useBottomNav ? .horizontal : .vertical, children: _mainController.navigationBars.map((i) => i.page).toList(), ); } else { child = PageView( - physics: const NeverScrollableScrollPhysics(), controller: _mainController.controller, + physics: const NeverScrollableScrollPhysics(), children: _mainController.navigationBars.map((i) => i.page).toList(), ); } + Widget? sideBar; Widget? bottomNav; + final EdgeInsets padding; if (_mainController.useBottomNav) { bottomNav = _bottomNav; - child = Row(children: [Expanded(child: child)]); - } else { - child = Row( - children: [ - _sideBar(theme), - VerticalDivider( - width: 1, - endIndent: _padding.bottom, - color: theme.colorScheme.outline.withValues(alpha: 0.06), - ), - Expanded(child: child), - ], + if (bottomNav != null) { + bottomNav = MediaQuery.removePadding( + context: context, + removeTop: true, + child: bottomNav, + ); + } + padding = .only( + top: _padding.top, + left: _padding.left, + right: _padding.right, ); + } else { + sideBar = DecoratedBox( + decoration: BoxDecoration( + border: Border( + right: BorderSide( + color: _colorScheme.outline.withValues(alpha: 0.06), + ), + ), + ), + child: _sideBar(), + ); + padding = .only(top: _padding.top, right: _padding.right); } - child = Scaffold( - extendBody: true, - resizeToAvoidBottomInset: false, - appBar: AppBar(toolbarHeight: 0), - body: Padding( - padding: EdgeInsets.only( - left: _mainController.useBottomNav ? _padding.left : 0.0, - right: _padding.right, - ), - child: child, + child = Material( + child: MainLayout( + sideBar: sideBar, + bottomNav: bottomNav, + body: Padding(padding: padding, child: child), ), - bottomNavigationBar: bottomNav, ); if (PlatformUtils.isMobile) { - child = AnnotatedRegion( + return AnnotatedRegion( value: SystemUiOverlayStyle( + statusBarColor: Colors.transparent, + statusBarBrightness: _colorScheme.brightness, + statusBarIconBrightness: _colorScheme.brightness.reverse, + systemStatusBarContrastEnforced: false, systemNavigationBarColor: Colors.transparent, - systemNavigationBarIconBrightness: theme.brightness.reverse, + systemNavigationBarIconBrightness: _colorScheme.brightness.reverse, ), child: child, ); @@ -517,10 +530,10 @@ class _MainAppState extends PopScopeState : icon; } - Widget userAndSearchVertical(ThemeData theme) { + Widget userAndSearchVertical() { return Column( children: [ - userAvatar(theme: theme, mainController: _mainController), + userAvatar(colorScheme: _colorScheme, mainController: _mainController), const SizedBox(height: 8), msgBadge(_mainController), IconButton( diff --git a/lib/pages/member_coin_arc/widgets/item.dart b/lib/pages/member_coin_arc/widgets/item.dart index 3ade8508c..e4e8f30e9 100644 --- a/lib/pages/member_coin_arc/widgets/item.dart +++ b/lib/pages/member_coin_arc/widgets/item.dart @@ -30,7 +30,6 @@ class MemberCoinLikeItem extends StatelessWidget { aid: item.param, ); return Card( - clipBehavior: Clip.hardEdge, child: InkWell( onTap: () async { if (item.isPgc == true) { @@ -56,6 +55,7 @@ class MemberCoinLikeItem extends StatelessWidget { }, onLongPress: onLongPress, onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, + borderRadius: const .all(.circular(12)), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -72,7 +72,7 @@ class MemberCoinLikeItem extends StatelessWidget { src: item.cover, width: maxWidth, height: maxHeight, - type: .emote, + borderRadius: const .vertical(top: .circular(12)), ), if (item.isCooperation == true) const PBadge( diff --git a/lib/pages/member_home/widgets/video_card_v_member_home.dart b/lib/pages/member_home/widgets/video_card_v_member_home.dart index 1fbf026cd..2adacb877 100644 --- a/lib/pages/member_home/widgets/video_card_v_member_home.dart +++ b/lib/pages/member_home/widgets/video_card_v_member_home.dart @@ -80,11 +80,11 @@ class VideoCardVMemberHome extends StatelessWidget { bvid: videoItem.bvid, ); return Card( - clipBehavior: Clip.hardEdge, child: InkWell( onTap: onPushDetail, onLongPress: onLongPress, onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, + borderRadius: const .all(.circular(12)), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -101,7 +101,7 @@ class VideoCardVMemberHome extends StatelessWidget { src: videoItem.cover, width: maxWidth, height: maxHeight, - type: .emote, + borderRadius: const .vertical(top: .circular(12)), ), if (videoItem.duration > 0) PBadge( diff --git a/lib/pages/member_opus/widgets/space_opus_item.dart b/lib/pages/member_opus/widgets/space_opus_item.dart index 84f7e4fde..6ea78e521 100644 --- a/lib/pages/member_opus/widgets/space_opus_item.dart +++ b/lib/pages/member_opus/widgets/space_opus_item.dart @@ -1,6 +1,5 @@ import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/stat/stat.dart'; -import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/models/common/stat_type.dart'; import 'package:PiliPlus/models_new/space/space_opus/item.dart'; import 'package:PiliPlus/utils/page_utils.dart'; @@ -18,13 +17,10 @@ class SpaceOpusItem extends StatelessWidget { Widget build(BuildContext context) { final hasPic = item.cover?.url?.isNotEmpty == true; return Card( - clipBehavior: Clip.hardEdge, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(6)), - ), + shape: const RoundedRectangleBorder(borderRadius: .all(.circular(6))), child: InkWell( onTap: () => PageUtils.pushDynFromId(id: item.opusId!), - borderRadius: const BorderRadius.all(Radius.circular(6)), + borderRadius: const .all(.circular(6)), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -36,7 +32,7 @@ class SpaceOpusItem extends StatelessWidget { width: constraints.maxWidth, height: constraints.maxWidth * item.cover!.ratio, src: item.cover!.url, - type: ImageType.emote, + borderRadius: const .vertical(top: .circular(6)), quality: 60, ), ), diff --git a/lib/pages/member_shop/widgets/item.dart b/lib/pages/member_shop/widgets/item.dart index 5c1beda8c..4d5478bbb 100644 --- a/lib/pages/member_shop/widgets/item.dart +++ b/lib/pages/member_shop/widgets/item.dart @@ -19,11 +19,9 @@ class MemberShopItem extends StatelessWidget { final colorScheme = Theme.of(context).colorScheme; final belowLabels = item.belowLabels?.map((e) => e.title).join('|'); return Card( - clipBehavior: Clip.hardEdge, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(6)), - ), + shape: const RoundedRectangleBorder(borderRadius: .all(.circular(6))), child: InkWell( + borderRadius: const .all(.circular(6)), onTap: () { if (item.cardUrl case final cardUrl?) { Get.toNamed('/webview', parameters: {'url': cardUrl}); @@ -34,7 +32,7 @@ class MemberShopItem extends StatelessWidget { children: [ LayoutBuilder( builder: (context, constraints) => NetworkImgLayer( - type: .emote, + borderRadius: const .vertical(top: .circular(6)), src: item.cover?.url, width: constraints.maxWidth, height: constraints.maxWidth, diff --git a/lib/pages/save_panel/view.dart b/lib/pages/save_panel/view.dart index ec485597f..edead0bd1 100644 --- a/lib/pages/save_panel/view.dart +++ b/lib/pages/save_panel/view.dart @@ -354,8 +354,7 @@ class _SavePanelState extends State { padding: const .symmetric(horizontal: 12), child: RepaintBoundary( key: boundaryKey, - child: Container( - clipBehavior: .hardEdge, + child: DecoratedBox( decoration: BoxDecoration( color: theme.colorScheme.surface, borderRadius: const .all(.circular(12)), @@ -390,7 +389,6 @@ class _SavePanelState extends State { title?.isNotEmpty == true) Container( height: 81, - clipBehavior: Clip.hardEdge, margin: const .symmetric(horizontal: 12), padding: const .all(8), decoration: BoxDecoration( diff --git a/lib/pages/search_panel/live/widgets/item.dart b/lib/pages/search_panel/live/widgets/item.dart index 225206745..b85e121d3 100644 --- a/lib/pages/search_panel/live/widgets/item.dart +++ b/lib/pages/search_panel/live/widgets/item.dart @@ -20,11 +20,11 @@ class LiveItem extends StatelessWidget { cover: liveItem.cover, ); return Card( - clipBehavior: Clip.hardEdge, child: InkWell( onTap: () => PageUtils.toLiveRoom(liveItem.roomid), onLongPress: onLongPress, onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, + borderRadius: const .all(.circular(12)), child: Column( crossAxisAlignment: .start, children: [ @@ -41,7 +41,7 @@ class LiveItem extends StatelessWidget { src: liveItem.cover, width: maxWidth, height: maxHeight, - type: .emote, + borderRadius: const .vertical(top: .circular(12)), ), Positioned( left: 0, diff --git a/lib/pages/video/reply/widgets/reply_item_grpc.dart b/lib/pages/video/reply/widgets/reply_item_grpc.dart index 5f3b40ef1..300c56bee 100644 --- a/lib/pages/video/reply/widgets/reply_item_grpc.dart +++ b/lib/pages/video/reply/widgets/reply_item_grpc.dart @@ -564,30 +564,32 @@ class ReplyItemGrpc extends StatelessWidget { List replies, ) { final extraRow = replies.length < replyItem.count.toInt(); - late final length = replies.length + (extraRow ? 1 : 0); + final length = replies.length + (extraRow ? 1 : 0); return Padding( - padding: const EdgeInsets.only(left: 42, right: 4), + padding: const .only(left: 42, right: 4), child: Material( + animationDuration: .zero, color: colorScheme.onInverseSurface, - borderRadius: const BorderRadius.all(Radius.circular(6)), - clipBehavior: Clip.hardEdge, - animationDuration: Duration.zero, + borderRadius: const .all(.circular(6)), child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, + crossAxisAlignment: .stretch, children: [ if (replies.isNotEmpty) - ...List.generate(replies.length, (index) { - final childReply = replies[index]; - EdgeInsets padding; + ...replies.mapIndexed((index, childReply) { + final EdgeInsets padding; + BorderRadius? borderRadius; if (length == 1) { - padding = const EdgeInsets.fromLTRB(8, 5, 8, 5); + padding = const .fromLTRB(8, 5, 8, 5); + borderRadius = const .all(.circular(6)); } else { if (index == 0) { - padding = const EdgeInsets.fromLTRB(8, 8, 8, 4); + padding = const .fromLTRB(8, 8, 8, 4); + borderRadius = const .vertical(top: .circular(6)); } else if (index == length - 1) { - padding = const EdgeInsets.fromLTRB(8, 4, 8, 8); + padding = const .fromLTRB(8, 4, 8, 8); + borderRadius = const .vertical(bottom: .circular(6)); } else { - padding = const EdgeInsets.fromLTRB(8, 4, 8, 4); + padding = const .fromLTRB(8, 4, 8, 4); } } void showMore() => showModalBottomSheet( @@ -607,6 +609,7 @@ class ReplyItemGrpc extends StatelessWidget { }, ); return InkWell( + borderRadius: borderRadius, onTap: () => replyReply?.call(replyItem, childReply.id.toInt()), onLongPress: showMore, @@ -670,10 +673,13 @@ class ReplyItemGrpc extends StatelessWidget { if (extraRow) InkWell( onTap: () => replyReply?.call(replyItem, null), + borderRadius: length == 1 + ? const .all(.circular(6)) + : const .vertical(bottom: .circular(6)), child: Padding( padding: length == 1 - ? const EdgeInsets.fromLTRB(8, 6, 8, 6) - : const EdgeInsets.fromLTRB(8, 5, 8, 8), + ? const .fromLTRB(8, 6, 8, 6) + : const .fromLTRB(8, 5, 8, 8), child: Text.rich( TextSpan( style: const TextStyle(fontSize: 12), diff --git a/lib/pages/video/view.dart b/lib/pages/video/view.dart index ead42d988..fe7077b48 100644 --- a/lib/pages/video/view.dart +++ b/lib/pages/video/view.dart @@ -12,6 +12,7 @@ import 'package:PiliPlus/common/widgets/scroll_behavior.dart' show NoOverscrollIndicator; import 'package:PiliPlus/common/widgets/scroll_physics.dart' show tabBarView, platformAlwaysClampingPhysics, platformClampingPhysics; +import 'package:PiliPlus/common/widgets/simple_app_bar.dart'; import 'package:PiliPlus/common/widgets/sliver/video_header.dart'; import 'package:PiliPlus/common/widgets/svg/play_icon.dart'; import 'package:PiliPlus/models/common/episode_panel_type.dart'; @@ -60,13 +61,13 @@ import 'package:PiliPlus/utils/max_screen_size.dart'; import 'package:PiliPlus/utils/mobile_observer.dart'; import 'package:PiliPlus/utils/num_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; +import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage_key.dart'; import 'package:PiliPlus/utils/theme_utils.dart'; import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; import 'package:flutter/foundation.dart' show kDebugMode, clampDouble; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart' show SystemUiOverlayStyle; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart'; @@ -471,12 +472,13 @@ class _VideoDetailPageVState extends State ? maxVideoHeight : minVideoHeight; - themeData = videoDetailController.plPlayerController.darkVideoPage + theme = videoDetailController.plPlayerController.darkVideoPage ? ThemeUtils.darkTheme : Theme.of(context); } bool removeAppBar(bool isFullScreen) => + PlatformUtils.isDesktop || videoDetailController.removeSafeArea || (isWindowMode && isFullScreen && !isPortrait); @@ -489,30 +491,30 @@ class _VideoDetailPageVState extends State appBar: removeAppBar(isFullScreen) ? null : PreferredSize( - preferredSize: const Size.fromHeight(0), + preferredSize: const .fromHeight(0), child: Obx( () { final scrollRatio = videoDetailController.scrollRatio.value; - return AppBar( - toolbarHeight: 0, - backgroundColor: isPortrait && scrollRatio > 0 - ? Color.lerp( - Colors.black, - themeData.colorScheme.surface, - scrollRatio, - ) - : Colors.black, - systemOverlayStyle: Platform.isAndroid - ? SystemUiOverlayStyle( - statusBarIconBrightness: - isPortrait && scrollRatio >= 0.5 - ? themeData.brightness.reverse - : .light, - systemNavigationBarIconBrightness: - themeData.brightness.reverse, - ) - : null, + Brightness? statusBarIconBrightness; + if (Platform.isAndroid) { + statusBarIconBrightness = + isPortrait && scrollRatio >= 0.5 + ? colorScheme.brightness.reverse + : .light; + } + final backgroundColor = isPortrait && scrollRatio > 0 + ? Color.lerp( + Colors.black, + colorScheme.surface, + scrollRatio, + )! + : Colors.black; + return SimpleAppBar( + height: padding.top, + backgroundColor: backgroundColor, + brightness: colorScheme.brightness, + statusBarIconBrightness: statusBarIconBrightness, ); }, ), @@ -624,17 +626,17 @@ class _VideoDetailPageVState extends State spacing: 2, mainAxisSize: .min, children: [ - Icon(icon, color: themeData.colorScheme.primary), + Icon(icon, color: colorScheme.primary), Text( '$playStat播放', - style: TextStyle(color: themeData.colorScheme.primary), + style: TextStyle(color: colorScheme.primary), ), ], ); return Opacity( opacity: videoDetailController.scrollRatio.value, child: Container( - color: themeData.colorScheme.surface, + color: colorScheme.surface, alignment: .topCenter, child: SizedBox( height: kToolbarHeight, @@ -654,7 +656,7 @@ class _VideoDetailPageVState extends State icon: Icon( FontAwesomeIcons.arrowLeft, size: 15, - color: themeData.colorScheme.onSurface, + color: colorScheme.onSurface, ), onPressed: Get.back, ), @@ -667,7 +669,7 @@ class _VideoDetailPageVState extends State icon: Icon( FontAwesomeIcons.house, size: 15, - color: themeData.colorScheme.onSurface, + color: colorScheme.onSurface, ), onPressed: videoDetailController.plPlayerController.onCloseAll, @@ -680,7 +682,7 @@ class _VideoDetailPageVState extends State Align( alignment: .centerRight, child: videoDetailController.playedTime == null - ? _moreBtn(themeData.colorScheme.onSurface) + ? _moreBtn(colorScheme.onSurface) : SizedBox( width: 42, height: 34, @@ -696,7 +698,7 @@ class _VideoDetailPageVState extends State icon: Icon( Icons.more_vert_outlined, size: 19, - color: themeData.colorScheme.onSurface, + color: colorScheme.onSurface, ), ), ), @@ -757,7 +759,13 @@ class _VideoDetailPageVState extends State resizeToAvoidBottomInset: false, appBar: removeAppBar(isFullScreen) ? null - : AppBar(backgroundColor: Colors.black, toolbarHeight: 0), + : PreferredSize( + preferredSize: const .fromHeight(0), + child: SimpleAppBar( + height: padding.top, + brightness: colorScheme.brightness, + ), + ), body: Padding( padding: isFullScreen ? EdgeInsets.zero @@ -992,7 +1000,13 @@ class _VideoDetailPageVState extends State resizeToAvoidBottomInset: false, appBar: removeAppBar(isFullScreen) ? null - : AppBar(backgroundColor: Colors.black, toolbarHeight: 0), + : PreferredSize( + preferredSize: const .fromHeight(0), + child: SimpleAppBar( + height: padding.top, + brightness: colorScheme.brightness, + ), + ), body: Padding( padding: isFullScreen ? EdgeInsets.zero @@ -1088,57 +1102,49 @@ class _VideoDetailPageVState extends State top: 0, left: 0, right: 0, - child: AppBar( - primary: false, - elevation: 0, - scrolledUnderElevation: 0, - foregroundColor: Colors.white, - backgroundColor: Colors.transparent, - automaticallyImplyLeading: false, - title: Row( - children: [ - SizedBox( - width: 42, - height: 34, - child: IconButton( - tooltip: '返回', - icon: const Icon( - FontAwesomeIcons.arrowLeft, - size: 15, - color: Colors.white, - shadows: [ - Shadow( - blurRadius: 1.5, - color: Colors.black, - ), - ], - ), - onPressed: Get.back, + height: kToolbarHeight, + child: Row( + children: [ + SizedBox( + width: 42, + height: 34, + child: IconButton( + tooltip: '返回', + icon: const Icon( + FontAwesomeIcons.arrowLeft, + size: 15, + color: Colors.white, + shadows: [ + Shadow( + blurRadius: 1.5, + color: Colors.black, + ), + ], ), + onPressed: Get.back, ), - SizedBox( - width: 42, - height: 34, - child: IconButton( - tooltip: '返回主页', - icon: const Icon( - FontAwesomeIcons.house, - size: 15, - color: Colors.white, - shadows: [ - Shadow( - blurRadius: 1.5, - color: Colors.black, - ), - ], - ), - onPressed: - videoDetailController.plPlayerController.onCloseAll, + ), + SizedBox( + width: 42, + height: 34, + child: IconButton( + tooltip: '返回主页', + icon: const Icon( + FontAwesomeIcons.house, + size: 15, + color: Colors.white, + shadows: [ + Shadow( + blurRadius: 1.5, + color: Colors.black, + ), + ], ), + onPressed: + videoDetailController.plPlayerController.onCloseAll, ), - ], - ), - actions: [ + ), + const Spacer(), _moreBtn( Colors.white, shadows: const [ @@ -1262,7 +1268,8 @@ class _VideoDetailPageVState extends State ), ); - late ThemeData themeData; + late ThemeData theme; + ColorScheme get colorScheme => theme.colorScheme; late bool isPortrait; late double maxWidth; late double maxHeight; @@ -1300,7 +1307,7 @@ class _VideoDetailPageVState extends State ); } return videoDetailController.plPlayerController.darkVideoPage - ? Theme(data: themeData, child: child) + ? Theme(data: theme, child: child) : child; } @@ -1336,7 +1343,7 @@ class _VideoDetailPageVState extends State dividerColor: Colors.transparent, controller: videoDetailController.tabCtr, indicator: flag ? const BoxDecoration() : null, - labelColor: flag ? themeData.colorScheme.onSurface : null, + labelColor: flag ? colorScheme.onSurface : null, labelStyle: TabBarTheme.of(context).labelStyle?.copyWith(fontSize: 13) ?? const TextStyle(fontSize: 13), @@ -1387,7 +1394,7 @@ class _VideoDetailPageVState extends State decoration: BoxDecoration( border: Border( bottom: BorderSide( - color: themeData.dividerColor.withValues(alpha: 0.1), + color: theme.dividerColor.withValues(alpha: 0.1), ), ), ), @@ -1418,7 +1425,7 @@ class _VideoDetailPageVState extends State '发弹幕', style: TextStyle( fontSize: 12, - color: themeData.colorScheme.onSurfaceVariant, + color: colorScheme.onSurfaceVariant, ), ), ), @@ -1446,8 +1453,8 @@ class _VideoDetailPageVState extends State ? CustomIcons.dm_on : CustomIcons.dm_off, color: enableShowDanmaku - ? themeData.colorScheme.secondary - : themeData.colorScheme.outline, + ? colorScheme.secondary + : colorScheme.outline, ), ); }, @@ -1577,7 +1584,7 @@ class _VideoDetailPageVState extends State shape: const RoundedRectangleBorder( borderRadius: .all(.circular(6)), ), - backgroundColor: themeData + backgroundColor: theme .colorScheme .secondaryContainer .withValues(alpha: 0.8), @@ -1674,9 +1681,7 @@ class _VideoDetailPageVState extends State height: 1, indent: 12, endIndent: 12, - color: themeData.colorScheme.outline.withValues( - alpha: 0.08, - ), + color: colorScheme.outline.withValues(alpha: .08), ), ), ), @@ -1730,7 +1735,7 @@ class _VideoDetailPageVState extends State height: 54, padding: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( - color: themeData.colorScheme.secondaryContainer.withValues( + color: colorScheme.secondaryContainer.withValues( alpha: 0.95, ), borderRadius: const BorderRadius.all(Radius.circular(14)), @@ -1742,7 +1747,7 @@ class _VideoDetailPageVState extends State Text( videoDetailController.watchLaterTitle, style: TextStyle( - color: themeData.colorScheme.onSecondaryContainer, + color: colorScheme.onSecondaryContainer, fontWeight: FontWeight.bold, letterSpacing: 0.2, ), @@ -1807,7 +1812,7 @@ class _VideoDetailPageVState extends State const SizedBox(height: 8), Divider( height: 1, - color: themeData.colorScheme.outline.withValues(alpha: 0.1), + color: colorScheme.outline.withValues(alpha: 0.1), ), ], Padding( @@ -1946,7 +1951,7 @@ class _VideoDetailPageVState extends State PageUtils.showVideoBottomSheet( context, child: videoDetailController.plPlayerController.darkVideoPage - ? Theme(data: themeData, child: child) + ? Theme(data: theme, child: child) : child, ); } else { @@ -2027,7 +2032,7 @@ class _VideoDetailPageVState extends State PageUtils.showVideoBottomSheet( context, child: videoDetailController.plPlayerController.darkVideoPage - ? Theme(data: themeData, child: child) + ? Theme(data: theme, child: child) : child, ); } else { diff --git a/lib/pages/whisper_detail/widget/chat_item.dart b/lib/pages/whisper_detail/widget/chat_item.dart index 8a58848c1..b73121b12 100644 --- a/lib/pages/whisper_detail/widget/chat_item.dart +++ b/lib/pages/whisper_detail/widget/chat_item.dart @@ -13,7 +13,6 @@ import 'package:PiliPlus/grpc/bilibili/im/interfaces/v1.pb.dart' show EmotionInfo; import 'package:PiliPlus/grpc/bilibili/im/type.pb.dart' show Msg, MsgType; import 'package:PiliPlus/http/search.dart'; -import 'package:PiliPlus/models/common/badge_type.dart'; import 'package:PiliPlus/models/common/image_preview_type.dart'; import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/utils/app_scheme.dart'; @@ -412,7 +411,6 @@ class ChatItem extends StatelessWidget { return Center( child: Container( - clipBehavior: Clip.hardEdge, constraints: const BoxConstraints(maxWidth: 400.0), decoration: BoxDecoration( borderRadius: Style.mdRadius, @@ -451,15 +449,15 @@ class ChatItem extends StatelessWidget { clipBehavior: Clip.none, children: [ NetworkImgLayer( - type: ImageType.emote, width: constrains.maxWidth, height: constrains.maxWidth / Style.aspectRatio16x9, src: content['cover'], + borderRadius: const .vertical(top: Style.imgRadius), ), PBadge( left: 6, bottom: 6, - type: PBadgeType.gray, + type: .gray, text: content['times'] == 0 ? '--:--' : DurationUtils.formatDuration(content['times']), diff --git a/lib/scripts/navigation_drawer.patch b/lib/scripts/navigation_drawer.patch index c6c7de367..6513dbace 100644 --- a/lib/scripts/navigation_drawer.patch +++ b/lib/scripts/navigation_drawer.patch @@ -1,8 +1,28 @@ diff --git a/packages/flutter/lib/src/material/navigation_drawer.dart b/packages/flutter/lib/src/material/navigation_drawer.dart -index 8a16764cb0c..6b8545c3781 100644 +index 8a16764cb0c..031c2267d97 100644 --- a/packages/flutter/lib/src/material/navigation_drawer.dart +++ b/packages/flutter/lib/src/material/navigation_drawer.dart -@@ -410,7 +410,7 @@ class _NavigationDestinationBuilder extends StatelessWidget { +@@ -67,8 +67,11 @@ class NavigationDrawer extends StatelessWidget { + this.onDestinationSelected, + this.selectedIndex = 0, + this.tilePadding = const EdgeInsets.symmetric(horizontal: 12.0), ++ this.flex = 1, + }); + ++ final int flex; ++ + /// The background color of the [Material] that holds the [NavigationDrawer]'s + /// contents. + /// +@@ -192,6 +195,7 @@ class NavigationDrawer extends StatelessWidget { + children: [ + ?header, + Expanded( ++ flex: flex, + child: Material( + type: MaterialType.transparency, + child: ListView(children: wrappedChildren), +@@ -410,7 +414,7 @@ class _NavigationDestinationBuilder extends StatelessWidget { child: _NavigationDestinationSemantics( child: SizedBox( height: navigationDrawerTheme.tileHeight ?? defaults.tileHeight, diff --git a/lib/scripts/patch.ps1 b/lib/scripts/patch.ps1 index c8faeeab5..304a2d170 100644 --- a/lib/scripts/patch.ps1 +++ b/lib/scripts/patch.ps1 @@ -67,6 +67,8 @@ $ScrollPositionPatch = "lib/scripts/scroll_position.patch" # expose `_shouldIgnorePointer` $ScrollablePatch = "lib/scripts/scrollable.patch" +$TabsPatch = "lib/scripts/tabs.patch" + # TODO: remove # https://github.com/flutter/flutter/issues/124078 # https://github.com/flutter/flutter/pull/183261 @@ -101,7 +103,7 @@ $patches = @($ModalBarrierPatch, $TextSelectionPatch, $MouseCursorPatch, $ImageAnimPatch, $LayoutBuilderPatch, $NavigationDrawerPatch, $PopupMenuPatch, $FABPatch, $NullSafetySelectableRegionPatch, $SelectableRegionPatch, $EditableTextPatch, $TextFieldPatch, - $ScrollPositionPatch, $ScrollablePatch) + $ScrollPositionPatch, $ScrollablePatch, $TabsPatch) switch ($platform.ToLower()) { "android" { diff --git a/lib/scripts/tabs.patch b/lib/scripts/tabs.patch new file mode 100644 index 000000000..a60fe5113 --- /dev/null +++ b/lib/scripts/tabs.patch @@ -0,0 +1,24 @@ +diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart +index 87a611a6f60..acf799edbd3 100644 +--- a/packages/flutter/lib/src/material/tabs.dart ++++ b/packages/flutter/lib/src/material/tabs.dart +@@ -2222,8 +2222,11 @@ class TabBarView extends StatefulWidget { + this.dragStartBehavior = DragStartBehavior.start, + this.viewportFraction = 1.0, + this.clipBehavior = Clip.hardEdge, ++ this.scrollDirection = Axis.horizontal, + }); + ++ final Axis scrollDirection; ++ + /// This widget's selection and animation state. + /// + /// If [TabController] is not provided, then the value of [DefaultTabController.of] +@@ -2522,6 +2525,7 @@ class _TabBarViewState extends State { + return NotificationListener( + onNotification: _handleScrollNotification, + child: PageView( ++ scrollDirection: widget.scrollDirection, + dragStartBehavior: widget.dragStartBehavior, + clipBehavior: widget.clipBehavior, + controller: _pageController,