Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,7 @@ app.*.map.json
/android/app/release
/dist/
dotenv
run.sh
run.sh
run.ps1
run-release.sh
run-release.ps1
5 changes: 4 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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!
We appreciate your contributions! ❤️ Happy coding!
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
<br>
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!

---

Expand Down
20 changes: 20 additions & 0 deletions lib/app.dart
Original file line number Diff line number Diff line change
@@ -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<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MaterialApp.router(
debugShowCheckedModeBanner: false,
routerConfig: AppRouteConfig.returnRouter(),
// home: const SplashScreen(),
);
}
}
219 changes: 134 additions & 85 deletions lib/core/services/file_services.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
}
Expand All @@ -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();
}
Expand All @@ -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();
}
Expand All @@ -126,13 +182,9 @@ class FileServices {
}

Future<XFile?> 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 {
Expand All @@ -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) {
Expand All @@ -152,7 +202,6 @@ class FileServices {
}

Future<XFile?> importJsonFiles() async {
// final fileSelector = FileSelectorPlatform.instance;
const XTypeGroup typeGroup = XTypeGroup(
label: 'JSON',
mimeTypes: ['application/json'],
Expand All @@ -171,4 +220,4 @@ class FileServices {
return null;
}
}
}
}
Loading