Skip to content

Commit 874133e

Browse files
authored
Merge pull request #22 from CookethOrg/dev
merging
2 parents 28d1893 + 24d5990 commit 874133e

File tree

12 files changed

+330
-232
lines changed

12 files changed

+330
-232
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,7 @@ app.*.map.json
4646
/android/app/release
4747
/dist/
4848
dotenv
49-
run.sh
49+
run.sh
50+
run.ps1
51+
run-release.sh
52+
run-release.ps1

CONTRIBUTING.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,12 @@ If you find a bug or have a feature request, please [open an issue](https://gith
4646
- Follow the [Code of Conduct](CODE_OF_CONDUCT.md).
4747
- Help others by reviewing and discussing pull requests.
4848

49+
## Governance
50+
[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.
51+
4952
## License
5053

5154
By contributing, you agree that your contributions will be licensed under the same license as Cooketh Flow.
5255

5356
---
54-
We appreciate your contributions! ❤️ Happy coding!
57+
We appreciate your contributions! ❤️ Happy coding!

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@ flutter run
7373
---
7474
## 📜 License
7575

76-
This project is licensed under the **MIT License** – see the [`LICENSE`](LICENSE) file for details.
76+
This project is licensed under the **MIT License** – see the [`LICENSE`](LICENSE) file for details.
77+
<br>
78+
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!
7779

7880
---
7981

lib/app.dart

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import 'package:cookethflow/core/routes/app_route_config.dart';
2+
import 'package:flutter/material.dart';
3+
4+
class MyApp extends StatefulWidget {
5+
const MyApp({super.key});
6+
7+
@override
8+
State<MyApp> createState() => _MyAppState();
9+
}
10+
11+
class _MyAppState extends State<MyApp> {
12+
@override
13+
Widget build(BuildContext context) {
14+
return MaterialApp.router(
15+
debugShowCheckedModeBanner: false,
16+
routerConfig: AppRouteConfig.returnRouter(),
17+
// home: const SplashScreen(),
18+
);
19+
}
20+
}

lib/core/services/file_services.dart

Lines changed: 134 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import 'dart:convert';
22
import 'dart:typed_data';
33
import 'package:file_selector_platform_interface/file_selector_platform_interface.dart';
44
import 'package:cookethflow/core/services/platform_file_service.dart';
5+
import 'package:flutter/foundation.dart';
6+
import 'package:universal_html/html.dart' as html;
57

68
class FileServices {
79
final FileSelectorPlatform fileSelector = FileSelectorPlatform.instance;
@@ -11,38 +13,51 @@ class FileServices {
1113
required String jsonString,
1214
}) async {
1315
try {
14-
const XTypeGroup typeGroup = XTypeGroup(
15-
label: 'JSON',
16-
extensions: ['json'],
17-
mimeTypes: ['application/json'],
18-
);
19-
2016
// Ensure the default name ends with .json
21-
if (!defaultName.toLowerCase().endsWith('.json')) {
22-
defaultName = '$defaultName.json';
23-
}
24-
25-
final String? path = await fileSelector.getSavePath(
26-
acceptedTypeGroups: [typeGroup],
27-
suggestedName: defaultName,
28-
);
29-
30-
if (path == null) {
31-
return 'Save operation cancelled';
17+
final sanitizedName = defaultName.toLowerCase().endsWith('.json')
18+
? defaultName
19+
: '$defaultName.json';
20+
21+
if (kIsWeb) {
22+
// Web: Use browser download API
23+
final bytes = Uint8List.fromList(utf8.encode(jsonString));
24+
final blob = html.Blob([bytes], 'application/json');
25+
final url = html.Url.createObjectUrlFromBlob(blob);
26+
final anchor = html.AnchorElement(href: url)
27+
..setAttribute('download', sanitizedName)
28+
..click();
29+
html.Url.revokeObjectUrl(url);
30+
return 'success';
31+
} else {
32+
// Desktop: Use file_selector
33+
const XTypeGroup typeGroup = XTypeGroup(
34+
label: 'JSON',
35+
extensions: ['json'],
36+
mimeTypes: ['application/json'],
37+
);
38+
39+
final String? path = await fileSelector.getSavePath(
40+
acceptedTypeGroups: [typeGroup],
41+
suggestedName: sanitizedName,
42+
);
43+
44+
if (path == null) {
45+
return 'Save operation cancelled';
46+
}
47+
48+
// Ensure the selected path ends with .json
49+
final String savePath =
50+
path.toLowerCase().endsWith('.json') ? path : '$path.json';
51+
52+
final XFile file = XFile.fromData(
53+
Uint8List.fromList(utf8.encode(jsonString)),
54+
mimeType: 'application/json',
55+
name: savePath.split('/').last,
56+
);
57+
58+
await file.saveTo(savePath);
59+
return 'success';
3260
}
33-
34-
// Ensure the selected path ends with .json
35-
final String savePath =
36-
path.toLowerCase().endsWith('.json') ? path : '$path.json';
37-
38-
final XFile file = XFile.fromData(
39-
Uint8List.fromList(utf8.encode(jsonString)),
40-
mimeType: 'application/json',
41-
name: savePath.split('/').last,
42-
);
43-
44-
await file.saveTo(savePath);
45-
return 'success';
4661
} catch (e) {
4762
return e.toString();
4863
}
@@ -53,30 +68,50 @@ class FileServices {
5368
required Uint8List pngBytes,
5469
}) async {
5570
try {
56-
const XTypeGroup typeGroup = XTypeGroup(
57-
label: 'PNG Images',
58-
extensions: ['png'],
59-
mimeTypes: ['image/png'],
60-
);
61-
62-
final String? path = await fileSelector.getSavePath(
63-
acceptedTypeGroups: [typeGroup],
64-
suggestedName: defaultName,
65-
);
66-
67-
if (path == null) {
68-
return 'Save operation cancelled';
71+
// Ensure the default name ends with .png
72+
final sanitizedName = defaultName.toLowerCase().endsWith('.png')
73+
? defaultName
74+
: '$defaultName.png';
75+
76+
if (kIsWeb) {
77+
// Web: Use browser download API
78+
final blob = html.Blob([pngBytes], 'image/png');
79+
final url = html.Url.createObjectUrlFromBlob(blob);
80+
final anchor = html.AnchorElement(href: url)
81+
..setAttribute('download', sanitizedName)
82+
..click();
83+
html.Url.revokeObjectUrl(url);
84+
return 'success';
85+
} else {
86+
// Desktop: Use file_selector
87+
const XTypeGroup typeGroup = XTypeGroup(
88+
label: 'PNG Images',
89+
extensions: ['png'],
90+
mimeTypes: ['image/png'],
91+
);
92+
93+
final String? path = await fileSelector.getSavePath(
94+
acceptedTypeGroups: [typeGroup],
95+
suggestedName: sanitizedName,
96+
);
97+
98+
if (path == null) {
99+
return 'Save operation cancelled';
100+
}
101+
102+
// Ensure the selected path ends with .png
103+
final String savePath =
104+
path.toLowerCase().endsWith('.png') ? path : '$path.png';
105+
106+
final XFile file = XFile.fromData(
107+
pngBytes,
108+
mimeType: 'image/png',
109+
name: savePath.split('/').last,
110+
);
111+
112+
await file.saveTo(savePath);
113+
return 'success';
69114
}
70-
71-
final XFile file = XFile.fromData(
72-
pngBytes,
73-
mimeType: 'image/png',
74-
name: path.split('/').last,
75-
path: path,
76-
);
77-
78-
await file.saveTo(path);
79-
return 'success';
80115
} catch (e) {
81116
return e.toString();
82117
}
@@ -87,30 +122,51 @@ class FileServices {
87122
required String svgString,
88123
}) async {
89124
try {
90-
const XTypeGroup typeGroup = XTypeGroup(
91-
label: 'SVG Images',
92-
extensions: ['svg'],
93-
mimeTypes: ['image/svg+xml'],
94-
);
95-
96-
final String? path = await fileSelector.getSavePath(
97-
acceptedTypeGroups: [typeGroup],
98-
suggestedName: defaultName,
99-
);
100-
101-
if (path == null) {
102-
return 'Save operation cancelled';
125+
// Ensure the default name ends with .svg
126+
final sanitizedName = defaultName.toLowerCase().endsWith('.svg')
127+
? defaultName
128+
: '$defaultName.svg';
129+
130+
if (kIsWeb) {
131+
// Web: Use browser download API
132+
final bytes = Uint8List.fromList(utf8.encode(svgString));
133+
final blob = html.Blob([bytes], 'image/svg+xml');
134+
final url = html.Url.createObjectUrlFromBlob(blob);
135+
final anchor = html.AnchorElement(href: url)
136+
..setAttribute('download', sanitizedName)
137+
..click();
138+
html.Url.revokeObjectUrl(url);
139+
return 'success';
140+
} else {
141+
// Desktop: Use file_selector
142+
const XTypeGroup typeGroup = XTypeGroup(
143+
label: 'SVG Images',
144+
extensions: ['svg'],
145+
mimeTypes: ['image/svg+xml'],
146+
);
147+
148+
final String? path = await fileSelector.getSavePath(
149+
acceptedTypeGroups: [typeGroup],
150+
suggestedName: sanitizedName,
151+
);
152+
153+
if (path == null) {
154+
return 'Save operation cancelled';
155+
}
156+
157+
// Ensure the selected path ends with .svg
158+
final String savePath =
159+
path.toLowerCase().endsWith('.svg') ? path : '$path.svg';
160+
161+
final XFile file = XFile.fromData(
162+
Uint8List.fromList(utf8.encode(svgString)),
163+
mimeType: 'image/svg+xml',
164+
name: savePath.split('/').last,
165+
);
166+
167+
await file.saveTo(savePath);
168+
return 'success';
103169
}
104-
105-
final XFile file = XFile.fromData(
106-
Uint8List.fromList(utf8.encode(svgString)),
107-
mimeType: 'image/svg+xml',
108-
name: path.split('/').last,
109-
path: path,
110-
);
111-
112-
await file.saveTo(path);
113-
return 'success';
114170
} catch (e) {
115171
return e.toString();
116172
}
@@ -126,13 +182,9 @@ class FileServices {
126182
}
127183

128184
Future<XFile?> selectImages() async {
129-
// For Linux, you can specify the MIME types or extensions
130-
// final fileSelector = FileSelectorPlatform.instance;
131185
const XTypeGroup typeGroup = XTypeGroup(
132186
label: 'Images',
133-
mimeTypes: ['image/*'], // All image types
134-
// Alternatively, you can specify extensions:
135-
// extensions: ['jpg', 'jpeg', 'png', 'gif', 'webp'],
187+
mimeTypes: ['image/*'],
136188
);
137189

138190
try {
@@ -141,8 +193,6 @@ class FileServices {
141193

142194
if (file != null) {
143195
print('Selected file: ${file.path}');
144-
// You can now read the file or display the image
145-
// For example, with Image.file(File(file.path))
146196
}
147197
return file;
148198
} catch (e) {
@@ -152,7 +202,6 @@ class FileServices {
152202
}
153203

154204
Future<XFile?> importJsonFiles() async {
155-
// final fileSelector = FileSelectorPlatform.instance;
156205
const XTypeGroup typeGroup = XTypeGroup(
157206
label: 'JSON',
158207
mimeTypes: ['application/json'],
@@ -171,4 +220,4 @@ class FileServices {
171220
return null;
172221
}
173222
}
174-
}
223+
}

0 commit comments

Comments
 (0)