From e5b0580acf087db873a21f4b51ca82a6f9fe4955 Mon Sep 17 00:00:00 2001 From: Merlin Schumacher Date: Mon, 23 Dec 2024 01:36:45 +0100 Subject: [PATCH] split view and add dynamic color --- android/app/src/main/AndroidManifest.xml | 16 +- .../app/src/main/res/values-night/styles.xml | 5 +- android/app/src/main/res/values/styles.xml | 5 +- devtools_options.yaml | 3 + lib/main.dart | 56 +++--- lib/views/document_result_view.dart | 177 ++++-------------- lib/widgets/filenameform_widget.dart | 38 ++++ lib/widgets/pdfdisplay_widget.dart | 91 +++++++++ lib/widgets/sharebutton_widget.dart | 20 ++ lib/widgets/textfield_widget.dart | 36 ++++ pubspec.lock | 24 +-- pubspec.yaml | 2 +- 12 files changed, 274 insertions(+), 199 deletions(-) create mode 100644 devtools_options.yaml create mode 100644 lib/widgets/filenameform_widget.dart create mode 100644 lib/widgets/pdfdisplay_widget.dart create mode 100644 lib/widgets/sharebutton_widget.dart create mode 100644 lib/widgets/textfield_widget.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 05cab0e..68d2893 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -17,12 +17,12 @@ while the Flutter UI initializes. After that, this theme continues to determine the Window background behind the Flutter UI. --> + android:name="io.flutter.embedding.android.NormalTheme" + android:resource="@style/NormalTheme" + /> - - + + - - + + - + \ No newline at end of file diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml index 06952be..39a9ca2 100644 --- a/android/app/src/main/res/values-night/styles.xml +++ b/android/app/src/main/res/values-night/styles.xml @@ -1,6 +1,7 @@ - + - + \ No newline at end of file diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index cb1ef88..111ba91 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -1,6 +1,7 @@ - + - + \ No newline at end of file diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/lib/main.dart b/lib/main.dart index 8f3aac0..add7626 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,7 +4,7 @@ import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:google_mlkit_document_scanner/google_mlkit_document_scanner.dart'; import 'package:super_simple_scan/views/document_result_view.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:flex_color_scheme/flex_color_scheme.dart'; +import 'package:dynamic_color/dynamic_color.dart'; void main() { WidgetsFlutterBinding.ensureInitialized(); @@ -15,34 +15,38 @@ void main() { class SuperSimpleScan extends StatelessWidget { const SuperSimpleScan({super.key}); + static final _defaultLightColorScheme = + ColorScheme.fromSwatch(primarySwatch: Colors.blue); + + static final _defaultDarkColorScheme = ColorScheme.fromSwatch( + primarySwatch: Colors.blue, brightness: Brightness.dark); + // This widget is the root of your application. @override Widget build(BuildContext context) { - return MaterialApp( - title: 'Super Simple Scan', - theme: FlexThemeData.light( - appBarElevation: 0.5, - useMaterial3: true, - typography: - Typography.material2021(platform: TargetPlatform.android)), - darkTheme: FlexThemeData.dark( - appBarElevation: 1, - useMaterial3: true, - typography: - Typography.material2021(platform: TargetPlatform.android)), - themeMode: ThemeMode.system, - localizationsDelegates: [ - AppLocalizations.delegate, - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - supportedLocales: const [ - Locale('en'), - Locale('de'), - ], - home: HomePage(title: "Super Simple Scan"), - ); + return DynamicColorBuilder(builder: (lightColorScheme, darkColorScheme) { + return MaterialApp( + title: 'Super Simple Scan', + theme: ThemeData( + colorScheme: lightColorScheme ?? _defaultLightColorScheme, + ), + darkTheme: ThemeData( + colorScheme: darkColorScheme ?? _defaultDarkColorScheme, + ), + themeMode: ThemeMode.system, + localizationsDelegates: [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: const [ + Locale('en'), + Locale('de'), + ], + home: HomePage(title: "Super Simple Scan"), + ); + }); } } diff --git a/lib/views/document_result_view.dart b/lib/views/document_result_view.dart index 328823a..7cac1bb 100644 --- a/lib/views/document_result_view.dart +++ b/lib/views/document_result_view.dart @@ -3,9 +3,10 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:flutter_pdfview/flutter_pdfview.dart'; import 'package:google_mlkit_document_scanner/google_mlkit_document_scanner.dart'; import 'package:share_plus/share_plus.dart'; +import 'package:super_simple_scan/widgets/filenameform_widget.dart'; +import 'package:super_simple_scan/widgets/pdfdisplay_widget.dart'; class DocumentResultView extends StatefulWidget { final DocumentScanningResult document; @@ -20,25 +21,12 @@ class DocumentResultViewState extends State with WidgetsBindingObserver { final GlobalKey _formKey = GlobalKey(); final fileNameFieldController = TextEditingController(text: ""); - int currentPage = 0; - int? totalPages = 0; - bool isReady = false; - String errorMessage = ''; -// Add a pdf suffix to the file name if it is missing - String _addPdfSuffix(String fileName) { - if (!fileName.toLowerCase().endsWith('.pdf')) { - return '$fileName.pdf'; - } - return fileName; - } - - void setDefaultFileName() { - DateTime now = DateTime.now(); - fileNameFieldController.text = - AppLocalizations.of(context)!.defaultFileName(now, now); - fileNameFieldController.selection = TextSelection( - baseOffset: 0, extentOffset: fileNameFieldController.text.length); + @override + void dispose() { + // Clean up the controller when the widget is disposed. + fileNameFieldController.dispose(); + super.dispose(); } @override @@ -60,131 +48,35 @@ class DocumentResultViewState extends State Expanded( child: SizedBox( height: pdfHeight, - child: Stack(children: [ - PDFView( - enableSwipe: true, - pageFling: true, - pageSnap: true, - autoSpacing: true, - filePath: widget.document.pdf!.uri, - defaultPage: currentPage, - swipeHorizontal: true, - fitPolicy: FitPolicy.BOTH, - fitEachPage: true, - preventLinkNavigation: false, - backgroundColor: - Theme.of(context).scaffoldBackgroundColor, - onRender: (pages) { - setState(() { - totalPages = pages; - isReady = true; - setDefaultFileName(); - }); - }, - onPageChanged: (page, total) { - setState(() { - currentPage = page ?? 0; - totalPages = total; - }); - }, - onPageError: (page, error) { - setState(() { - errorMessage = '$page: ${error.toString()}'; - }); - print('$page: ${error.toString()}'); - }, - ), - errorMessage.isEmpty - ? !isReady - ? Center( - child: CircularProgressIndicator(), - ) - : Container() - : Center( - child: Text(errorMessage), - ), - Positioned( - bottom: 0, - width: MediaQuery.of(context).size.width - 32, - child: Container( - height: 50, - alignment: Alignment.center, - padding: const EdgeInsets.all(8.0), - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Theme.of(context) - .scaffoldBackgroundColor - .withAlpha(0), - Theme.of(context).scaffoldBackgroundColor, - ], - ), - ), - child: Text( - "${currentPage + 1} / $totalPages", - style: TextStyle( - color: Theme.of(context).colorScheme.primary, - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ), - ) - ])), + child: PdfDisplayWidget(pdfUrl: widget.document.pdf!.uri)), ), Padding( padding: const EdgeInsets.symmetric(vertical: 16.0), ), - Form( - key: _formKey, - child: Column( - children: [ - TextFormField( - controller: fileNameFieldController, - autofocus: true, - decoration: InputDecoration( - hintText: - AppLocalizations.of(context)!.enterFileNamePrompt, - suffixText: '.pdf', - ), - validator: (String? value) { - if (value == null || value.isEmpty) { - return AppLocalizations.of(context)!.fileNameIsRequired; - } - return null; - }, - onTap: () => fileNameFieldController.selection = - TextSelection( - baseOffset: 0, - extentOffset: fileNameFieldController.text.length), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 16.0), - child: FilledButton.icon( - style: ElevatedButton.styleFrom( - textStyle: TextStyle(fontSize: 20), - ), - onPressed: () { - if (_formKey.currentState!.validate()) { - _shareDocument(widget.document.pdf!, - _addPdfSuffix(fileNameFieldController.text)); - } - }, - label: Text(AppLocalizations.of(context)!.shareButton), - icon: Icon(Icons.share), - ), - ), - ], - ), - ), + FileNameFormWidget( + formKey: _formKey, + fileNameFieldController: fileNameFieldController, + onSubmitted: _shareDocument, + defaultFileName: _getDefaultFileName()), ], ), ), ); } + // Add a pdf suffix to the file name if it is missing + String _addPdfSuffix(String fileName) { + if (!fileName.toLowerCase().endsWith('.pdf')) { + return '$fileName.pdf'; + } + return fileName; + } + + String _getDefaultFileName() { + DateTime now = DateTime.now(); + return AppLocalizations.of(context)!.defaultFileName(now, now); + } + Future _readFileByte(String filePath) async { Uri myUri = Uri.parse(filePath); File audioFile = File.fromUri(myUri); @@ -198,22 +90,19 @@ class DocumentResultViewState extends State return bytes; } - void _shareDocument( - DocumentScanningResultPdf document, String fileName) async { - Uint8List? bytes = await _readFileByte(document.uri); + void _shareDocument() async { + if (!_formKey.currentState!.validate()) { + return; + } + + Uint8List? bytes = await _readFileByte(widget.document.pdf!.uri); if (bytes == null) { return; } + String fileName = _addPdfSuffix(fileNameFieldController.text); Share.shareXFiles( [XFile.fromData(bytes, name: fileName, mimeType: 'application/pdf')], fileNameOverrides: [fileName], ); } - - @override - void dispose() { - // Clean up the controller when the widget is disposed. - fileNameFieldController.dispose(); - super.dispose(); - } } diff --git a/lib/widgets/filenameform_widget.dart b/lib/widgets/filenameform_widget.dart new file mode 100644 index 0000000..09ec7b3 --- /dev/null +++ b/lib/widgets/filenameform_widget.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:super_simple_scan/widgets/sharebutton_widget.dart'; +import 'package:super_simple_scan/widgets/textfield_widget.dart'; + +class FileNameFormWidget extends StatelessWidget { + final GlobalKey _formKey; + final TextEditingController fileNameFieldController; + final void Function() onSubmitted; + final String defaultFileName; + + FileNameFormWidget( + {super.key, + required GlobalKey formKey, + required this.fileNameFieldController, + required this.onSubmitted, + required this.defaultFileName}) + : _formKey = formKey { + fileNameFieldController.text = defaultFileName; + fileNameFieldController.selection = TextSelection( + baseOffset: 0, extentOffset: fileNameFieldController.text.length); + } + + @override + Widget build(BuildContext context) { + return Form( + key: _formKey, + child: Column( + children: [ + FileNameInputTextField(controller: fileNameFieldController), + Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: ShareButton( + onPressed: onSubmitted, + )) + ], + )); + } +} diff --git a/lib/widgets/pdfdisplay_widget.dart b/lib/widgets/pdfdisplay_widget.dart new file mode 100644 index 0000000..cd8ccf0 --- /dev/null +++ b/lib/widgets/pdfdisplay_widget.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_pdfview/flutter_pdfview.dart'; + +class PdfDisplayWidget extends StatefulWidget { + final String pdfUrl; + + const PdfDisplayWidget({super.key, required this.pdfUrl}); + + @override + PdfDisplayWidgetState createState() => PdfDisplayWidgetState(); +} + +class PdfDisplayWidgetState extends State { + int currentPage = 0; + int? totalPages = 0; + bool isReady = false; + String errorMessage = ''; + + @override + Widget build(BuildContext context) { + return Stack(children: [ + PDFView( + enableSwipe: true, + pageFling: true, + pageSnap: true, + autoSpacing: true, + filePath: widget.pdfUrl, + defaultPage: currentPage, + swipeHorizontal: true, + fitPolicy: FitPolicy.BOTH, + fitEachPage: true, + preventLinkNavigation: false, + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + onRender: (pages) { + setState(() { + totalPages = pages; + isReady = true; + }); + }, + onPageChanged: (page, total) { + setState(() { + currentPage = page ?? 0; + totalPages = total; + }); + }, + onPageError: (page, error) { + setState(() { + errorMessage = '$page: ${error.toString()}'; + }); + print('$page: ${error.toString()}'); + }, + ), + errorMessage.isEmpty + ? !isReady + ? Center( + child: CircularProgressIndicator(), + ) + : Container() + : Center( + child: Text(errorMessage), + ), + Positioned( + bottom: 0, + width: MediaQuery.of(context).size.width - 32, + child: Container( + height: 50, + alignment: Alignment.center, + padding: const EdgeInsets.all(8.0), + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Theme.of(context).scaffoldBackgroundColor.withAlpha(0), + Theme.of(context).scaffoldBackgroundColor, + ], + ), + ), + child: Text( + "${currentPage + 1} / $totalPages", + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ) + ]); + } +} diff --git a/lib/widgets/sharebutton_widget.dart b/lib/widgets/sharebutton_widget.dart new file mode 100644 index 0000000..510b1cc --- /dev/null +++ b/lib/widgets/sharebutton_widget.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class ShareButton extends StatelessWidget { + final void Function() onPressed; + + const ShareButton({super.key, required this.onPressed}); + + @override + Widget build(BuildContext context) { + return FilledButton.icon( + style: ElevatedButton.styleFrom( + textStyle: TextStyle(fontSize: 20), + ), + onPressed: onPressed, + label: Text(AppLocalizations.of(context)!.shareButton), + icon: Icon(Icons.share), + ); + } +} diff --git a/lib/widgets/textfield_widget.dart b/lib/widgets/textfield_widget.dart new file mode 100644 index 0000000..e983905 --- /dev/null +++ b/lib/widgets/textfield_widget.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class FileNameInputTextField extends StatelessWidget { + final TextEditingController controller; + final String? Function(String?)? validator; + final void Function(String)? onChanged; + + const FileNameInputTextField({ + super.key, + required this.controller, + this.validator, + this.onChanged, + }); + + @override + Widget build(BuildContext context) { + return TextFormField( + autofocus: true, + controller: controller, + decoration: InputDecoration( + labelText: AppLocalizations.of(context)!.enterFileNamePrompt, + suffixText: '.pdf', + ), + validator: (String? value) { + if (value == null || value.isEmpty) { + return AppLocalizations.of(context)!.fileNameIsRequired; + } + return null; + }, + onChanged: onChanged, + onTap: () => controller.selection = + TextSelection(baseOffset: 0, extentOffset: controller.text.length), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 153d863..b7789e5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -97,6 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + dynamic_color: + dependency: "direct main" + description: + name: dynamic_color + sha256: eae98052fa6e2826bdac3dd2e921c6ce2903be15c6b7f8b6d8a5d49b5086298d + url: "https://pub.dev" + source: hosted + version: "1.7.0" fake_async: dependency: transitive description: @@ -129,22 +137,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" - flex_color_scheme: - dependency: "direct main" - description: - name: flex_color_scheme - sha256: "90f4fe67b9561ae8a4af117df65a8ce9988624025667c54e6d304e65cff77d52" - url: "https://pub.dev" - source: hosted - version: "8.0.2" - flex_seed_scheme: - dependency: transitive - description: - name: flex_seed_scheme - sha256: "7639d2c86268eff84a909026eb169f008064af0fb3696a651b24b0fa24a40334" - url: "https://pub.dev" - source: hosted - version: "3.4.1" flutter: dependency: "direct main" description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 5f6e53a..44687d1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,7 +42,7 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 share_plus: ^10.1.3 - flex_color_scheme: ^8.0.2 + dynamic_color: ^1.7.0 dev_dependencies: flutter_test: