Skip to content

Commit a9a978d

Browse files
authored
Merge pull request #33 from adeeteya/feature_update
✨ Major Feature update
2 parents 3fab710 + 99af140 commit a9a978d

18 files changed

+1818
-234
lines changed

.github/workflows/pr-checker.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ on:
44
pull_request:
55

66
env:
7-
FLUTTER_VERSION: 3.35.4
7+
FLUTTER_VERSION: 3.35.6
88

99
jobs:
1010
lint:

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ Please star⭐ the repo if you like what you see😊.
8282
- [x] Ability to open .md files directly from the explorer
8383
- [x] Convenient way to style text (bold,italics,headings and etc)
8484
- [x] Convenient way to add links
85+
- [x] Convenient way to add tables
8586
- [x] Ability to preview JPEG, PNG, GIF, WebP, BMP, and WBMP image formats.
8687
- [x] Easily open links from the preview
8788
- [x] Light and Dark Theme Modes available
@@ -91,6 +92,10 @@ Please star⭐ the repo if you like what you see😊.
9192
- [x] Ability to clear text and start from scratch
9293
- [x] Create new .md files
9394
- [x] Edit existing .md files
95+
- [x] LaTex support
96+
- [x] Horizontal Swipe to Switch between Preview and Editing View in Single View Mode
97+
- [x] Default Folder for Opening and Saving .md files
98+
- [x] Added Print/Save as Pdf Option
9499

95100
## 📸 Screenshots
96101

lib/device_preference_notifier.dart

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,45 @@
11
import 'package:flutter/foundation.dart';
22
import 'package:shared_preferences/shared_preferences.dart';
33

4-
enum _SharedPreferencesKeys { isDarkMode, isSplitLayout }
4+
enum _SharedPreferencesKeys { isDarkMode, isSplitLayout, defaultFolderPath }
55

66
class DevicePreferences {
77
final bool isDarkMode;
88
final bool isSplitLayout;
9+
final String? defaultFolderPath;
910

10-
DevicePreferences({this.isDarkMode = false, this.isSplitLayout = false});
11+
DevicePreferences({
12+
this.isDarkMode = false,
13+
this.isSplitLayout = false,
14+
this.defaultFolderPath,
15+
});
1116

12-
DevicePreferences copyWith({bool? isDarkMode, bool? isSplitLayout}) {
17+
DevicePreferences copyWith({
18+
bool? isDarkMode,
19+
bool? isSplitLayout,
20+
String? defaultFolderPath,
21+
}) {
1322
return DevicePreferences(
1423
isDarkMode: isDarkMode ?? this.isDarkMode,
1524
isSplitLayout: isSplitLayout ?? this.isSplitLayout,
25+
defaultFolderPath: defaultFolderPath ?? this.defaultFolderPath,
1626
);
1727
}
1828

1929
@override
2030
bool operator ==(Object other) {
2131
return other is DevicePreferences &&
2232
other.isDarkMode == isDarkMode &&
23-
other.isSplitLayout == isSplitLayout;
33+
other.isSplitLayout == isSplitLayout &&
34+
other.defaultFolderPath == defaultFolderPath;
2435
}
2536

2637
@override
27-
int get hashCode => Object.hash(isDarkMode, isSplitLayout);
38+
int get hashCode => Object.hash(isDarkMode, isSplitLayout, defaultFolderPath);
2839

2940
@override
3041
String toString() {
31-
return 'DevicePreferences(isDarkMode: $isDarkMode, isSplitLayout: $isSplitLayout)';
42+
return 'DevicePreferences(isDarkMode: $isDarkMode, isSplitLayout: $isSplitLayout, defaultFolderPath: $defaultFolderPath)';
3243
}
3344
}
3445

@@ -45,9 +56,13 @@ class DevicePreferenceNotifier extends ValueNotifier<DevicePreferences> {
4556
PlatformDispatcher.instance.platformBrightness == Brightness.dark;
4657
final isSplitLayout =
4758
_prefs.getBool(_SharedPreferencesKeys.isSplitLayout.name) ?? true;
59+
final defaultFolderPath = _prefs.getString(
60+
_SharedPreferencesKeys.defaultFolderPath.name,
61+
);
4862
value = DevicePreferences(
4963
isDarkMode: isDarkMode,
5064
isSplitLayout: isSplitLayout,
65+
defaultFolderPath: defaultFolderPath,
5166
);
5267
notifyListeners();
5368
}
@@ -69,4 +84,10 @@ class DevicePreferenceNotifier extends ValueNotifier<DevicePreferences> {
6984
);
7085
notifyListeners();
7186
}
87+
88+
Future<void> setDefaultFolderPath(String path) async {
89+
value = value.copyWith(defaultFolderPath: path);
90+
await _prefs.setString(_SharedPreferencesKeys.defaultFolderPath.name, path);
91+
notifyListeners();
92+
}
7293
}

