diff --git a/.gitignore b/.gitignore
index 6661eb5..02315c0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -46,4 +46,7 @@ app.*.map.json
/android/app/release
/dist/
dotenv
-run.sh
\ No newline at end of file
+run.sh
+run.ps1
+run-release.sh
+run-release.ps1
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e92a634..5b849a9 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -46,9 +46,12 @@ If you find a bug or have a feature request, please [open an issue](https://gith
- Follow the [Code of Conduct](CODE_OF_CONDUCT.md).
- Help others by reviewing and discussing pull requests.
+## Governance
+[Cooketh Flow](https://github.com/CookethOrg/Cooketh-Flow) is led by [Subroto Banerjee](https://github.com/TeeWrath) as BDFL, who makes final decisions on the project’s direction. Contributors are encouraged to share ideas via GitHub Issues, but the BDFL reserves the right to guide the roadmap.
+
## License
By contributing, you agree that your contributions will be licensed under the same license as Cooketh Flow.
---
-We appreciate your contributions! ❤️ Happy coding!
\ No newline at end of file
+We appreciate your contributions! ❤️ Happy coding!
diff --git a/README.md b/README.md
index 1f7f21a..6ccf7f6 100644
--- a/README.md
+++ b/README.md
@@ -73,7 +73,9 @@ flutter run
---
## 📜 License
-This project is licensed under the **MIT License** – see the [`LICENSE`](LICENSE) file for details.
+This project is licensed under the **MIT License** – see the [`LICENSE`](LICENSE) file for details.
+
+I, [Subroto Banerjee](https://github.com/TeeWrath), am the Benevolent Dictator For Life (BDFL) of [Cooketh Flow](https://github.com/CookethOrg/Cooketh-Flow), leading its vision and development. I welcome contributions under our MIT license to make this visual thinking tool awesome!
---
diff --git a/lib/app.dart b/lib/app.dart
new file mode 100644
index 0000000..92746c5
--- /dev/null
+++ b/lib/app.dart
@@ -0,0 +1,20 @@
+import 'package:cookethflow/core/routes/app_route_config.dart';
+import 'package:flutter/material.dart';
+
+class MyApp extends StatefulWidget {
+ const MyApp({super.key});
+
+ @override
+ State createState() => _MyAppState();
+}
+
+class _MyAppState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp.router(
+ debugShowCheckedModeBanner: false,
+ routerConfig: AppRouteConfig.returnRouter(),
+ // home: const SplashScreen(),
+ );
+ }
+}
\ No newline at end of file
diff --git a/lib/core/services/file_services.dart b/lib/core/services/file_services.dart
index 5032583..0d68802 100644
--- a/lib/core/services/file_services.dart
+++ b/lib/core/services/file_services.dart
@@ -2,6 +2,8 @@ import 'dart:convert';
import 'dart:typed_data';
import 'package:file_selector_platform_interface/file_selector_platform_interface.dart';
import 'package:cookethflow/core/services/platform_file_service.dart';
+import 'package:flutter/foundation.dart';
+import 'package:universal_html/html.dart' as html;
class FileServices {
final FileSelectorPlatform fileSelector = FileSelectorPlatform.instance;
@@ -11,38 +13,51 @@ class FileServices {
required String jsonString,
}) async {
try {
- const XTypeGroup typeGroup = XTypeGroup(
- label: 'JSON',
- extensions: ['json'],
- mimeTypes: ['application/json'],
- );
-
// Ensure the default name ends with .json
- if (!defaultName.toLowerCase().endsWith('.json')) {
- defaultName = '$defaultName.json';
- }
-
- final String? path = await fileSelector.getSavePath(
- acceptedTypeGroups: [typeGroup],
- suggestedName: defaultName,
- );
-
- if (path == null) {
- return 'Save operation cancelled';
+ final sanitizedName = defaultName.toLowerCase().endsWith('.json')
+ ? defaultName
+ : '$defaultName.json';
+
+ if (kIsWeb) {
+ // Web: Use browser download API
+ final bytes = Uint8List.fromList(utf8.encode(jsonString));
+ final blob = html.Blob([bytes], 'application/json');
+ final url = html.Url.createObjectUrlFromBlob(blob);
+ final anchor = html.AnchorElement(href: url)
+ ..setAttribute('download', sanitizedName)
+ ..click();
+ html.Url.revokeObjectUrl(url);
+ return 'success';
+ } else {
+ // Desktop: Use file_selector
+ const XTypeGroup typeGroup = XTypeGroup(
+ label: 'JSON',
+ extensions: ['json'],
+ mimeTypes: ['application/json'],
+ );
+
+ final String? path = await fileSelector.getSavePath(
+ acceptedTypeGroups: [typeGroup],
+ suggestedName: sanitizedName,
+ );
+
+ if (path == null) {
+ return 'Save operation cancelled';
+ }
+
+ // Ensure the selected path ends with .json
+ final String savePath =
+ path.toLowerCase().endsWith('.json') ? path : '$path.json';
+
+ final XFile file = XFile.fromData(
+ Uint8List.fromList(utf8.encode(jsonString)),
+ mimeType: 'application/json',
+ name: savePath.split('/').last,
+ );
+
+ await file.saveTo(savePath);
+ return 'success';
}
-
- // Ensure the selected path ends with .json
- final String savePath =
- path.toLowerCase().endsWith('.json') ? path : '$path.json';
-
- final XFile file = XFile.fromData(
- Uint8List.fromList(utf8.encode(jsonString)),
- mimeType: 'application/json',
- name: savePath.split('/').last,
- );
-
- await file.saveTo(savePath);
- return 'success';
} catch (e) {
return e.toString();
}
@@ -53,30 +68,50 @@ class FileServices {
required Uint8List pngBytes,
}) async {
try {
- const XTypeGroup typeGroup = XTypeGroup(
- label: 'PNG Images',
- extensions: ['png'],
- mimeTypes: ['image/png'],
- );
-
- final String? path = await fileSelector.getSavePath(
- acceptedTypeGroups: [typeGroup],
- suggestedName: defaultName,
- );
-
- if (path == null) {
- return 'Save operation cancelled';
+ // Ensure the default name ends with .png
+ final sanitizedName = defaultName.toLowerCase().endsWith('.png')
+ ? defaultName
+ : '$defaultName.png';
+
+ if (kIsWeb) {
+ // Web: Use browser download API
+ final blob = html.Blob([pngBytes], 'image/png');
+ final url = html.Url.createObjectUrlFromBlob(blob);
+ final anchor = html.AnchorElement(href: url)
+ ..setAttribute('download', sanitizedName)
+ ..click();
+ html.Url.revokeObjectUrl(url);
+ return 'success';
+ } else {
+ // Desktop: Use file_selector
+ const XTypeGroup typeGroup = XTypeGroup(
+ label: 'PNG Images',
+ extensions: ['png'],
+ mimeTypes: ['image/png'],
+ );
+
+ final String? path = await fileSelector.getSavePath(
+ acceptedTypeGroups: [typeGroup],
+ suggestedName: sanitizedName,
+ );
+
+ if (path == null) {
+ return 'Save operation cancelled';
+ }
+
+ // Ensure the selected path ends with .png
+ final String savePath =
+ path.toLowerCase().endsWith('.png') ? path : '$path.png';
+
+ final XFile file = XFile.fromData(
+ pngBytes,
+ mimeType: 'image/png',
+ name: savePath.split('/').last,
+ );
+
+ await file.saveTo(savePath);
+ return 'success';
}
-
- final XFile file = XFile.fromData(
- pngBytes,
- mimeType: 'image/png',
- name: path.split('/').last,
- path: path,
- );
-
- await file.saveTo(path);
- return 'success';
} catch (e) {
return e.toString();
}
@@ -87,30 +122,51 @@ class FileServices {
required String svgString,
}) async {
try {
- const XTypeGroup typeGroup = XTypeGroup(
- label: 'SVG Images',
- extensions: ['svg'],
- mimeTypes: ['image/svg+xml'],
- );
-
- final String? path = await fileSelector.getSavePath(
- acceptedTypeGroups: [typeGroup],
- suggestedName: defaultName,
- );
-
- if (path == null) {
- return 'Save operation cancelled';
+ // Ensure the default name ends with .svg
+ final sanitizedName = defaultName.toLowerCase().endsWith('.svg')
+ ? defaultName
+ : '$defaultName.svg';
+
+ if (kIsWeb) {
+ // Web: Use browser download API
+ final bytes = Uint8List.fromList(utf8.encode(svgString));
+ final blob = html.Blob([bytes], 'image/svg+xml');
+ final url = html.Url.createObjectUrlFromBlob(blob);
+ final anchor = html.AnchorElement(href: url)
+ ..setAttribute('download', sanitizedName)
+ ..click();
+ html.Url.revokeObjectUrl(url);
+ return 'success';
+ } else {
+ // Desktop: Use file_selector
+ const XTypeGroup typeGroup = XTypeGroup(
+ label: 'SVG Images',
+ extensions: ['svg'],
+ mimeTypes: ['image/svg+xml'],
+ );
+
+ final String? path = await fileSelector.getSavePath(
+ acceptedTypeGroups: [typeGroup],
+ suggestedName: sanitizedName,
+ );
+
+ if (path == null) {
+ return 'Save operation cancelled';
+ }
+
+ // Ensure the selected path ends with .svg
+ final String savePath =
+ path.toLowerCase().endsWith('.svg') ? path : '$path.svg';
+
+ final XFile file = XFile.fromData(
+ Uint8List.fromList(utf8.encode(svgString)),
+ mimeType: 'image/svg+xml',
+ name: savePath.split('/').last,
+ );
+
+ await file.saveTo(savePath);
+ return 'success';
}
-
- final XFile file = XFile.fromData(
- Uint8List.fromList(utf8.encode(svgString)),
- mimeType: 'image/svg+xml',
- name: path.split('/').last,
- path: path,
- );
-
- await file.saveTo(path);
- return 'success';
} catch (e) {
return e.toString();
}
@@ -126,13 +182,9 @@ class FileServices {
}
Future selectImages() async {
- // For Linux, you can specify the MIME types or extensions
- // final fileSelector = FileSelectorPlatform.instance;
const XTypeGroup typeGroup = XTypeGroup(
label: 'Images',
- mimeTypes: ['image/*'], // All image types
- // Alternatively, you can specify extensions:
- // extensions: ['jpg', 'jpeg', 'png', 'gif', 'webp'],
+ mimeTypes: ['image/*'],
);
try {
@@ -141,8 +193,6 @@ class FileServices {
if (file != null) {
print('Selected file: ${file.path}');
- // You can now read the file or display the image
- // For example, with Image.file(File(file.path))
}
return file;
} catch (e) {
@@ -152,7 +202,6 @@ class FileServices {
}
Future importJsonFiles() async {
- // final fileSelector = FileSelectorPlatform.instance;
const XTypeGroup typeGroup = XTypeGroup(
label: 'JSON',
mimeTypes: ['application/json'],
@@ -171,4 +220,4 @@ class FileServices {
return null;
}
}
-}
+}
\ No newline at end of file
diff --git a/lib/core/services/platform_file_service.dart b/lib/core/services/platform_file_service.dart
index b4dc37d..e10ff18 100644
--- a/lib/core/services/platform_file_service.dart
+++ b/lib/core/services/platform_file_service.dart
@@ -1,47 +1,39 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
-import 'dart:async';
-
import 'package:flutter/foundation.dart';
import 'package:flutter_document_picker/flutter_document_picker.dart';
+import 'package:universal_html/html.dart' as html;
class PlatformFileService {
static Future