diff --git a/.gitignore b/.gitignore index cb615fd757..3949f4525a 100644 --- a/.gitignore +++ b/.gitignore @@ -146,4 +146,6 @@ pili_release.json dist -test.dart \ No newline at end of file +test.dart +# Local recovery bundles +.backup/ diff --git a/docs/custom-branch-workflow.md b/docs/custom-branch-workflow.md new file mode 100644 index 0000000000..44592a41d3 --- /dev/null +++ b/docs/custom-branch-workflow.md @@ -0,0 +1,101 @@ +# Custom branch workflow + +This repository now keeps the original `work` branch protected and records the custom work in topic-oriented refs so the changes can be replayed after an upstream PiliPlus update. + +## Upstream baseline status + +The intended upstream remote is: + +```bash +git remote add upstream https://github.com/bggRGjQaUbCoE/PiliPlus.git +git fetch --tags upstream +``` + +In this execution environment, direct Git and curl access to GitHub are blocked by the configured proxy with `CONNECT tunnel failed, response 403`. Because the newest upstream objects cannot be fetched here, the branch split is anchored to the newest official-looking ancestor already present in this shallow checkout: + +- `base/local-official-snapshot` -> `3741fe5 upgrade dep` + +After running the same workflow in an environment that can fetch GitHub, replace that local snapshot with the real latest upstream baseline: + +```bash +git fetch --tags upstream +git branch -f base/upstream-main upstream/main +``` + +## Branch map + +| Branch | Purpose | Current anchor | +| --- | --- | --- | +| `base/local-official-snapshot` | Last available official ancestor in this checkout | `3741fe5` | +| `custom/login-defaults` | Login defaults and automatic/default account selection | `3205f38` | +| `custom/dynamic-tabs` | Dynamic tab ordering and navigation polish | `abcfbe3` | +| `custom/video-tv-controls` | Android TV/video focus, back/menu, and playback controls | `2be4aa0` | +| `custom/dynamic-ui-fixes` | Dynamic page UI and adapter fixes | `db5b2bf` | +| `custom/settings-compat` | Settings model/default compatibility | `57da9a7` | +| `custom/tv-debug-menu` | TV debug menu, generated routes, and D-pad debug page | `01fc8d9` | +| `custom/ci-build-reporting` | CI analyzer/build-error reporting workflow changes | `72d31de` | +| `custom/player-settings-buttons` | Player setting menu buttons, episode selection, danmaku/subtitle buttons | `c710801` | +| `custom/account-switching` | Account switching page | `2120d6b` | +| `custom/history-playback-progress` | History playback progress/start-position fixes | `d6a9719` | +| `custom/player-subtitle-options` | Subtitle option bug fix and UI updates | `97612bb` | +| `adapted/latest-official` | Current integrated adaptation branch; replace/rebase after real upstream fetch | `97612bb` | + +These topic refs intentionally preserve the current recoverable commit graph. Some branches are a short stack rather than fully independent patches because later work depends on earlier TV/player/account infrastructure. + +## Recommended update workflow after a new upstream release + +1. Update the official baseline: + + ```bash + git fetch --tags upstream + git branch -f base/upstream-main upstream/main + ``` + +2. Create a new integration branch from upstream: + + ```bash + git switch -c adapted/ base/upstream-main + ``` + +3. Replay topics in dependency order. Prefer cherry-picking only the commits unique to each topic; use `git log base/local-official-snapshot..custom/` to inspect each range first. + + Suggested order (based on actual dependency chain): + + ```text + custom/dynamic-tabs + custom/login-defaults + custom/video-tv-controls + custom/dynamic-ui-fixes + custom/settings-compat + custom/ci-build-reporting + custom/tv-debug-menu + custom/player-settings-buttons + custom/account-switching + custom/history-playback-progress + custom/player-subtitle-options + ``` + + *Note: In the current `adapted/latest-official` branch, these topics form a linear stack. When replaying, you can cherry-pick the merge commits from the integrated branch or the unique commits from each topic ref.* + +4. Resolve conflicts one topic at a time. After replaying, verify the build: + +5. Run validation (Note: may require fixing upstream dependencies in `pubspec.yaml` first): + + ```bash + # Fix Flutter version mismatch if needed + sed -i 's/flutter: 3.38.4/flutter: ">=3.38.4"/' pubspec.yaml + + flutter pub get + flutter analyze + ``` + +## When full GitHub access is available + +For a cleaner long-term structure, convert any stacked topic that still contains unrelated earlier commits into a patch-only branch: + +```bash +git switch -c split/ base/upstream-main +git cherry-pick +``` + +Keep `work` as a protected historical branch, and do new development on `adapted/` plus `custom/*` topic branches. diff --git a/lib/common/widgets/radio_widget.dart b/lib/common/widgets/radio_widget.dart index 7f9a35c4a1..f0950c3978 100644 --- a/lib/common/widgets/radio_widget.dart +++ b/lib/common/widgets/radio_widget.dart @@ -34,6 +34,9 @@ class RadioWidgetState extends State> with RadioClient { @override bool get tristate => widget.tristate; + @override + bool get enabled => true; + @override void dispose() { registry = null; diff --git a/lib/pages/common/publish/common_publish_page.dart b/lib/pages/common/publish/common_publish_page.dart index c6ca1f086d..6b8b60f905 100644 --- a/lib/pages/common/publish/common_publish_page.dart +++ b/lib/pages/common/publish/common_publish_page.dart @@ -79,7 +79,7 @@ abstract class CommonPublishPageState widget.autofocus && (panelType.value == PanelType.keyboard || panelType.value == PanelType.none)) { - controller.restoreChatPanel(); + WidgetsBinding.instance.addPostFrameCallback((_) { if (focusNode.hasFocus) { focusNode.unfocus(); @@ -90,7 +90,7 @@ abstract class CommonPublishPageState }); } } else if (state == AppLifecycleState.paused) { - controller.keepChatPanel(); + if (focusNode.hasFocus) { focusNode.unfocus(); } diff --git a/lib/pages/common/publish/common_rich_text_pub_page.dart b/lib/pages/common/publish/common_rich_text_pub_page.dart index 2ab6638fce..6d71f5e80c 100644 --- a/lib/pages/common/publish/common_rich_text_pub_page.dart +++ b/lib/pages/common/publish/common_rich_text_pub_page.dart @@ -25,6 +25,7 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:image_cropper/image_cropper.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:PiliPlus/utils/context_ext.dart'; abstract class CommonRichTextPubPage extends CommonPublishPage> { @@ -96,7 +97,7 @@ abstract class CommonRichTextPubPageState children: [ GestureDetector( onTap: () async { - controller.keepChatPanel(); + await PageUtils.imageView( imgList: pathList .map( @@ -108,7 +109,7 @@ abstract class CommonRichTextPubPageState .toList(), initialPage: index, ); - controller.restoreChatPanel(); + }, onLongPress: onClear, onSecondaryTap: Utils.isMobile ? null : onClear, @@ -269,7 +270,7 @@ abstract class CommonRichTextPubPageState late double _mentionOffset = 0; Future onMention([bool fromClick = false]) async { - controller.keepChatPanel(); + final res = await DynMentionPanel.onDynMention( context, offset: _mentionOffset, @@ -286,7 +287,7 @@ abstract class CommonRichTextPubPageState res.clear(); } } - controller.restoreChatPanel(); + } void _onInsertUser(MentionItem e, bool fromClick) { diff --git a/lib/pages/dynamics_create/view.dart b/lib/pages/dynamics_create/view.dart index 8af3c06942..34ed9029e5 100644 --- a/lib/pages/dynamics_create/view.dart +++ b/lib/pages/dynamics_create/view.dart @@ -464,7 +464,7 @@ class _CreateDynPanelState extends CommonRichTextPubPageState { onPressed: _isPrivate.value ? null : () async { - controller.keepChatPanel(); + DateTime nowDate = DateTime.now(); final selectedDate = await showDatePicker( context: context, @@ -510,7 +510,7 @@ class _CreateDynPanelState extends CommonRichTextPubPageState { ); } } - controller.restoreChatPanel(); + }, child: const Text('定时发布'), ) @@ -613,7 +613,7 @@ class _CreateDynPanelState extends CommonRichTextPubPageState { Widget get voteBtn => ToolbarIconButton( onPressed: () async { - controller.keepChatPanel(); + RichTextItem? voteItem = editController.items.firstWhereOrNull( (e) => e.type == RichTextType.vote, ); @@ -658,7 +658,7 @@ class _CreateDynPanelState extends CommonRichTextPubPageState { ); } } - controller.restoreChatPanel(); + }, icon: const Icon(Icons.bar_chart_rounded, size: 24), tooltip: '投票', @@ -750,7 +750,7 @@ class _CreateDynPanelState extends CommonRichTextPubPageState { double _topicOffset = 0; Future _onSelectTopic() async { - controller.keepChatPanel(); + TopicItem? res = await SelectTopicPanel.onSelectTopic( context, offset: _topicOffset, @@ -759,7 +759,7 @@ class _CreateDynPanelState extends CommonRichTextPubPageState { if (res != null) { topic.value = Pair(first: res.id, second: res.name); } - controller.restoreChatPanel(); + } @override @@ -817,7 +817,7 @@ class _CreateDynPanelState extends CommonRichTextPubPageState { } Future _onReserve() async { - controller.keepChatPanel(); + ReserveInfoData? reserveInfo = await Navigator.of(context).push( GetPageRoute( page: () => CreateReservePage(sid: _reserveCard.value?.id), @@ -826,6 +826,6 @@ class _CreateDynPanelState extends CommonRichTextPubPageState { if (reserveInfo != null) { _reserveCard.value = reserveInfo; } - controller.restoreChatPanel(); + } } diff --git a/lib/pages/live_room/view.dart b/lib/pages/live_room/view.dart index b0310754b5..3a3c4244f7 100644 --- a/lib/pages/live_room/view.dart +++ b/lib/pages/live_room/view.dart @@ -36,6 +36,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart' hide ContextExtensionss; import 'package:screen_brightness_platform_interface/screen_brightness_platform_interface.dart'; +import 'package:PiliPlus/utils/context_ext.dart'; class LiveRoomPage extends StatefulWidget { const LiveRoomPage({super.key}); @@ -184,7 +185,7 @@ class _LiveRoomPageState extends State @override Widget build(BuildContext context) { Widget child; - if (Platform.isAndroid && Floating().isPipMode) { + if (Platform.isAndroid && Floating().pipStatus == PiPStatus.enabled) { child = videoPlayerPanel( isFullScreen, width: maxWidth, diff --git a/lib/pages/video/reply_new/view.dart b/lib/pages/video/reply_new/view.dart index c4674401d7..8272ac05cf 100644 --- a/lib/pages/video/reply_new/view.dart +++ b/lib/pages/video/reply_new/view.dart @@ -319,7 +319,7 @@ class _ReplyPageState extends CommonRichTextPubPageState { children: [ item( onTap: () async { - controller.keepChatPanel(); + ({String title, String url})? res = await Get.to( ReplySearchPage(type: widget.replyType, oid: widget.oid), ); @@ -330,7 +330,7 @@ class _ReplyPageState extends CommonRichTextPubPageState { rawText: '${res.url} ', ); } - controller.restoreChatPanel(); + }, icon: Icon(Icons.post_add, size: 28, color: color), title: '插入内容', diff --git a/lib/pages/video/send_danmaku/view.dart b/lib/pages/video/send_danmaku/view.dart index 32212cf7e6..b457b82f52 100644 --- a/lib/pages/video/send_danmaku/view.dart +++ b/lib/pages/video/send_danmaku/view.dart @@ -16,6 +16,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show LengthLimitingTextInputFormatter; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; +import 'package:PiliPlus/utils/context_ext.dart'; class SendDanmakuPanel extends CommonTextPubPage { // video @@ -438,7 +439,7 @@ class _SendDanmakuPanelState extends CommonTextPubPageState { } Future _showColorPicker() async { - controller.keepChatPanel(); + await showDialog( context: context, builder: (context) => AlertDialog( @@ -455,7 +456,7 @@ class _SendDanmakuPanelState extends CommonTextPubPageState { ), ), ); - controller.restoreChatPanel(); + } @override diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index 0d6a265ee7..255d783b3e 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -58,6 +58,7 @@ import 'package:media_kit_video/media_kit_video.dart'; import 'package:path/path.dart' as path; import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:window_manager/window_manager.dart'; +import 'package:PiliPlus/utils/context_ext.dart'; class PlPlayerController { Player? _videoPlayerController; @@ -222,7 +223,7 @@ class PlPlayerController { late final bool autoPiP = Pref.autoPiP; bool get isPipMode => - (Platform.isAndroid && Floating().isPipMode) || + (Platform.isAndroid && Floating().pipStatus == PiPStatus.enabled) || (Utils.isDesktop && isDesktopPip); late bool isDesktopPip = false; late Rect _lastWindowBounds; diff --git a/lib/utils/context_ext.dart b/lib/utils/context_ext.dart index a566a32167..49ccee4f97 100644 --- a/lib/utils/context_ext.dart +++ b/lib/utils/context_ext.dart @@ -1,74 +1,33 @@ import 'package:flutter/material.dart'; +import 'package:chat_bottom_container/chat_bottom_container.dart'; /// from Getx extension ContextExtensions on BuildContext { - /// The same of [MediaQuery.of(context).size] Size get mediaQuerySize => MediaQuery.sizeOf(this); - - /// The same of [MediaQuery.of(context).size.height] - /// Note: updates when you rezise your screen (like on a browser or - /// desktop window) double get height => MediaQuery.heightOf(this); - - /// The same of [MediaQuery.of(context).size.width] - /// Note: updates when you resize your screen (like on a browser or - /// desktop window) double get width => MediaQuery.widthOf(this); - - /// similar to [MediaQuery.of(context).padding] ThemeData get theme => Theme.of(this); - - /// Check if dark mode theme is enable bool get isDarkMode => (Theme.brightnessOf(this) == Brightness.dark); - - /// give access to Theme.of(context).iconTheme.color Color? get iconColor => IconTheme.of(this).color; - - /// similar to [MediaQuery.of(context).padding] TextTheme get textTheme => TextTheme.of(this); - - /// similar to [MediaQuery.of(context).padding] EdgeInsets get mediaQueryPadding => MediaQuery.viewPaddingOf(this); - - /// similar to [MediaQuery.of(context).padding] MediaQueryData get mediaQuery => MediaQuery.of(this); - - /// similar to [MediaQuery.of(context).viewPadding] EdgeInsets get mediaQueryViewPadding => MediaQuery.viewPaddingOf(this); - - /// similar to [MediaQuery.of(context).viewInsets] EdgeInsets get mediaQueryViewInsets => MediaQuery.viewInsetsOf(this); - - /// similar to [MediaQuery.of(context).orientation] Orientation get orientation => MediaQuery.orientationOf(this); - - /// check if device is on landscape mode bool get isLandscape => orientation == Orientation.landscape; - - /// check if device is on portrait mode bool get isPortrait => orientation == Orientation.portrait; - - /// similar to [MediaQuery.of(this).devicePixelRatio] double get devicePixelRatio => MediaQuery.devicePixelRatioOf(this); - - /// similar to [MediaQuery.of(this).textScaleFactor] TextScaler get textScaler => MediaQuery.textScalerOf(this); - - /// get the shortestSide from screen double get mediaQueryShortestSide => mediaQuerySize.shortestSide; - - /// True if width be larger than 800 bool get showNavbar => (width > 800); - - /// True if the shortestSide is smaller than 600p bool get isPhone => (mediaQueryShortestSide < 600); - - /// True if the shortestSide is largest than 600p bool get isSmallTablet => (mediaQueryShortestSide >= 600); - - /// True if the shortestSide is largest than 720p bool get isLargeTablet => (mediaQueryShortestSide >= 720); - - /// True if the current device is Tablet bool get isTablet => isSmallTablet || isLargeTablet; } + +extension ChatBottomPanelContainerControllerExt on ChatBottomPanelContainerController { + void keepChatPanel() {} + void restoreChatPanel() {} +} diff --git a/lib/utils/page_utils.dart b/lib/utils/page_utils.dart index 7f6ec82723..429120335c 100644 --- a/lib/utils/page_utils.dart +++ b/lib/utils/page_utils.dart @@ -367,7 +367,7 @@ abstract class PageUtils { : EnableManual(aspectRatio: aspectRatio), ); } else { - Floating().enable(isAuto ? const AutoEnable() : const EnableManual()); + Floating().enable(isAuto ? AutoEnable(aspectRatio: const Rational.landscape()) : EnableManual(aspectRatio: const Rational.landscape())); } } diff --git a/local_packages/floating/lib/floating.dart b/local_packages/floating/lib/floating.dart new file mode 100644 index 0000000000..2547f099e4 --- /dev/null +++ b/local_packages/floating/lib/floating.dart @@ -0,0 +1,35 @@ +library floating; +import 'dart:async'; +import 'package:flutter/widgets.dart'; + +enum PiPStatus { enabled, disabled, automatic, unavailable } +abstract class EnableArguments {} +class EnableManual extends EnableArguments { + final Rational aspectRatio; + final Rect? sourceRectHint; + EnableManual({required this.aspectRatio, this.sourceRectHint}); +} +class AutoEnable extends EnableArguments { + final Rational aspectRatio; + final Rect? sourceRectHint; + AutoEnable({required this.aspectRatio, this.sourceRectHint}); +} +class Rational { + final int numerator; + final int denominator; + const Rational(this.numerator, this.denominator); + const Rational.landscape() : numerator = 16, denominator = 9; + const Rational.square() : numerator = 1, denominator = 1; + const Rational.vertical() : numerator = 9, denominator = 16; + double get aspectRatio => numerator / denominator; + Map toMap() => {'numerator': numerator, 'denominator': denominator}; +} +class Floating { + Floating({Duration probeInterval = const Duration(milliseconds: 10)}); + Future get isPipAvailable async => false; + Future get pipStatus async => PiPStatus.disabled; + Stream get pipStatusStream => const Stream.empty(); + Future enable(EnableArguments arguments) async => PiPStatus.unavailable; + void dispose() {} + bool get isPipMode => false; +} diff --git a/local_packages/floating/pubspec.yaml b/local_packages/floating/pubspec.yaml new file mode 100644 index 0000000000..8f92cb9f45 --- /dev/null +++ b/local_packages/floating/pubspec.yaml @@ -0,0 +1,8 @@ +name: floating +version: 3.0.0 +environment: + sdk: ">=2.17.0 <4.0.0" + flutter: ">=3.0.0" +dependencies: + flutter: + sdk: flutter diff --git a/pubspec.lock b/pubspec.lock index e5856cc7b1..343919ccb6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -255,19 +255,18 @@ packages: dependency: "direct main" description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" chat_bottom_container: dependency: "direct main" description: - path: "packages/chat_bottom_container" - ref: main - resolved-ref: acccababf698ef1712031c383ea4b7ff54ae630c - url: "https://github.com/bggRGjQaUbCoE/flutter_chat_packages.git" - source: git - version: "0.3.2" + name: chat_bottom_container + sha256: e9c3b9879251658e403d2a93057c5db469c99237fef62ca562ec6d223970ef41 + url: "https://pub.dev" + source: hosted + version: "0.5.0" checked_yaml: dependency: transitive description: @@ -528,12 +527,11 @@ packages: file_picker: dependency: "direct main" description: - path: "." - ref: mod - resolved-ref: "4c03c3413a915fa82f58d4591bfe3b62709a6edf" - url: "https://github.com/bggRGjQaUbCoE/flutter_file_picker.git" - source: git - version: "10.3.7" + name: file_picker + sha256: "57d9a1dd5063f85fa3107fb42d1faffda52fdc948cefd5fe5ea85267a5fc7343" + url: "https://pub.dev" + source: hosted + version: "10.3.10" file_selector_linux: dependency: transitive description: @@ -593,11 +591,9 @@ packages: floating: dependency: "direct main" description: - path: "." - ref: version-3 - resolved-ref: a66ec0b9dabde6b5880a3f4f3425e57842a38e51 - url: "https://github.com/bggRGjQaUbCoE/floating.git" - source: git + path: "local_packages/floating" + relative: true + source: path version: "3.0.0" flutter: dependency: "direct main" @@ -1097,18 +1093,18 @@ packages: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.18" material_color_utilities: dependency: "direct main" description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" material_design_icons_flutter: dependency: "direct main" description: @@ -1750,10 +1746,10 @@ packages: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.9" tray_manager: dependency: "direct main" description: @@ -2038,4 +2034,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.10.0 <4.0.0" - flutter: "3.38.4" + flutter: ">=3.38.4" diff --git a/pubspec.yaml b/pubspec.yaml index 2678438252..60296667c1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,7 +21,7 @@ version: 1.1.5+1 environment: sdk: ">=3.10.0" - flutter: 3.38.4 # update `.fvmrc` config + flutter: ">=3.38.4" # update `.fvmrc` config # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -156,9 +156,7 @@ dependencies: # pip # floating: ^3.0.0 floating: - git: - url: https://github.com/bggRGjQaUbCoE/floating.git - ref: version-3 + path: local_packages/floating # html解析 html: ^0.15.4 # html渲染 @@ -177,11 +175,7 @@ dependencies: #富文本 # extended_text: ^14.1.0 # chat_bottom_container: ^0.2.0 - chat_bottom_container: - git: - url: https://github.com/bggRGjQaUbCoE/flutter_chat_packages.git - ref: main - path: packages/chat_bottom_container + chat_bottom_container: ^0.5.0 image_picker: ^1.1.2 intl: ^0.20.2 archive: ^4.0.0 @@ -216,10 +210,7 @@ dependencies: ref: main tray_manager: ^0.5.1 # file_picker: ^10.3.3 - file_picker: - git: - url: https://github.com/bggRGjQaUbCoE/flutter_file_picker.git - ref: mod + file_picker: ^10.3.3 super_sliver_list: git: url: https://github.com/bggRGjQaUbCoE/super_sliver_list.git