From 909b174a143a5245a805b6043b4336ac00ee73f8 Mon Sep 17 00:00:00 2001 From: lollipopkit Date: Wed, 8 May 2024 23:02:20 +0800 Subject: [PATCH] opt.: free copy --- lib/core/route/base.dart | 17 ++-- lib/core/route/page.dart | 2 + lib/data/res/ui.dart | 4 + lib/data/store/setting.dart | 3 + lib/l10n/app_en.arb | 2 + lib/l10n/app_zh.arb | 2 + lib/view/page/home/chat.dart | 158 ++++++++++++++------------------ lib/view/page/home/home.dart | 2 + lib/view/page/home/md_copy.dart | 71 ++++++++++++++ lib/view/page/setting.dart | 9 ++ 10 files changed, 176 insertions(+), 94 deletions(-) create mode 100644 lib/view/page/home/md_copy.dart diff --git a/lib/core/route/base.dart b/lib/core/route/base.dart index 83debf6..490174b 100644 --- a/lib/core/route/base.dart +++ b/lib/core/route/base.dart @@ -26,13 +26,16 @@ class AppRoute { final ret = middlewares?.any((e) => !e((context: context, route: this))); if (ret == true) return Future.value(null); - return Navigator.push( - context, - MaterialPageRoute( - builder: (context) => page(key: key, args: args), - settings: RouteSettings(name: path), - ), - ); + final route = Stores.setting.cupertinoRoute.fetch() + ? CupertinoPageRoute( + builder: (_) => page(key: key, args: args), + settings: RouteSettings(name: path), + ) + : MaterialPageRoute( + builder: (_) => page(key: key, args: args), + settings: RouteSettings(name: path), + ); + return Navigator.push(context, route); } } diff --git a/lib/core/route/page.dart b/lib/core/route/page.dart index dda0276..a8b07ec 100644 --- a/lib/core/route/page.dart +++ b/lib/core/route/page.dart @@ -2,8 +2,10 @@ library route; import 'dart:async'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:gpt_box/core/analysis.dart'; +import 'package:gpt_box/data/store/all.dart'; import 'package:gpt_box/view/page/about.dart'; import 'package:gpt_box/view/page/backup/view.dart'; import 'package:gpt_box/view/page/debug.dart'; diff --git a/lib/data/res/ui.dart b/lib/data/res/ui.dart index 2429632..9e4d428 100644 --- a/lib/data/res/ui.dart +++ b/lib/data/res/ui.dart @@ -82,4 +82,8 @@ abstract final class UIs { static const bgColor = DynColor(light: Colors.white, dark: Colors.black); static const textColor = DynColor(light: Colors.black, dark: Colors.white); + + /// Sizes + + static const textSize = 13.0; } diff --git a/lib/data/store/setting.dart b/lib/data/store/setting.dart index 2a8360f..d66c9c6 100644 --- a/lib/data/store/setting.dart +++ b/lib/data/store/setting.dart @@ -1,4 +1,5 @@ import 'package:gpt_box/core/store.dart'; +import 'package:gpt_box/core/util/platform/base.dart'; class SettingStore extends Store { SettingStore() : super('setting'); @@ -54,4 +55,6 @@ class SettingStore extends Store { late final replay = property('replay', false); late final countly = property('countly', true); + + late final cupertinoRoute = property('cupertinoRoute', isIOS || isMacOS); } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 45bc809..ef4045c 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -82,6 +82,7 @@ "privacyTip": "This app does not collect any private data.\nThe data this app collects is used only for the purpose of improving the user experience.", "profile": "Profile", "promptsSettingsItem": "Prompts", + "raw": "Raw", "rename": "Rename", "replay": "Replay", "res": "Resource", @@ -89,6 +90,7 @@ "restoreOpenaiTip": "Document can be found [here]({url})", "rmDuplication": "Remove duplication", "rmDuplicationFmt": "Sure to delete [{count}] items?", + "route": "Route", "save": "Save", "search": "Search", "secretKey": "Secret key", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index eb2712b..c29d406 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -82,6 +82,7 @@ "privacyTip": "此 app 不搜集任何隐私信息。所搜集的信息只用于改进 app。", "profile": "配置", "promptsSettingsItem": "提示词", + "raw": "原始", "rename": "重命名", "replay": "重放", "res": "资源", @@ -89,6 +90,7 @@ "restoreOpenaiTip": "文档可以在 [这里]({url}) 找到", "rmDuplication": "删除重复", "rmDuplicationFmt": "确定要删除 [{count}]个重复项目吗?", + "route": "路由", "save": "保存", "search": "搜索", "secretKey": "密钥", diff --git a/lib/view/page/home/chat.dart b/lib/view/page/home/chat.dart index 29c1e7a..12fb975 100644 --- a/lib/view/page/home/chat.dart +++ b/lib/view/page/home/chat.dart @@ -135,47 +135,34 @@ class _ChatPageState extends State<_ChatPage> children: [ _buildChatItemTitle(chatItems, chatItem), UIs.height13, - BlurOverlay( - key: Key(chatItem.id), - bottom: () => _buildChatItemFuncs(chatItems, chatItem).card, - popup: () { - final child = Padding( - padding: const EdgeInsets.symmetric(horizontal: 11), - child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(vertical: 7), - child: SelectableText( - chatItem.toMarkdown, - showCursor: true, - style: TextStyle( - fontSize: 14, - color: UIs.textColor.fromBool(RNode.dark.value), - ), - ), + GestureDetector( + onLongPress: () { + Navigator.of(context).push(MaterialPageRoute( + builder: (_) => _MarkdownCopyPage( + text: chatItem.toMarkdown, + actions: _buildChatItemFuncs(chatItems, chatItem), + heroTag: chatItem.id, ), - ); - return Material( - color: Colors.black, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(13), - ), - child: child, - ); + )); }, - child: ListenableBuilder( - listenable: node, - builder: (_, __) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: chatItem.content - .map((e) => switch (e.type) { - ChatContentType.audio => _buildAudio(e), - ChatContentType.image => _buildImage(e), - _ => _buildText(e), - }) - .joinWith(UIs.height13), - ); - }, + child: Hero( + tag: chatItem.id, + child: ListenableBuilder( + listenable: node, + builder: (_, __) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: chatItem.content + .map((e) => switch (e.type) { + ChatContentType.audio => _buildAudio(e), + ChatContentType.image => _buildImage(e), + _ => _buildText(e), + }) + .joinWith(UIs.height13), + ); + }, + ), ), ), UIs.height13, @@ -245,65 +232,62 @@ class _ChatPageState extends State<_ChatPage> ); } - Widget _buildChatItemFuncs( + List _buildChatItemFuncs( List chatItems, ChatHistoryItem chatItem, ) { final replayEnabled = chatItem.role == ChatRole.user && Stores.setting.replay.fetch(); - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (replayEnabled) - ListenableBuilder( - listenable: _sendBtnRN, - builder: (_, __) { - /// TODO: Can't replay image message. - final isImgChat = - chatItem.content.any((e) => e.type == ChatContentType.image); - if (isImgChat) return UIs.placeholder; - final isWorking = _chatStreamSubs.containsKey(_curChatId); - if (isWorking) return UIs.placeholder; - return IconButton( - onPressed: () => _onTapReplay( - context, - _curChatId, - chatItem, - ), - icon: const Icon(Icons.refresh, size: 17), - ); - }, - ), - if (replayEnabled) - IconButton( - onPressed: () => _onTapEditMsg(context, chatItem), - icon: const Icon(Icons.edit, size: 17), - ), - IconButton( - onPressed: () async { - BlurOverlay.close?.call(); - final idx = chatItems.indexOf(chatItem) + 1; - final result = await context.showRoundDialog( - title: l10n.attention, - child: Text( - l10n.delFmt('${chatItem.role.localized}#$idx', l10n.chat), + return [ + if (replayEnabled) + ListenableBuilder( + listenable: _sendBtnRN, + builder: (_, __) { + /// TODO: Can't replay image message. + final isImgChat = + chatItem.content.any((e) => e.type == ChatContentType.image); + if (isImgChat) return UIs.placeholder; + final isWorking = _chatStreamSubs.containsKey(_curChatId); + if (isWorking) return UIs.placeholder; + return IconButton( + onPressed: () => _onTapReplay( + context, + _curChatId, + chatItem, ), - actions: Btns.oks(onTap: () => context.pop(true), red: true), + icon: const Icon(Icons.refresh), ); - if (result != true) return; - chatItems.remove(chatItem); - _storeChat(_curChatId, context); - _historyRNMap[_curChatId]?.build(); - _chatRN.build(); }, - icon: const Icon(Icons.delete, size: 17), ), + if (replayEnabled) IconButton( - onPressed: () => _onCopy(chatItem.toMarkdown), - icon: const Icon(Icons.copy, size: 15), + onPressed: () => _onTapEditMsg(context, chatItem), + icon: const Icon(Icons.edit), ), - ], - ); + IconButton( + onPressed: () async { + BlurOverlay.close?.call(); + final idx = chatItems.indexOf(chatItem) + 1; + final result = await context.showRoundDialog( + title: l10n.attention, + child: Text( + l10n.delFmt('${chatItem.role.localized}#$idx', l10n.chat), + ), + actions: Btns.oks(onTap: () => context.pop(true), red: true), + ); + if (result != true) return; + chatItems.remove(chatItem); + _storeChat(_curChatId, context); + _historyRNMap[_curChatId]?.build(); + _chatRN.build(); + }, + icon: const Icon(Icons.delete), + ), + IconButton( + onPressed: () => _onCopy(chatItem.toMarkdown), + icon: const Icon(MingCute.copy_2_fill), + ), + ]; } void _onCopy(String content) { diff --git a/lib/view/page/home/home.dart b/lib/view/page/home/home.dart index 3981002..0c1fcf5 100644 --- a/lib/view/page/home/home.dart +++ b/lib/view/page/home/home.dart @@ -8,6 +8,7 @@ import 'package:dart_openai/dart_openai.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; //import 'package:flutter_tiktoken/flutter_tiktoken.dart'; import 'package:gpt_box/core/build_mode.dart'; import 'package:gpt_box/core/ext/chat_history.dart'; @@ -71,6 +72,7 @@ part 'appbar.dart'; part 'bottom.dart'; part 'url_scheme.dart'; part 'req.dart'; +part 'md_copy.dart'; class HomePage extends StatefulWidget { const HomePage({super.key}); diff --git a/lib/view/page/home/md_copy.dart b/lib/view/page/home/md_copy.dart new file mode 100644 index 0000000..eb4207d --- /dev/null +++ b/lib/view/page/home/md_copy.dart @@ -0,0 +1,71 @@ +part of 'home.dart'; + +final class _MarkdownCopyPage extends StatelessWidget { + final String text; + final List actions; + final String heroTag; + + const _MarkdownCopyPage({ + required this.text, + required this.actions, + required this.heroTag, + }); + +// @override +// State<_MarkdownCopyPage> createState() => _MarkdownCopyPageState(); +// } + +// final class _MarkdownCopyPageState extends State<_MarkdownCopyPage> +// with SingleTickerProviderStateMixin { +// late final _animeCtrl = AnimationController( +// vsync: this, +// duration: Durations.medium1, +// ); + +// @override +// void initState() { +// super.initState(); +// _animeCtrl.forward(); +// } + + // @override + // Widget build(BuildContext context) { + // return PopScope( + // canPop: true, + // onPopInvoked: (_) async { + // await _animeCtrl.reverse(); + // }, + // child: _buildBody(), + // ); + // } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: CustomAppBar( + title: Text(l10n.raw), + centerTitle: false, + actions: actions, + ), + body: _buildBody(), + ); + } + + Widget _buildBody() { + return Hero( + tag: heroTag, + child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(vertical: 7, horizontal: 13), + child: SelectableText( + text, + autofocus: true, + showCursor: true, + style: TextStyle( + fontSize: 14, + color: UIs.textColor.fromBool(RNode.dark.value), + ), + ), + ), + ); + } +} diff --git a/lib/view/page/setting.dart b/lib/view/page/setting.dart index 676edfe..3798ffc 100644 --- a/lib/view/page/setting.dart +++ b/lib/view/page/setting.dart @@ -705,6 +705,7 @@ class _SettingPageState extends State { title: Text(l10n.more), children: [ _buildCountly(), + _buildCupertinoRoute(), ], ); } @@ -716,4 +717,12 @@ class _SettingPageState extends State { trailing: StoreSwitch(prop: _store.countly), ); } + + Widget _buildCupertinoRoute() { + return ListTile( + leading: const Icon(MingCute.route_fill), + title: Text('Cupertino ${l10n.route}'), + trailing: StoreSwitch(prop: _store.cupertinoRoute), + ); + } }