diff --git a/lib/app/mobile/view/card/mobile_card_page.dart b/lib/app/mobile/view/card/mobile_card_page.dart index fe8ed2b..18fbd40 100644 --- a/lib/app/mobile/view/card/mobile_card_page.dart +++ b/lib/app/mobile/view/card/mobile_card_page.dart @@ -12,39 +12,27 @@ import 'package:wenznote/editor/widget/toggle_item.dart'; import '../user/mobile_user_icon.dart'; import 'mobile_card_page_controller.dart'; -class MobileCardPage extends MvcView { - const MobileCardPage({super.key, required super.controller}); +class _AppBar extends StatelessWidget { + final MobileCardPageController controller; + final appbarHeight = 48.0; + final searchBarHeight = 56.0; + + const _AppBar({Key? key, required this.controller}) : super(key: key); @override Widget build(BuildContext context) { - return Container( - color: MobileTheme.of(context).mobileBgColor, - child: CustomScrollView( - controller: controller.scrollController, - physics: const BouncingScrollPhysics(), - slivers: [ - buildSliverAppBar(context), - buildFilterWidget(context), - buildContent(context), - ], - ), - ); - } - - SliverAppBar buildSliverAppBar(BuildContext context) { - const appbarHeight = 48.0; - const searchBarHeight = 56.0; return SliverAppBar( surfaceTintColor: Colors.transparent, title: Text( "卡片", - style: TextStyle(color: MobileTheme.of(context).fontColor,fontSize: 18), + style: + TextStyle(color: MobileTheme.of(context).fontColor, fontSize: 18), ), leading: IconButton( onPressed: () { Scaffold.of(context).openDrawer(); }, - icon: MobileUserIcon(), + icon: const MobileUserIcon(), ), actions: [ IconButton( @@ -84,7 +72,9 @@ class MobileCardPage extends MvcView { crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded( - child: buildSearchEdit(context), + child: _SearchEditor( + controller: controller, + ), ), ], ), @@ -96,7 +86,75 @@ class MobileCardPage extends MvcView { ); } - Widget buildSearchEdit(BuildContext context) { + void showCreateCardDialog(BuildContext context) { + showCreateDialog(context, "创建卡片集", "请输入卡片集合名称"); + } + + void showCreateDialog( + BuildContext context, String title, String placeHolder) { + var textController = fluent.TextEditingController(text: ""); + void doCreate() { + var name = textController.text.trim(); + if (name.isEmpty) { + return; + } + controller.createCardSet(name); + } + + showDialog( + useSafeArea: true, + context: context, + builder: (context) { + return Padding( + padding: MediaQuery.of(context).viewInsets, + child: fluent.ContentDialog( + title: fluent.Text(title), + constraints: BoxConstraints(maxWidth: 300), + content: fluent.Column( + mainAxisSize: MainAxisSize.min, + children: [ + fluent.Container( + margin: const EdgeInsets.only(bottom: 10, top: 10), + child: fluent.TextBox( + placeholder: placeHolder, + controller: textController, + autofocus: true, + onSubmitted: (e) { + Navigator.pop(context, '确定'); + doCreate(); + }, + ), + ), + ], + ), + actions: [ + fluent.Button( + child: const Text('取消'), + onPressed: () { + Navigator.pop(context, '取消'); + // Delete file here + }, + ), + fluent.FilledButton( + onPressed: () { + Navigator.pop(context, '确定'); + doCreate(); + }, + child: const Text("确定")), + ], + ), + ); + }); + } +} + +class _SearchEditor extends StatelessWidget { + final MobileCardPageController controller; + + const _SearchEditor({Key? key, required this.controller}) : super(key: key); + + @override + Widget build(BuildContext context) { return Obx( () => TextField( cursorColor: MobileTheme.of(context).cursorColor, @@ -143,8 +201,15 @@ class MobileCardPage extends MvcView { ), ); } +} + +class _FilterWidget extends StatelessWidget { + final MobileCardPageController controller; + + const _FilterWidget({Key? key, required this.controller}) : super(key: key); - Widget buildFilterWidget(BuildContext context) { + @override + Widget build(BuildContext context) { return SliverPersistentHeader( pinned: true, delegate: StickyDelegate( @@ -248,10 +313,17 @@ class MobileCardPage extends MvcView { )), ); } +} - SliverPadding buildContent(BuildContext context) { +class _ContentWidget extends StatelessWidget { + final MobileCardPageController controller; + + const _ContentWidget({Key? key, required this.controller}) : super(key: key); + + @override + Widget build(BuildContext context) { return SliverPadding( - padding: EdgeInsets.only( + padding: const EdgeInsets.only( left: 16, right: 16, bottom: 16, @@ -262,11 +334,8 @@ class MobileCardPage extends MvcView { return SliverList( delegate: SliverChildBuilderDelegate( (context, index) { - return buildCardItem( - context, - modelList, - index, - ); + return _CardItemWidget( + controller: controller, cardModel: modelList[index]); }, childCount: modelList.length, ), @@ -275,181 +344,124 @@ class MobileCardPage extends MvcView { ), ); } +} - Widget buildCardItem( - BuildContext context, - List modelList, - int index, - ) { - return Obx(() { - var cardSetItem = modelList[index]; - var color = cardSetItem.color.withOpacity(0.8); - if (Theme.of(context).brightness == Brightness.dark) { - var trans = 0.5; - var blue = (color.blue * trans).toInt(); - var red = (color.red * trans).toInt(); - var green = (color.green * trans).toInt(); - color = Color.fromARGB(color.alpha, red, green, blue); - } - return ToggleItem( - onTap: (context) { - controller.openCardSet(context, cardSetItem); - }, - onSecondaryTap: (context, event) { - showCardSetItemMenu(context, event.globalPosition, cardSetItem); - }, - onLongPress: (context, event) { - showCardSetItemMenu(context, event.globalPosition, cardSetItem); - }, - itemBuilder: - (BuildContext context, bool checked, bool hover, bool pressed) { - return Container( - height: 240, - margin: EdgeInsets.symmetric( - vertical: 5, - ), - decoration: BoxDecoration( - color: hover ? color.withOpacity(1) : color, - borderRadius: BorderRadius.circular(10), - ), - child: Stack( - children: [ - Align( - alignment: Alignment.topLeft, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - "${cardSetItem.title}", - style: TextStyle( - fontSize: 16, - fontFamily: "微软雅黑", - ), +class _CardItemWidget extends StatelessWidget { + final MobileCardPageController controller; + final MobileCardModel cardModel; + + const _CardItemWidget( + {Key? key, required this.controller, required this.cardModel}) + : super(key: key); + + @override + Widget build(BuildContext context) { + var cardSetItem = cardModel; + var color = cardSetItem.color.withOpacity(0.8); + if (Theme.of(context).brightness == Brightness.dark) { + var trans = 0.5; + var blue = (color.blue * trans).toInt(); + var red = (color.red * trans).toInt(); + var green = (color.green * trans).toInt(); + color = Color.fromARGB(color.alpha, red, green, blue); + } + return ToggleItem( + onTap: (context) { + controller.openCardSet(context, cardSetItem); + }, + onSecondaryTap: (context, event) { + showCardSetItemMenu(context, event.globalPosition, cardSetItem); + }, + onLongPress: (context, event) { + showCardSetItemMenu(context, event.globalPosition, cardSetItem); + }, + itemBuilder: + (BuildContext context, bool checked, bool hover, bool pressed) { + return Container( + height: 240, + margin: EdgeInsets.symmetric( + vertical: 5, + ), + decoration: BoxDecoration( + color: hover ? color.withOpacity(1) : color, + borderRadius: BorderRadius.circular(10), + ), + child: Stack( + children: [ + Align( + alignment: Alignment.topLeft, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + "${cardSetItem.title}", + style: TextStyle( + fontSize: 16, + fontFamily: "微软雅黑", ), ), ), - Align( - alignment: Alignment.bottomLeft, - child: Container( - padding: const EdgeInsets.all(8.0), - child: Row( - children: [ - Text( - "今日学习", - style: TextStyle( - fontFamily: "微软雅黑", - fontSize: 12, - ), - ), - SizedBox( - width: 4, - ), - Text( - "${cardSetItem.todayStudyCount}", - style: TextStyle( - color: Colors.redAccent, - fontFamily: "微软雅黑", - fontSize: 12, - ), - ), - Text( - "/${cardSetItem.todayStudyQueueCount}", - style: TextStyle( - fontFamily: "微软雅黑", - fontSize: 12, - ), + ), + Align( + alignment: Alignment.bottomLeft, + child: Container( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + Text( + "今日学习", + style: TextStyle( + fontFamily: "微软雅黑", + fontSize: 12, ), - SizedBox( - width: 20, + ), + SizedBox( + width: 4, + ), + Text( + "${cardSetItem.todayStudyCount}", + style: TextStyle( + color: Colors.redAccent, + fontFamily: "微软雅黑", + fontSize: 12, ), - Text( - "待复习", - style: TextStyle( - fontFamily: "微软雅黑", - fontSize: 12, - ), + ), + Text( + "/${cardSetItem.todayStudyQueueCount}", + style: TextStyle( + fontFamily: "微软雅黑", + fontSize: 12, ), - SizedBox( - width: 4, + ), + SizedBox( + width: 20, + ), + Text( + "待复习", + style: TextStyle( + fontFamily: "微软雅黑", + fontSize: 12, ), - Text( - "${cardSetItem.reviewCount}", - style: TextStyle( - color: Colors.green, - fontFamily: "微软雅黑", - fontSize: 12, - ), + ), + SizedBox( + width: 4, + ), + Text( + "${cardSetItem.reviewCount}", + style: TextStyle( + color: Colors.green, + fontFamily: "微软雅黑", + fontSize: 12, ), - ], - ), + ), + ], ), ), - ], - ), - ); - }, - ); - }); - } - - void showCreateCardDialog(BuildContext context) { - showCreateDialog(context, "创建卡片集", "请输入卡片集合名称"); - } - - void showCreateDialog( - BuildContext context, String title, String placeHolder) { - var textController = fluent.TextEditingController(text: ""); - void doCreate() { - var name = textController.text.trim(); - if (name.isEmpty) { - return; - } - controller.createCardSet(name); - } - - showDialog( - useSafeArea: true, - context: context, - builder: (context) { - return Padding( - padding: MediaQuery.of(context).viewInsets, - child: fluent.ContentDialog( - title: fluent.Text(title), - constraints: BoxConstraints(maxWidth: 300), - content: fluent.Column( - mainAxisSize: MainAxisSize.min, - children: [ - fluent.Container( - margin: const EdgeInsets.only(bottom: 10, top: 10), - child: fluent.TextBox( - placeholder: placeHolder, - controller: textController, - autofocus: true, - onSubmitted: (e) { - Navigator.pop(context, '确定'); - doCreate(); - }, - ), - ), - ], ), - actions: [ - fluent.Button( - child: const Text('取消'), - onPressed: () { - Navigator.pop(context, '取消'); - // Delete file here - }, - ), - fluent.FilledButton( - onPressed: () { - Navigator.pop(context, '确定'); - doCreate(); - }, - child: const Text("确定")), - ], - ), - ); - }); + ], + ), + ); + }, + ); } void showCardSetItemMenu(BuildContext context, Offset globalPosition, @@ -542,3 +554,23 @@ class MobileCardPage extends MvcView { }); } } + +class MobileCardPage extends MvcView { + const MobileCardPage({super.key, required super.controller}); + + @override + Widget build(BuildContext context) { + return Container( + color: MobileTheme.of(context).mobileBgColor, + child: CustomScrollView( + controller: controller.scrollController, + physics: const BouncingScrollPhysics(), + slivers: [ + _AppBar(controller: controller), + _FilterWidget(controller: controller), + _ContentWidget(controller: controller), + ], + ), + ); + } +} diff --git a/lib/app/mobile/view/doc/mobile_doc_page.dart b/lib/app/mobile/view/doc/mobile_doc_page.dart index b10b8b3..370d143 100644 --- a/lib/app/mobile/view/doc/mobile_doc_page.dart +++ b/lib/app/mobile/view/doc/mobile_doc_page.dart @@ -16,39 +16,27 @@ import 'package:wenznote/model/note/po/doc_dir_po.dart'; import 'package:wenznote/model/note/po/doc_po.dart'; import 'package:wenznote/service/search/search_result_vo.dart'; -class MobileDocPage extends MvcView { - const MobileDocPage({super.key, required super.controller}); +class _SliverAppBar extends StatelessWidget { + final appbarHeight = 48.0; + final searchBarHeight = 56.0; + final MobileDocPageController controller; + + const _SliverAppBar({Key? key, required this.controller}) : super(key: key); @override Widget build(BuildContext context) { - return Container( - color: MobileTheme.of(context).mobileBgColor, - child: CustomScrollView( - controller: controller.scrollController, - physics: const BouncingScrollPhysics(), - slivers: [ - buildSliverAppBar(context), - buildPathWidget(context), - buildContent(context), - ], - ), - ); - } - - SliverAppBar buildSliverAppBar(BuildContext context) { - const appbarHeight = 48.0; - const searchBarHeight = 56.0; return SliverAppBar( surfaceTintColor: Colors.transparent, title: Text( "笔记", - style: TextStyle(color: MobileTheme.of(context).fontColor,fontSize: 18), + style: + TextStyle(color: MobileTheme.of(context).fontColor, fontSize: 18), ), leading: IconButton( onPressed: () { Scaffold.of(context).openDrawer(); }, - icon: MobileUserIcon(), + icon: const MobileUserIcon(), ), actions: [ fluent.Builder(builder: (context) { @@ -56,7 +44,7 @@ class MobileDocPage extends MvcView { onPressed: () { showCreateMenu(context); }, - icon: Icon( + icon: const Icon( Icons.add, size: 32, )); @@ -80,8 +68,8 @@ class MobileDocPage extends MvcView { child: Container( height: 40, alignment: Alignment.centerLeft, - margin: EdgeInsets.symmetric(vertical: 10, horizontal: 16), - padding: EdgeInsets.symmetric(horizontal: 10), + margin: const EdgeInsets.symmetric(vertical: 10, horizontal: 16), + padding: const EdgeInsets.symmetric(horizontal: 10), decoration: BoxDecoration( color: MobileTheme.of(context).mobileContentBgColor, borderRadius: BorderRadius.circular(10), @@ -90,7 +78,7 @@ class MobileDocPage extends MvcView { crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded( - child: buildSearchEdit(context), + child: _SearchEditWidget(controller: controller), ), ], ), @@ -102,7 +90,120 @@ class MobileDocPage extends MvcView { ); } - Widget buildSearchEdit(BuildContext context) { + void showCreateMenu(BuildContext context) { + var editTheme = MobileTheme.of(context); + showDropMenu( + context, + childrenWidth: 140, + childrenHeight: 48, + margin: 10, + offset: const Offset(0, -10), + modal: true, + menus: [ + DropMenu( + text: Row( + children: [ + Text( + "新建笔记", + style: TextStyle( + color: editTheme.fontColor, + ), + ), + ], + ), + onPress: (ctx) { + hideDropMenu(ctx); + controller.createDoc(context, ""); + }, + ), + DropMenu( + text: Row( + children: [ + Text( + "新建文件夹", + style: TextStyle( + color: editTheme.fontColor, + ), + ), + ], + ), + onPress: (ctx) { + hideDropMenu(ctx); + showCreateDialog(context, "新建文件夹", "", false); + }, + ), + ], + ); + } + + void showCreateDialog(BuildContext context, String title, String placeHolder, + bool isCreateDoc) { + var textController = fluent.TextEditingController(text: ""); + void doCreate() { + if (textController.text != "") { + if (isCreateDoc) { + controller.createDoc(context, textController.text); + } else { + controller.createDirectory(context, textController.text); + } + } + } + + showDialog( + useSafeArea: true, + context: context, + builder: (context) { + return fluent.Padding( + padding: MediaQuery.of(context).viewInsets, + child: fluent.ContentDialog( + title: fluent.Text(title), + constraints: const BoxConstraints(maxWidth: 300), + content: fluent.Column( + mainAxisSize: MainAxisSize.min, + children: [ + fluent.Container( + margin: const EdgeInsets.only(bottom: 10, top: 10), + child: fluent.TextBox( + placeholder: placeHolder, + controller: textController, + autofocus: true, + onSubmitted: (e) { + Navigator.pop(context, '确定'); + doCreate(); + }, + ), + ), + ], + ), + actions: [ + fluent.Button( + child: const Text('取消'), + onPressed: () { + Navigator.pop(context, '取消'); + // Delete file here + }, + ), + fluent.FilledButton( + onPressed: () { + Navigator.pop(context, '确定'); + doCreate(); + }, + child: const Text("确定")), + ], + ), + ); + }); + } +} + +class _SearchEditWidget extends StatelessWidget { + final MobileDocPageController controller; + + const _SearchEditWidget({Key? key, required this.controller}) + : super(key: key); + + @override + Widget build(BuildContext context) { return Obx(() { return TextField( focusNode: controller.searchFocusNode, @@ -115,13 +216,13 @@ class MobileDocPage extends MvcView { controller.searchText.value = text; controller.doSearch(); }, - style: TextStyle( + style: const TextStyle( height: 1, fontSize: 16, ), decoration: InputDecoration( - prefixIconConstraints: BoxConstraints(maxWidth: 40), - suffixIconConstraints: BoxConstraints(maxWidth: 40), + prefixIconConstraints: const BoxConstraints(maxWidth: 40), + suffixIconConstraints: const BoxConstraints(maxWidth: 40), prefixIcon: Icon( Icons.search_rounded, color: MobileTheme.of(context).fontColor.withOpacity(0.3), @@ -151,23 +252,30 @@ class MobileDocPage extends MvcView { ); }); } +} - Widget buildPathWidget(BuildContext context) { +class _PathEditWidget extends StatelessWidget { + final MobileDocPageController controller; + + const _PathEditWidget({Key? key, required this.controller}) : super(key: key); + + @override + Widget build(BuildContext context) { return SliverPersistentHeader( pinned: true, delegate: StickyDelegate( child: PreferredSize( - preferredSize: Size(double.infinity, 40), + preferredSize: const Size(double.infinity, 40), child: Container( color: MobileTheme.of(context).mobileBgColor, - padding: EdgeInsets.symmetric( + padding: const EdgeInsets.symmetric( horizontal: 16, ), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Padding( - padding: const EdgeInsets.only(right: 8.0), + const Padding( + padding: EdgeInsets.only(right: 8.0), child: Icon( Icons.folder, color: Colors.grey, @@ -183,12 +291,12 @@ class MobileDocPage extends MvcView { for (var item in controller.pathList) fluent.BreadcrumbItem( label: Container( - constraints: BoxConstraints(maxWidth: 100), - padding: EdgeInsets.symmetric(vertical: 4), + constraints: const BoxConstraints(maxWidth: 100), + padding: const EdgeInsets.symmetric(vertical: 4), child: Text( item.name ?? "null", maxLines: 1, - style: TextStyle( + style: const TextStyle( overflow: TextOverflow.ellipsis, ), ), @@ -206,8 +314,8 @@ class MobileDocPage extends MvcView { return ToggleItem( itemBuilder: (ctx, checked, hovered, pressed) { return Container( - padding: EdgeInsets.all(4), - child: Icon( + padding: const EdgeInsets.all(4), + child: const Icon( fluent.FluentIcons.more, size: 14.0, ), @@ -230,7 +338,7 @@ class MobileDocPage extends MvcView { for (var item in overflowItems) DropMenu( text: item.label, - icon: Icon( + icon: const Icon( Icons.folder, color: Colors.grey, ), @@ -258,8 +366,8 @@ class MobileDocPage extends MvcView { controller.fetchData(); }, child: Container( - padding: EdgeInsets.all(6), - child: Icon(Icons.refresh_outlined)), + padding: const EdgeInsets.all(6), + child: const Icon(Icons.refresh_outlined)), ), ), ], @@ -268,10 +376,17 @@ class MobileDocPage extends MvcView { )), ); } +} + +class _ContentWidget extends StatelessWidget { + final MobileDocPageController controller; + + const _ContentWidget({Key? key, required this.controller}) : super(key: key); - Widget buildContent(BuildContext context) { + @override + Widget build(BuildContext context) { return SliverPadding( - padding: EdgeInsets.only( + padding: const EdgeInsets.only( left: 16, right: 16, bottom: 16, @@ -282,13 +397,15 @@ class MobileDocPage extends MvcView { delegate: SliverChildBuilderDelegate( (context, index) { if (controller.isSearchList) { - return buildSearchItem( - context, - controller.searchList, - index, + return _SearchItemWidget( + controller: controller, + searchItem: controller.searchList[index], ); } - return buildDocItem(context, controller.modelList, index); + return _DocItemWidget( + controller: controller, + docItem: controller.modelList[index], + ); }, childCount: controller.isSearchList ? controller.searchList.length @@ -299,242 +416,99 @@ class MobileDocPage extends MvcView { ), ); } +} + +class _DocItemWidget extends StatelessWidget { + final MobileDocModel docItem; + final MobileDocPageController controller; - Widget buildSearchItem( - BuildContext context, - List searchList, - int index, - ) { + const _DocItemWidget( + {Key? key, required this.controller, required this.docItem}) + : super(key: key); + + @override + Widget build(BuildContext context) { + bool isFolder = docItem.isFolder; return Obx(() { - var searchModel = searchList[index]; - return InkWell( - onTap: () { - var docModel = searchList[index]; - controller.openSearchItem(docModel); + var selected = controller.selectItem.value == docItem.uuid; + return ToggleItem( + checked: selected, + onTapDown: (ctx) { + controller.selectItem.value = docItem.uuid; }, - child: Container( - margin: const EdgeInsets.only( - top: 6, - bottom: 6, + onTap: (ctx) { + controller.openDocOrDirectory(ctx, docItem); + controller.selectItem.value = docItem.uuid; + }, + onSecondaryTap: (ctx, details) { + controller.selectItem.value = docItem.uuid; + showDocItemDropMenu(ctx, details.globalPosition, docItem); + }, + onLongPress: (ctx, details) { + controller.selectItem.value = docItem.uuid; + showDocItemDropMenu(ctx, details.globalPosition, docItem); + }, + itemBuilder: + (BuildContext context, bool checked, bool hover, bool pressed) { + return Container( + padding: const EdgeInsets.symmetric( + vertical: 10, + horizontal: 10, + ), + margin: const EdgeInsets.symmetric(vertical: 4), + height: 60, + decoration: BoxDecoration( + color: (hover || selected || pressed) + ? (Theme.of(context).brightness == Brightness.dark + ? Colors.grey.shade900 + : Colors.grey.shade200) + : null, + borderRadius: BorderRadius.circular(10), + ), + child: Row( + children: [ + Icon( + isFolder ? Icons.folder : fluent.FluentIcons.edit_note, + size: 32, + color: isFolder ? Colors.orange : Colors.grey, + ), + const SizedBox( + width: 10, + ), + Text(docItem.name ?? "未命名笔记"), + ], + ), + ); + }, + ); + }); + } + + + void showDocItemDropMenu( + BuildContext context, Offset globalOffset, MobileDocModel docItem) { + HapticFeedback.selectionClick(); + showMouseDropMenu(context, globalOffset & const Size(4, 4), + modal: true, + childrenWidth: 200, + childrenHeight: 48, + menus: [ + DropMenu( + text: const Text("打开"), + onPress: (ctx) { + hideDropMenu(ctx); + controller.openDocOrDirectory(context, docItem); + }, ), - constraints: const BoxConstraints( - maxHeight: 300, + DropMenu( + text: const Text("重命名"), + onPress: (ctx) { + hideDropMenu(ctx); + showRenameDialog(context, docItem); + }, ), - child: ClipRRect( - borderRadius: BorderRadius.circular(10), - child: Container( - color: MobileTheme.of(context).bgColor3, - child: Stack( - alignment: Alignment.bottomCenter, - children: [ - searchModel.buildEditWidget(context), - Container( - height: 0, - padding: const EdgeInsets.symmetric(horizontal: 10), - decoration: BoxDecoration( - color: MobileTheme.of(context).bgColor3, - boxShadow: [ - BoxShadow( - color: MobileTheme.buildColor(context, - darkColor: Colors.black.withOpacity(0.2), - lightColor: Colors.grey.withOpacity(0.2))!, - blurRadius: 40, - spreadRadius: 10, - ), - ], - ), - ), - Container( - height: 40, - color: MobileTheme.of(context).bgColor3, - padding: const EdgeInsets.symmetric(horizontal: 10), - child: Row( - children: [ - Text( - searchModel.getTypeTitle(), - style: TextStyle( - fontSize: 12, - color: MobileTheme.of(context) - .fontColor - .withOpacity(0.4), - ), - ), - const SizedBox( - width: 10, - ), - Expanded( - child: Text( - searchModel.getTimeString(), - style: TextStyle( - fontSize: 12, - color: MobileTheme.of(context) - .fontColor - .withOpacity(0.4), - ), - ), - ), - Builder(builder: (context) { - return fluent.IconButton( - onPressed: () { - //显示菜单 - showDropMenu( - context, - menus: [ - DropMenu( - height: 48, - icon: const Icon( - Icons.copy, - ), - text: Padding( - padding: const EdgeInsets.all(8.0), - child: const Text("复制内容"), - ), - onPress: (context) { - Navigator.of(context).pop(); - controller.copySearchItem(context, index); - }, - ), - if (searchModel.doc.type != "doc") - DropMenu( - height: 48, - icon: const Icon( - Icons.drive_file_move_outline, - ), - text: Padding( - padding: const EdgeInsets.all(8.0), - child: const Text("存到笔记"), - ), - onPress: (context) { - Navigator.of(context).pop(); - controller.moveSearchItem( - context, index); - }, - ), - DropMenu( - height: 48, - icon: const Icon( - Icons.delete_outline, - color: Colors.redAccent, - ), - text: Padding( - padding: const EdgeInsets.all(8.0), - child: const Text( - "删除", - style: - TextStyle(color: Colors.redAccent), - ), - ), - onPress: (context) { - Navigator.of(context).pop(); - controller.deleteSearchItem( - context, index); - }, - ), - ], - modal: true, - ); - }, - icon: const Icon( - Icons.more_horiz_outlined, - size: 16, - ), - ); - }), - ], - ), - ), - ], - ), - ), - ), - ), - ); - }); - } - - Widget buildDocItem( - BuildContext context, List modelList, int index) { - var docItem = controller.modelList[index]; - bool isFolder = docItem.isFolder; - return Obx(() { - var selected = controller.selectItem.value == docItem.uuid; - return ToggleItem( - checked: selected, - onTapDown: (ctx) { - controller.selectItem.value = docItem.uuid; - }, - onTap: (ctx) { - controller.openDocOrDirectory(ctx, docItem); - controller.selectItem.value = docItem.uuid; - }, - onSecondaryTap: (ctx, details) { - controller.selectItem.value = docItem.uuid; - showDocItemDropMenu(ctx, details.globalPosition, docItem); - }, - onLongPress: (ctx, details) { - controller.selectItem.value = docItem.uuid; - showDocItemDropMenu(ctx, details.globalPosition, docItem); - }, - itemBuilder: - (BuildContext context, bool checked, bool hover, bool pressed) { - return Container( - padding: EdgeInsets.symmetric( - vertical: 10, - horizontal: 10, - ), - margin: EdgeInsets.symmetric(vertical: 4), - height: 60, - decoration: BoxDecoration( - color: (hover || selected || pressed) - ? (Theme.of(context).brightness == Brightness.dark - ? Colors.grey.shade900 - : Colors.grey.shade200) - : null, - borderRadius: BorderRadius.circular(10), - ), - child: Row( - children: [ - Icon( - isFolder ? Icons.folder : fluent.FluentIcons.edit_note, - size: 32, - color: isFolder ? Colors.orange : Colors.grey, - ), - SizedBox( - width: 10, - ), - Text("${docItem.name ?? "未命名笔记"}"), - ], - ), - ); - }, - ); - }); - } - - void showDocItemDropMenu( - BuildContext context, Offset globalOffset, MobileDocModel docItem) { - HapticFeedback.selectionClick(); - showMouseDropMenu(context, globalOffset & Size(4, 4), - modal: true, - childrenWidth: 200, - childrenHeight: 48, - menus: [ DropMenu( - text: Text("打开"), - onPress: (ctx) { - hideDropMenu(ctx); - controller.openDocOrDirectory(context, docItem); - }, - ), - DropMenu( - text: Text("重命名"), - onPress: (ctx) { - hideDropMenu(ctx); - showRenameDialog(context, docItem); - }, - ), - DropMenu( - text: Text("移动到"), + text: const Text("移动到"), onPress: (ctx) { hideDropMenu(ctx); showMoveDialog(context, [docItem]); @@ -542,7 +516,7 @@ class MobileDocPage extends MvcView { ), if (docItem.isDoc) DropMenu( - text: Text("制作卡片"), + text: const Text("制作卡片"), onPress: (ctx) { hideDropMenu(ctx); showCreateCardDialog( @@ -551,7 +525,7 @@ class MobileDocPage extends MvcView { ), DropSplit(), DropMenu( - text: Text("删除"), + text: const Text("删除"), onPress: (ctx) { hideDropMenu(ctx); if (docItem.isFolder) { @@ -564,110 +538,6 @@ class MobileDocPage extends MvcView { ]); } - void showCreateMenu(BuildContext context) { - var editTheme = MobileTheme.of(context); - showDropMenu( - context, - childrenWidth: 140, - childrenHeight: 48, - margin: 10, - offset: const Offset(0, -10), - modal: true, - menus: [ - DropMenu( - text: Row( - children: [ - Text( - "新建笔记", - style: TextStyle( - color: editTheme.fontColor, - ), - ), - ], - ), - onPress: (ctx) { - hideDropMenu(ctx); - controller.createDoc(context, ""); - }, - ), - DropMenu( - text: Row( - children: [ - Text( - "新建文件夹", - style: TextStyle( - color: editTheme.fontColor, - ), - ), - ], - ), - onPress: (ctx) { - hideDropMenu(ctx); - showCreateDialog(context, "新建文件夹", "", false); - }, - ), - ], - ); - } - - void showCreateDialog(BuildContext context, String title, String placeHolder, - bool isCreateDoc) { - var textController = fluent.TextEditingController(text: ""); - void doCreate() { - if (textController.text != "") { - if (isCreateDoc) { - controller.createDoc(context, textController.text); - } else { - controller.createDirectory(context, textController.text); - } - } - } - - showDialog( - useSafeArea: true, - context: context, - builder: (context) { - return fluent.Padding( - padding: MediaQuery.of(context).viewInsets, - child: fluent.ContentDialog( - title: fluent.Text(title), - constraints: BoxConstraints(maxWidth: 300), - content: fluent.Column( - mainAxisSize: MainAxisSize.min, - children: [ - fluent.Container( - margin: const EdgeInsets.only(bottom: 10, top: 10), - child: fluent.TextBox( - placeholder: placeHolder, - controller: textController, - autofocus: true, - onSubmitted: (e) { - Navigator.pop(context, '确定'); - doCreate(); - }, - ), - ), - ], - ), - actions: [ - fluent.Button( - child: const Text('取消'), - onPressed: () { - Navigator.pop(context, '取消'); - // Delete file here - }, - ), - fluent.FilledButton( - onPressed: () { - Navigator.pop(context, '确定'); - doCreate(); - }, - child: const Text("确定")), - ], - ), - ); - }); - } void showRenameDialog(BuildContext context, MobileDocModel docItem) { var textController = fluent.TextEditingController(text: docItem.name ?? ""); @@ -734,7 +604,7 @@ class MobileDocPage extends MvcView { title: "移动到", actionLabel: "移动到此处", width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).size.height*0.8, + height: MediaQuery.of(context).size.height * 0.8, filter: (path) { return controller.canMoveToPath(list, path); }, @@ -752,3 +622,180 @@ class MobileDocPage extends MvcView { context, cardName, list.map((e) => e.value as DocPO).toList()); } } + +class _SearchItemWidget extends StatelessWidget { + final MobileDocPageController controller; + final SearchResultVO searchItem; + + const _SearchItemWidget( + {Key? key, required this.controller, required this.searchItem}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Obx(() { + return InkWell( + onTap: () { + controller.openSearchItem(searchItem); + }, + child: Container( + margin: const EdgeInsets.only( + top: 6, + bottom: 6, + ), + constraints: const BoxConstraints( + maxHeight: 300, + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(10), + child: Container( + color: MobileTheme.of(context).bgColor3, + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + searchItem.buildEditWidget(context), + Container( + height: 0, + padding: const EdgeInsets.symmetric(horizontal: 10), + decoration: BoxDecoration( + color: MobileTheme.of(context).bgColor3, + boxShadow: [ + BoxShadow( + color: MobileTheme.buildColor(context, + darkColor: Colors.black.withOpacity(0.2), + lightColor: Colors.grey.withOpacity(0.2))!, + blurRadius: 40, + spreadRadius: 10, + ), + ], + ), + ), + Container( + height: 40, + color: MobileTheme.of(context).bgColor3, + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Row( + children: [ + Text( + searchItem.getTypeTitle(), + style: TextStyle( + fontSize: 12, + color: MobileTheme.of(context) + .fontColor + .withOpacity(0.4), + ), + ), + const SizedBox( + width: 10, + ), + Expanded( + child: Text( + searchItem.getTimeString(), + style: TextStyle( + fontSize: 12, + color: MobileTheme.of(context) + .fontColor + .withOpacity(0.4), + ), + ), + ), + Builder(builder: (context) { + return fluent.IconButton( + onPressed: () { + //显示菜单 + showDropMenu( + context, + menus: [ + DropMenu( + height: 48, + icon: const Icon( + Icons.copy, + ), + text: const Padding( + padding: EdgeInsets.all(8.0), + child: Text("复制内容"), + ), + onPress: (context) { + Navigator.of(context).pop(); + controller.copySearchItem( + context, searchItem); + }, + ), + if (searchItem.doc.type != "doc") + DropMenu( + height: 48, + icon: const Icon( + Icons.drive_file_move_outline, + ), + text: const Padding( + padding: EdgeInsets.all(8.0), + child: Text("存到笔记"), + ), + onPress: (context) { + Navigator.of(context).pop(); + controller.moveSearchItem( + context, searchItem); + }, + ), + DropMenu( + height: 48, + icon: const Icon( + Icons.delete_outline, + color: Colors.redAccent, + ), + text: const Padding( + padding: EdgeInsets.all(8.0), + child: Text( + "删除", + style: + TextStyle(color: Colors.redAccent), + ), + ), + onPress: (context) { + Navigator.of(context).pop(); + controller.deleteSearchItem( + context, searchItem); + }, + ), + ], + modal: true, + ); + }, + icon: const Icon( + Icons.more_horiz_outlined, + size: 16, + ), + ); + }), + ], + ), + ), + ], + ), + ), + ), + ), + ); + }); + } +} + +class MobileDocPage extends MvcView { + const MobileDocPage({super.key, required super.controller}); + + @override + Widget build(BuildContext context) { + return Container( + color: MobileTheme.of(context).mobileBgColor, + child: CustomScrollView( + controller: controller.scrollController, + physics: const BouncingScrollPhysics(), + slivers: [ + _SliverAppBar(controller: controller), + _PathEditWidget(controller: controller), + _ContentWidget(controller: controller), + ], + ), + ); + } +} diff --git a/lib/app/mobile/view/doc/mobile_doc_page_controller.dart b/lib/app/mobile/view/doc/mobile_doc_page_controller.dart index 4820120..a9aca68 100644 --- a/lib/app/mobile/view/doc/mobile_doc_page_controller.dart +++ b/lib/app/mobile/view/doc/mobile_doc_page_controller.dart @@ -128,12 +128,11 @@ class MobileDocPageController extends ServiceManagerController { void openSearchItem(SearchResultVO searchItem) {} - void copySearchItem(BuildContext context, int index) {} + void copySearchItem(BuildContext context, SearchResultVO item) {} - void moveSearchItem(BuildContext context, int index) {} + void moveSearchItem(BuildContext context, SearchResultVO index) {} - void deleteSearchItem(BuildContext context, int index) async { - var searchItem = searchList[index]; + void deleteSearchItem(BuildContext context, SearchResultVO searchItem) async { await serviceManager.todayService.deleteNote(searchItem.doc); await serviceManager.editService.deleteDocFile(searchItem.doc.uuid!); fetchData(); diff --git a/pubspec.lock b/pubspec.lock index 3386c6f..804fd34 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1902,9 +1902,11 @@ packages: ydart: dependency: "direct main" description: - path: "D:/GitHub/ydart" - relative: false - source: path + path: "." + ref: HEAD + resolved-ref: "76ec6818779e8d83e4572ca98dda6e8cf9874dd9" + url: "https://github.com/lyming99/ydart" + source: git version: "1.0.0+1" sdks: dart: ">=3.2.5 <4.0.0"