@ -0,0 +1,4 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<resources> |
||||||
|
<string name="app_name">Flutter Quill Demo</string> |
||||||
|
</resources> |
Before Width: | Height: | Size: 401 KiB After Width: | Height: | Size: 401 KiB |
Before Width: | Height: | Size: 348 KiB After Width: | Height: | Size: 348 KiB |
Before Width: | Height: | Size: 342 KiB After Width: | Height: | Size: 342 KiB |
Before Width: | Height: | Size: 279 KiB After Width: | Height: | Size: 279 KiB |
@ -0,0 +1,114 @@ |
|||||||
|
/// GENERATED CODE - DO NOT MODIFY BY HAND |
||||||
|
/// ***************************************************** |
||||||
|
/// FlutterGen |
||||||
|
/// ***************************************************** |
||||||
|
|
||||||
|
// coverage:ignore-file |
||||||
|
// ignore_for_file: type=lint |
||||||
|
// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use |
||||||
|
|
||||||
|
import 'package:flutter/widgets.dart'; |
||||||
|
|
||||||
|
class $AssetsImagesGen { |
||||||
|
const $AssetsImagesGen(); |
||||||
|
|
||||||
|
/// File path: assets/images/screenshot_1.png |
||||||
|
AssetGenImage get screenshot1 => |
||||||
|
const AssetGenImage('assets/images/screenshot_1.png'); |
||||||
|
|
||||||
|
/// File path: assets/images/screenshot_2.png |
||||||
|
AssetGenImage get screenshot2 => |
||||||
|
const AssetGenImage('assets/images/screenshot_2.png'); |
||||||
|
|
||||||
|
/// File path: assets/images/screenshot_3.png |
||||||
|
AssetGenImage get screenshot3 => |
||||||
|
const AssetGenImage('assets/images/screenshot_3.png'); |
||||||
|
|
||||||
|
/// File path: assets/images/screenshot_4.png |
||||||
|
AssetGenImage get screenshot4 => |
||||||
|
const AssetGenImage('assets/images/screenshot_4.png'); |
||||||
|
|
||||||
|
/// List of all assets |
||||||
|
List<AssetGenImage> get values => |
||||||
|
[screenshot1, screenshot2, screenshot3, screenshot4]; |
||||||
|
} |
||||||
|
|
||||||
|
class Assets { |
||||||
|
Assets._(); |
||||||
|
|
||||||
|
static const $AssetsImagesGen images = $AssetsImagesGen(); |
||||||
|
} |
||||||
|
|
||||||
|
class AssetGenImage { |
||||||
|
const AssetGenImage(this._assetName); |
||||||
|
|
||||||
|
final String _assetName; |
||||||
|
|
||||||
|
Image image({ |
||||||
|
Key? key, |
||||||
|
AssetBundle? bundle, |
||||||
|
ImageFrameBuilder? frameBuilder, |
||||||
|
ImageErrorWidgetBuilder? errorBuilder, |
||||||
|
String? semanticLabel, |
||||||
|
bool excludeFromSemantics = false, |
||||||
|
double? scale, |
||||||
|
double? width, |
||||||
|
double? height, |
||||||
|
Color? color, |
||||||
|
Animation<double>? opacity, |
||||||
|
BlendMode? colorBlendMode, |
||||||
|
BoxFit? fit, |
||||||
|
AlignmentGeometry alignment = Alignment.center, |
||||||
|
ImageRepeat repeat = ImageRepeat.noRepeat, |
||||||
|
Rect? centerSlice, |
||||||
|
bool matchTextDirection = false, |
||||||
|
bool gaplessPlayback = false, |
||||||
|
bool isAntiAlias = false, |
||||||
|
String? package, |
||||||
|
FilterQuality filterQuality = FilterQuality.low, |
||||||
|
int? cacheWidth, |
||||||
|
int? cacheHeight, |
||||||
|
}) { |
||||||
|
return Image.asset( |
||||||
|
_assetName, |
||||||
|
key: key, |
||||||
|
bundle: bundle, |
||||||
|
frameBuilder: frameBuilder, |
||||||
|
errorBuilder: errorBuilder, |
||||||
|
semanticLabel: semanticLabel, |
||||||
|
excludeFromSemantics: excludeFromSemantics, |
||||||
|
scale: scale, |
||||||
|
width: width, |
||||||
|
height: height, |
||||||
|
color: color, |
||||||
|
opacity: opacity, |
||||||
|
colorBlendMode: colorBlendMode, |
||||||
|
fit: fit, |
||||||
|
alignment: alignment, |
||||||
|
repeat: repeat, |
||||||
|
centerSlice: centerSlice, |
||||||
|
matchTextDirection: matchTextDirection, |
||||||
|
gaplessPlayback: gaplessPlayback, |
||||||
|
isAntiAlias: isAntiAlias, |
||||||
|
package: package, |
||||||
|
filterQuality: filterQuality, |
||||||
|
cacheWidth: cacheWidth, |
||||||
|
cacheHeight: cacheHeight, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
ImageProvider provider({ |
||||||
|
AssetBundle? bundle, |
||||||
|
String? package, |
||||||
|
}) { |
||||||
|
return AssetImage( |
||||||
|
_assetName, |
||||||
|
bundle: bundle, |
||||||
|
package: package, |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
String get path => _assetName; |
||||||
|
|
||||||
|
String get keyName => _assetName; |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
/// GENERATED CODE - DO NOT MODIFY BY HAND |
||||||
|
/// ***************************************************** |
||||||
|
/// FlutterGen |
||||||
|
/// ***************************************************** |
||||||
|
|
||||||
|
// coverage:ignore-file |
||||||
|
// ignore_for_file: type=lint |
||||||
|
// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use |
||||||
|
|
||||||
|
class FontFamily { |
||||||
|
FontFamily._(); |
||||||
|
|
||||||
|
/// Font family: SF-UI-Display |
||||||
|
static const String sFUIDisplay = 'SF-UI-Display'; |
||||||
|
|
||||||
|
/// Font family: ibarra-real-nova |
||||||
|
static const String ibarraRealNova = 'ibarra-real-nova'; |
||||||
|
|
||||||
|
/// Font family: monospace |
||||||
|
static const String monospace = 'monospace'; |
||||||
|
|
||||||
|
/// Font family: nunito |
||||||
|
static const String nunito = 'nunito'; |
||||||
|
|
||||||
|
/// Font family: pacifico |
||||||
|
static const String pacifico = 'pacifico'; |
||||||
|
|
||||||
|
/// Font family: roboto-mono |
||||||
|
static const String robotoMono = 'roboto-mono'; |
||||||
|
|
||||||
|
/// Font family: sans-serif |
||||||
|
static const String sansSerif = 'sans-serif'; |
||||||
|
|
||||||
|
/// Font family: serif |
||||||
|
static const String serif = 'serif'; |
||||||
|
|
||||||
|
/// Font family: square-peg |
||||||
|
static const String squarePeg = 'square-peg'; |
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
|
@ -1,879 +0,0 @@ |
|||||||
// ignore_for_file: avoid_redundant_argument_values |
|
||||||
|
|
||||||
import 'dart:async' show Timer; |
|
||||||
import 'dart:convert'; |
|
||||||
import 'dart:io' show File; |
|
||||||
import 'dart:ui'; |
|
||||||
|
|
||||||
import 'package:desktop_drop/desktop_drop.dart'; |
|
||||||
import 'package:file_picker/file_picker.dart'; |
|
||||||
import 'package:flutter/foundation.dart'; |
|
||||||
import 'package:flutter/material.dart'; |
|
||||||
import 'package:flutter/services.dart'; |
|
||||||
import 'package:flutter_quill/extensions.dart'; |
|
||||||
import 'package:flutter_quill/flutter_quill.dart'; |
|
||||||
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; |
|
||||||
import 'package:flutter_quill_extensions/logic/services/image_picker/image_picker.dart'; |
|
||||||
import 'package:flutter_quill_extensions/presentation/embeds/widgets/image.dart'; |
|
||||||
import 'package:image_cropper/image_cropper.dart'; |
|
||||||
import 'package:path/path.dart' as path; |
|
||||||
import 'package:path_provider/path_provider.dart'; |
|
||||||
import 'package:quill_html_converter/quill_html_converter.dart'; |
|
||||||
|
|
||||||
import '../samples/sample_data.dart'; |
|
||||||
import '../samples/sample_data_nomedia.dart'; |
|
||||||
import '../samples/sample_data_testing.dart'; |
|
||||||
import '../widgets/time_stamp_embed_widget.dart'; |
|
||||||
import 'read_only_page.dart'; |
|
||||||
|
|
||||||
enum _SelectionType { |
|
||||||
none, |
|
||||||
word, |
|
||||||
// line, |
|
||||||
} |
|
||||||
|
|
||||||
class HomePage extends StatefulWidget { |
|
||||||
const HomePage({super.key}); |
|
||||||
|
|
||||||
@override |
|
||||||
_HomePageState createState() => _HomePageState(); |
|
||||||
} |
|
||||||
|
|
||||||
class _HomePageState extends State<HomePage> { |
|
||||||
late final QuillController _controller; |
|
||||||
late final Future<void> _loadDocumentFromAssetsFuture; |
|
||||||
final FocusNode _focusNode = FocusNode(); |
|
||||||
Timer? _selectAllTimer; |
|
||||||
_SelectionType _selectionType = _SelectionType.none; |
|
||||||
var _isReadOnly = false; |
|
||||||
|
|
||||||
@override |
|
||||||
void dispose() { |
|
||||||
_selectAllTimer?.cancel(); |
|
||||||
// Dispose the controller to free resources |
|
||||||
_controller.dispose(); |
|
||||||
super.dispose(); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
void initState() { |
|
||||||
super.initState(); |
|
||||||
_loadDocumentFromAssetsFuture = _loadFromAssets(); |
|
||||||
} |
|
||||||
|
|
||||||
Future<void> _loadFromAssets() async { |
|
||||||
try { |
|
||||||
final doc = Document.fromJson(sampleDataTesting); |
|
||||||
_controller = QuillController( |
|
||||||
document: doc, |
|
||||||
selection: const TextSelection.collapsed(offset: 0), |
|
||||||
); |
|
||||||
} catch (error) { |
|
||||||
print(error.toString()); |
|
||||||
final doc = Document() |
|
||||||
..insert(0, 'Error while loading the document: ${error.toString()}'); |
|
||||||
_controller = QuillController( |
|
||||||
document: doc, |
|
||||||
selection: const TextSelection.collapsed(offset: 0), |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build(BuildContext context) { |
|
||||||
return FutureBuilder( |
|
||||||
future: _loadDocumentFromAssetsFuture, |
|
||||||
builder: (context, snapshot) { |
|
||||||
if (snapshot.connectionState == ConnectionState.waiting) { |
|
||||||
return const Scaffold( |
|
||||||
body: Center(child: CircularProgressIndicator.adaptive()), |
|
||||||
); |
|
||||||
} |
|
||||||
return Scaffold( |
|
||||||
appBar: AppBar( |
|
||||||
title: const Text( |
|
||||||
'Flutter Quill', |
|
||||||
), |
|
||||||
actions: [ |
|
||||||
IconButton( |
|
||||||
tooltip: 'Print to log', |
|
||||||
onPressed: () { |
|
||||||
print( |
|
||||||
jsonEncode(_controller.document.toDelta().toJson()), |
|
||||||
); |
|
||||||
ScaffoldMessenger.of(context).showSnackBar( |
|
||||||
const SnackBar( |
|
||||||
content: Text( |
|
||||||
'The quill delta json has been printed to the log.', |
|
||||||
), |
|
||||||
), |
|
||||||
); |
|
||||||
}, |
|
||||||
icon: const Icon( |
|
||||||
Icons.print, |
|
||||||
), |
|
||||||
), |
|
||||||
IconButton( |
|
||||||
tooltip: 'Toggle read only', |
|
||||||
onPressed: () { |
|
||||||
setState(() => _isReadOnly = !_isReadOnly); |
|
||||||
}, |
|
||||||
icon: Icon( |
|
||||||
_isReadOnly ? Icons.lock : Icons.edit, |
|
||||||
), |
|
||||||
), |
|
||||||
IconButton( |
|
||||||
onPressed: () { |
|
||||||
_insertTimeStamp( |
|
||||||
_controller, |
|
||||||
DateTime.now().toString(), |
|
||||||
); |
|
||||||
}, |
|
||||||
icon: const Icon(Icons.add_alarm_rounded), |
|
||||||
), |
|
||||||
IconButton( |
|
||||||
onPressed: () => showDialog( |
|
||||||
context: context, |
|
||||||
builder: (context) => AlertDialog( |
|
||||||
content: Text( |
|
||||||
_controller.document.toPlainText( |
|
||||||
[ |
|
||||||
...FlutterQuillEmbeds.editorBuilders(), |
|
||||||
TimeStampEmbedBuilderWidget() |
|
||||||
], |
|
||||||
), |
|
||||||
), |
|
||||||
), |
|
||||||
), |
|
||||||
icon: const Icon(Icons.text_fields_rounded), |
|
||||||
) |
|
||||||
], |
|
||||||
), |
|
||||||
drawer: Drawer( |
|
||||||
child: ListView( |
|
||||||
children: [ |
|
||||||
DrawerHeader( |
|
||||||
child: IconButton( |
|
||||||
tooltip: 'Open document by json delta', |
|
||||||
onPressed: () async { |
|
||||||
final scaffoldMessenger = ScaffoldMessenger.of(context); |
|
||||||
final navigator = Navigator.of(context); |
|
||||||
try { |
|
||||||
final result = await FilePicker.platform.pickFiles( |
|
||||||
dialogTitle: 'Pick json delta', |
|
||||||
type: FileType.custom, |
|
||||||
allowedExtensions: ['json'], |
|
||||||
allowMultiple: false, |
|
||||||
); |
|
||||||
final file = result?.files.firstOrNull; |
|
||||||
final filePath = file?.path; |
|
||||||
if (file == null || filePath == null) { |
|
||||||
return; |
|
||||||
} |
|
||||||
final jsonString = await XFile(filePath).readAsString(); |
|
||||||
_controller.document = |
|
||||||
Document.fromJson(jsonDecode(jsonString)); |
|
||||||
} catch (e) { |
|
||||||
print( |
|
||||||
'Error while loading json delta file: ${e.toString()}', |
|
||||||
); |
|
||||||
scaffoldMessenger.showSnackBar( |
|
||||||
SnackBar( |
|
||||||
content: Text( |
|
||||||
'Error while loading json delta file: ${e.toString()}', |
|
||||||
), |
|
||||||
), |
|
||||||
); |
|
||||||
} finally { |
|
||||||
navigator.pop(); |
|
||||||
} |
|
||||||
}, |
|
||||||
icon: const Icon(Icons.file_copy), |
|
||||||
), |
|
||||||
), |
|
||||||
ListTile( |
|
||||||
title: const Text('Load sample data'), |
|
||||||
onTap: () async { |
|
||||||
final scaffoldMessenger = ScaffoldMessenger.of(context); |
|
||||||
final navigator = Navigator.of(context); |
|
||||||
try { |
|
||||||
_controller.document = Document.fromJson( |
|
||||||
sampleData, |
|
||||||
); |
|
||||||
} catch (e) { |
|
||||||
print( |
|
||||||
'Error while loading json delta file: ${e.toString()}', |
|
||||||
); |
|
||||||
scaffoldMessenger.showSnackBar(SnackBar( |
|
||||||
content: Text( |
|
||||||
'Error while loading json delta file: ${e.toString()}', |
|
||||||
), |
|
||||||
)); |
|
||||||
} finally { |
|
||||||
navigator.pop(); |
|
||||||
} |
|
||||||
}, |
|
||||||
), |
|
||||||
ListTile( |
|
||||||
title: const Text('Load sample data with no media'), |
|
||||||
onTap: () async { |
|
||||||
final scaffoldMessenger = ScaffoldMessenger.of(context); |
|
||||||
final navigator = Navigator.of(context); |
|
||||||
try { |
|
||||||
_controller.document = Document.fromJson( |
|
||||||
sampleDataNoMedia, |
|
||||||
); |
|
||||||
} catch (e) { |
|
||||||
print( |
|
||||||
'Error while loading json delta file: ${e.toString()}', |
|
||||||
); |
|
||||||
scaffoldMessenger.showSnackBar(SnackBar( |
|
||||||
content: Text( |
|
||||||
'Error while loading json delta file: ${e.toString()}', |
|
||||||
), |
|
||||||
)); |
|
||||||
} finally { |
|
||||||
navigator.pop(); |
|
||||||
} |
|
||||||
}, |
|
||||||
), |
|
||||||
ListTile( |
|
||||||
title: const Text('Load testing sample data '), |
|
||||||
onTap: () async { |
|
||||||
final scaffoldMessenger = ScaffoldMessenger.of(context); |
|
||||||
final navigator = Navigator.of(context); |
|
||||||
try { |
|
||||||
_controller.document = Document.fromJson( |
|
||||||
sampleDataTesting, |
|
||||||
); |
|
||||||
} catch (e) { |
|
||||||
print( |
|
||||||
'Error while loading json delta file: ${e.toString()}', |
|
||||||
); |
|
||||||
scaffoldMessenger.showSnackBar(SnackBar( |
|
||||||
content: Text( |
|
||||||
'Error while loading json delta file: ${e.toString()}', |
|
||||||
), |
|
||||||
)); |
|
||||||
} finally { |
|
||||||
navigator.pop(); |
|
||||||
} |
|
||||||
}, |
|
||||||
), |
|
||||||
ListTile( |
|
||||||
title: const Text('Convert to/from HTML'), |
|
||||||
onTap: () async { |
|
||||||
final scaffoldMessenger = ScaffoldMessenger.of(context); |
|
||||||
final navigator = Navigator.of(context); |
|
||||||
try { |
|
||||||
final html = _controller.document.toDelta().toHtml(); |
|
||||||
_controller.document = |
|
||||||
Document.fromDelta(DeltaHtmlExt.fromHtml(html)); |
|
||||||
} catch (e) { |
|
||||||
scaffoldMessenger.showSnackBar( |
|
||||||
SnackBar( |
|
||||||
content: Text( |
|
||||||
'Error while convert to/from HTML: ${e.toString()}', |
|
||||||
), |
|
||||||
), |
|
||||||
); |
|
||||||
} finally { |
|
||||||
navigator.pop(); |
|
||||||
} |
|
||||||
}, |
|
||||||
), |
|
||||||
_buildMenuBar(context), |
|
||||||
], |
|
||||||
), |
|
||||||
), |
|
||||||
body: _buildWelcomeEditor(context), |
|
||||||
); |
|
||||||
}, |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
bool _onTripleClickSelection() { |
|
||||||
final controller = _controller; |
|
||||||
|
|
||||||
_selectAllTimer?.cancel(); |
|
||||||
_selectAllTimer = null; |
|
||||||
|
|
||||||
// If you want to select all text after paragraph, uncomment this line |
|
||||||
// if (_selectionType == _SelectionType.line) { |
|
||||||
// final selection = TextSelection( |
|
||||||
// baseOffset: 0, |
|
||||||
// extentOffset: controller.document.length, |
|
||||||
// ); |
|
||||||
|
|
||||||
// controller.updateSelection(selection, ChangeSource.REMOTE); |
|
||||||
|
|
||||||
// _selectionType = _SelectionType.none; |
|
||||||
|
|
||||||
// return true; |
|
||||||
// } |
|
||||||
|
|
||||||
if (controller.selection.isCollapsed) { |
|
||||||
_selectionType = _SelectionType.none; |
|
||||||
} |
|
||||||
|
|
||||||
if (_selectionType == _SelectionType.none) { |
|
||||||
_selectionType = _SelectionType.word; |
|
||||||
_startTripleClickTimer(); |
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
if (_selectionType == _SelectionType.word) { |
|
||||||
final child = controller.document.queryChild( |
|
||||||
controller.selection.baseOffset, |
|
||||||
); |
|
||||||
final offset = child.node?.documentOffset ?? 0; |
|
||||||
final length = child.node?.length ?? 0; |
|
||||||
|
|
||||||
final selection = TextSelection( |
|
||||||
baseOffset: offset, |
|
||||||
extentOffset: offset + length, |
|
||||||
); |
|
||||||
|
|
||||||
controller.updateSelection(selection, ChangeSource.remote); |
|
||||||
|
|
||||||
// _selectionType = _SelectionType.line; |
|
||||||
|
|
||||||
_selectionType = _SelectionType.none; |
|
||||||
|
|
||||||
_startTripleClickTimer(); |
|
||||||
|
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
return false; |
|
||||||
} |
|
||||||
|
|
||||||
void _startTripleClickTimer() { |
|
||||||
_selectAllTimer = Timer(const Duration(milliseconds: 900), () { |
|
||||||
_selectionType = _SelectionType.none; |
|
||||||
}); |
|
||||||
} |
|
||||||
|
|
||||||
OnDragDoneCallback get _onDragDone { |
|
||||||
return (details) { |
|
||||||
final scaffoldMessenger = ScaffoldMessenger.of(context); |
|
||||||
final file = details.files.first; |
|
||||||
final isSupported = |
|
||||||
imageFileExtensions.any((ext) => file.name.endsWith(ext)); |
|
||||||
if (!isSupported) { |
|
||||||
scaffoldMessenger.showSnackBar( |
|
||||||
SnackBar( |
|
||||||
content: Text( |
|
||||||
'Only images are supported right now: ${file.mimeType}, ${file.name}, ${file.path}, $imageFileExtensions', |
|
||||||
), |
|
||||||
), |
|
||||||
); |
|
||||||
return; |
|
||||||
} |
|
||||||
_controller.insertImageBlock( |
|
||||||
imageSource: file.path, |
|
||||||
); |
|
||||||
scaffoldMessenger.showSnackBar( |
|
||||||
const SnackBar( |
|
||||||
content: Text('Image is inserted.'), |
|
||||||
), |
|
||||||
); |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
QuillEditor get quillEditor { |
|
||||||
if (kIsWeb) { |
|
||||||
return QuillEditor( |
|
||||||
focusNode: _focusNode, |
|
||||||
scrollController: ScrollController(), |
|
||||||
configurations: QuillEditorConfigurations( |
|
||||||
builder: (context, rawEditor) { |
|
||||||
return DropTarget( |
|
||||||
onDragDone: _onDragDone, |
|
||||||
child: rawEditor, |
|
||||||
); |
|
||||||
}, |
|
||||||
placeholder: 'Add content', |
|
||||||
readOnly: false, |
|
||||||
scrollable: true, |
|
||||||
autoFocus: false, |
|
||||||
expands: false, |
|
||||||
padding: EdgeInsets.zero, |
|
||||||
onTapUp: (details, p1) { |
|
||||||
return _onTripleClickSelection(); |
|
||||||
}, |
|
||||||
customStyles: const DefaultStyles( |
|
||||||
h1: DefaultTextBlockStyle( |
|
||||||
TextStyle( |
|
||||||
fontSize: 32, |
|
||||||
height: 1.15, |
|
||||||
fontWeight: FontWeight.w300, |
|
||||||
), |
|
||||||
VerticalSpacing(16, 0), |
|
||||||
VerticalSpacing(0, 0), |
|
||||||
null, |
|
||||||
), |
|
||||||
sizeSmall: TextStyle(fontSize: 9), |
|
||||||
), |
|
||||||
embedBuilders: [ |
|
||||||
...FlutterQuillEmbeds.editorWebBuilders(), |
|
||||||
TimeStampEmbedBuilderWidget() |
|
||||||
], |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
||||||
return QuillEditor( |
|
||||||
configurations: QuillEditorConfigurations( |
|
||||||
builder: (context, rawEditor) { |
|
||||||
return DropTarget( |
|
||||||
onDragDone: _onDragDone, |
|
||||||
child: rawEditor, |
|
||||||
); |
|
||||||
}, |
|
||||||
placeholder: 'Add content', |
|
||||||
readOnly: _isReadOnly, |
|
||||||
autoFocus: false, |
|
||||||
enableSelectionToolbar: isMobile(supportWeb: false), |
|
||||||
expands: false, |
|
||||||
padding: EdgeInsets.zero, |
|
||||||
onImagePaste: _onImagePaste, |
|
||||||
onTapUp: (details, p1) { |
|
||||||
return _onTripleClickSelection(); |
|
||||||
}, |
|
||||||
customStyles: const DefaultStyles( |
|
||||||
h1: DefaultTextBlockStyle( |
|
||||||
TextStyle( |
|
||||||
fontSize: 32, |
|
||||||
height: 1.15, |
|
||||||
fontWeight: FontWeight.w300, |
|
||||||
), |
|
||||||
VerticalSpacing(16, 0), |
|
||||||
VerticalSpacing(0, 0), |
|
||||||
null, |
|
||||||
), |
|
||||||
sizeSmall: TextStyle(fontSize: 9), |
|
||||||
subscript: TextStyle( |
|
||||||
fontFamily: 'SF-UI-Display', |
|
||||||
fontFeatures: [FontFeature.subscripts()], |
|
||||||
), |
|
||||||
superscript: TextStyle( |
|
||||||
fontFamily: 'SF-UI-Display', |
|
||||||
fontFeatures: [FontFeature.superscripts()], |
|
||||||
), |
|
||||||
), |
|
||||||
embedBuilders: [ |
|
||||||
...FlutterQuillEmbeds.editorBuilders( |
|
||||||
imageEmbedConfigurations: |
|
||||||
const QuillEditorImageEmbedConfigurations(), |
|
||||||
), |
|
||||||
TimeStampEmbedBuilderWidget() |
|
||||||
], |
|
||||||
), |
|
||||||
scrollController: ScrollController(), |
|
||||||
focusNode: _focusNode, |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
/// When inserting an image |
|
||||||
OnImageInsertCallback get onImageInsert { |
|
||||||
return (image, controller) async { |
|
||||||
final croppedFile = await ImageCropper().cropImage( |
|
||||||
sourcePath: image, |
|
||||||
aspectRatioPresets: [ |
|
||||||
CropAspectRatioPreset.square, |
|
||||||
CropAspectRatioPreset.ratio3x2, |
|
||||||
CropAspectRatioPreset.original, |
|
||||||
CropAspectRatioPreset.ratio4x3, |
|
||||||
CropAspectRatioPreset.ratio16x9 |
|
||||||
], |
|
||||||
uiSettings: [ |
|
||||||
AndroidUiSettings( |
|
||||||
toolbarTitle: 'Cropper', |
|
||||||
toolbarColor: Colors.deepOrange, |
|
||||||
toolbarWidgetColor: Colors.white, |
|
||||||
initAspectRatio: CropAspectRatioPreset.original, |
|
||||||
lockAspectRatio: false, |
|
||||||
), |
|
||||||
IOSUiSettings( |
|
||||||
title: 'Cropper', |
|
||||||
), |
|
||||||
WebUiSettings( |
|
||||||
context: context, |
|
||||||
), |
|
||||||
], |
|
||||||
); |
|
||||||
final newImage = croppedFile?.path; |
|
||||||
if (newImage == null) { |
|
||||||
return; |
|
||||||
} |
|
||||||
if (isWeb()) { |
|
||||||
controller.insertImageBlock(imageSource: newImage); |
|
||||||
return; |
|
||||||
} |
|
||||||
final newSavedImage = await _onImagePickCallback(File(newImage)); |
|
||||||
controller.insertImageBlock(imageSource: newSavedImage); |
|
||||||
}; |
|
||||||
} |
|
||||||
|
|
||||||
QuillToolbar get quillToolbar { |
|
||||||
final customButtons = [ |
|
||||||
QuillToolbarCustomButtonOptions( |
|
||||||
icon: const Icon(Icons.ac_unit), |
|
||||||
onPressed: () { |
|
||||||
debugPrint('snowflake1'); |
|
||||||
}, |
|
||||||
), |
|
||||||
QuillToolbarCustomButtonOptions( |
|
||||||
icon: const Icon(Icons.ac_unit), |
|
||||||
onPressed: () { |
|
||||||
debugPrint('snowflake2'); |
|
||||||
}, |
|
||||||
), |
|
||||||
QuillToolbarCustomButtonOptions( |
|
||||||
icon: const Icon(Icons.ac_unit), |
|
||||||
onPressed: () { |
|
||||||
debugPrint('snowflake3'); |
|
||||||
}, |
|
||||||
), |
|
||||||
]; |
|
||||||
if (kIsWeb) { |
|
||||||
return QuillToolbar( |
|
||||||
configurations: QuillToolbarConfigurations( |
|
||||||
customButtons: customButtons, |
|
||||||
embedButtons: FlutterQuillEmbeds.toolbarButtons( |
|
||||||
cameraButtonOptions: const QuillToolbarCameraButtonOptions(), |
|
||||||
imageButtonOptions: QuillToolbarImageButtonOptions( |
|
||||||
imageButtonConfigurations: QuillToolbarImageConfigurations( |
|
||||||
onImageInsertedCallback: (image) async {}, |
|
||||||
onImageInsertCallback: onImageInsert, |
|
||||||
), |
|
||||||
), |
|
||||||
), |
|
||||||
buttonOptions: QuillToolbarButtonOptions( |
|
||||||
base: QuillToolbarBaseButtonOptions( |
|
||||||
afterButtonPressed: _focusNode.requestFocus, |
|
||||||
), |
|
||||||
), |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
||||||
if (isDesktop(supportWeb: false)) { |
|
||||||
return QuillToolbar( |
|
||||||
configurations: QuillToolbarConfigurations( |
|
||||||
customButtons: customButtons, |
|
||||||
embedButtons: FlutterQuillEmbeds.toolbarButtons( |
|
||||||
cameraButtonOptions: const QuillToolbarCameraButtonOptions(), |
|
||||||
imageButtonOptions: QuillToolbarImageButtonOptions( |
|
||||||
imageButtonConfigurations: QuillToolbarImageConfigurations( |
|
||||||
onImageInsertedCallback: (image) async { |
|
||||||
ScaffoldMessenger.of(context).showSnackBar( |
|
||||||
const SnackBar( |
|
||||||
content: Text('Image inserted'), |
|
||||||
), |
|
||||||
); |
|
||||||
}, |
|
||||||
), |
|
||||||
), |
|
||||||
), |
|
||||||
showAlignmentButtons: true, |
|
||||||
buttonOptions: QuillToolbarButtonOptions( |
|
||||||
base: QuillToolbarBaseButtonOptions( |
|
||||||
afterButtonPressed: _focusNode.requestFocus, |
|
||||||
), |
|
||||||
), |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
||||||
return QuillToolbar( |
|
||||||
configurations: QuillToolbarConfigurations( |
|
||||||
customButtons: customButtons, |
|
||||||
embedButtons: FlutterQuillEmbeds.toolbarButtons( |
|
||||||
cameraButtonOptions: const QuillToolbarCameraButtonOptions(), |
|
||||||
videoButtonOptions: QuillToolbarVideoButtonOptions( |
|
||||||
videoConfigurations: QuillToolbarVideoConfigurations( |
|
||||||
onVideoInsertedCallback: (video) => |
|
||||||
_onVideoPickCallback(File(video)), |
|
||||||
), |
|
||||||
), |
|
||||||
imageButtonOptions: QuillToolbarImageButtonOptions( |
|
||||||
imageButtonConfigurations: QuillToolbarImageConfigurations( |
|
||||||
onImageInsertCallback: onImageInsert, |
|
||||||
onImageInsertedCallback: (image) async { |
|
||||||
ScaffoldMessenger.of(context).showSnackBar( |
|
||||||
const SnackBar( |
|
||||||
content: Text('Image inserted'), |
|
||||||
), |
|
||||||
); |
|
||||||
}, |
|
||||||
), |
|
||||||
// provide a callback to enable picking images from device. |
|
||||||
// if omit, "image" button only allows adding images from url. |
|
||||||
// same goes for videos. |
|
||||||
// onImagePickCallback: _onImagePickCallback, |
|
||||||
// uncomment to provide a custom "pick from" dialog. |
|
||||||
// mediaPickSettingSelector: _selectMediaPickSetting, |
|
||||||
// uncomment to provide a custom "pick from" dialog. |
|
||||||
// cameraPickSettingSelector: _selectCameraPickSetting, |
|
||||||
), |
|
||||||
// videoButtonOptions: QuillToolbarVideoButtonOptions( |
|
||||||
// onVideoPickCallback: _onVideoPickCallback, |
|
||||||
// ), |
|
||||||
), |
|
||||||
showAlignmentButtons: true, |
|
||||||
buttonOptions: QuillToolbarButtonOptions( |
|
||||||
base: QuillToolbarBaseButtonOptions( |
|
||||||
afterButtonPressed: _focusNode.requestFocus, |
|
||||||
), |
|
||||||
), |
|
||||||
), |
|
||||||
// afterButtonPressed: _focusNode.requestFocus, |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
Widget _buildWelcomeEditor(BuildContext context) { |
|
||||||
// BUG in web!! should not releated to this pull request |
|
||||||
/// |
|
||||||
///══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═════════════════════ |
|
||||||
///══════════════════════════════════════ |
|
||||||
// The following bool object was thrown building MediaQuery |
|
||||||
//(MediaQueryData(size: Size(769.0, 1205.0), |
|
||||||
// devicePixelRatio: 1.0, textScaleFactor: 1.0, platformBrightness: |
|
||||||
//Brightness.dark, padding: |
|
||||||
// EdgeInsets.zero, viewPadding: EdgeInsets.zero, viewInsets: |
|
||||||
// EdgeInsets.zero, |
|
||||||
// systemGestureInsets: |
|
||||||
// EdgeInsets.zero, alwaysUse24HourFormat: false, accessibleNavigation: |
|
||||||
// false, |
|
||||||
// highContrast: false, |
|
||||||
// disableAnimations: false, invertColors: false, boldText: false, |
|
||||||
//navigationMode: traditional, |
|
||||||
// gestureSettings: DeviceGestureSettings(touchSlop: null), displayFeatures: |
|
||||||
// [] |
|
||||||
// )): |
|
||||||
// false |
|
||||||
// The relevant error-causing widget was: |
|
||||||
// SafeArea |
|
||||||
/// |
|
||||||
/// |
|
||||||
return SafeArea( |
|
||||||
child: QuillProvider( |
|
||||||
configurations: QuillConfigurations( |
|
||||||
controller: _controller, |
|
||||||
sharedConfigurations: QuillSharedConfigurations( |
|
||||||
animationConfigurations: QuillAnimationConfigurations.enableAll(), |
|
||||||
locale: const Locale( |
|
||||||
'de', |
|
||||||
), // won't take affect since we defined FlutterQuillLocalizations.delegate |
|
||||||
), |
|
||||||
), |
|
||||||
child: Column( |
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween, |
|
||||||
children: <Widget>[ |
|
||||||
Expanded( |
|
||||||
flex: 15, |
|
||||||
child: Container( |
|
||||||
padding: const EdgeInsets.only(left: 16, right: 16), |
|
||||||
child: quillEditor, |
|
||||||
), |
|
||||||
), |
|
||||||
if (!_isReadOnly) |
|
||||||
kIsWeb |
|
||||||
? Expanded( |
|
||||||
child: Container( |
|
||||||
padding: const EdgeInsets.symmetric( |
|
||||||
vertical: 16, horizontal: 8), |
|
||||||
child: quillToolbar, |
|
||||||
)) |
|
||||||
: Container( |
|
||||||
child: quillToolbar, |
|
||||||
) |
|
||||||
], |
|
||||||
), |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
// Future<String?> _openFileSystemPickerForDesktop(BuildContext context) |
|
||||||
// async { |
|
||||||
// return await FilesystemPicker.open( |
|
||||||
// context: context, |
|
||||||
// rootDirectory: await getApplicationDocumentsDirectory(), |
|
||||||
// fsType: FilesystemType.file, |
|
||||||
// fileTileSelectMode: FileTileSelectMode.wholeTile, |
|
||||||
// ); |
|
||||||
// } |
|
||||||
|
|
||||||
// Renders the image picked by imagePicker from local file storage |
|
||||||
// You can also upload the picked image to any server (eg : AWS s3 |
|
||||||
// or Firebase) and then return the uploaded image URL. |
|
||||||
Future<String> _onImagePickCallback(File file) async { |
|
||||||
// Copies the picked file from temporary cache to applications directory |
|
||||||
final appDocDir = await getApplicationDocumentsDirectory(); |
|
||||||
// final copiedFile = |
|
||||||
// await file.copy('${appDocDir.path}/${path.basename(file.path)}'); |
|
||||||
final copiedFile = await file.copy(path.join( |
|
||||||
appDocDir.path, |
|
||||||
'${DateTime.now().toIso8601String()}${path.extension(file.path)}', |
|
||||||
)); |
|
||||||
return copiedFile.path.toString(); |
|
||||||
} |
|
||||||
|
|
||||||
// Future<String?> _webImagePickImpl( |
|
||||||
// OnImagePickCallback onImagePickCallback) async { |
|
||||||
// final result = await FilePicker.platform.pickFiles(); |
|
||||||
// if (result == null) { |
|
||||||
// return null; |
|
||||||
// } |
|
||||||
|
|
||||||
// // Take first, because we don't allow picking multiple files. |
|
||||||
// final fileName = result.files.first.name; |
|
||||||
// final file = File(fileName); |
|
||||||
|
|
||||||
// return onImagePickCallback(file); |
|
||||||
// } |
|
||||||
|
|
||||||
// Renders the video picked by imagePicker from local file storage |
|
||||||
// You can also upload the picked video to any server (eg : AWS s3 |
|
||||||
// or Firebase) and then return the uploaded video URL. |
|
||||||
Future<String> _onVideoPickCallback(File file) async { |
|
||||||
// Copies the picked file from temporary cache to applications directory |
|
||||||
final appDocDir = await getApplicationDocumentsDirectory(); |
|
||||||
final copiedFile = |
|
||||||
await file.copy('${appDocDir.path}/${path.basename(file.path)}'); |
|
||||||
return copiedFile.path.toString(); |
|
||||||
} |
|
||||||
|
|
||||||
// // ignore: unused_element |
|
||||||
// Future<MediaPickSetting?> _selectMediaPickSetting(BuildContext context) => |
|
||||||
// showDialog<MediaPickSetting>( |
|
||||||
// context: context, |
|
||||||
// builder: (ctx) => AlertDialog( |
|
||||||
// contentPadding: EdgeInsets.zero, |
|
||||||
// content: Column( |
|
||||||
// mainAxisSize: MainAxisSize.min, |
|
||||||
// children: [ |
|
||||||
// TextButton.icon( |
|
||||||
// icon: const Icon(Icons.collections), |
|
||||||
// label: const Text('Gallery'), |
|
||||||
// onPressed: () => Navigator.pop(ctx, |
|
||||||
// MediaPickSetting.gallery), |
|
||||||
// ), |
|
||||||
// TextButton.icon( |
|
||||||
// icon: const Icon(Icons.link), |
|
||||||
// label: const Text('Link'), |
|
||||||
// onPressed: () => Navigator.pop(ctx, MediaPickSetting.link), |
|
||||||
// ) |
|
||||||
// ], |
|
||||||
// ), |
|
||||||
// ), |
|
||||||
// ); |
|
||||||
|
|
||||||
// // ignore: unused_element |
|
||||||
// Future<MediaPickSetting?> _selectCameraPickSetting(BuildContext context) => |
|
||||||
// showDialog<MediaPickSetting>( |
|
||||||
// context: context, |
|
||||||
// builder: (ctx) => AlertDialog( |
|
||||||
// contentPadding: EdgeInsets.zero, |
|
||||||
// content: Column( |
|
||||||
// mainAxisSize: MainAxisSize.min, |
|
||||||
// children: [ |
|
||||||
// TextButton.icon( |
|
||||||
// icon: const Icon(Icons.camera), |
|
||||||
// label: const Text('Capture a photo'), |
|
||||||
// onPressed: () => Navigator.pop(ctx, MediaPickSetting.camera), |
|
||||||
// ), |
|
||||||
// TextButton.icon( |
|
||||||
// icon: const Icon(Icons.video_call), |
|
||||||
// label: const Text('Capture a video'), |
|
||||||
// onPressed: () => Navigator.pop(ctx, MediaPickSetting.video), |
|
||||||
// ) |
|
||||||
// ], |
|
||||||
// ), |
|
||||||
// ), |
|
||||||
// ); |
|
||||||
|
|
||||||
Widget _buildMenuBar(BuildContext context) { |
|
||||||
final size = MediaQuery.sizeOf(context); |
|
||||||
return Column( |
|
||||||
mainAxisAlignment: MainAxisAlignment.center, |
|
||||||
children: [ |
|
||||||
Divider( |
|
||||||
thickness: 2, |
|
||||||
indent: size.width * 0.1, |
|
||||||
endIndent: size.width * 0.1, |
|
||||||
), |
|
||||||
ListTile( |
|
||||||
title: const Center( |
|
||||||
child: Text( |
|
||||||
'Read only demo', |
|
||||||
)), |
|
||||||
dense: true, |
|
||||||
visualDensity: VisualDensity.compact, |
|
||||||
onTap: _openReadOnlyPage, |
|
||||||
), |
|
||||||
Divider( |
|
||||||
thickness: 2, |
|
||||||
indent: size.width * 0.1, |
|
||||||
endIndent: size.width * 0.1, |
|
||||||
), |
|
||||||
], |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
void _openReadOnlyPage() { |
|
||||||
Navigator.pop(super.context); |
|
||||||
Navigator.push( |
|
||||||
super.context, |
|
||||||
MaterialPageRoute( |
|
||||||
builder: (context) => const ReadOnlyPage(), |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
Future<String> _onImagePaste(Uint8List imageBytes) async { |
|
||||||
// Saves the image to applications directory |
|
||||||
final appDocDir = await getApplicationDocumentsDirectory(); |
|
||||||
final file = await File( |
|
||||||
'${appDocDir.path}/${path.basename('${DateTime.now().millisecondsSinceEpoch}.png')}', |
|
||||||
).writeAsBytes(imageBytes, flush: true); |
|
||||||
return file.path.toString(); |
|
||||||
} |
|
||||||
|
|
||||||
static void _insertTimeStamp(QuillController controller, String string) { |
|
||||||
controller.document.insert(controller.selection.extentOffset, '\n'); |
|
||||||
controller.updateSelection( |
|
||||||
TextSelection.collapsed( |
|
||||||
offset: controller.selection.extentOffset + 1, |
|
||||||
), |
|
||||||
ChangeSource.local, |
|
||||||
); |
|
||||||
|
|
||||||
controller.document.insert( |
|
||||||
controller.selection.extentOffset, |
|
||||||
TimeStampEmbed(string), |
|
||||||
); |
|
||||||
|
|
||||||
controller.updateSelection( |
|
||||||
TextSelection.collapsed( |
|
||||||
offset: controller.selection.extentOffset + 1, |
|
||||||
), |
|
||||||
ChangeSource.local, |
|
||||||
); |
|
||||||
|
|
||||||
controller.document.insert(controller.selection.extentOffset, ' '); |
|
||||||
controller.updateSelection( |
|
||||||
TextSelection.collapsed( |
|
||||||
offset: controller.selection.extentOffset + 1, |
|
||||||
), |
|
||||||
ChangeSource.local, |
|
||||||
); |
|
||||||
|
|
||||||
controller.document.insert(controller.selection.extentOffset, '\n'); |
|
||||||
controller.updateSelection( |
|
||||||
TextSelection.collapsed( |
|
||||||
offset: controller.selection.extentOffset + 1, |
|
||||||
), |
|
||||||
ChangeSource.local, |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
@ -1,81 +0,0 @@ |
|||||||
// ignore_for_file: avoid_redundant_argument_values |
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart'; |
|
||||||
import 'package:flutter/material.dart'; |
|
||||||
import 'package:flutter_quill/extensions.dart'; |
|
||||||
import 'package:flutter_quill/flutter_quill.dart'; |
|
||||||
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; |
|
||||||
|
|
||||||
import '../widgets/demo_scaffold.dart'; |
|
||||||
|
|
||||||
class ReadOnlyPage extends StatefulWidget { |
|
||||||
const ReadOnlyPage({super.key}); |
|
||||||
|
|
||||||
@override |
|
||||||
_ReadOnlyPageState createState() => _ReadOnlyPageState(); |
|
||||||
} |
|
||||||
|
|
||||||
class _ReadOnlyPageState extends State<ReadOnlyPage> { |
|
||||||
final FocusNode _focusNode = FocusNode(); |
|
||||||
|
|
||||||
bool _edit = false; |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build(BuildContext context) { |
|
||||||
return DemoScaffold( |
|
||||||
documentFilename: isDesktop(supportWeb: false) |
|
||||||
? 'assets/sample_data_nomedia.json' |
|
||||||
: 'sample_data_nomedia.json', |
|
||||||
builder: _buildContent, |
|
||||||
showToolbar: _edit == true, |
|
||||||
floatingActionButton: FloatingActionButton.extended( |
|
||||||
label: Text(_edit == true ? 'Done' : 'Edit'), |
|
||||||
onPressed: _toggleEdit, |
|
||||||
icon: Icon(_edit == true ? Icons.check : Icons.edit), |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
Widget _buildContent(BuildContext context, QuillController? controller) { |
|
||||||
var quillEditor = QuillEditor( |
|
||||||
configurations: QuillEditorConfigurations( |
|
||||||
expands: false, |
|
||||||
padding: EdgeInsets.zero, |
|
||||||
embedBuilders: kIsWeb |
|
||||||
? FlutterQuillEmbeds.editorWebBuilders() |
|
||||||
: FlutterQuillEmbeds.editorBuilders(), |
|
||||||
scrollable: true, |
|
||||||
autoFocus: true, |
|
||||||
), |
|
||||||
scrollController: ScrollController(), |
|
||||||
focusNode: _focusNode, |
|
||||||
// readOnly: !_edit, |
|
||||||
); |
|
||||||
if (kIsWeb) { |
|
||||||
quillEditor = QuillEditor( |
|
||||||
configurations: QuillEditorConfigurations( |
|
||||||
autoFocus: true, |
|
||||||
expands: false, |
|
||||||
padding: EdgeInsets.zero, |
|
||||||
embedBuilders: FlutterQuillEmbeds.editorWebBuilders(), |
|
||||||
scrollable: true, |
|
||||||
), |
|
||||||
scrollController: ScrollController(), |
|
||||||
focusNode: _focusNode, |
|
||||||
); |
|
||||||
} |
|
||||||
return Container( |
|
||||||
decoration: BoxDecoration( |
|
||||||
border: Border.all(color: Colors.grey.shade200), |
|
||||||
), |
|
||||||
padding: const EdgeInsets.all(8), |
|
||||||
child: quillEditor, |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
void _toggleEdit() { |
|
||||||
setState(() { |
|
||||||
_edit = !_edit; |
|
||||||
}); |
|
||||||
} |
|
||||||
} |
|
@ -1,13 +0,0 @@ |
|||||||
import 'package:flutter/material.dart'; |
|
||||||
import 'package:flutter_quill/flutter_quill.dart'; |
|
||||||
|
|
||||||
class TestingHomePage extends StatelessWidget { |
|
||||||
const TestingHomePage({super.key}); |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build(BuildContext context) { |
|
||||||
return const Scaffold( |
|
||||||
appBar: QuillToolbar(), |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,13 @@ |
|||||||
|
import 'package:flutter/material.dart' |
||||||
|
show ScaffoldMessenger, ScaffoldMessengerState, SnackBar; |
||||||
|
import 'package:flutter/widgets.dart' show BuildContext, Text; |
||||||
|
|
||||||
|
extension ScaffoldMessengerStateExt on ScaffoldMessengerState { |
||||||
|
void showText(String text) { |
||||||
|
showSnackBar(SnackBar(content: Text(text))); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
extension BuildContextExt on BuildContext { |
||||||
|
ScaffoldMessengerState get messenger => ScaffoldMessenger.of(this); |
||||||
|
} |
@ -0,0 +1,53 @@ |
|||||||
|
import 'package:flutter/material.dart'; |
||||||
|
|
||||||
|
class HomeScreenExampleItem extends StatelessWidget { |
||||||
|
const HomeScreenExampleItem({ |
||||||
|
required this.title, |
||||||
|
required this.icon, |
||||||
|
required this.text, |
||||||
|
required this.onPressed, |
||||||
|
super.key, |
||||||
|
}); |
||||||
|
final String title; |
||||||
|
final Widget icon; |
||||||
|
final String text; |
||||||
|
final VoidCallback onPressed; |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(BuildContext context) { |
||||||
|
return Column( |
||||||
|
children: [ |
||||||
|
SizedBox( |
||||||
|
height: 200, |
||||||
|
width: double.infinity, |
||||||
|
child: GestureDetector( |
||||||
|
onTap: onPressed, |
||||||
|
child: Card( |
||||||
|
child: SingleChildScrollView( |
||||||
|
child: Column( |
||||||
|
children: [ |
||||||
|
const SizedBox(height: 2), |
||||||
|
Text( |
||||||
|
title, |
||||||
|
style: Theme.of(context).textTheme.titleLarge, |
||||||
|
), |
||||||
|
const SizedBox(height: 8), |
||||||
|
icon, |
||||||
|
const SizedBox(height: 8), |
||||||
|
Padding( |
||||||
|
padding: const EdgeInsets.all(8), |
||||||
|
child: Text( |
||||||
|
text, |
||||||
|
style: Theme.of(context).textTheme.bodyMedium, |
||||||
|
), |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
], |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,188 @@ |
|||||||
|
import 'dart:convert' show jsonDecode; |
||||||
|
|
||||||
|
import 'package:cross_file/cross_file.dart'; |
||||||
|
import 'package:file_picker/file_picker.dart' show FilePicker, FileType; |
||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:flutter_quill/flutter_quill.dart'; |
||||||
|
|
||||||
|
import '../../extensions/scaffold_messenger.dart'; |
||||||
|
import '../../quill/quill_screen.dart'; |
||||||
|
import '../../quill/samples/quill_default_sample.dart'; |
||||||
|
import '../../quill/samples/quill_images_sample.dart'; |
||||||
|
import '../../quill/samples/quill_text_sample.dart'; |
||||||
|
import '../../quill/samples/quill_videos_sample.dart'; |
||||||
|
import '../../settings/widgets/settings_screen.dart'; |
||||||
|
import 'example_item.dart'; |
||||||
|
|
||||||
|
class HomeScreen extends StatelessWidget { |
||||||
|
const HomeScreen({super.key}); |
||||||
|
|
||||||
|
static const routeName = '/home'; |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(BuildContext context) { |
||||||
|
return Scaffold( |
||||||
|
appBar: AppBar( |
||||||
|
title: const Text('Flutter Quill Demo'), |
||||||
|
), |
||||||
|
drawer: Drawer( |
||||||
|
child: ListView( |
||||||
|
children: [ |
||||||
|
const DrawerHeader( |
||||||
|
child: Text( |
||||||
|
'Flutter Quill Demo', |
||||||
|
), |
||||||
|
), |
||||||
|
ListTile( |
||||||
|
title: const Text('Settings'), |
||||||
|
leading: const Icon(Icons.settings), |
||||||
|
onTap: () { |
||||||
|
Navigator.of(context) |
||||||
|
..pop() |
||||||
|
..pushNamed(SettingsScreen.routeName); |
||||||
|
}, |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
), |
||||||
|
body: SafeArea( |
||||||
|
child: Column( |
||||||
|
children: [ |
||||||
|
SizedBox( |
||||||
|
width: double.infinity, |
||||||
|
child: Text( |
||||||
|
'Welcome to Flutter Quill Demo!', |
||||||
|
style: Theme.of(context).textTheme.titleLarge, |
||||||
|
textAlign: TextAlign.center, |
||||||
|
), |
||||||
|
), |
||||||
|
Expanded( |
||||||
|
child: ListView( |
||||||
|
padding: const EdgeInsets.all(16), |
||||||
|
children: [ |
||||||
|
HomeScreenExampleItem( |
||||||
|
title: 'Default', |
||||||
|
icon: const Icon( |
||||||
|
Icons.home, |
||||||
|
size: 50, |
||||||
|
), |
||||||
|
text: |
||||||
|
'If you want to see how the editor work with default content, ' |
||||||
|
'see any samples or you are working on it', |
||||||
|
onPressed: () => Navigator.of(context).pushNamed( |
||||||
|
QuillScreen.routeName, |
||||||
|
arguments: QuillScreenArgs( |
||||||
|
document: Document.fromJson(quillDefaultSample), |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
const SizedBox(height: 4), |
||||||
|
HomeScreenExampleItem( |
||||||
|
title: 'Images', |
||||||
|
icon: const Icon( |
||||||
|
Icons.image, |
||||||
|
size: 50, |
||||||
|
), |
||||||
|
text: 'If you want to see how the editor work with images, ' |
||||||
|
'see any samples or you are working on it', |
||||||
|
onPressed: () => Navigator.of(context).pushNamed( |
||||||
|
QuillScreen.routeName, |
||||||
|
arguments: QuillScreenArgs( |
||||||
|
document: Document.fromJson(quillImagesSample), |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
const SizedBox(height: 4), |
||||||
|
HomeScreenExampleItem( |
||||||
|
title: 'Videos', |
||||||
|
icon: const Icon( |
||||||
|
Icons.video_chat, |
||||||
|
size: 50, |
||||||
|
), |
||||||
|
text: 'If you want to see how the editor work with videos, ' |
||||||
|
'see any samples or you are working on it', |
||||||
|
onPressed: () => Navigator.of(context).pushNamed( |
||||||
|
QuillScreen.routeName, |
||||||
|
arguments: QuillScreenArgs( |
||||||
|
document: Document.fromJson(quillVideosSample), |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
HomeScreenExampleItem( |
||||||
|
title: 'Text', |
||||||
|
icon: const Icon( |
||||||
|
Icons.edit_document, |
||||||
|
size: 50, |
||||||
|
), |
||||||
|
text: 'If you want to see how the editor work with text, ' |
||||||
|
'see any samples or you are working on it', |
||||||
|
onPressed: () => Navigator.of(context).pushNamed( |
||||||
|
QuillScreen.routeName, |
||||||
|
arguments: QuillScreenArgs( |
||||||
|
document: Document.fromJson(quillTextSample), |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
HomeScreenExampleItem( |
||||||
|
title: 'Open a document by delta json', |
||||||
|
icon: const Icon( |
||||||
|
Icons.file_copy, |
||||||
|
size: 50, |
||||||
|
), |
||||||
|
text: 'If you want to load a document by delta json file', |
||||||
|
onPressed: () async { |
||||||
|
final scaffoldMessenger = ScaffoldMessenger.of(context); |
||||||
|
final navigator = Navigator.of(context); |
||||||
|
try { |
||||||
|
final result = await FilePicker.platform.pickFiles( |
||||||
|
dialogTitle: 'Pick json delta', |
||||||
|
type: FileType.custom, |
||||||
|
allowedExtensions: ['json'], |
||||||
|
allowMultiple: false, |
||||||
|
); |
||||||
|
final file = result?.files.firstOrNull; |
||||||
|
final filePath = file?.path; |
||||||
|
if (file == null || filePath == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
final jsonString = await XFile(filePath).readAsString(); |
||||||
|
|
||||||
|
navigator.pushNamed( |
||||||
|
QuillScreen.routeName, |
||||||
|
arguments: QuillScreenArgs( |
||||||
|
document: Document.fromJson(jsonDecode(jsonString)), |
||||||
|
), |
||||||
|
); |
||||||
|
} catch (e) { |
||||||
|
print( |
||||||
|
'Error while loading json delta file: ${e.toString()}', |
||||||
|
); |
||||||
|
scaffoldMessenger.showText( |
||||||
|
'Error while loading json delta file: ${e.toString()}', |
||||||
|
); |
||||||
|
} |
||||||
|
}, |
||||||
|
), |
||||||
|
HomeScreenExampleItem( |
||||||
|
title: 'Empty', |
||||||
|
icon: const Icon( |
||||||
|
Icons.insert_drive_file, |
||||||
|
size: 50, |
||||||
|
), |
||||||
|
text: 'Want start clean? be my guest', |
||||||
|
onPressed: () => Navigator.of(context).pushNamed( |
||||||
|
QuillScreen.routeName, |
||||||
|
arguments: QuillScreenArgs( |
||||||
|
document: Document(), |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -1,6 +1,7 @@ |
|||||||
import 'dart:convert'; |
import 'dart:convert' show jsonDecode, jsonEncode; |
||||||
|
|
||||||
import 'package:flutter/material.dart'; |
import 'package:flutter/material.dart' show Icons; |
||||||
|
import 'package:flutter/widgets.dart'; |
||||||
import 'package:flutter_quill/flutter_quill.dart'; |
import 'package:flutter_quill/flutter_quill.dart'; |
||||||
|
|
||||||
class TimeStampEmbed extends Embeddable { |
class TimeStampEmbed extends Embeddable { |
@ -0,0 +1,116 @@ |
|||||||
|
import 'dart:io' as io show Directory, File; |
||||||
|
|
||||||
|
import 'package:cached_network_image/cached_network_image.dart' |
||||||
|
show CachedNetworkImageProvider; |
||||||
|
import 'package:desktop_drop/desktop_drop.dart' show DropTarget; |
||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:flutter_quill/extensions.dart' show isAndroid, isIOS, isWeb; |
||||||
|
import 'package:flutter_quill/flutter_quill.dart'; |
||||||
|
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; |
||||||
|
import 'package:flutter_quill_extensions/presentation/embeds/widgets/image.dart' |
||||||
|
show getImageProviderByImageSource, imageFileExtensions; |
||||||
|
import 'package:path/path.dart' as path; |
||||||
|
|
||||||
|
import '../extensions/scaffold_messenger.dart'; |
||||||
|
import 'embeds/timestamp_embed.dart'; |
||||||
|
|
||||||
|
class MyQuillEditor extends StatelessWidget { |
||||||
|
const MyQuillEditor({ |
||||||
|
required this.configurations, |
||||||
|
required this.scrollController, |
||||||
|
required this.focusNode, |
||||||
|
super.key, |
||||||
|
}); |
||||||
|
|
||||||
|
final QuillEditorConfigurations configurations; |
||||||
|
final ScrollController scrollController; |
||||||
|
final FocusNode focusNode; |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(BuildContext context) { |
||||||
|
return QuillEditor( |
||||||
|
scrollController: scrollController, |
||||||
|
focusNode: focusNode, |
||||||
|
configurations: configurations.copyWith( |
||||||
|
scrollable: true, |
||||||
|
placeholder: 'Start writting your notes...', |
||||||
|
padding: const EdgeInsets.all(16), |
||||||
|
onImagePaste: (imageBytes) async { |
||||||
|
if (isWeb()) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
// We will save it to system temporary files |
||||||
|
final newFileName = '${DateTime.now().toIso8601String()}.png'; |
||||||
|
final newPath = path.join( |
||||||
|
io.Directory.systemTemp.path, |
||||||
|
newFileName, |
||||||
|
); |
||||||
|
final file = await io.File( |
||||||
|
newPath, |
||||||
|
).writeAsBytes(imageBytes, flush: true); |
||||||
|
return file.path; |
||||||
|
}, |
||||||
|
embedBuilders: [ |
||||||
|
...(isWeb() |
||||||
|
? FlutterQuillEmbeds.editorWebBuilders() |
||||||
|
: FlutterQuillEmbeds.editorBuilders( |
||||||
|
imageEmbedConfigurations: QuillEditorImageEmbedConfigurations( |
||||||
|
imageErrorWidgetBuilder: (context, error, stackTrace) { |
||||||
|
return Text( |
||||||
|
'Error while loading an image: ${error.toString()}', |
||||||
|
); |
||||||
|
}, |
||||||
|
imageProviderBuilder: (imageUrl) { |
||||||
|
// cached_network_image is supported |
||||||
|
// only for Android, iOS and web |
||||||
|
|
||||||
|
// We will use it only if image from network |
||||||
|
if (isAndroid(supportWeb: false) || |
||||||
|
isIOS(supportWeb: false) || |
||||||
|
isWeb()) { |
||||||
|
if (isHttpBasedUrl(imageUrl)) { |
||||||
|
return CachedNetworkImageProvider( |
||||||
|
imageUrl, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
return getImageProviderByImageSource( |
||||||
|
imageUrl, |
||||||
|
imageProviderBuilder: null, |
||||||
|
assetsPrefix: QuillSharedExtensionsConfigurations.get( |
||||||
|
context: context) |
||||||
|
.assetsPrefix, |
||||||
|
); |
||||||
|
}, |
||||||
|
), |
||||||
|
)), |
||||||
|
TimeStampEmbedBuilderWidget(), |
||||||
|
], |
||||||
|
builder: (context, rawEditor) { |
||||||
|
// The `desktop_drop` plugin doesn't support iOS platform for now |
||||||
|
if (isIOS(supportWeb: false)) { |
||||||
|
return rawEditor; |
||||||
|
} |
||||||
|
return DropTarget( |
||||||
|
onDragDone: (details) { |
||||||
|
final scaffoldMessenger = ScaffoldMessenger.of(context); |
||||||
|
final file = details.files.first; |
||||||
|
final isSupported = imageFileExtensions.any(file.name.endsWith); |
||||||
|
if (!isSupported) { |
||||||
|
scaffoldMessenger.showText( |
||||||
|
'Only images are supported right now: ${file.mimeType}, ${file.name}, ${file.path}, $imageFileExtensions', |
||||||
|
); |
||||||
|
return; |
||||||
|
} |
||||||
|
context.requireQuillController.insertImageBlock( |
||||||
|
imageSource: file.path, |
||||||
|
); |
||||||
|
scaffoldMessenger.showText('Image is inserted.'); |
||||||
|
}, |
||||||
|
child: rawEditor, |
||||||
|
); |
||||||
|
}, |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,141 @@ |
|||||||
|
import 'dart:convert' show jsonEncode; |
||||||
|
|
||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:flutter_quill/flutter_quill.dart'; |
||||||
|
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart' |
||||||
|
show FlutterQuillEmbeds, QuillSharedExtensionsConfigurations; |
||||||
|
|
||||||
|
import 'package:quill_html_converter/quill_html_converter.dart'; |
||||||
|
import 'package:share_plus/share_plus.dart' show Share; |
||||||
|
|
||||||
|
import '../extensions/scaffold_messenger.dart'; |
||||||
|
import '../shared/widgets/home_screen_button.dart'; |
||||||
|
import 'quill_editor.dart'; |
||||||
|
import 'quill_toolbar.dart'; |
||||||
|
|
||||||
|
@immutable |
||||||
|
class QuillScreenArgs { |
||||||
|
const QuillScreenArgs({required this.document}); |
||||||
|
|
||||||
|
final Document document; |
||||||
|
} |
||||||
|
|
||||||
|
class QuillScreen extends StatefulWidget { |
||||||
|
const QuillScreen({ |
||||||
|
required this.args, |
||||||
|
super.key, |
||||||
|
}); |
||||||
|
|
||||||
|
final QuillScreenArgs args; |
||||||
|
|
||||||
|
static const routeName = '/quill'; |
||||||
|
|
||||||
|
@override |
||||||
|
State<QuillScreen> createState() => _QuillScreenState(); |
||||||
|
} |
||||||
|
|
||||||
|
class _QuillScreenState extends State<QuillScreen> { |
||||||
|
final _controller = QuillController.basic(); |
||||||
|
final _editorFocusNode = FocusNode(); |
||||||
|
final _editorScrollController = ScrollController(); |
||||||
|
var _isReadOnly = false; |
||||||
|
|
||||||
|
@override |
||||||
|
void initState() { |
||||||
|
super.initState(); |
||||||
|
_controller.document = widget.args.document; |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
void dispose() { |
||||||
|
_controller.dispose(); |
||||||
|
_editorFocusNode.dispose(); |
||||||
|
_editorScrollController.dispose(); |
||||||
|
super.dispose(); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(BuildContext context) { |
||||||
|
return Scaffold( |
||||||
|
appBar: AppBar( |
||||||
|
title: const Text('Flutter Quill'), |
||||||
|
actions: [ |
||||||
|
IconButton( |
||||||
|
tooltip: 'Load with HTML', |
||||||
|
onPressed: () { |
||||||
|
final html = _controller.document.toDelta().toHtml(); |
||||||
|
_controller.document = |
||||||
|
Document.fromDelta(DeltaHtmlExt.fromHtml(html)); |
||||||
|
}, |
||||||
|
icon: const Icon(Icons.html), |
||||||
|
), |
||||||
|
IconButton( |
||||||
|
tooltip: 'Share', |
||||||
|
onPressed: () { |
||||||
|
final plainText = _controller.document.toPlainText( |
||||||
|
FlutterQuillEmbeds.defaultEditorBuilders(), |
||||||
|
); |
||||||
|
if (plainText.trim().isEmpty) { |
||||||
|
ScaffoldMessenger.of(context).showText( |
||||||
|
"We can't share empty document, please enter some text first", |
||||||
|
); |
||||||
|
return; |
||||||
|
} |
||||||
|
Share.share(plainText); |
||||||
|
}, |
||||||
|
icon: const Icon(Icons.share), |
||||||
|
), |
||||||
|
IconButton( |
||||||
|
tooltip: 'Print to log', |
||||||
|
onPressed: () { |
||||||
|
print( |
||||||
|
jsonEncode(_controller.document.toDelta().toJson()), |
||||||
|
); |
||||||
|
ScaffoldMessenger.of(context).showText( |
||||||
|
'The quill delta json has been printed to the log.', |
||||||
|
); |
||||||
|
}, |
||||||
|
icon: const Icon(Icons.print), |
||||||
|
), |
||||||
|
const HomeScreenButton(), |
||||||
|
], |
||||||
|
), |
||||||
|
body: QuillProvider( |
||||||
|
configurations: QuillConfigurations( |
||||||
|
controller: _controller, |
||||||
|
sharedConfigurations: QuillSharedConfigurations( |
||||||
|
animationConfigurations: QuillAnimationConfigurations.disableAll(), |
||||||
|
extraConfigurations: const { |
||||||
|
QuillSharedExtensionsConfigurations.key: |
||||||
|
QuillSharedExtensionsConfigurations( |
||||||
|
assetsPrefix: 'assets', |
||||||
|
), |
||||||
|
}, |
||||||
|
), |
||||||
|
), |
||||||
|
child: Column( |
||||||
|
children: [ |
||||||
|
if (!_isReadOnly) const MyQuillToolbar(), |
||||||
|
Builder( |
||||||
|
builder: (context) { |
||||||
|
return Expanded( |
||||||
|
child: MyQuillEditor( |
||||||
|
configurations: QuillEditorConfigurations( |
||||||
|
readOnly: _isReadOnly, |
||||||
|
), |
||||||
|
scrollController: _editorScrollController, |
||||||
|
focusNode: _editorFocusNode, |
||||||
|
), |
||||||
|
); |
||||||
|
}, |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
), |
||||||
|
floatingActionButton: FloatingActionButton( |
||||||
|
child: Icon(_isReadOnly ? Icons.lock : Icons.edit), |
||||||
|
onPressed: () => setState(() => _isReadOnly = !_isReadOnly), |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,289 @@ |
|||||||
|
import 'dart:io' as io show File; |
||||||
|
|
||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart'; |
||||||
|
import 'package:flutter_quill/extensions.dart' show isAndroid, isIOS, isWeb; |
||||||
|
import 'package:flutter_quill/flutter_quill.dart'; |
||||||
|
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; |
||||||
|
import 'package:image_cropper/image_cropper.dart'; |
||||||
|
import 'package:path/path.dart' as path; |
||||||
|
import 'package:path_provider/path_provider.dart' |
||||||
|
show getApplicationDocumentsDirectory; |
||||||
|
|
||||||
|
import '../extensions/scaffold_messenger.dart'; |
||||||
|
import '../settings/cubit/settings_cubit.dart'; |
||||||
|
import 'embeds/timestamp_embed.dart'; |
||||||
|
|
||||||
|
class MyQuillToolbar extends StatelessWidget { |
||||||
|
const MyQuillToolbar({super.key}); |
||||||
|
|
||||||
|
Future<void> onImageInsertWithCropping( |
||||||
|
String image, |
||||||
|
QuillController controller, |
||||||
|
BuildContext context, |
||||||
|
) async { |
||||||
|
final croppedFile = await ImageCropper().cropImage( |
||||||
|
sourcePath: image, |
||||||
|
aspectRatioPresets: [ |
||||||
|
CropAspectRatioPreset.square, |
||||||
|
CropAspectRatioPreset.ratio3x2, |
||||||
|
CropAspectRatioPreset.original, |
||||||
|
CropAspectRatioPreset.ratio4x3, |
||||||
|
CropAspectRatioPreset.ratio16x9 |
||||||
|
], |
||||||
|
uiSettings: [ |
||||||
|
AndroidUiSettings( |
||||||
|
toolbarTitle: 'Cropper', |
||||||
|
toolbarColor: Colors.deepOrange, |
||||||
|
toolbarWidgetColor: Colors.white, |
||||||
|
initAspectRatio: CropAspectRatioPreset.original, |
||||||
|
lockAspectRatio: false, |
||||||
|
), |
||||||
|
IOSUiSettings( |
||||||
|
title: 'Cropper', |
||||||
|
), |
||||||
|
WebUiSettings( |
||||||
|
context: context, |
||||||
|
), |
||||||
|
], |
||||||
|
); |
||||||
|
final newImage = croppedFile?.path; |
||||||
|
if (newImage == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
if (isWeb()) { |
||||||
|
controller.insertImageBlock(imageSource: newImage); |
||||||
|
return; |
||||||
|
} |
||||||
|
final newSavedImage = await saveImage(io.File(newImage)); |
||||||
|
controller.insertImageBlock(imageSource: newSavedImage); |
||||||
|
} |
||||||
|
|
||||||
|
Future<void> onImageInsert(String image, QuillController controller) async { |
||||||
|
if (isWeb()) { |
||||||
|
controller.insertImageBlock(imageSource: image); |
||||||
|
return; |
||||||
|
} |
||||||
|
final newSavedImage = await saveImage(io.File(image)); |
||||||
|
controller.insertImageBlock(imageSource: newSavedImage); |
||||||
|
} |
||||||
|
|
||||||
|
/// For mobile platforms it will copies the picked file from temporary cache |
||||||
|
/// to applications directory |
||||||
|
/// |
||||||
|
/// for desktop platforms, it will do the same but from user files this time |
||||||
|
Future<String> saveImage(io.File file) async { |
||||||
|
final appDocDir = await getApplicationDocumentsDirectory(); |
||||||
|
final fileExt = path.extension(file.path); |
||||||
|
final newFileName = '${DateTime.now().toIso8601String()}$fileExt'; |
||||||
|
final newPath = path.join( |
||||||
|
appDocDir.path, |
||||||
|
newFileName, |
||||||
|
); |
||||||
|
final copiedFile = await file.copy(newPath); |
||||||
|
return copiedFile.path; |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(BuildContext context) { |
||||||
|
return BlocBuilder<SettingsCubit, SettingsState>( |
||||||
|
buildWhen: (previous, current) => |
||||||
|
previous.useCustomQuillToolbar != current.useCustomQuillToolbar, |
||||||
|
builder: (context, state) { |
||||||
|
if (state.useCustomQuillToolbar) { |
||||||
|
// For more info |
||||||
|
// https://github.com/singerdmx/flutter-quill/blob/master/doc/custom_toolbar.md |
||||||
|
return QuillToolbarProvider( |
||||||
|
toolbarConfigurations: const QuillToolbarConfigurations(), |
||||||
|
child: QuillBaseToolbar( |
||||||
|
configurations: QuillBaseToolbarConfigurations( |
||||||
|
toolbarSize: 15 * 2, |
||||||
|
multiRowsDisplay: false, |
||||||
|
childrenBuilder: (context) { |
||||||
|
final controller = context.requireQuillController; |
||||||
|
return [ |
||||||
|
QuillToolbarImageButton( |
||||||
|
controller: controller, |
||||||
|
options: const QuillToolbarImageButtonOptions(), |
||||||
|
), |
||||||
|
QuillToolbarHistoryButton( |
||||||
|
controller: controller, |
||||||
|
options: |
||||||
|
const QuillToolbarHistoryButtonOptions(isUndo: true), |
||||||
|
), |
||||||
|
QuillToolbarHistoryButton( |
||||||
|
controller: controller, |
||||||
|
options: |
||||||
|
const QuillToolbarHistoryButtonOptions(isUndo: false), |
||||||
|
), |
||||||
|
QuillToolbarToggleStyleButton( |
||||||
|
attribute: Attribute.bold, |
||||||
|
controller: controller, |
||||||
|
options: const QuillToolbarToggleStyleButtonOptions( |
||||||
|
iconData: Icons.format_bold, |
||||||
|
iconSize: 20, |
||||||
|
), |
||||||
|
), |
||||||
|
QuillToolbarToggleStyleButton( |
||||||
|
attribute: Attribute.italic, |
||||||
|
controller: controller, |
||||||
|
options: const QuillToolbarToggleStyleButtonOptions( |
||||||
|
iconData: Icons.format_italic, |
||||||
|
iconSize: 20, |
||||||
|
), |
||||||
|
), |
||||||
|
QuillToolbarToggleStyleButton( |
||||||
|
attribute: Attribute.underline, |
||||||
|
controller: controller, |
||||||
|
options: const QuillToolbarToggleStyleButtonOptions( |
||||||
|
iconData: Icons.format_underline, |
||||||
|
iconSize: 20, |
||||||
|
), |
||||||
|
), |
||||||
|
QuillToolbarClearFormatButton( |
||||||
|
controller: controller, |
||||||
|
options: const QuillToolbarClearFormatButtonOptions( |
||||||
|
iconData: Icons.format_clear, |
||||||
|
iconSize: 20, |
||||||
|
), |
||||||
|
), |
||||||
|
VerticalDivider( |
||||||
|
indent: 12, |
||||||
|
endIndent: 12, |
||||||
|
color: Colors.grey.shade400, |
||||||
|
), |
||||||
|
QuillToolbarSelectHeaderStyleButtons( |
||||||
|
controller: controller, |
||||||
|
options: |
||||||
|
const QuillToolbarSelectHeaderStyleButtonsOptions( |
||||||
|
iconSize: 20, |
||||||
|
), |
||||||
|
), |
||||||
|
QuillToolbarToggleStyleButton( |
||||||
|
attribute: Attribute.ol, |
||||||
|
controller: controller, |
||||||
|
options: const QuillToolbarToggleStyleButtonOptions( |
||||||
|
iconData: Icons.format_list_numbered, |
||||||
|
iconSize: 20, |
||||||
|
), |
||||||
|
), |
||||||
|
QuillToolbarToggleStyleButton( |
||||||
|
attribute: Attribute.ul, |
||||||
|
controller: controller, |
||||||
|
options: const QuillToolbarToggleStyleButtonOptions( |
||||||
|
iconData: Icons.format_list_bulleted, |
||||||
|
iconSize: 20, |
||||||
|
), |
||||||
|
), |
||||||
|
QuillToolbarToggleStyleButton( |
||||||
|
attribute: Attribute.blockQuote, |
||||||
|
controller: controller, |
||||||
|
options: const QuillToolbarToggleStyleButtonOptions( |
||||||
|
iconData: Icons.format_quote, |
||||||
|
iconSize: 20, |
||||||
|
), |
||||||
|
), |
||||||
|
VerticalDivider( |
||||||
|
indent: 12, |
||||||
|
endIndent: 12, |
||||||
|
color: Colors.grey.shade400, |
||||||
|
), |
||||||
|
QuillToolbarIndentButton( |
||||||
|
controller: controller, |
||||||
|
isIncrease: true, |
||||||
|
options: const QuillToolbarIndentButtonOptions( |
||||||
|
iconData: Icons.format_indent_increase, |
||||||
|
iconSize: 20, |
||||||
|
)), |
||||||
|
QuillToolbarIndentButton( |
||||||
|
controller: controller, |
||||||
|
isIncrease: false, |
||||||
|
options: const QuillToolbarIndentButtonOptions( |
||||||
|
iconData: Icons.format_indent_decrease, |
||||||
|
iconSize: 20, |
||||||
|
), |
||||||
|
), |
||||||
|
]; |
||||||
|
}, |
||||||
|
), |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
return QuillToolbar( |
||||||
|
configurations: QuillToolbarConfigurations( |
||||||
|
customButtons: [ |
||||||
|
QuillToolbarCustomButtonOptions( |
||||||
|
icon: const Icon(Icons.add_alarm_rounded), |
||||||
|
onPressed: () { |
||||||
|
final controller = context.requireQuillController; |
||||||
|
controller.document |
||||||
|
.insert(controller.selection.extentOffset, '\n'); |
||||||
|
controller.updateSelection( |
||||||
|
TextSelection.collapsed( |
||||||
|
offset: controller.selection.extentOffset + 1, |
||||||
|
), |
||||||
|
ChangeSource.local, |
||||||
|
); |
||||||
|
|
||||||
|
controller.document.insert( |
||||||
|
controller.selection.extentOffset, |
||||||
|
TimeStampEmbed( |
||||||
|
DateTime.now().toString(), |
||||||
|
), |
||||||
|
); |
||||||
|
|
||||||
|
controller.updateSelection( |
||||||
|
TextSelection.collapsed( |
||||||
|
offset: controller.selection.extentOffset + 1, |
||||||
|
), |
||||||
|
ChangeSource.local, |
||||||
|
); |
||||||
|
|
||||||
|
controller.document |
||||||
|
.insert(controller.selection.extentOffset, ' '); |
||||||
|
controller.updateSelection( |
||||||
|
TextSelection.collapsed( |
||||||
|
offset: controller.selection.extentOffset + 1, |
||||||
|
), |
||||||
|
ChangeSource.local, |
||||||
|
); |
||||||
|
|
||||||
|
controller.document |
||||||
|
.insert(controller.selection.extentOffset, '\n'); |
||||||
|
controller.updateSelection( |
||||||
|
TextSelection.collapsed( |
||||||
|
offset: controller.selection.extentOffset + 1, |
||||||
|
), |
||||||
|
ChangeSource.local, |
||||||
|
); |
||||||
|
}, |
||||||
|
), |
||||||
|
QuillToolbarCustomButtonOptions( |
||||||
|
icon: const Icon(Icons.ac_unit), |
||||||
|
onPressed: () { |
||||||
|
ScaffoldMessenger.of(context) |
||||||
|
..clearSnackBars() |
||||||
|
..showText( |
||||||
|
'Custom button!', |
||||||
|
); |
||||||
|
}, |
||||||
|
), |
||||||
|
], |
||||||
|
embedButtons: FlutterQuillEmbeds.toolbarButtons( |
||||||
|
imageButtonOptions: QuillToolbarImageButtonOptions( |
||||||
|
imageButtonConfigurations: QuillToolbarImageConfigurations( |
||||||
|
onImageInsertCallback: isAndroid(supportWeb: false) || |
||||||
|
isIOS(supportWeb: false) || |
||||||
|
isWeb() |
||||||
|
? (image, controller) => |
||||||
|
onImageInsertWithCropping(image, controller, context) |
||||||
|
: onImageInsert, |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
); |
||||||
|
}, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -1,6 +1,8 @@ |
|||||||
const sampleData = [ |
import '../../../gen/assets.gen.dart'; |
||||||
|
|
||||||
|
final quillDefaultSample = [ |
||||||
{ |
{ |
||||||
'insert': {'image': 'assets/images/1.png'}, |
'insert': {'image': Assets.images.screenshot1.path}, |
||||||
'attributes': { |
'attributes': { |
||||||
'width': '100', |
'width': '100', |
||||||
'height': '100', |
'height': '100', |
@ -1,4 +1,4 @@ |
|||||||
const sampleDataNoMedia = [ |
const quillTextSample = [ |
||||||
{'insert': 'Flutter Quill'}, |
{'insert': 'Flutter Quill'}, |
||||||
{ |
{ |
||||||
'attributes': {'header': 1}, |
'attributes': {'header': 1}, |
@ -0,0 +1,19 @@ |
|||||||
|
const quillVideosSample = [ |
||||||
|
{'insert': '\n'}, |
||||||
|
{ |
||||||
|
'insert': {'video': 'https://youtu.be/xz6_AlJkDPA'}, |
||||||
|
'attributes': { |
||||||
|
'width': '300', |
||||||
|
'height': '300', |
||||||
|
'style': 'width:400px; height:500px;' |
||||||
|
} |
||||||
|
}, |
||||||
|
{'insert': '\n'}, |
||||||
|
{'insert': '\n'}, |
||||||
|
{'insert': 'And this is just a youtube video'}, |
||||||
|
{'insert': '\n'}, |
||||||
|
{ |
||||||
|
'insert': 'This sample is not complete.', |
||||||
|
}, |
||||||
|
{'insert': '\n'}, |
||||||
|
]; |
@ -0,0 +1,26 @@ |
|||||||
|
import 'package:bloc/bloc.dart'; |
||||||
|
import 'package:flutter/material.dart' show ThemeMode; |
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart'; |
||||||
|
import 'package:hydrated_bloc/hydrated_bloc.dart' show HydratedMixin; |
||||||
|
|
||||||
|
part 'settings_state.dart'; |
||||||
|
part 'settings_cubit.freezed.dart'; |
||||||
|
part 'settings_cubit.g.dart'; |
||||||
|
|
||||||
|
class SettingsCubit extends Cubit<SettingsState> with HydratedMixin { |
||||||
|
SettingsCubit() : super(const SettingsState()); |
||||||
|
|
||||||
|
void updateSettings(SettingsState newSettingsState) { |
||||||
|
emit(newSettingsState); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
SettingsState? fromJson(Map<String, dynamic> json) { |
||||||
|
return SettingsState.fromJson(json); |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Map<String, dynamic>? toJson(SettingsState state) { |
||||||
|
return state.toJson(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,202 @@ |
|||||||
|
// coverage:ignore-file |
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND |
||||||
|
// ignore_for_file: type=lint |
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark |
||||||
|
|
||||||
|
part of 'settings_cubit.dart'; |
||||||
|
|
||||||
|
// ************************************************************************** |
||||||
|
// FreezedGenerator |
||||||
|
// ************************************************************************** |
||||||
|
|
||||||
|
T _$identity<T>(T value) => value; |
||||||
|
|
||||||
|
final _privateConstructorUsedError = UnsupportedError( |
||||||
|
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); |
||||||
|
|
||||||
|
SettingsState _$SettingsStateFromJson(Map<String, dynamic> json) { |
||||||
|
return _SettingsState.fromJson(json); |
||||||
|
} |
||||||
|
|
||||||
|
/// @nodoc |
||||||
|
mixin _$SettingsState { |
||||||
|
ThemeMode get themeMode => throw _privateConstructorUsedError; |
||||||
|
DefaultScreen get defaultScreen => throw _privateConstructorUsedError; |
||||||
|
bool get useCustomQuillToolbar => throw _privateConstructorUsedError; |
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; |
||||||
|
@JsonKey(ignore: true) |
||||||
|
$SettingsStateCopyWith<SettingsState> get copyWith => |
||||||
|
throw _privateConstructorUsedError; |
||||||
|
} |
||||||
|
|
||||||
|
/// @nodoc |
||||||
|
abstract class $SettingsStateCopyWith<$Res> { |
||||||
|
factory $SettingsStateCopyWith( |
||||||
|
SettingsState value, $Res Function(SettingsState) then) = |
||||||
|
_$SettingsStateCopyWithImpl<$Res, SettingsState>; |
||||||
|
@useResult |
||||||
|
$Res call( |
||||||
|
{ThemeMode themeMode, |
||||||
|
DefaultScreen defaultScreen, |
||||||
|
bool useCustomQuillToolbar}); |
||||||
|
} |
||||||
|
|
||||||
|
/// @nodoc |
||||||
|
class _$SettingsStateCopyWithImpl<$Res, $Val extends SettingsState> |
||||||
|
implements $SettingsStateCopyWith<$Res> { |
||||||
|
_$SettingsStateCopyWithImpl(this._value, this._then); |
||||||
|
|
||||||
|
// ignore: unused_field |
||||||
|
final $Val _value; |
||||||
|
// ignore: unused_field |
||||||
|
final $Res Function($Val) _then; |
||||||
|
|
||||||
|
@pragma('vm:prefer-inline') |
||||||
|
@override |
||||||
|
$Res call({ |
||||||
|
Object? themeMode = null, |
||||||
|
Object? defaultScreen = null, |
||||||
|
Object? useCustomQuillToolbar = null, |
||||||
|
}) { |
||||||
|
return _then(_value.copyWith( |
||||||
|
themeMode: null == themeMode |
||||||
|
? _value.themeMode |
||||||
|
: themeMode // ignore: cast_nullable_to_non_nullable |
||||||
|
as ThemeMode, |
||||||
|
defaultScreen: null == defaultScreen |
||||||
|
? _value.defaultScreen |
||||||
|
: defaultScreen // ignore: cast_nullable_to_non_nullable |
||||||
|
as DefaultScreen, |
||||||
|
useCustomQuillToolbar: null == useCustomQuillToolbar |
||||||
|
? _value.useCustomQuillToolbar |
||||||
|
: useCustomQuillToolbar // ignore: cast_nullable_to_non_nullable |
||||||
|
as bool, |
||||||
|
) as $Val); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// @nodoc |
||||||
|
abstract class _$$SettingsStateImplCopyWith<$Res> |
||||||
|
implements $SettingsStateCopyWith<$Res> { |
||||||
|
factory _$$SettingsStateImplCopyWith( |
||||||
|
_$SettingsStateImpl value, $Res Function(_$SettingsStateImpl) then) = |
||||||
|
__$$SettingsStateImplCopyWithImpl<$Res>; |
||||||
|
@override |
||||||
|
@useResult |
||||||
|
$Res call( |
||||||
|
{ThemeMode themeMode, |
||||||
|
DefaultScreen defaultScreen, |
||||||
|
bool useCustomQuillToolbar}); |
||||||
|
} |
||||||
|
|
||||||
|
/// @nodoc |
||||||
|
class __$$SettingsStateImplCopyWithImpl<$Res> |
||||||
|
extends _$SettingsStateCopyWithImpl<$Res, _$SettingsStateImpl> |
||||||
|
implements _$$SettingsStateImplCopyWith<$Res> { |
||||||
|
__$$SettingsStateImplCopyWithImpl( |
||||||
|
_$SettingsStateImpl _value, $Res Function(_$SettingsStateImpl) _then) |
||||||
|
: super(_value, _then); |
||||||
|
|
||||||
|
@pragma('vm:prefer-inline') |
||||||
|
@override |
||||||
|
$Res call({ |
||||||
|
Object? themeMode = null, |
||||||
|
Object? defaultScreen = null, |
||||||
|
Object? useCustomQuillToolbar = null, |
||||||
|
}) { |
||||||
|
return _then(_$SettingsStateImpl( |
||||||
|
themeMode: null == themeMode |
||||||
|
? _value.themeMode |
||||||
|
: themeMode // ignore: cast_nullable_to_non_nullable |
||||||
|
as ThemeMode, |
||||||
|
defaultScreen: null == defaultScreen |
||||||
|
? _value.defaultScreen |
||||||
|
: defaultScreen // ignore: cast_nullable_to_non_nullable |
||||||
|
as DefaultScreen, |
||||||
|
useCustomQuillToolbar: null == useCustomQuillToolbar |
||||||
|
? _value.useCustomQuillToolbar |
||||||
|
: useCustomQuillToolbar // ignore: cast_nullable_to_non_nullable |
||||||
|
as bool, |
||||||
|
)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// @nodoc |
||||||
|
@JsonSerializable() |
||||||
|
class _$SettingsStateImpl implements _SettingsState { |
||||||
|
const _$SettingsStateImpl( |
||||||
|
{this.themeMode = ThemeMode.system, |
||||||
|
this.defaultScreen = DefaultScreen.home, |
||||||
|
this.useCustomQuillToolbar = false}); |
||||||
|
|
||||||
|
factory _$SettingsStateImpl.fromJson(Map<String, dynamic> json) => |
||||||
|
_$$SettingsStateImplFromJson(json); |
||||||
|
|
||||||
|
@override |
||||||
|
@JsonKey() |
||||||
|
final ThemeMode themeMode; |
||||||
|
@override |
||||||
|
@JsonKey() |
||||||
|
final DefaultScreen defaultScreen; |
||||||
|
@override |
||||||
|
@JsonKey() |
||||||
|
final bool useCustomQuillToolbar; |
||||||
|
|
||||||
|
@override |
||||||
|
String toString() { |
||||||
|
return 'SettingsState(themeMode: $themeMode, defaultScreen: $defaultScreen, useCustomQuillToolbar: $useCustomQuillToolbar)'; |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
bool operator ==(dynamic other) { |
||||||
|
return identical(this, other) || |
||||||
|
(other.runtimeType == runtimeType && |
||||||
|
other is _$SettingsStateImpl && |
||||||
|
(identical(other.themeMode, themeMode) || |
||||||
|
other.themeMode == themeMode) && |
||||||
|
(identical(other.defaultScreen, defaultScreen) || |
||||||
|
other.defaultScreen == defaultScreen) && |
||||||
|
(identical(other.useCustomQuillToolbar, useCustomQuillToolbar) || |
||||||
|
other.useCustomQuillToolbar == useCustomQuillToolbar)); |
||||||
|
} |
||||||
|
|
||||||
|
@JsonKey(ignore: true) |
||||||
|
@override |
||||||
|
int get hashCode => |
||||||
|
Object.hash(runtimeType, themeMode, defaultScreen, useCustomQuillToolbar); |
||||||
|
|
||||||
|
@JsonKey(ignore: true) |
||||||
|
@override |
||||||
|
@pragma('vm:prefer-inline') |
||||||
|
_$$SettingsStateImplCopyWith<_$SettingsStateImpl> get copyWith => |
||||||
|
__$$SettingsStateImplCopyWithImpl<_$SettingsStateImpl>(this, _$identity); |
||||||
|
|
||||||
|
@override |
||||||
|
Map<String, dynamic> toJson() { |
||||||
|
return _$$SettingsStateImplToJson( |
||||||
|
this, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
abstract class _SettingsState implements SettingsState { |
||||||
|
const factory _SettingsState( |
||||||
|
{final ThemeMode themeMode, |
||||||
|
final DefaultScreen defaultScreen, |
||||||
|
final bool useCustomQuillToolbar}) = _$SettingsStateImpl; |
||||||
|
|
||||||
|
factory _SettingsState.fromJson(Map<String, dynamic> json) = |
||||||
|
_$SettingsStateImpl.fromJson; |
||||||
|
|
||||||
|
@override |
||||||
|
ThemeMode get themeMode; |
||||||
|
@override |
||||||
|
DefaultScreen get defaultScreen; |
||||||
|
@override |
||||||
|
bool get useCustomQuillToolbar; |
||||||
|
@override |
||||||
|
@JsonKey(ignore: true) |
||||||
|
_$$SettingsStateImplCopyWith<_$SettingsStateImpl> get copyWith => |
||||||
|
throw _privateConstructorUsedError; |
||||||
|
} |
@ -0,0 +1,40 @@ |
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND |
||||||
|
|
||||||
|
part of 'settings_cubit.dart'; |
||||||
|
|
||||||
|
// ************************************************************************** |
||||||
|
// JsonSerializableGenerator |
||||||
|
// ************************************************************************** |
||||||
|
|
||||||
|
_$SettingsStateImpl _$$SettingsStateImplFromJson(Map<String, dynamic> json) => |
||||||
|
_$SettingsStateImpl( |
||||||
|
themeMode: $enumDecodeNullable(_$ThemeModeEnumMap, json['themeMode']) ?? |
||||||
|
ThemeMode.system, |
||||||
|
defaultScreen: |
||||||
|
$enumDecodeNullable(_$DefaultScreenEnumMap, json['defaultScreen']) ?? |
||||||
|
DefaultScreen.home, |
||||||
|
useCustomQuillToolbar: json['useCustomQuillToolbar'] as bool? ?? false, |
||||||
|
); |
||||||
|
|
||||||
|
Map<String, dynamic> _$$SettingsStateImplToJson(_$SettingsStateImpl instance) => |
||||||
|
<String, dynamic>{ |
||||||
|
'themeMode': _$ThemeModeEnumMap[instance.themeMode]!, |
||||||
|
'defaultScreen': _$DefaultScreenEnumMap[instance.defaultScreen]!, |
||||||
|
'useCustomQuillToolbar': instance.useCustomQuillToolbar, |
||||||
|
}; |
||||||
|
|
||||||
|
const _$ThemeModeEnumMap = { |
||||||
|
ThemeMode.system: 'system', |
||||||
|
ThemeMode.light: 'light', |
||||||
|
ThemeMode.dark: 'dark', |
||||||
|
}; |
||||||
|
|
||||||
|
const _$DefaultScreenEnumMap = { |
||||||
|
DefaultScreen.home: 'home', |
||||||
|
DefaultScreen.settings: 'settings', |
||||||
|
DefaultScreen.defaultSample: 'defaultSample', |
||||||
|
DefaultScreen.imagesSample: 'imagesSample', |
||||||
|
DefaultScreen.videosSample: 'videosSample', |
||||||
|
DefaultScreen.textSample: 'textSample', |
||||||
|
DefaultScreen.emptySample: 'emptySample', |
||||||
|
}; |
@ -0,0 +1,22 @@ |
|||||||
|
part of 'settings_cubit.dart'; |
||||||
|
|
||||||
|
enum DefaultScreen { |
||||||
|
home, |
||||||
|
settings, |
||||||
|
defaultSample, |
||||||
|
imagesSample, |
||||||
|
videosSample, |
||||||
|
textSample, |
||||||
|
emptySample, |
||||||
|
} |
||||||
|
|
||||||
|
@freezed |
||||||
|
class SettingsState with _$SettingsState { |
||||||
|
const factory SettingsState({ |
||||||
|
@Default(ThemeMode.system) ThemeMode themeMode, |
||||||
|
@Default(DefaultScreen.home) DefaultScreen defaultScreen, |
||||||
|
@Default(false) bool useCustomQuillToolbar, |
||||||
|
}) = _SettingsState; |
||||||
|
factory SettingsState.fromJson(Map<String, Object?> json) => |
||||||
|
_$SettingsStateFromJson(json); |
||||||
|
} |
@ -0,0 +1,122 @@ |
|||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart'; |
||||||
|
|
||||||
|
import '../../shared/widgets/dialog_action.dart'; |
||||||
|
import '../../shared/widgets/home_screen_button.dart'; |
||||||
|
import '../cubit/settings_cubit.dart'; |
||||||
|
|
||||||
|
class SettingsScreen extends StatelessWidget { |
||||||
|
const SettingsScreen({super.key}); |
||||||
|
|
||||||
|
static const routeName = '/settings'; |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(BuildContext context) { |
||||||
|
final materialTheme = Theme.of(context); |
||||||
|
final isDark = materialTheme.brightness == Brightness.dark; |
||||||
|
return Scaffold( |
||||||
|
appBar: AppBar( |
||||||
|
title: const Text('Settings'), |
||||||
|
actions: const [ |
||||||
|
HomeScreenButton(), |
||||||
|
], |
||||||
|
), |
||||||
|
body: BlocBuilder<SettingsCubit, SettingsState>( |
||||||
|
builder: (context, state) { |
||||||
|
return ListView( |
||||||
|
children: [ |
||||||
|
CheckboxListTile.adaptive( |
||||||
|
value: isDark, |
||||||
|
onChanged: (value) { |
||||||
|
final isNewValueDark = value ?? false; |
||||||
|
context.read<SettingsCubit>().updateSettings( |
||||||
|
state.copyWith( |
||||||
|
themeMode: |
||||||
|
isNewValueDark ? ThemeMode.dark : ThemeMode.light, |
||||||
|
), |
||||||
|
); |
||||||
|
}, |
||||||
|
title: const Text('Dark Theme'), |
||||||
|
subtitle: const Text( |
||||||
|
'By default we will use your system theme, but you can set if you want dark or light theme', |
||||||
|
), |
||||||
|
secondary: Icon(isDark ? Icons.nightlight : Icons.sunny), |
||||||
|
), |
||||||
|
ListTile( |
||||||
|
title: const Text('Default screen'), |
||||||
|
subtitle: const Text( |
||||||
|
'Which screen should be used when the flutter app starts?', |
||||||
|
), |
||||||
|
leading: const Icon(Icons.home), |
||||||
|
onTap: () async { |
||||||
|
final settingsBloc = context.read<SettingsCubit>(); |
||||||
|
final newDefaultScreen = |
||||||
|
await showAdaptiveDialog<DefaultScreen>( |
||||||
|
context: context, |
||||||
|
builder: (context) { |
||||||
|
return AlertDialog.adaptive( |
||||||
|
title: const Text('Select default screen'), |
||||||
|
content: Column( |
||||||
|
mainAxisSize: MainAxisSize.min, |
||||||
|
children: [ |
||||||
|
...DefaultScreen.values.map( |
||||||
|
(e) => Material( |
||||||
|
child: ListTile( |
||||||
|
onTap: () { |
||||||
|
Navigator.of(context).pop(e); |
||||||
|
}, |
||||||
|
title: Text(e.name), |
||||||
|
leading: CircleAvatar( |
||||||
|
child: Text((e.index + 1).toString()), |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
), |
||||||
|
], |
||||||
|
), |
||||||
|
actions: [ |
||||||
|
AppDialogAction( |
||||||
|
onPressed: () => Navigator.of(context).pop(null), |
||||||
|
options: const DialogActionOptions( |
||||||
|
cupertinoDialogActionOptions: |
||||||
|
CupertinoDialogActionOptions( |
||||||
|
isDefaultAction: true, |
||||||
|
), |
||||||
|
), |
||||||
|
child: const Text('Cancel'), |
||||||
|
), |
||||||
|
], |
||||||
|
); |
||||||
|
}, |
||||||
|
); |
||||||
|
if (newDefaultScreen != null) { |
||||||
|
settingsBloc.updateSettings( |
||||||
|
settingsBloc.state |
||||||
|
.copyWith(defaultScreen: newDefaultScreen), |
||||||
|
); |
||||||
|
} |
||||||
|
}, |
||||||
|
), |
||||||
|
CheckboxListTile.adaptive( |
||||||
|
value: state.useCustomQuillToolbar, |
||||||
|
onChanged: (value) { |
||||||
|
final useCustomToolbarNewValue = value ?? false; |
||||||
|
context.read<SettingsCubit>().updateSettings( |
||||||
|
state.copyWith( |
||||||
|
useCustomQuillToolbar: useCustomToolbarNewValue, |
||||||
|
), |
||||||
|
); |
||||||
|
}, |
||||||
|
title: const Text('Use custom Quill toolbar'), |
||||||
|
subtitle: const Text( |
||||||
|
'By default we will default QuillToolbar, but you can decide if you the built-in or the custom one', |
||||||
|
), |
||||||
|
secondary: const Icon(Icons.dashboard_customize), |
||||||
|
), |
||||||
|
], |
||||||
|
); |
||||||
|
}, |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,63 @@ |
|||||||
|
import 'package:flutter/cupertino.dart'; |
||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:flutter_quill/extensions.dart'; |
||||||
|
|
||||||
|
@immutable |
||||||
|
final class CupertinoDialogActionOptions { |
||||||
|
const CupertinoDialogActionOptions({ |
||||||
|
this.isDefaultAction = false, |
||||||
|
}); |
||||||
|
|
||||||
|
final bool isDefaultAction; |
||||||
|
} |
||||||
|
|
||||||
|
@immutable |
||||||
|
final class MaterialDialogActionOptions { |
||||||
|
const MaterialDialogActionOptions({ |
||||||
|
this.textStyle, |
||||||
|
}); |
||||||
|
|
||||||
|
final ButtonStyle? textStyle; |
||||||
|
} |
||||||
|
|
||||||
|
@immutable |
||||||
|
class DialogActionOptions { |
||||||
|
const DialogActionOptions({ |
||||||
|
this.cupertinoDialogActionOptions, |
||||||
|
this.materialDialogActionOptions, |
||||||
|
}); |
||||||
|
|
||||||
|
final CupertinoDialogActionOptions? cupertinoDialogActionOptions; |
||||||
|
final MaterialDialogActionOptions? materialDialogActionOptions; |
||||||
|
} |
||||||
|
|
||||||
|
class AppDialogAction extends StatelessWidget { |
||||||
|
const AppDialogAction({ |
||||||
|
required this.child, |
||||||
|
required this.onPressed, |
||||||
|
this.options, |
||||||
|
super.key, |
||||||
|
}); |
||||||
|
|
||||||
|
final VoidCallback? onPressed; |
||||||
|
final Widget child; |
||||||
|
|
||||||
|
final DialogActionOptions? options; |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(BuildContext context) { |
||||||
|
if (isAppleOS(supportWeb: true)) { |
||||||
|
return CupertinoDialogAction( |
||||||
|
onPressed: onPressed, |
||||||
|
isDefaultAction: |
||||||
|
options?.cupertinoDialogActionOptions?.isDefaultAction ?? false, |
||||||
|
child: child, |
||||||
|
); |
||||||
|
} |
||||||
|
return TextButton( |
||||||
|
onPressed: onPressed, |
||||||
|
style: options?.materialDialogActionOptions?.textStyle, |
||||||
|
child: child, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart'; |
||||||
|
|
||||||
|
import '../../settings/cubit/settings_cubit.dart'; |
||||||
|
|
||||||
|
class HomeScreenButton extends StatelessWidget { |
||||||
|
const HomeScreenButton({super.key}); |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(BuildContext context) { |
||||||
|
return IconButton( |
||||||
|
onPressed: () { |
||||||
|
final settingsCubit = context.read<SettingsCubit>(); |
||||||
|
settingsCubit.updateSettings( |
||||||
|
settingsCubit.state.copyWith( |
||||||
|
defaultScreen: DefaultScreen.home, |
||||||
|
), |
||||||
|
); |
||||||
|
}, |
||||||
|
icon: const Icon(Icons.home), |
||||||
|
tooltip: 'Set the default to home screen', |
||||||
|
); |
||||||
|
} |
||||||
|
} |
@ -1,3 +0,0 @@ |
|||||||
// class PlatformViewRegistry { |
|
||||||
// static void registerViewFactory(String viewId, dynamic cb) {} |
|
||||||
// } |
|
@ -1,7 +0,0 @@ |
|||||||
// import 'dart:ui' if (dart.library.html) 'dart:ui_web' as ui; |
|
||||||
|
|
||||||
// class PlatformViewRegistry { |
|
||||||
// static void registerViewFactory(String viewId, dynamic cb) { |
|
||||||
// ui.platformViewRegistry.registerViewFactory(viewId, cb); |
|
||||||
// } |
|
||||||
// } |
|
@ -1,115 +0,0 @@ |
|||||||
library universal_ui; |
|
||||||
|
|
||||||
// import 'package:flutter_quill/flutter_quill.dart'; |
|
||||||
// import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; |
|
||||||
|
|
||||||
// import '../widgets/responsive_widget.dart'; |
|
||||||
// import 'fake_ui.dart' if (dart.library.html) 'real_ui.dart' as ui_instance; |
|
||||||
|
|
||||||
// class PlatformViewRegistryFix { |
|
||||||
// void registerViewFactory(dynamic x, dynamic y) { |
|
||||||
// if (kIsWeb) { |
|
||||||
// ui_instance.PlatformViewRegistry.registerViewFactory( |
|
||||||
// x, |
|
||||||
// y, |
|
||||||
// ); |
|
||||||
// } |
|
||||||
// } |
|
||||||
// } |
|
||||||
|
|
||||||
// class UniversalUI { |
|
||||||
// PlatformViewRegistryFix platformViewRegistry = PlatformViewRegistryFix(); |
|
||||||
// } |
|
||||||
|
|
||||||
// var ui = UniversalUI(); |
|
||||||
|
|
||||||
// class ImageEmbedBuilderWeb extends EmbedBuilder { |
|
||||||
// @override |
|
||||||
// String get key => BlockEmbed.imageType; |
|
||||||
|
|
||||||
// @override |
|
||||||
// Widget build( |
|
||||||
// BuildContext context, |
|
||||||
// QuillController controller, |
|
||||||
// Embed node, |
|
||||||
// bool readOnly, |
|
||||||
// bool inline, |
|
||||||
// TextStyle textStyle, |
|
||||||
// ) { |
|
||||||
// final imageUrl = node.value.data; |
|
||||||
// if (isImageBase64(imageUrl)) { |
|
||||||
// // TODO: handle imageUrl of base64 |
|
||||||
// return const SizedBox(); |
|
||||||
// } |
|
||||||
// final size = MediaQuery.sizeOf(context); |
|
||||||
// UniversalUI().platformViewRegistry.registerViewFactory(imageUrl, |
|
||||||
//(viewId) { |
|
||||||
// return html.ImageElement() |
|
||||||
// ..src = imageUrl |
|
||||||
// ..style.height = 'auto' |
|
||||||
// ..style.width = 'auto'; |
|
||||||
// }); |
|
||||||
// return Padding( |
|
||||||
// padding: EdgeInsets.only( |
|
||||||
// right: ResponsiveWidget.isMediumScreen(context) |
|
||||||
// ? size.width * 0.5 |
|
||||||
// : (ResponsiveWidget.isLargeScreen(context)) |
|
||||||
// ? size.width * 0.75 |
|
||||||
// : size.width * 0.2, |
|
||||||
// ), |
|
||||||
// child: SizedBox( |
|
||||||
// height: size.height * 0.45, |
|
||||||
// child: HtmlElementView( |
|
||||||
// viewType: imageUrl, |
|
||||||
// ), |
|
||||||
// ), |
|
||||||
// ); |
|
||||||
// } |
|
||||||
// } |
|
||||||
|
|
||||||
// class VideoEmbedBuilderWeb extends EmbedBuilder { |
|
||||||
// @override |
|
||||||
// String get key => BlockEmbed.videoType; |
|
||||||
|
|
||||||
// @override |
|
||||||
// Widget build( |
|
||||||
// BuildContext context, |
|
||||||
// QuillController controller, |
|
||||||
// Embed node, |
|
||||||
// bool readOnly, |
|
||||||
// bool inline, |
|
||||||
// TextStyle textStyle, |
|
||||||
// ) { |
|
||||||
// var videoUrl = node.value.data; |
|
||||||
// if (videoUrl.contains('youtube.com') || videoUrl.contains('youtu.be')) { |
|
||||||
// final youtubeID = YoutubePlayer.convertUrlToId(videoUrl); |
|
||||||
// if (youtubeID != null) { |
|
||||||
// videoUrl = 'https://www.youtube.com/embed/$youtubeID'; |
|
||||||
// } |
|
||||||
// } |
|
||||||
|
|
||||||
// final size = MediaQuery.sizeOf(context); |
|
||||||
|
|
||||||
// UniversalUI().platformViewRegistry.registerViewFactory( |
|
||||||
// videoUrl, |
|
||||||
// (id) => html.IFrameElement() |
|
||||||
// ..width = size.width.toString() |
|
||||||
// ..height = size.height.toString() |
|
||||||
// ..src = videoUrl |
|
||||||
// ..style.border = 'none', |
|
||||||
// ); |
|
||||||
|
|
||||||
// return SizedBox( |
|
||||||
// height: 500, |
|
||||||
// child: HtmlElementView( |
|
||||||
// viewType: videoUrl, |
|
||||||
// ), |
|
||||||
// ); |
|
||||||
// } |
|
||||||
// } |
|
||||||
|
|
||||||
// List<EmbedBuilder> get defaultEmbedBuildersWeb => [ |
|
||||||
// ...FlutterQuillEmbeds.editorsWebBuilders(), |
|
||||||
// // ImageEmbedBuilderWeb(), |
|
||||||
// // VideoEmbedBuilderWeb(), |
|
||||||
// ]; |
|
@ -1,142 +0,0 @@ |
|||||||
import 'dart:convert'; |
|
||||||
import 'dart:io' show Platform; |
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart'; |
|
||||||
import 'package:flutter/material.dart'; |
|
||||||
import 'package:flutter/services.dart'; |
|
||||||
import 'package:flutter_quill/flutter_quill.dart'; |
|
||||||
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; |
|
||||||
|
|
||||||
typedef DemoContentBuilder = Widget Function( |
|
||||||
BuildContext context, QuillController? controller); |
|
||||||
|
|
||||||
// Common scaffold for all examples. |
|
||||||
class DemoScaffold extends StatefulWidget { |
|
||||||
const DemoScaffold({ |
|
||||||
required this.documentFilename, |
|
||||||
required this.builder, |
|
||||||
this.actions, |
|
||||||
this.showToolbar = true, |
|
||||||
this.floatingActionButton, |
|
||||||
super.key, |
|
||||||
}); |
|
||||||
|
|
||||||
/// Filename of the document to load into the editor. |
|
||||||
final String documentFilename; |
|
||||||
final DemoContentBuilder builder; |
|
||||||
final List<Widget>? actions; |
|
||||||
final Widget? floatingActionButton; |
|
||||||
final bool showToolbar; |
|
||||||
|
|
||||||
@override |
|
||||||
_DemoScaffoldState createState() => _DemoScaffoldState(); |
|
||||||
} |
|
||||||
|
|
||||||
class _DemoScaffoldState extends State<DemoScaffold> { |
|
||||||
final _scaffoldKey = GlobalKey<ScaffoldState>(); |
|
||||||
QuillController? _controller; |
|
||||||
|
|
||||||
bool _loading = false; |
|
||||||
|
|
||||||
@override |
|
||||||
void didChangeDependencies() { |
|
||||||
super.didChangeDependencies(); |
|
||||||
if (_controller == null && !_loading) { |
|
||||||
_loading = true; |
|
||||||
_loadFromAssets(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
void dispose() { |
|
||||||
_controller?.dispose(); |
|
||||||
super.dispose(); |
|
||||||
} |
|
||||||
|
|
||||||
Future<void> _loadFromAssets() async { |
|
||||||
try { |
|
||||||
final result = |
|
||||||
await rootBundle.loadString('assets/${widget.documentFilename}'); |
|
||||||
final doc = Document.fromJson(jsonDecode(result)); |
|
||||||
setState(() { |
|
||||||
_controller = QuillController( |
|
||||||
document: doc, selection: const TextSelection.collapsed(offset: 0)); |
|
||||||
_loading = false; |
|
||||||
}); |
|
||||||
} catch (error) { |
|
||||||
final doc = Document()..insert(0, 'Empty asset'); |
|
||||||
setState(() { |
|
||||||
_controller = QuillController( |
|
||||||
document: doc, selection: const TextSelection.collapsed(offset: 0)); |
|
||||||
_loading = false; |
|
||||||
}); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Future<String?> _openFileSystemPickerForDesktop(BuildContext context) |
|
||||||
//async { |
|
||||||
// return await FilesystemPicker.open( |
|
||||||
// context: context, |
|
||||||
// rootDirectory: await getApplicationDocumentsDirectory(), |
|
||||||
// fsType: FilesystemType.file, |
|
||||||
// fileTileSelectMode: FileTileSelectMode.wholeTile, |
|
||||||
// ); |
|
||||||
// } |
|
||||||
|
|
||||||
QuillToolbar get quillToolbar { |
|
||||||
if (_isDesktop()) { |
|
||||||
return QuillToolbar( |
|
||||||
configurations: QuillToolbarConfigurations( |
|
||||||
embedButtons: FlutterQuillEmbeds.toolbarButtons( |
|
||||||
// ignore: avoid_redundant_argument_values |
|
||||||
imageButtonOptions: const QuillToolbarImageButtonOptions( |
|
||||||
// filePickImpl: openFileSystemPickerForDesktop, |
|
||||||
), |
|
||||||
), |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
||||||
return QuillToolbar( |
|
||||||
configurations: QuillToolbarConfigurations( |
|
||||||
embedButtons: FlutterQuillEmbeds.toolbarButtons(), |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build(BuildContext context) { |
|
||||||
if (_controller == null) { |
|
||||||
return const Scaffold(body: Center(child: Text('Loading...'))); |
|
||||||
} |
|
||||||
final actions = widget.actions ?? <Widget>[]; |
|
||||||
|
|
||||||
return QuillProvider( |
|
||||||
configurations: QuillConfigurations(controller: _controller!), |
|
||||||
child: Scaffold( |
|
||||||
key: _scaffoldKey, |
|
||||||
appBar: AppBar( |
|
||||||
elevation: 0, |
|
||||||
backgroundColor: Theme.of(context).canvasColor, |
|
||||||
centerTitle: false, |
|
||||||
titleSpacing: 0, |
|
||||||
leading: IconButton( |
|
||||||
icon: Icon( |
|
||||||
Icons.chevron_left, |
|
||||||
color: Colors.grey.shade800, |
|
||||||
size: 18, |
|
||||||
), |
|
||||||
onPressed: () => Navigator.pop(context), |
|
||||||
), |
|
||||||
title: _loading || !widget.showToolbar ? null : quillToolbar, |
|
||||||
actions: actions, |
|
||||||
), |
|
||||||
floatingActionButton: widget.floatingActionButton, |
|
||||||
body: _loading |
|
||||||
? const Center(child: Text('Loading...')) |
|
||||||
: widget.builder(context, _controller), |
|
||||||
), |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
bool _isDesktop() => !kIsWeb && !Platform.isAndroid && !Platform.isIOS; |
|
||||||
} |
|
@ -1,43 +0,0 @@ |
|||||||
import 'package:flutter/material.dart'; |
|
||||||
|
|
||||||
class ResponsiveWidget extends StatelessWidget { |
|
||||||
const ResponsiveWidget({ |
|
||||||
required this.largeScreen, |
|
||||||
this.mediumScreen, |
|
||||||
this.smallScreen, |
|
||||||
super.key, |
|
||||||
}); |
|
||||||
|
|
||||||
final Widget largeScreen; |
|
||||||
final Widget? mediumScreen; |
|
||||||
final Widget? smallScreen; |
|
||||||
|
|
||||||
static bool isSmallScreen(BuildContext context) { |
|
||||||
return MediaQuery.sizeOf(context).width < 800; |
|
||||||
} |
|
||||||
|
|
||||||
static bool isLargeScreen(BuildContext context) { |
|
||||||
return MediaQuery.sizeOf(context).width > 1200; |
|
||||||
} |
|
||||||
|
|
||||||
static bool isMediumScreen(BuildContext context) { |
|
||||||
return MediaQuery.sizeOf(context).width >= 800 && |
|
||||||
MediaQuery.sizeOf(context).width <= 1200; |
|
||||||
} |
|
||||||
|
|
||||||
@override |
|
||||||
Widget build(BuildContext context) { |
|
||||||
return LayoutBuilder( |
|
||||||
builder: (context, constraints) { |
|
||||||
if (constraints.maxWidth > 1200) { |
|
||||||
return largeScreen; |
|
||||||
} else if (constraints.maxWidth <= 1200 && |
|
||||||
constraints.maxWidth >= 800) { |
|
||||||
return mediumScreen ?? largeScreen; |
|
||||||
} else { |
|
||||||
return smallScreen ?? largeScreen; |
|
||||||
} |
|
||||||
}, |
|
||||||
); |
|
||||||
} |
|
||||||
} |
|
@ -1,7 +1,7 @@ |
|||||||
# Flutter-related |
# Flutter-related |
||||||
**/Flutter/ephemeral/ |
**/Flutter/ephemeral/ |
||||||
**/Pods/ |
**/Pods/ |
||||||
|
|
||||||
# Xcode-related |
# Xcode-related |
||||||
**/xcuserdata/ |
**/dgph |
||||||
Podfile.lock |
**/xcuserdata/ |
||||||
|
@ -1,2 +1,2 @@ |
|||||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" |
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" |
||||||
#include "ephemeral/Flutter-Generated.xcconfig" |
#include "ephemeral/Flutter-Generated.xcconfig" |
||||||
|
@ -1,2 +1,2 @@ |
|||||||
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" |
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" |
||||||
#include "ephemeral/Flutter-Generated.xcconfig" |
#include "ephemeral/Flutter-Generated.xcconfig" |
||||||
|
@ -0,0 +1,88 @@ |
|||||||
|
PODS: |
||||||
|
- desktop_drop (0.0.1): |
||||||
|
- FlutterMacOS |
||||||
|
- device_info_plus (0.0.1): |
||||||
|
- FlutterMacOS |
||||||
|
- file_selector_macos (0.0.1): |
||||||
|
- FlutterMacOS |
||||||
|
- FlutterMacOS (1.0.0) |
||||||
|
- FMDB (2.7.5): |
||||||
|
- FMDB/standard (= 2.7.5) |
||||||
|
- FMDB/standard (2.7.5) |
||||||
|
- gal (1.0.0): |
||||||
|
- Flutter |
||||||
|
- FlutterMacOS |
||||||
|
- pasteboard (0.0.1): |
||||||
|
- FlutterMacOS |
||||||
|
- path_provider_foundation (0.0.1): |
||||||
|
- Flutter |
||||||
|
- FlutterMacOS |
||||||
|
- share_plus (0.0.1): |
||||||
|
- FlutterMacOS |
||||||
|
- sqflite (0.0.2): |
||||||
|
- FlutterMacOS |
||||||
|
- FMDB (>= 2.7.5) |
||||||
|
- url_launcher_macos (0.0.1): |
||||||
|
- FlutterMacOS |
||||||
|
- video_player_avfoundation (0.0.1): |
||||||
|
- Flutter |
||||||
|
- FlutterMacOS |
||||||
|
|
||||||
|
DEPENDENCIES: |
||||||
|
- desktop_drop (from `Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos`) |
||||||
|
- device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) |
||||||
|
- file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`) |
||||||
|
- FlutterMacOS (from `Flutter/ephemeral`) |
||||||
|
- gal (from `Flutter/ephemeral/.symlinks/plugins/gal/darwin`) |
||||||
|
- pasteboard (from `Flutter/ephemeral/.symlinks/plugins/pasteboard/macos`) |
||||||
|
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) |
||||||
|
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`) |
||||||
|
- sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/macos`) |
||||||
|
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) |
||||||
|
- video_player_avfoundation (from `Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin`) |
||||||
|
|
||||||
|
SPEC REPOS: |
||||||
|
trunk: |
||||||
|
- FMDB |
||||||
|
|
||||||
|
EXTERNAL SOURCES: |
||||||
|
desktop_drop: |
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos |
||||||
|
device_info_plus: |
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos |
||||||
|
file_selector_macos: |
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos |
||||||
|
FlutterMacOS: |
||||||
|
:path: Flutter/ephemeral |
||||||
|
gal: |
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/gal/darwin |
||||||
|
pasteboard: |
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/pasteboard/macos |
||||||
|
path_provider_foundation: |
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin |
||||||
|
share_plus: |
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos |
||||||
|
sqflite: |
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/sqflite/macos |
||||||
|
url_launcher_macos: |
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos |
||||||
|
video_player_avfoundation: |
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin |
||||||
|
|
||||||
|
SPEC CHECKSUMS: |
||||||
|
desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898 |
||||||
|
device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f |
||||||
|
file_selector_macos: 468fb6b81fac7c0e88d71317f3eec34c3b008ff9 |
||||||
|
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 |
||||||
|
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a |
||||||
|
gal: 61e868295d28fe67ffa297fae6dacebf56fd53e1 |
||||||
|
pasteboard: 9b69dba6fedbb04866be632205d532fe2f6b1d99 |
||||||
|
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 |
||||||
|
share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7 |
||||||
|
sqflite: a5789cceda41d54d23f31d6de539d65bb14100ea |
||||||
|
url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 |
||||||
|
video_player_avfoundation: 8563f13d8fc8b2c29dc2d09e60b660e4e8128837 |
||||||
|
|
||||||
|
PODFILE CHECKSUM: c2e95c8c0fe03c5c57e438583cae4cc732296009 |
||||||
|
|
||||||
|
COCOAPODS: 1.14.2 |
@ -1,8 +1,8 @@ |
|||||||
<?xml version="1.0" encoding="UTF-8"?> |
<?xml version="1.0" encoding="UTF-8"?> |
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||||
<plist version="1.0"> |
<plist version="1.0"> |
||||||
<dict> |
<dict> |
||||||
<key>IDEDidComputeMac32BitWarning</key> |
<key>IDEDidComputeMac32BitWarning</key> |
||||||
<true/> |
<true/> |
||||||
</dict> |
</dict> |
||||||
</plist> |
</plist> |
||||||
|
@ -1,8 +1,8 @@ |
|||||||
<?xml version="1.0" encoding="UTF-8"?> |
<?xml version="1.0" encoding="UTF-8"?> |
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||||
<plist version="1.0"> |
<plist version="1.0"> |
||||||
<dict> |
<dict> |
||||||
<key>IDEDidComputeMac32BitWarning</key> |
<key>IDEDidComputeMac32BitWarning</key> |
||||||
<true/> |
<true/> |
||||||
</dict> |
</dict> |
||||||
</plist> |
</plist> |
||||||
|
@ -1,9 +1,9 @@ |
|||||||
import Cocoa |
import Cocoa |
||||||
import FlutterMacOS |
import FlutterMacOS |
||||||
|
|
||||||
@NSApplicationMain |
@NSApplicationMain |
||||||
class AppDelegate: FlutterAppDelegate { |
class AppDelegate: FlutterAppDelegate { |
||||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { |
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { |
||||||
return true |
return true |
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -1,68 +1,68 @@ |
|||||||
{ |
{ |
||||||
"images" : [ |
"images" : [ |
||||||
{ |
{ |
||||||
"size" : "16x16", |
"size" : "16x16", |
||||||
"idiom" : "mac", |
"idiom" : "mac", |
||||||
"filename" : "app_icon_16.png", |
"filename" : "app_icon_16.png", |
||||||
"scale" : "1x" |
"scale" : "1x" |
||||||
}, |
}, |
||||||
{ |
{ |
||||||
"size" : "16x16", |
"size" : "16x16", |
||||||
"idiom" : "mac", |
"idiom" : "mac", |
||||||
"filename" : "app_icon_32.png", |
"filename" : "app_icon_32.png", |
||||||
"scale" : "2x" |
"scale" : "2x" |
||||||
}, |
}, |
||||||
{ |
{ |
||||||
"size" : "32x32", |
"size" : "32x32", |
||||||
"idiom" : "mac", |
"idiom" : "mac", |
||||||
"filename" : "app_icon_32.png", |
"filename" : "app_icon_32.png", |
||||||
"scale" : "1x" |
"scale" : "1x" |
||||||
}, |
}, |
||||||
{ |
{ |
||||||
"size" : "32x32", |
"size" : "32x32", |
||||||
"idiom" : "mac", |
"idiom" : "mac", |
||||||
"filename" : "app_icon_64.png", |
"filename" : "app_icon_64.png", |
||||||
"scale" : "2x" |
"scale" : "2x" |
||||||
}, |
}, |
||||||
{ |
{ |
||||||
"size" : "128x128", |
"size" : "128x128", |
||||||
"idiom" : "mac", |
"idiom" : "mac", |
||||||
"filename" : "app_icon_128.png", |
"filename" : "app_icon_128.png", |
||||||
"scale" : "1x" |
"scale" : "1x" |
||||||
}, |
}, |
||||||
{ |
{ |
||||||
"size" : "128x128", |
"size" : "128x128", |
||||||
"idiom" : "mac", |
"idiom" : "mac", |
||||||
"filename" : "app_icon_256.png", |
"filename" : "app_icon_256.png", |
||||||
"scale" : "2x" |
"scale" : "2x" |
||||||
}, |
}, |
||||||
{ |
{ |
||||||
"size" : "256x256", |
"size" : "256x256", |
||||||
"idiom" : "mac", |
"idiom" : "mac", |
||||||
"filename" : "app_icon_256.png", |
"filename" : "app_icon_256.png", |
||||||
"scale" : "1x" |
"scale" : "1x" |
||||||
}, |
}, |
||||||
{ |
{ |
||||||
"size" : "256x256", |
"size" : "256x256", |
||||||
"idiom" : "mac", |
"idiom" : "mac", |
||||||
"filename" : "app_icon_512.png", |
"filename" : "app_icon_512.png", |
||||||
"scale" : "2x" |
"scale" : "2x" |
||||||
}, |
}, |
||||||
{ |
{ |
||||||
"size" : "512x512", |
"size" : "512x512", |
||||||
"idiom" : "mac", |
"idiom" : "mac", |
||||||
"filename" : "app_icon_512.png", |
"filename" : "app_icon_512.png", |
||||||
"scale" : "1x" |
"scale" : "1x" |
||||||
}, |
}, |
||||||
{ |
{ |
||||||
"size" : "512x512", |
"size" : "512x512", |
||||||
"idiom" : "mac", |
"idiom" : "mac", |
||||||
"filename" : "app_icon_1024.png", |
"filename" : "app_icon_1024.png", |
||||||
"scale" : "2x" |
"scale" : "2x" |
||||||
} |
} |
||||||
], |
], |
||||||
"info" : { |
"info" : { |
||||||
"version" : 1, |
"version" : 1, |
||||||
"author" : "xcode" |
"author" : "xcode" |
||||||
} |
} |
||||||
} |
} |
||||||
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 101 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 520 B |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2.2 KiB |
@ -1,339 +1,343 @@ |
|||||||
<?xml version="1.0" encoding="UTF-8"?> |
<?xml version="1.0" encoding="UTF-8"?> |
||||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> |
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct"> |
||||||
<dependencies> |
<dependencies> |
||||||
<deployment identifier="macosx"/> |
<deployment identifier="macosx"/> |
||||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/> |
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/> |
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> |
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> |
||||||
</dependencies> |
</dependencies> |
||||||
<objects> |
<objects> |
||||||
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication"> |
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication"> |
||||||
<connections> |
<connections> |
||||||
<outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/> |
<outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/> |
||||||
</connections> |
</connections> |
||||||
</customObject> |
</customObject> |
||||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> |
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> |
||||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/> |
<customObject id="-3" userLabel="Application" customClass="NSObject"/> |
||||||
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Runner" customModuleProvider="target"> |
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Runner" customModuleProvider="target"> |
||||||
<connections> |
<connections> |
||||||
<outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/> |
<outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/> |
||||||
<outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/> |
<outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/> |
||||||
</connections> |
</connections> |
||||||
</customObject> |
</customObject> |
||||||
<customObject id="YLy-65-1bz" customClass="NSFontManager"/> |
<customObject id="YLy-65-1bz" customClass="NSFontManager"/> |
||||||
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6"> |
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6"> |
||||||
<items> |
<items> |
||||||
<menuItem title="APP_NAME" id="1Xt-HY-uBw"> |
<menuItem title="APP_NAME" id="1Xt-HY-uBw"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<menu key="submenu" title="APP_NAME" systemMenu="apple" id="uQy-DD-JDr"> |
<menu key="submenu" title="APP_NAME" systemMenu="apple" id="uQy-DD-JDr"> |
||||||
<items> |
<items> |
||||||
<menuItem title="About APP_NAME" id="5kV-Vb-QxS"> |
<menuItem title="About APP_NAME" id="5kV-Vb-QxS"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<connections> |
<connections> |
||||||
<action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/> |
<action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/> |
<menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/> |
||||||
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/> |
<menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/> |
||||||
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/> |
<menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/> |
||||||
<menuItem title="Services" id="NMo-om-nkz"> |
<menuItem title="Services" id="NMo-om-nkz"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/> |
<menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/> |
<menuItem isSeparatorItem="YES" id="4je-JR-u6R"/> |
||||||
<menuItem title="Hide APP_NAME" keyEquivalent="h" id="Olw-nP-bQN"> |
<menuItem title="Hide APP_NAME" keyEquivalent="h" id="Olw-nP-bQN"> |
||||||
<connections> |
<connections> |
||||||
<action selector="hide:" target="-1" id="PnN-Uc-m68"/> |
<action selector="hide:" target="-1" id="PnN-Uc-m68"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO"> |
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO"> |
||||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/> |
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/> |
||||||
<connections> |
<connections> |
||||||
<action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/> |
<action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Show All" id="Kd2-mp-pUS"> |
<menuItem title="Show All" id="Kd2-mp-pUS"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<connections> |
<connections> |
||||||
<action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/> |
<action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/> |
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/> |
||||||
<menuItem title="Quit APP_NAME" keyEquivalent="q" id="4sb-4s-VLi"> |
<menuItem title="Quit APP_NAME" keyEquivalent="q" id="4sb-4s-VLi"> |
||||||
<connections> |
<connections> |
||||||
<action selector="terminate:" target="-1" id="Te7-pn-YzF"/> |
<action selector="terminate:" target="-1" id="Te7-pn-YzF"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
</items> |
</items> |
||||||
</menu> |
</menu> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Edit" id="5QF-Oa-p0T"> |
<menuItem title="Edit" id="5QF-Oa-p0T"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<menu key="submenu" title="Edit" id="W48-6f-4Dl"> |
<menu key="submenu" title="Edit" id="W48-6f-4Dl"> |
||||||
<items> |
<items> |
||||||
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg"> |
<menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg"> |
||||||
<connections> |
<connections> |
||||||
<action selector="undo:" target="-1" id="M6e-cu-g7V"/> |
<action selector="undo:" target="-1" id="M6e-cu-g7V"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam"> |
<menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam"> |
||||||
<connections> |
<connections> |
||||||
<action selector="redo:" target="-1" id="oIA-Rs-6OD"/> |
<action selector="redo:" target="-1" id="oIA-Rs-6OD"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/> |
<menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/> |
||||||
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG"> |
<menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG"> |
||||||
<connections> |
<connections> |
||||||
<action selector="cut:" target="-1" id="YJe-68-I9s"/> |
<action selector="cut:" target="-1" id="YJe-68-I9s"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU"> |
<menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU"> |
||||||
<connections> |
<connections> |
||||||
<action selector="copy:" target="-1" id="G1f-GL-Joy"/> |
<action selector="copy:" target="-1" id="G1f-GL-Joy"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL"> |
<menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL"> |
||||||
<connections> |
<connections> |
||||||
<action selector="paste:" target="-1" id="UvS-8e-Qdg"/> |
<action selector="paste:" target="-1" id="UvS-8e-Qdg"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk"> |
<menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk"> |
||||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/> |
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/> |
||||||
<connections> |
<connections> |
||||||
<action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/> |
<action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Delete" id="pa3-QI-u2k"> |
<menuItem title="Delete" id="pa3-QI-u2k"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<connections> |
<connections> |
||||||
<action selector="delete:" target="-1" id="0Mk-Ml-PaM"/> |
<action selector="delete:" target="-1" id="0Mk-Ml-PaM"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m"> |
<menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m"> |
||||||
<connections> |
<connections> |
||||||
<action selector="selectAll:" target="-1" id="VNm-Mi-diN"/> |
<action selector="selectAll:" target="-1" id="VNm-Mi-diN"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/> |
<menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/> |
||||||
<menuItem title="Find" id="4EN-yA-p0u"> |
<menuItem title="Find" id="4EN-yA-p0u"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<menu key="submenu" title="Find" id="1b7-l0-nxx"> |
<menu key="submenu" title="Find" id="1b7-l0-nxx"> |
||||||
<items> |
<items> |
||||||
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W"> |
<menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W"> |
||||||
<connections> |
<connections> |
||||||
<action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/> |
<action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz"> |
<menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz"> |
||||||
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/> |
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/> |
||||||
<connections> |
<connections> |
||||||
<action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/> |
<action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye"> |
<menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye"> |
||||||
<connections> |
<connections> |
||||||
<action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/> |
<action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV"> |
<menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV"> |
||||||
<connections> |
<connections> |
||||||
<action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/> |
<action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt"> |
<menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt"> |
||||||
<connections> |
<connections> |
||||||
<action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/> |
<action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd"> |
<menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd"> |
||||||
<connections> |
<connections> |
||||||
<action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/> |
<action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
</items> |
</items> |
||||||
</menu> |
</menu> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7"> |
<menuItem title="Spelling and Grammar" id="Dv1-io-Yv7"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg"> |
<menu key="submenu" title="Spelling" id="3IN-sU-3Bg"> |
||||||
<items> |
<items> |
||||||
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI"> |
<menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI"> |
||||||
<connections> |
<connections> |
||||||
<action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/> |
<action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7"> |
<menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7"> |
||||||
<connections> |
<connections> |
||||||
<action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/> |
<action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/> |
<menuItem isSeparatorItem="YES" id="bNw-od-mp5"/> |
||||||
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN"> |
<menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<connections> |
<connections> |
||||||
<action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/> |
<action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG"> |
<menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<connections> |
<connections> |
||||||
<action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/> |
<action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v"> |
<menuItem title="Correct Spelling Automatically" id="78Y-hA-62v"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<connections> |
<connections> |
||||||
<action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/> |
<action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
</items> |
</items> |
||||||
</menu> |
</menu> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Substitutions" id="9ic-FL-obx"> |
<menuItem title="Substitutions" id="9ic-FL-obx"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr"> |
<menu key="submenu" title="Substitutions" id="FeM-D8-WVr"> |
||||||
<items> |
<items> |
||||||
<menuItem title="Show Substitutions" id="z6F-FW-3nz"> |
<menuItem title="Show Substitutions" id="z6F-FW-3nz"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<connections> |
<connections> |
||||||
<action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/> |
<action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/> |
<menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/> |
||||||
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM"> |
<menuItem title="Smart Copy/Paste" id="9yt-4B-nSM"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<connections> |
<connections> |
||||||
<action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/> |
<action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Smart Quotes" id="hQb-2v-fYv"> |
<menuItem title="Smart Quotes" id="hQb-2v-fYv"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<connections> |
<connections> |
||||||
<action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/> |
<action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Smart Dashes" id="rgM-f4-ycn"> |
<menuItem title="Smart Dashes" id="rgM-f4-ycn"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<connections> |
<connections> |
||||||
<action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/> |
<action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Smart Links" id="cwL-P1-jid"> |
<menuItem title="Smart Links" id="cwL-P1-jid"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<connections> |
<connections> |
||||||
<action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/> |
<action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Data Detectors" id="tRr-pd-1PS"> |
<menuItem title="Data Detectors" id="tRr-pd-1PS"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<connections> |
<connections> |
||||||
<action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/> |
<action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Text Replacement" id="HFQ-gK-NFA"> |
<menuItem title="Text Replacement" id="HFQ-gK-NFA"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<connections> |
<connections> |
||||||
<action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/> |
<action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
</items> |
</items> |
||||||
</menu> |
</menu> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Transformations" id="2oI-Rn-ZJC"> |
<menuItem title="Transformations" id="2oI-Rn-ZJC"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<menu key="submenu" title="Transformations" id="c8a-y6-VQd"> |
<menu key="submenu" title="Transformations" id="c8a-y6-VQd"> |
||||||
<items> |
<items> |
||||||
<menuItem title="Make Upper Case" id="vmV-6d-7jI"> |
<menuItem title="Make Upper Case" id="vmV-6d-7jI"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<connections> |
<connections> |
||||||
<action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/> |
<action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Make Lower Case" id="d9M-CD-aMd"> |
<menuItem title="Make Lower Case" id="d9M-CD-aMd"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<connections> |
<connections> |
||||||
<action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/> |
<action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Capitalize" id="UEZ-Bs-lqG"> |
<menuItem title="Capitalize" id="UEZ-Bs-lqG"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<connections> |
<connections> |
||||||
<action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/> |
<action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
</items> |
</items> |
||||||
</menu> |
</menu> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Speech" id="xrE-MZ-jX0"> |
<menuItem title="Speech" id="xrE-MZ-jX0"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<menu key="submenu" title="Speech" id="3rS-ZA-NoH"> |
<menu key="submenu" title="Speech" id="3rS-ZA-NoH"> |
||||||
<items> |
<items> |
||||||
<menuItem title="Start Speaking" id="Ynk-f8-cLZ"> |
<menuItem title="Start Speaking" id="Ynk-f8-cLZ"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<connections> |
<connections> |
||||||
<action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/> |
<action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Stop Speaking" id="Oyz-dy-DGm"> |
<menuItem title="Stop Speaking" id="Oyz-dy-DGm"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<connections> |
<connections> |
||||||
<action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/> |
<action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
</items> |
</items> |
||||||
</menu> |
</menu> |
||||||
</menuItem> |
</menuItem> |
||||||
</items> |
</items> |
||||||
</menu> |
</menu> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="View" id="H8h-7b-M4v"> |
<menuItem title="View" id="H8h-7b-M4v"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<menu key="submenu" title="View" id="HyV-fh-RgO"> |
<menu key="submenu" title="View" id="HyV-fh-RgO"> |
||||||
<items> |
<items> |
||||||
<menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa"> |
<menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa"> |
||||||
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/> |
<modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/> |
||||||
<connections> |
<connections> |
||||||
<action selector="toggleFullScreen:" target="-1" id="dU3-MA-1Rq"/> |
<action selector="toggleFullScreen:" target="-1" id="dU3-MA-1Rq"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
</items> |
</items> |
||||||
</menu> |
</menu> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Window" id="aUF-d1-5bR"> |
<menuItem title="Window" id="aUF-d1-5bR"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo"> |
<menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo"> |
||||||
<items> |
<items> |
||||||
<menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV"> |
<menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV"> |
||||||
<connections> |
<connections> |
||||||
<action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/> |
<action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem title="Zoom" id="R4o-n2-Eq4"> |
<menuItem title="Zoom" id="R4o-n2-Eq4"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<connections> |
<connections> |
||||||
<action selector="performZoom:" target="-1" id="DIl-cC-cCs"/> |
<action selector="performZoom:" target="-1" id="DIl-cC-cCs"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
<menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/> |
<menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/> |
||||||
<menuItem title="Bring All to Front" id="LE2-aR-0XJ"> |
<menuItem title="Bring All to Front" id="LE2-aR-0XJ"> |
||||||
<modifierMask key="keyEquivalentModifierMask"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
<connections> |
<connections> |
||||||
<action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/> |
<action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/> |
||||||
</connections> |
</connections> |
||||||
</menuItem> |
</menuItem> |
||||||
</items> |
</items> |
||||||
</menu> |
</menu> |
||||||
</menuItem> |
</menuItem> |
||||||
</items> |
<menuItem title="Help" id="EPT-qC-fAb"> |
||||||
<point key="canvasLocation" x="142" y="-258"/> |
<modifierMask key="keyEquivalentModifierMask"/> |
||||||
</menu> |
<menu key="submenu" title="Help" systemMenu="help" id="rJ0-wn-3NY"/> |
||||||
<window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="Runner" customModuleProvider="target"> |
</menuItem> |
||||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/> |
</items> |
||||||
<rect key="contentRect" x="335" y="390" width="800" height="600"/> |
<point key="canvasLocation" x="142" y="-258"/> |
||||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/> |
</menu> |
||||||
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ"> |
<window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="Runner" customModuleProvider="target"> |
||||||
<rect key="frame" x="0.0" y="0.0" width="800" height="600"/> |
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/> |
||||||
<autoresizingMask key="autoresizingMask"/> |
<rect key="contentRect" x="335" y="390" width="800" height="600"/> |
||||||
</view> |
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/> |
||||||
</window> |
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ"> |
||||||
</objects> |
<rect key="frame" x="0.0" y="0.0" width="800" height="600"/> |
||||||
</document> |
<autoresizingMask key="autoresizingMask"/> |
||||||
|
</view> |
||||||
|
</window> |
||||||
|
</objects> |
||||||
|
</document> |
||||||
|
@ -1,14 +1,14 @@ |
|||||||
// Application-level settings for the Runner target. |
// Application-level settings for the Runner target. |
||||||
// |
// |
||||||
// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the |
// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the |
||||||
// future. If not, the values below would default to using the project name when this becomes a |
// future. If not, the values below would default to using the project name when this becomes a |
||||||
// 'flutter create' template. |
// 'flutter create' template. |
||||||
|
|
||||||
// The application's name. By default this is also the title of the Flutter window. |
// The application's name. By default this is also the title of the Flutter window. |
||||||
PRODUCT_NAME = app |
PRODUCT_NAME = example |
||||||
|
|
||||||
// The application's bundle identifier |
// The application's bundle identifier |
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.app |
PRODUCT_BUNDLE_IDENTIFIER = com.example.example |
||||||
|
|
||||||
// The copyright displayed in application information |
// The copyright displayed in application information |
||||||
PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved. |
PRODUCT_COPYRIGHT = Copyright © 2023 com.example. All rights reserved. |
||||||
|
@ -1,2 +1,2 @@ |
|||||||
#include "../../Flutter/Flutter-Debug.xcconfig" |
#include "../../Flutter/Flutter-Debug.xcconfig" |
||||||
#include "Warnings.xcconfig" |
#include "Warnings.xcconfig" |
||||||
|
@ -1,2 +1,2 @@ |
|||||||
#include "../../Flutter/Flutter-Release.xcconfig" |
#include "../../Flutter/Flutter-Release.xcconfig" |
||||||
#include "Warnings.xcconfig" |
#include "Warnings.xcconfig" |
||||||
|
@ -1,13 +1,13 @@ |
|||||||
WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings |
WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings |
||||||
GCC_WARN_UNDECLARED_SELECTOR = YES |
GCC_WARN_UNDECLARED_SELECTOR = YES |
||||||
CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES |
CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES |
||||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE |
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE |
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES |
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES |
||||||
CLANG_WARN_PRAGMA_PACK = YES |
CLANG_WARN_PRAGMA_PACK = YES |
||||||
CLANG_WARN_STRICT_PROTOTYPES = YES |
CLANG_WARN_STRICT_PROTOTYPES = YES |
||||||
CLANG_WARN_COMMA = YES |
CLANG_WARN_COMMA = YES |
||||||
GCC_WARN_STRICT_SELECTOR_MATCH = YES |
GCC_WARN_STRICT_SELECTOR_MATCH = YES |
||||||
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES |
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES |
||||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES |
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES |
||||||
GCC_WARN_SHADOW = YES |
GCC_WARN_SHADOW = YES |
||||||
CLANG_WARN_UNREACHABLE_CODE = YES |
CLANG_WARN_UNREACHABLE_CODE = YES |
||||||
|
@ -1,16 +1,18 @@ |
|||||||
<?xml version="1.0" encoding="UTF-8"?> |
<?xml version="1.0" encoding="UTF-8"?> |
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||||
<plist version="1.0"> |
<plist version="1.0"> |
||||||
<dict> |
<dict> |
||||||
<key>com.apple.security.app-sandbox</key> |
<key>com.apple.security.app-sandbox</key> |
||||||
<true/> |
<true/> |
||||||
<key>com.apple.security.cs.allow-jit</key> |
<key>com.apple.security.cs.allow-jit</key> |
||||||
<true/> |
<true/> |
||||||
<key>com.apple.security.network.server</key> |
<key>com.apple.security.network.server</key> |
||||||
<true/> |
<true/> |
||||||
<key>com.apple.security.network.client</key> |
<key>com.apple.security.network.client</key> |
||||||
<true/> |
<true/> |
||||||
<key>com.apple.security.files.user-selected.read-only</key> |
<key>com.apple.security.files.user-selected.read-only</key> |
||||||
<true/> |
<true/> |
||||||
</dict> |
<!-- <key>com.apple.security.files.user-selected.read-write</key> |
||||||
</plist> |
<true/> --> |
||||||
|
</dict> |
||||||
|
</plist> |
||||||
|
@ -1,32 +1,36 @@ |
|||||||
<?xml version="1.0" encoding="UTF-8"?> |
<?xml version="1.0" encoding="UTF-8"?> |
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||||
<plist version="1.0"> |
<plist version="1.0"> |
||||||
<dict> |
<dict> |
||||||
<key>CFBundleDevelopmentRegion</key> |
<key>CFBundleDevelopmentRegion</key> |
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string> |
<string>$(DEVELOPMENT_LANGUAGE)</string> |
||||||
<key>CFBundleExecutable</key> |
<key>CFBundleExecutable</key> |
||||||
<string>$(EXECUTABLE_NAME)</string> |
<string>$(EXECUTABLE_NAME)</string> |
||||||
<key>CFBundleIconFile</key> |
<key>CFBundleIconFile</key> |
||||||
<string></string> |
<string></string> |
||||||
<key>CFBundleIdentifier</key> |
<key>CFBundleIdentifier</key> |
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> |
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> |
||||||
<key>CFBundleInfoDictionaryVersion</key> |
<key>CFBundleInfoDictionaryVersion</key> |
||||||
<string>6.0</string> |
<string>6.0</string> |
||||||
<key>CFBundleName</key> |
<key>CFBundleName</key> |
||||||
<string>$(PRODUCT_NAME)</string> |
<string>$(PRODUCT_NAME)</string> |
||||||
<key>CFBundlePackageType</key> |
<key>CFBundlePackageType</key> |
||||||
<string>APPL</string> |
<string>APPL</string> |
||||||
<key>CFBundleShortVersionString</key> |
<key>CFBundleShortVersionString</key> |
||||||
<string>$(FLUTTER_BUILD_NAME)</string> |
<string>$(FLUTTER_BUILD_NAME)</string> |
||||||
<key>CFBundleVersion</key> |
<key>CFBundleVersion</key> |
||||||
<string>$(FLUTTER_BUILD_NUMBER)</string> |
<string>$(FLUTTER_BUILD_NUMBER)</string> |
||||||
<key>LSMinimumSystemVersion</key> |
<key>LSMinimumSystemVersion</key> |
||||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string> |
<string>$(MACOSX_DEPLOYMENT_TARGET)</string> |
||||||
<key>NSHumanReadableCopyright</key> |
<key>NSHumanReadableCopyright</key> |
||||||
<string>$(PRODUCT_COPYRIGHT)</string> |
<string>$(PRODUCT_COPYRIGHT)</string> |
||||||
<key>NSMainNibFile</key> |
<key>NSMainNibFile</key> |
||||||
<string>MainMenu</string> |
<string>MainMenu</string> |
||||||
<key>NSPrincipalClass</key> |
<key>NSPrincipalClass</key> |
||||||
<string>NSApplication</string> |
<string>NSApplication</string> |
||||||
</dict> |
<key>NSPhotoLibraryUsageDescription</key> |
||||||
</plist> |
<string>We need permission to the photo library in order for inserting images in the text editor</string> |
||||||
|
<key>NSPhotoLibraryAddUsageDescription</key> |
||||||
|
<string>We need this permission for saving the images in the editor</string> |
||||||
|
</dict> |
||||||
|
</plist> |
||||||
|
@ -1,15 +1,15 @@ |
|||||||
import Cocoa |
import Cocoa |
||||||
import FlutterMacOS |
import FlutterMacOS |
||||||
|
|
||||||
class MainFlutterWindow: NSWindow { |
class MainFlutterWindow: NSWindow { |
||||||
override func awakeFromNib() { |
override func awakeFromNib() { |
||||||
let flutterViewController = FlutterViewController.init() |
let flutterViewController = FlutterViewController() |
||||||
let windowFrame = self.frame |
let windowFrame = self.frame |
||||||
self.contentViewController = flutterViewController |
self.contentViewController = flutterViewController |
||||||
self.setFrame(windowFrame, display: true) |
self.setFrame(windowFrame, display: true) |
||||||
|
|
||||||
RegisterGeneratedPlugins(registry: flutterViewController) |
RegisterGeneratedPlugins(registry: flutterViewController) |
||||||
|
|
||||||
super.awakeFromNib() |
super.awakeFromNib() |
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -1,12 +1,14 @@ |
|||||||
<?xml version="1.0" encoding="UTF-8"?> |
<?xml version="1.0" encoding="UTF-8"?> |
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
||||||
<plist version="1.0"> |
<plist version="1.0"> |
||||||
<dict> |
<dict> |
||||||
<key>com.apple.security.app-sandbox</key> |
<key>com.apple.security.app-sandbox</key> |
||||||
<true/> |
<true/> |
||||||
<key>com.apple.security.network.client</key> |
<key>com.apple.security.network.client</key> |
||||||
<true/> |
<true/> |
||||||
<key>com.apple.security.files.user-selected.read-only</key> |
<key>com.apple.security.files.user-selected.read-only</key> |
||||||
<true/> |
<true/> |
||||||
</dict> |
<!-- <key>com.apple.security.files.user-selected.read-write</key> |
||||||
</plist> |
<true/> --> |
||||||
|
</dict> |
||||||
|
</plist> |
||||||
|
@ -1,29 +1 @@ |
|||||||
// This is a basic Flutter widget test. |
void main() {} |
||||||
// |
|
||||||
// To perform an interaction with a widget in your test, use the WidgetTester |
|
||||||
// utility that Flutter provides. For example, you can send tap and scroll |
|
||||||
// gestures. You can also use WidgetTester to find child widgets in the widget |
|
||||||
// tree, read text, and verify that the values of widget properties are correct. |
|
||||||
|
|
||||||
import 'package:app/main.dart'; |
|
||||||
import 'package:flutter/material.dart'; |
|
||||||
import 'package:flutter_test/flutter_test.dart'; |
|
||||||
|
|
||||||
void main() { |
|
||||||
testWidgets('Counter increments smoke test', (tester) async { |
|
||||||
// Build our app and trigger a frame. |
|
||||||
await tester.pumpWidget(const MyApp()); |
|
||||||
|
|
||||||
// Verify that our counter starts at 0. |
|
||||||
expect(find.text('0'), findsOneWidget); |
|
||||||
expect(find.text('1'), findsNothing); |
|
||||||
|
|
||||||
// Tap the '+' icon and trigger a frame. |
|
||||||
await tester.tap(find.byIcon(Icons.add)); |
|
||||||
await tester.pump(); |
|
||||||
|
|
||||||
// Verify that our counter has incremented. |
|
||||||
expect(find.text('0'), findsNothing); |
|
||||||
expect(find.text('1'), findsOneWidget); |
|
||||||
}); |
|
||||||
} |
|
||||||
|