lib/home.dart

Lines changed: 101 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
1+
import 'dart:async';
12
import 'dart:convert';
23
import 'dart:io';
34

45
import 'package:file_picker/file_picker.dart';
56
import 'package:flutter/foundation.dart';
67
import 'package:flutter/material.dart';
78
import 'package:flutter/services.dart';
9+
import 'package:htmltopdfwidgets/htmltopdfwidgets.dart' as html2pdf;
10+
import 'package:markdown/markdown.dart' as md;
811
import 'package:markdown_editor/device_preference_notifier.dart';
912
import 'package:markdown_editor/l10n/generated/app_localizations.dart';
1013
import 'package:markdown_editor/widgets/MarkdownBody/custom_image_config.dart';
1114
import 'package:markdown_editor/widgets/MarkdownBody/custom_text_node.dart';
1215
import 'package:markdown_editor/widgets/MarkdownBody/latex_node.dart';
1316
import 'package:markdown_editor/widgets/MarkdownTextInput/markdown_text_input.dart';
1417
import 'package:markdown_widget/markdown_widget.dart';
18+
import 'package:pdf/widgets.dart' as pw;
19+
import 'package:printing/printing.dart';
20+
import 'package:url_launcher/url_launcher.dart';
1521

16-
enum MenuItem { switchTheme, switchView, open, clear, save }
22+
enum MenuItem { switchTheme, switchView, open, clear, save, print, donate }
1723

1824
class Home extends StatefulWidget {
1925
final DevicePreferenceNotifier devicePreferenceNotifier;
@@ -95,6 +101,8 @@ class _HomeState extends State<Home> {
95101
try {
96102
final FilePickerResult? result = await FilePicker.platform.pickFiles(
97103
type: FileType.custom,
104+
initialDirectory:
105+
widget.devicePreferenceNotifier.value.defaultFolderPath,
98106
allowedExtensions: ['md'],
99107
);
100108
if (result != null) {
@@ -107,6 +115,13 @@ class _HomeState extends State<Home> {
107115
_inputText = await file.readAsString();
108116
_textEditingController.text = _inputText;
109117
setState(() {});
118+
final folderPath = _filePath.substring(
119+
0,
120+
_filePath.lastIndexOf(Platform.pathSeparator),
121+
);
122+
unawaited(
123+
widget.devicePreferenceNotifier.setDefaultFolderPath(folderPath),
124+
);
110125
}
111126
} catch (e) {
112127
if (mounted) {
@@ -174,13 +189,46 @@ class _HomeState extends State<Home> {
174189
);
175190
return;
176191
} else {
177-
await FilePicker.platform.saveFile(
192+
final filePath = await FilePicker.platform.saveFile(
178193
dialogTitle: AppLocalizations.of(context)!.saveFileDialogTitle,
179194
fileName: (!kIsWeb && Platform.isWindows) ? null : "$_fileName.md",
195+
initialDirectory:
196+
widget.devicePreferenceNotifier.value.defaultFolderPath,
180197
type: FileType.custom,
181198
allowedExtensions: ['md'],
182199
bytes: utf8.encode(_inputText),
183200
);
201+
if (filePath != null) {
202+
final folderPath = filePath.substring(
203+
0,
204+
filePath.lastIndexOf(Platform.pathSeparator),
205+
);
206+
unawaited(
207+
widget.devicePreferenceNotifier.setDefaultFolderPath(folderPath),
208+
);
209+
}
210+
}
211+
}
212+
213+
Future<void> _printFile() async {
214+
if (_inputText.isEmpty) {
215+
ScaffoldMessenger.of(context).showSnackBar(
216+
SnackBar(
217+
content: Text(AppLocalizations.of(context)!.emptyInputTextContent),
218+
),
219+
);
220+
return;
221+
} else {
222+
final htmlFromMarkdown = md.markdownToHtml(_inputText);
223+
await Printing.layoutPdf(
224+
usePrinterSettings: true,
225+
onLayout: (format) async {
226+
final pdf = pw.Document();
227+
final widgets = await html2pdf.HTMLToPdf().convert(htmlFromMarkdown);
228+
pdf.addPage(pw.MultiPage(build: (context) => widgets));
229+
return pdf.save();
230+
},
231+
);
184232
}
185233
}
186234

@@ -272,18 +320,28 @@ class _HomeState extends State<Home> {
272320
child: AnimatedSwitcher(
273321
duration: const Duration(milliseconds: 300),
274322
reverseDuration: const Duration(milliseconds: 300),
275-
child: _isPreview
276-
? _markdownPreviewWidget()
277-
: MarkdownTextInput(
278-
(String value) {
279-
setState(() {
280-
_inputText = value;
281-
});
282-
},
283-
_inputText,
284-
controller: _textEditingController,
285-
label: AppLocalizations.of(context)!.markdownTextInputLabel,
286-
),
323+
child: GestureDetector(
324+
onHorizontalDragEnd: (drag) {
325+
if (drag.primaryVelocity == null) {
326+
return;
327+
}
328+
setState(() {
329+
_isPreview = !_isPreview;
330+
});
331+
},
332+
child: _isPreview
333+
? _markdownPreviewWidget()
334+
: MarkdownTextInput(
335+
(String value) {
336+
setState(() {
337+
_inputText = value;
338+
});
339+
},
340+
_inputText,
341+
controller: _textEditingController,
342+
label: AppLocalizations.of(context)!.markdownTextInputLabel,
343+
),
344+
),
287345
),
288346
);
289347
}
@@ -334,6 +392,15 @@ class _HomeState extends State<Home> {
334392
case MenuItem.save:
335393
await _saveFile();
336394
break;
395+
case MenuItem.print:
396+
await _printFile();
397+
break;
398+
case MenuItem.donate:
399+
await launchUrl(
400+
Uri.parse("https://buymeacoffee.com/adeeteya"),
401+
mode: LaunchMode.externalApplication,
402+
);
403+
break;
337404
}
338405
},
339406
itemBuilder: (context) => [
@@ -391,6 +458,26 @@ class _HomeState extends State<Home> {
391458
],
392459
),
393460
),
461+
PopupMenuItem(
462+
value: MenuItem.print,
463+
child: Row(
464+
children: [
465+
const Icon(Icons.print),
466+
const SizedBox(width: 8),
467+
Text(AppLocalizations.of(context)!.print),
468+
],
469+
),
470+
),
471+
PopupMenuItem(
472+
value: MenuItem.donate,
473+
child: Row(
474+
children: [
475+
const Icon(Icons.volunteer_activism),
476+
const SizedBox(width: 8),
477+
Text(AppLocalizations.of(context)!.donate),
478+
],
479+
),
480+
),
394481
],
395482
),
396483
],

