Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(markdown): support markdown editing and embed rendering #143

Merged
merged 1 commit into from
Jan 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions lib/components/base/button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,19 @@ class MultiFabLayoutDelegate extends MultiChildLayoutDelegate {
}

class PageBackButton extends StatelessWidget {
const PageBackButton({super.key});
final Function()? onBack;

const PageBackButton({
super.key,
this.onBack,
});

@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Center(
child: IconButton(
onPressed: Get.back,
onPressed: onBack ?? Get.back,
icon: const Icon(Icons.arrow_back_rounded),
color: colorScheme.onSurface,
tooltip: l10n.back,
Expand Down
14 changes: 12 additions & 2 deletions lib/components/markdown_bar/markdown_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class MarkdownToolbar extends StatefulWidget {
this.checkboxTooltip = 'Checkbox',
this.quoteTooltip = 'Quote',
this.horizontalRuleTooltip = 'Horizontal rule',
required this.beforeImagePressed,
});

/// It is recommended that you use your own TextField outside this widget for more customization.
Expand Down Expand Up @@ -243,6 +244,8 @@ class MarkdownToolbar extends StatefulWidget {
/// Set a custom tooltip [String] for the horizontal rule button. Leave blank `''` to disable tooltip.
final String horizontalRuleTooltip;

final Future<String?> Function() beforeImagePressed;

@override
State<MarkdownToolbar> createState() => MarkdownToolbarState();
}
Expand Down Expand Up @@ -602,8 +605,13 @@ class MarkdownToolbarState extends State<MarkdownToolbar> {
void onLinkPressed() =>
onToolbarItemPressed(markdownToolbarOption: MarkdownToolbarOption.link);

void onImagePressed() =>
onToolbarItemPressed(markdownToolbarOption: MarkdownToolbarOption.image);
void onImagePressed() async {
final path = await widget.beforeImagePressed();
onToolbarItemPressed(
markdownToolbarOption: MarkdownToolbarOption.image,
mediaPath: path,
);
}

void onCodePressed() =>
onToolbarItemPressed(markdownToolbarOption: MarkdownToolbarOption.code);
Expand All @@ -626,6 +634,7 @@ class MarkdownToolbarState extends State<MarkdownToolbar> {
void onToolbarItemPressed({
required MarkdownToolbarOption markdownToolbarOption,
int? option,
String? mediaPath,
}) {
widget.useIncludedTextField
? _includedFocusNode.requestFocus()
Expand All @@ -644,6 +653,7 @@ class MarkdownToolbarState extends State<MarkdownToolbar> {
customCodeCharacter: widget.codeCharacter,
customBulletedListCharacter: widget.bulletedListCharacter,
customHorizontalRuleCharacter: widget.horizontalRuleCharacter,
mediaPath: mediaPath,
);
}

Expand Down
16 changes: 11 additions & 5 deletions lib/components/markdown_bar/parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class Format {
String? customCodeCharacter,
String? customBulletedListCharacter,
String? customHorizontalRuleCharacter,
String? mediaPath,
}) {
switch (markdownToolbarOption) {
case MarkdownToolbarOption.bold:
Expand Down Expand Up @@ -174,6 +175,7 @@ class Format {
formatImage(
controller: controller,
selection: selection,
imgPath: mediaPath,
);
break;
case MarkdownToolbarOption.horizontalRule:
Expand Down Expand Up @@ -588,17 +590,21 @@ void formatTextLink({
void formatImage({
required TextEditingController controller,
required TextSelection selection,
required String? imgPath,
}) {
const String altPlaceholder = 'Alt text';
const String linkPlaceholder = '/link/to/picture.jpg';
const String altPlaceholder = '';
const String linkPlaceholder = '';

final beforeText = controller.text.substring(0, selection.start);
final afterText =
controller.text.substring(selection.end, controller.text.length);
controller.text = '$beforeText![$altPlaceholder]($linkPlaceholder)$afterText';
controller.text =
'$beforeText![$altPlaceholder](${imgPath ?? linkPlaceholder})$afterText';

controller.selection = TextSelection(
baseOffset: selection.start + 4 + altPlaceholder.length,
extentOffset:
selection.start + altPlaceholder.length + 4 + linkPlaceholder.length);
extentOffset: selection.start +
altPlaceholder.length +
4 +
(imgPath ?? linkPlaceholder).length);
}
23 changes: 23 additions & 0 deletions lib/components/markdown_embed/audio_embed.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import 'package:flutter/widgets.dart';
import 'package:moodiary/utils/file_util.dart';

import '../audio_player/audio_player_view.dart';

class MarkdownAudioEmbed extends StatelessWidget {
final String audioName;
final bool isEdit;

const MarkdownAudioEmbed(
{super.key, required this.audioName, required this.isEdit});

@override
Widget build(
BuildContext context,
) {
final path = isEdit
? FileUtil.getCachePath(audioName)
: FileUtil.getRealPath('audio', audioName);

return AudioPlayerComponent(path: path); // 使用音频播放器组件渲染
}
}
43 changes: 43 additions & 0 deletions lib/components/markdown_embed/image_embed.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:moodiary/router/app_routes.dart';
import 'package:moodiary/utils/file_util.dart';
import 'package:refreshed/refreshed.dart';

class MarkdownImageEmbed extends StatelessWidget {
final bool isEdit;
final String imageName;

const MarkdownImageEmbed(
{super.key, required this.isEdit, required this.imageName});

@override
Widget build(
BuildContext context,
) {
final imagePath =
isEdit ? imageName : FileUtil.getRealPath('image', imageName);

return Center(
child: GestureDetector(
onTap: () {
if (!isEdit) {
Get.toNamed(AppRoutes.photoPage, arguments: [
[imagePath],
0,
]);
}
},
child: ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 300),
child: Card.outlined(
clipBehavior: Clip.hardEdge,
color: Colors.transparent,
child: Image.file(File(imagePath)),
),
),
),
);
}
}
32 changes: 32 additions & 0 deletions lib/components/markdown_embed/video_embed.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
import 'package:moodiary/components/video_player/video_player_view.dart';
import 'package:moodiary/utils/file_util.dart';

class MarkdownVideoEmbed extends StatelessWidget {
final bool isEdit;
final String videoName;

const MarkdownVideoEmbed(
{super.key, required this.isEdit, required this.videoName});

@override
Widget build(
BuildContext context,
) {
final videoPath =
isEdit ? videoName : FileUtil.getRealPath('video', videoName);
final colorScheme = Theme.of(context).colorScheme;
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 300),
child: Card.outlined(
clipBehavior: Clip.hardEdge,
color: colorScheme.surfaceContainerLowest,
child: VideoPlayerComponent(
videoPath: videoPath,
),
),
),
); // 使用音频播放器组件渲染
}
}
3 changes: 2 additions & 1 deletion lib/l10n/intl_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -258,5 +258,6 @@
"diaryEdit": "Edit",
"diaryShare": "Share",
"diaryCount": "{count} Words",
"dataSync": "Data sync"
"dataSync": "Data sync",
"diaryType": "Type"
}
3 changes: 2 additions & 1 deletion lib/l10n/intl_zh.arb
Original file line number Diff line number Diff line change
Expand Up @@ -258,5 +258,6 @@
"diaryEdit": "编辑",
"diaryShare": "分享",
"diaryCount": "{count} 字",
"dataSync": "数据同步"
"dataSync": "数据同步",
"diaryType": "类型"
}
21 changes: 15 additions & 6 deletions lib/pages/diary_details/diary_details_logic.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:moodiary/common/models/isar/diary.dart';
import 'package:moodiary/common/values/diary_type.dart';
import 'package:moodiary/presentation/isar.dart';
import 'package:moodiary/router/app_routes.dart';
import 'package:refreshed/refreshed.dart';
Expand All @@ -15,18 +16,26 @@ class DiaryDetailsLogic extends GetxController {
final DiaryDetailsState state = DiaryDetailsState();

// 编辑器控制器
late QuillController quillController = QuillController(
document: Document.fromJson(jsonDecode(state.diary.content)),
readOnly: true,
selection: const TextSelection.collapsed(offset: 0),
);
QuillController? quillController;

// 图片预览
late final PageController pageController = PageController();

@override
void onInit() {
if (state.diary.type != DiaryType.markdown.value) {
quillController = QuillController(
document: Document.fromJson(jsonDecode(state.diary.content)),
readOnly: true,
selection: const TextSelection.collapsed(offset: 0),
);
}
super.onInit();
}

@override
void onClose() {
quillController.dispose();
quillController?.dispose();
pageController.dispose();
super.onClose();
}
Expand Down
61 changes: 44 additions & 17 deletions lib/pages/diary_details/diary_details_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@ import 'dart:io';
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_highlight/themes/a11y-dark.dart';
import 'package:flutter_highlight/themes/a11y-light.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:intl/intl.dart';
import 'package:markdown_widget/markdown_widget.dart';
import 'package:moodiary/common/models/isar/diary.dart';
import 'package:moodiary/common/values/diary_type.dart';
import 'package:moodiary/common/values/icons.dart';
import 'package:moodiary/components/base/button.dart';
import 'package:moodiary/components/markdown_embed/image_embed.dart';
import 'package:moodiary/components/mood_icon/mood_icon_view.dart';
import 'package:moodiary/components/quill_embed/audio_embed.dart';
import 'package:moodiary/components/quill_embed/image_embed.dart';
Expand All @@ -26,6 +31,23 @@ class DiaryDetailsPage extends StatelessWidget {

final tag = (Get.arguments[0] as Diary).id;

Widget _buildMarkdownWidget(
{required Brightness brightness, required String data}) {
final config = brightness == Brightness.dark
? MarkdownConfig.darkConfig
: MarkdownConfig.defaultConfig;
return MarkdownBlock(
data: data,
config: config.copy(configs: [
ImgConfig(builder: (src, _) {
return MarkdownImageEmbed(isEdit: false, imageName: src);
}),
brightness == Brightness.dark
? PreConfig.darkConfig.copy(theme: a11yDarkTheme)
: const PreConfig().copy(theme: a11yLightTheme)
]));
}

@override
Widget build(BuildContext context) {
final logic = Bind.find<DiaryDetailsLogic>(tag: tag);
Expand Down Expand Up @@ -402,23 +424,28 @@ class DiaryDetailsPage extends StatelessWidget {
),
Card.filled(
color: customColorScheme.surfaceContainerLow,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: QuillEditor.basic(
controller: logic.quillController,
config: QuillEditorConfig(
showCursor: false,
customStyles: ThemeUtil.getInstance(context,
customColorScheme: customColorScheme),
embedBuilders: [
ImageEmbedBuilder(isEdit: false),
VideoEmbedBuilder(isEdit: false),
AudioEmbedBuilder(isEdit: false),
TextIndentEmbedBuilder(isEdit: false),
],
),
),
),
child: state.diary.type == DiaryType.markdown.value
? Padding(
padding: const EdgeInsets.all(8.0),
child: _buildMarkdownWidget(
brightness: customColorScheme.brightness,
data: state.diary.content),
)
: QuillEditor.basic(
controller: logic.quillController!,
config: QuillEditorConfig(
showCursor: false,
padding: const EdgeInsets.all(8.0),
customStyles: ThemeUtil.getInstance(context,
customColorScheme: customColorScheme),
embedBuilders: [
ImageEmbedBuilder(isEdit: false),
VideoEmbedBuilder(isEdit: false),
AudioEmbedBuilder(isEdit: false),
TextIndentEmbedBuilder(isEdit: false),
],
),
),
),
],
),
Expand Down
6 changes: 3 additions & 3 deletions lib/pages/draw/draw_logic.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ class DrawLogic extends GetxController {
super.onClose();
}

Future<void> getImageData() async {
Future<void> getImageData(BuildContext context) async {
final data = await drawingController.getImageData();
final image = data!.buffer.asUint8List();
Get.back();
editLogic.pickDraw(image);
if (context.mounted) Navigator.pop(context);
if (context.mounted) await editLogic.pickDraw(image, context);
}

void pickColor(Color color) {
Expand Down
4 changes: 2 additions & 2 deletions lib/pages/draw/draw_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ class DrawPage extends StatelessWidget {
title: l10n.hint,
message: l10n.sureToSave,
style: AdaptiveStyle.material);
if (res == OkCancelResult.ok) {
await logic.getImageData();
if (res == OkCancelResult.ok && context.mounted) {
await logic.getImageData(context);
}
},
child: Text(l10n.save),
Expand Down
Loading
Loading