From af2126574846ea0fc79f72ed283e83da84d82c29 Mon Sep 17 00:00:00 2001 From: Aggelos Tselios Date: Tue, 2 Jan 2024 19:25:05 +0200 Subject: [PATCH] Update copyright and text editor view --- lib/config.dart | 2 +- lib/custom_text_styles.dart | 2 +- lib/l10n/app_fi.arb | 42 ++++++++++ lib/l10n/app_hu.arb | 42 ++++++++++ lib/main.dart | 49 +----------- lib/multiline_text_input.dart | 2 +- lib/settings_screen.dart | 2 +- lib/single_line_input.dart | 10 +++ lib/tabbed_text_editor.dart | 144 ++++++++++++++++++++++++++++++++++ lib/text_edit.dart | 113 ++++++++++++-------------- lib/utils.dart | 71 ++++++++++++++++- pubspec.lock | 8 ++ pubspec.yaml | 1 + 13 files changed, 375 insertions(+), 113 deletions(-) create mode 100644 lib/l10n/app_fi.arb create mode 100644 lib/l10n/app_hu.arb create mode 100644 lib/tabbed_text_editor.dart diff --git a/lib/config.dart b/lib/config.dart index 8ca4a91..8be20a8 100644 --- a/lib/config.dart +++ b/lib/config.dart @@ -1,6 +1,6 @@ /* * FlText: A simple and nice-looking text editor. - * Copyright (C) 2023 Aggelos Tselios + * Copyright (C) 2023-2024 Aggelos Tselios * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/lib/custom_text_styles.dart b/lib/custom_text_styles.dart index cd640a8..fda3ca5 100644 --- a/lib/custom_text_styles.dart +++ b/lib/custom_text_styles.dart @@ -1,6 +1,6 @@ /* * FlText: A simple and nice-looking text editor. - * Copyright (C) 2023 Aggelos Tselios + * Copyright (C) 2023-2024 Aggelos Tselios * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/lib/l10n/app_fi.arb b/lib/l10n/app_fi.arb new file mode 100644 index 0000000..4b06811 --- /dev/null +++ b/lib/l10n/app_fi.arb @@ -0,0 +1,42 @@ +{ + "@@locale": "fi", + "settings": "Vettopwo", + "aboutFltext": "Lwo FlTexte...", + "fltextDesc": "FlTexte sal pykettet, nawirende sotscenenjottop nirutgo Edartseg pe Flutter, jokoguvobe sexegtota wininde byrakuvobe nekepe.", + "githubRepo": "GitHub Repositori", + "welcomeToFltext": "Kenn wippetu Efltextseke", + "startByEither": "Apeltäptua joc...", + "creatingAFile": "Ageppetua sal verfäil...", + "or": "jocett...", + "selectAFile": "Mäpxe sal fäil...", + "tooManyFilesSelected": "äää", + "tooManyFilesSelected_t": "äää", + "openAnExistingFile": "Ajextua sal everfäilnde", + "newFile": "Ageppetua verfäil...", + "close": "Gowap", + "beginTypingYourTextHere": "Apeltäptua aniruttuhepa sotscenenetu kipp...", + "loading": "Jukk...", + "errorWhileLoadingFile": "Ekennende jukkopseg fäile!", + "thereWasAnErrorLoadingFile": "There was an error while loading the file: ", + "editFile": "Jotopp fäildene", + "saveFileAt": "Äðeðmogtua fäile wä...", + "operationCancelled": "Operation Cancelled", + "noFileWasSelected": "No filename was selected, possibly because you cancelled the operation (Went back or closed the window). Try again.", + "editorFont": "Editor font", + "editorFont_t": "Set the font used by the text editor. By default, the system font is used.", + "saveSuccess": "Saved succesfully.", + "warning": "Warning", + "androidWarning": "On Android, you won't get a dialog to save the file because it's unsupported by the libraries we use. When you create a new file, you should get a dialog to enter the filename on Android. We know this is frustrating, and we apologize for any extra effort.", + "selectColor": "Select color", + "selectColorShade": "Select color shade", + "selectedColorAndShades": "Selected color and its shades", + "colorScheme": "Color scheme", + "selectColorSchemeUsed": "Select the color scheme used by the application", + "recents": "Nenytte", + "unableToOpenURL": "Unable to open URL", + "anUnknownErrorOccured_URL": "An unknown error occured while attempting to open the URL requested.", + "pleaseEnterFilename": "Please enter the filename for your new text file here. It will be saved in your documents directory on Android/Fuschia. (No extension is added automatically!)", + "filenameTitle": "Enter filename...", + "errorCreatingNewFile": "Error while creating new file", + "cancel": "Dnep" + } \ No newline at end of file diff --git a/lib/l10n/app_hu.arb b/lib/l10n/app_hu.arb new file mode 100644 index 0000000..c9b836e --- /dev/null +++ b/lib/l10n/app_hu.arb @@ -0,0 +1,42 @@ +{ + "@@locale": "hu", + "settings": "Balitaasok", + "aboutFltext": "Szetika mi to Fltext...", + "fltextDesc": "FlText is a blazingly fast, lightweight text editor written in Dart using Flutter, designed to maintain usability without giving away on looks.", + "githubRepo": "GitHub Repository", + "welcomeToFltext": "Dzso repat eis to FlText", + "startByEither": "Arisztie eite...", + "creatingAFile": "Ipeiate en' akta...", + "or": "eidallos...", + "selectAFile": "Ivalastate en' akta...", + "tooManyFilesSelected": "Nagyÿk kaktak ivalastan!", + "tooManyFilesSelected_t": "Nagyÿk kaktak ivalastan. To FlText dÿnattei na enichsei mÿnasza en' akta ana fora ecs tora. Kelme na epilechste mÿnasza en' akta ecs epechsergasian.", + "openAnExistingFile": "Enichstie en' yfistamena akta...", + "newFile": "Neu akta...", + "close": "Csukninn", + "beginTypingYourTextHere": "Arisztie na irite to keimena sei dotte...", + "loading": "Fortosi...", + "errorWhileLoadingFile": "Szfalma eis fortosi c' aktas", + "thereWasAnErrorLoadingFile": "There was an error while loading the file: ", + "editFile": "Epechsergasia c' keimenos", + "saveFileAt": "Save file at...", + "operationCancelled": "Operation Cancelled", + "noFileWasSelected": "No filename was selected, possibly because you cancelled the operation (Went back or closed the window). Try again.", + "editorFont": "Editor font", + "editorFont_t": "Set the font used by the text editor. By default, the system font is used.", + "saveSuccess": "Saved succesfully.", + "warning": "Warning", + "androidWarning": "On Android, you won't get a dialog to save the file because it's unsupported by the libraries we use. When you create a new file, you should get a dialog to enter the filename on Android. We know this is frustrating, and we apologize for any extra effort.", + "selectColor": "Select color", + "selectColorShade": "Select color shade", + "selectedColorAndShades": "Selected color and its shades", + "colorScheme": "Color scheme", + "selectColorSchemeUsed": "Select the color scheme used by the application", + "recents": "Praszfatak", + "unableToOpenURL": "Unable to open URL", + "anUnknownErrorOccured_URL": "An unknown error occured while attempting to open the URL requested.", + "pleaseEnterFilename": "Please enter the filename for your new text file here. It will be saved in your documents directory on Android/Fuschia. (No extension is added automatically!)", + "filenameTitle": "Enter filename...", + "errorCreatingNewFile": "Error while creating new file", + "cancel": "Akerro" +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 3f8ae43..610a790 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,6 @@ /* * FlText: A simple and nice-looking text editor. - * Copyright (C) 2023 Aggelos Tselios + * Copyright (C) 2023-2024 Aggelos Tselios * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,13 +15,12 @@ import 'package:file_picker/file_picker.dart'; import 'package:fltext/custom_text_styles.dart'; import 'package:fltext/settings_screen.dart'; import 'package:fltext/single_line_input.dart'; -import 'package:fltext/text_edit.dart'; +import 'package:fltext/tabbed_text_editor.dart'; import 'package:fltext/utils.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:url_launcher/url_launcher.dart'; import 'package:fltext/config.dart'; import 'package:path_provider/path_provider.dart'; @@ -31,7 +30,6 @@ void main() { runApp(const FlText()); } -final githubURL = Uri.parse("https://github.com/tseli0s/FlText"); final ThemeData darkTheme = ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: Color(accentColor), @@ -133,28 +131,7 @@ class _HomepageState extends State { ListTile( leading: const Icon(Icons.info), title: Text(AppLocalizations.of(context)!.aboutFltext), - onTap: () { - showAboutDialog( - applicationName: "FlText", - applicationVersion: "0.1.0", - applicationIcon: SizedBox.fromSize( - size: const Size.square(48), - child: const Image( - image: AssetImage('assets/logo.png'), - ), - ), - children: [ - Text(AppLocalizations.of(context)!.fltextDesc), - TextButton( - onPressed: () async { - await openURL(githubURL); - }, - child: Text(AppLocalizations.of(context)!.githubRepo), - ) - ], - context: context, - ); - }, + onTap: () => showFltextAbout(context), ), ], ), @@ -292,27 +269,9 @@ class _HomepageState extends State { context, CupertinoPageRoute( builder: (context) { - return TextEditor(filename: filename); + return TabbedTextEditor(filename: filename); }, ), ); } - - Future openURL(Uri url) async { - /* - * Like the documentation says, launchUrl may either throw an exception or return false on failure, - * depending on the error. Here we handle both so we never have to manually debug what went wrong. - */ - try { - if (await launchUrl(url) != true) { - final title = AppLocalizations.of(context)!.unableToOpenURL; - final msg = AppLocalizations.of(context)!.anUnknownErrorOccured_URL; - showErrorDialog(context, title, msg); - } - } catch (e) { - final title = AppLocalizations.of(context)!.unableToOpenURL; - final msg = "Failed to load ${url.toString()}: ${e.toString()}"; - showErrorDialog(context, title, msg); - } - } } diff --git a/lib/multiline_text_input.dart b/lib/multiline_text_input.dart index db1885d..e6aa6cb 100644 --- a/lib/multiline_text_input.dart +++ b/lib/multiline_text_input.dart @@ -1,6 +1,6 @@ /* * FlText: A simple and nice-looking text editor. - * Copyright (C) 2023 Aggelos Tselios + * Copyright (C) 2023-2024 Aggelos Tselios * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/lib/settings_screen.dart b/lib/settings_screen.dart index a557485..db325fe 100644 --- a/lib/settings_screen.dart +++ b/lib/settings_screen.dart @@ -2,7 +2,7 @@ /* * FlText: A simple and nice-looking text editor. - * Copyright (C) 2023 Aggelos Tselios + * Copyright (C) 2023-2024 Aggelos Tselios * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/lib/single_line_input.dart b/lib/single_line_input.dart index 810b0a2..c303e44 100644 --- a/lib/single_line_input.dart +++ b/lib/single_line_input.dart @@ -1,3 +1,13 @@ +/* + * FlText: A simple and nice-looking text editor. + * Copyright (C) 2023-2024 Aggelos Tselios + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; diff --git a/lib/tabbed_text_editor.dart b/lib/tabbed_text_editor.dart new file mode 100644 index 0000000..e3001f6 --- /dev/null +++ b/lib/tabbed_text_editor.dart @@ -0,0 +1,144 @@ +// ignore_for_file: unused_element + +/* + * FlText: A simple and nice-looking text editor. + * Copyright (C) 2023-2024 Aggelos Tselios + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + */ + +import 'dart:io'; + +import 'package:fltext/text_edit.dart'; +import 'package:fltext/utils.dart'; +import 'package:flutter/material.dart'; +import 'package:menu_bar/menu_bar.dart'; + +class TabbedTextEditor extends StatefulWidget { + final List filesOpen = []; + + TabbedTextEditor({super.key, String? filename}) { + filesOpen.add(filename ?? ""); + } + + @override + State createState() => TabbedTextEditorState(); +} + +class TabbedTextEditorState extends State { + @override + Widget build(BuildContext context) { + var l = 0; + final List tabs = []; + final List children = []; + + for (final f in widget.filesOpen) { + tabs.add( + Tab( + icon: const Icon(Icons.file_open), + text: f != "" ? f : "* New file", + ), + ); + children.add( + TextEditor( + filename: f != "" ? f : null, + ), + ); + l++; + } + + /* + return DefaultTabController( + length: l, + child: Scaffold( + appBar: TabBar(tabs: tabs), + body: TabBarView(children: children), + ), + ); + */ + return MenuBarWidget( + barButtons: [ + BarButton( + text: const Text('File'), + submenu: SubMenu( + menuItems: [ + MenuButton( + text: const Text('Save'), + onTap: () {}, + icon: const Icon(Icons.save), + shortcutText: 'Ctrl+S', + ), + const MenuDivider(), + MenuButton( + text: const Text('Save as'), + onTap: () {}, + icon: const Icon(Icons.save), + shortcutText: 'Ctrl+Shift+S', + ), + const MenuDivider(), + MenuButton( + text: const Text('Back to home'), + onTap: () { + Navigator.of(context).popUntil((route) => route.isFirst); + }, + icon: const Icon(Icons.exit_to_app), + ), + const MenuDivider(), + MenuButton( + text: const Text('Exit'), + onTap: () { + exit(0); + }, + icon: const Icon(Icons.exit_to_app), + shortcutText: 'Ctrl+Q', + ), + ], + ), + ), + BarButton( + text: const Text('Edit'), + submenu: SubMenu( + menuItems: [ + MenuButton( + text: const Text('Preferences...'), + onTap: () {}, + ), + ], + ), + ), + BarButton( + text: const Text('Help'), + submenu: SubMenu( + menuItems: [ + MenuButton( + text: const Text('View License'), + onTap: () { + showLicense(context); + }, + ), + MenuButton( + text: const Text('About'), + onTap: () { + showFltextAbout(context); + }, + icon: const Icon(Icons.info), + ), + ], + ), + ), + ], + + // Set the child, i.e. the application under the menu bar + child: DefaultTabController( + length: l, + child: Scaffold( + appBar: TabBar(tabs: tabs), + body: TabBarView(children: children), + ), + ), + ); + } +} diff --git a/lib/text_edit.dart b/lib/text_edit.dart index 29b05f2..7c6fdba 100644 --- a/lib/text_edit.dart +++ b/lib/text_edit.dart @@ -1,6 +1,6 @@ /* * FlText: A simple and nice-looking text editor. - * Copyright (C) 2023 Aggelos Tselios + * Copyright (C) 2023-2024 Aggelos Tselios * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,7 +19,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -// ignore: must_be_immutable class TextEditor extends StatefulWidget { final TextEditingController textController = TextEditingController(); final String? filename; @@ -99,68 +98,56 @@ class TextEditorState extends State { return Scaffold( body: b, - appBar: AppBar( - backgroundColor: Color(accentColor), - title: Text(AppLocalizations.of(context)!.editFile), - titleTextStyle: const TextStyle( - fontWeight: FontWeight.bold, - ), - centerTitle: true, - actions: [ - IconButton( - onPressed: () async { - final contents = widget.textController.text; - String? filename = widget.filename; - if (filename != null) { - final f = File(filename); - f.writeAsString(contents); - final snackBar = SnackBar( - content: Text(AppLocalizations.of(context)!.saveSuccess), - padding: const EdgeInsets.all(16.0), - behavior: SnackBarBehavior.floating, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10.0), - ), - action: SnackBarAction( - label: AppLocalizations.of(context)!.close, - onPressed: () {}, - ), - ); - ScaffoldMessenger.of(context).showSnackBar(snackBar); - } else { - String? result = await FilePicker.platform.saveFile( - dialogTitle: AppLocalizations.of(context)!.saveFileAt, - lockParentWindow: true, - ); + floatingActionButton: FloatingActionButton( + child: const Icon(Icons.save_alt), + onPressed: () async { + final contents = widget.textController.text; + String? filename = widget.filename; + if (filename != null) { + final f = File(filename); + f.writeAsString(contents); + final snackBar = SnackBar( + content: Text(AppLocalizations.of(context)!.saveSuccess), + padding: const EdgeInsets.all(16.0), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10.0), + ), + action: SnackBarAction( + label: AppLocalizations.of(context)!.close, + onPressed: () {}, + ), + ); + ScaffoldMessenger.of(context).showSnackBar(snackBar); + } else { + String? result = await FilePicker.platform.saveFile( + dialogTitle: AppLocalizations.of(context)!.saveFileAt, + lockParentWindow: true, + ); - if (result != null) { - final f = File(result); - f.writeAsString(contents); - final snackBar = SnackBar( - content: Text(AppLocalizations.of(context)!.saveSuccess), - padding: const EdgeInsets.all(16.0), - behavior: SnackBarBehavior.floating, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10.0), - ), - action: SnackBarAction( - label: AppLocalizations.of(context)!.close, - onPressed: () {}, - ), - ); - ScaffoldMessenger.of(context).showSnackBar(snackBar); - } else { - final title = - AppLocalizations.of(context)!.operationCancelled; - final message = - AppLocalizations.of(context)!.noFileWasSelected; - showErrorDialog(context, title, message); - } - } - }, - icon: const Icon(Icons.save), - ) - ], + if (result != null) { + final f = File(result); + f.writeAsString(contents); + final snackBar = SnackBar( + content: Text(AppLocalizations.of(context)!.saveSuccess), + padding: const EdgeInsets.all(16.0), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0), + ), + action: SnackBarAction( + label: AppLocalizations.of(context)!.close, + onPressed: () {}, + ), + ); + ScaffoldMessenger.of(context).showSnackBar(snackBar); + } else { + final title = AppLocalizations.of(context)!.operationCancelled; + final message = AppLocalizations.of(context)!.noFileWasSelected; + showErrorDialog(context, title, message); + } + } + }, ), ); } diff --git a/lib/utils.dart b/lib/utils.dart index 756d45f..233e445 100644 --- a/lib/utils.dart +++ b/lib/utils.dart @@ -1,6 +1,8 @@ +// ignore_for_file: use_build_context_synchronously + /* * FlText: A simple and nice-looking text editor. - * Copyright (C) 2023 Aggelos Tselios + * Copyright (C) 2023-2024 Aggelos Tselios * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -12,6 +14,9 @@ import 'package:flutter/cupertino.dart'; import 'package:flex_color_picker/flex_color_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:url_launcher/url_launcher.dart'; + +final githubURL = Uri.parse("https://github.com/tseli0s/FlText"); void showErrorDialog(BuildContext context, String title, String message) { showCupertinoModalPopup( @@ -95,3 +100,67 @@ Future colorPickerDialog(BuildContext context) async { const BoxConstraints(minHeight: 460, minWidth: 300, maxWidth: 320), ); } + +void showFltextAbout(BuildContext context) { + showAboutDialog( + applicationName: "FlText", + applicationVersion: "0.1.0", + applicationIcon: SizedBox.fromSize( + size: const Size.square(48), + child: const Image( + image: AssetImage('assets/logo.png'), + ), + ), + children: [ + Text(AppLocalizations.of(context)!.fltextDesc), + TextButton( + onPressed: () async { + await openURL(context, githubURL); + }, + child: Text(AppLocalizations.of(context)!.githubRepo), + ) + ], + context: context, + ); +} + +Future openURL(BuildContext context, Uri url) async { + /* + * Like the documentation says, launchUrl may either throw an exception or return false on failure, + * depending on the error. Here we handle both so we never have to manually debug what went wrong. + */ + try { + if (await launchUrl(url) != true) { + final title = AppLocalizations.of(context)!.unableToOpenURL; + final msg = AppLocalizations.of(context)!.anUnknownErrorOccured_URL; + showErrorDialog(context, title, msg); + } + } catch (e) { + final title = AppLocalizations.of(context)!.unableToOpenURL; + final msg = "Failed to load ${url.toString()}: ${e.toString()}"; + showErrorDialog(context, title, msg); + } +} + +void showLicense(BuildContext context) { + showDialog( + context: context, + builder: (context) { + return AlertDialog.adaptive( + title: const Text('FlText License'), + content: const Text( + "This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see ."), + actions: [ + TextButton( + style: TextButton.styleFrom( + textStyle: Theme.of(context).textTheme.labelLarge, + ), + child: Text(AppLocalizations.of(context)!.close), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ], + ); + }); +} diff --git a/pubspec.lock b/pubspec.lock index 4e1b0c7..1b41d97 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -261,6 +261,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.8.0" + menu_bar: + dependency: "direct dev" + description: + name: menu_bar + sha256: ab436747560ce19666fb37c617482085c12d9d9679f1d7e321df8f104fd025a8 + url: "https://pub.dev" + source: hosted + version: "0.5.3" meta: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 94d8d75..3a71c93 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,6 +27,7 @@ dev_dependencies: flutter_lints: ^3.0.0 flutter_launcher_icons: ^0.13.1 + menu_bar: ^0.5.3 flutter_launcher_icons: android: true