lib/l10n/app_en.arb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,5 +130,29 @@
130130
"confirmAppExitContent": "Are you sure you want to exit this app?",
131131
"@confirmAppExitContent": {
132132
"description": "Content for the Confirm Exit Dialog"
133+
},
134+
"donate": "Donate",
135+
"@donate": {
136+
"description": "Menu Item for Donate"
137+
},
138+
"print": "Print",
139+
"@print": {
140+
"description": "Menu Item for Print"
141+
},
142+
"table": "Table",
143+
"@table": {
144+
"description": "Tooltip for the Table Icon Button"
145+
},
146+
"rows": "Rows",
147+
"@rows": {
148+
"description": "Label for the Rows Text Field in the Table Dialog"
149+
},
150+
"columns": "Columns",
151+
"@columns": {
152+
"description": "Label for the Columns Text Field in the Table Dialog"
153+
},
154+
"insert": "Insert",
155+
"@insert": {
156+
"description": "Text Action for the Insert Table Dialog"
133157
}
134158
}

lib/l10n/app_es.arb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,19 @@
44
"cancel": "Cancelar",
55
"appTitle": "Editor de markdown",
66
"previewToolTip": "Avance",
7-
"switchThemeMenuItem": "Tema de cambio",
8-
"switchViewMenuItem": "Vista de interruptor",
9-
"openFileMenuItem": "Abierto",
7+
"switchThemeMenuItem": "Cambiar tema",
8+
"switchViewMenuItem": "Cambiar vista",
9+
"openFileMenuItem": "Abrir",
1010
"markdownTextInputLabel": "Ingrese su texto de Markdown aquí",
1111
"error": "Error",
1212
"unableToOpenFileError": "No se puede abrir el archivo, intente abrirlo desde la opción de archivo abierto en el menú",
1313
"unableToOpenFileFromMenuError": "No se puede abrir el archivo",
1414
"emptyInputTextContent": "Por favor ingrese algún texto",
15-
"clear": "Claro",
15+
"clear": "Borrar todo",
1616
"clearAllTitle": "Despejar todo",
1717
"clearAllContent": "¿Estás seguro de que quieres borrar todo el texto?",
1818
"saveFileDialogTitle": "Ingrese el nombre del archivo",
19-
"save": "Ahorrar",
19+
"save": "Guardar",
2020
"linkDialogTextTitle": "Texto",
2121
"linkDialogLinkTitle": "Enlace",
2222
"enterLinkTextDialogTitle": "Ingrese el enlace",

0 commit comments

Comments
 (0)