Skip to content

Commit 3549a3a

Browse files
committed
Ignore hex files (fix #27), add sorting for firmware, add multilang firmwares
1 parent 364629c commit 3549a3a

File tree

1 file changed

+73
-44
lines changed

1 file changed

+73
-44
lines changed

lib/pages/flash_pinecil_page.dart

Lines changed: 73 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,10 @@ class _LogMessage {
3333

3434
class _FirmwareEntry {
3535
final String name;
36-
final String url;
36+
final String fileName;
37+
final String zipUrl;
3738

38-
_FirmwareEntry(this.name, this.url);
39+
_FirmwareEntry(this.name, this.fileName, this.zipUrl);
3940
}
4041

4142
class _FlashPinecilPageState extends State<FlashPinecilPage> {
@@ -49,23 +50,23 @@ class _FlashPinecilPageState extends State<FlashPinecilPage> {
4950
int _progress = 0;
5051
bool _errorOccured = false;
5152
late Future<List<_FirmwareEntry>> _firmwareListFuture;
52-
String? _firmwareType;
53+
_FirmwareEntry? _firmwareToFlash;
5354
final _dio = Dio();
54-
String? _pinecilZipUrl;
5555

5656
bool get _isPinecilConnected => (_pinecilState == _PinecilState.Connected ||
5757
_pinecilState == _PinecilState.ConnectedNoDriver);
5858
final _firmwarePathController = TextEditingController();
5959

60-
void startUSBTimer() {
61-
_usbTimer = Timer.periodic(const Duration(seconds: 1), updatePinecilStatus);
60+
void _startUSBTimer() {
61+
_usbTimer =
62+
Timer.periodic(const Duration(seconds: 1), _updatePinecilStatus);
6263
}
6364

6465
@override
6566
void initState() {
6667
super.initState();
67-
startUSBTimer();
68-
_firmwareListFuture = getLatestFirmwares();
68+
_startUSBTimer();
69+
_firmwareListFuture = _getLatestFirmwares();
6970
}
7071

7172
@override
@@ -75,46 +76,71 @@ class _FlashPinecilPageState extends State<FlashPinecilPage> {
7576
_firmwarePathController.dispose();
7677
}
7778

78-
Future<List<_FirmwareEntry>> getLatestFirmwares() async {
79+
Future<void> _parseJsonMetadata(List<_FirmwareEntry> firmwaresList,
80+
ArchiveFile jsonArchive, String zipUrl) async {
81+
final metadata = jsonDecode(utf8.decode(jsonArchive.content));
82+
final list = List<_FirmwareEntry>.empty(growable: true);
83+
for (String firmwareFile in metadata['contents'].keys) {
84+
if (firmwareFile.contains('.hex')) continue;
85+
final firmwareInfo = metadata['contents'][firmwareFile];
86+
list.add(
87+
_FirmwareEntry(
88+
"IronOS ${metadata['release']} - ${firmwareInfo['language_name']}",
89+
firmwareFile,
90+
zipUrl),
91+
);
92+
}
93+
list.sort((a, b) => a.fileName.compareTo(b.fileName));
94+
firmwaresList.addAll(list);
95+
}
96+
97+
Future<List<_FirmwareEntry>> _getLatestFirmwares() async {
98+
final list = List<_FirmwareEntry>.empty(growable: true);
7999
try {
80100
final githubResponse = await _dio
81101
.get("https://api.github.com/repos/Ralim/IronOS/releases/latest");
82102
if (githubResponse.statusCode != 200) {
83103
throw "Failed to fetch Github releases";
84104
}
85105
List<dynamic> assets = githubResponse.data['assets'];
86-
_pinecilZipUrl = assets.singleWhere((element) =>
106+
final pinecilZipUrl = assets.singleWhere((element) =>
87107
element['name'] == 'Pinecil.zip')?['browser_download_url'];
108+
final pinecilMultilangZipUrl = assets.singleWhere((element) =>
109+
element['name'] == 'Pinecil_multi-lang.zip')?['browser_download_url'];
88110
final metadata =
89111
assets.singleWhere((element) => element['name'] == "metadata.zip");
90112
final metadataRequest = await _dio.get(metadata['browser_download_url'],
91113
options: Options(responseType: ResponseType.bytes));
92114
final metadataZip = ZipDecoder().decodeBytes(metadataRequest.data);
115+
final pinecilMultilangJsonFile = metadataZip
116+
.singleWhere((element) => element.name == "Pinecil_multi-lang.json");
117+
await _parseJsonMetadata(
118+
list, pinecilMultilangJsonFile, pinecilMultilangZipUrl);
93119
final pinecilJsonFile =
94120
metadataZip.singleWhere((element) => element.name == "Pinecil.json");
95-
final pinecilMetadata = jsonDecode(utf8.decode(pinecilJsonFile.content));
121+
await _parseJsonMetadata(list, pinecilJsonFile, pinecilZipUrl);
122+
/*final pinecilMetadata = jsonDecode(utf8.decode(pinecilJsonFile.content));
96123
return List<_FirmwareEntry>.generate(
97124
pinecilMetadata['contents'].keys.length, (index) {
98125
final firmwareFile = pinecilMetadata['contents'].keys.toList()[index];
99126
final firmwareInfo = pinecilMetadata['contents'][firmwareFile];
100127
return _FirmwareEntry(
101128
"IronOS ${pinecilMetadata['release']} - ${firmwareInfo['language_name']}",
102129
firmwareFile);
103-
});
130+
});*/
104131
} catch (ex) {
105132
print(ex);
106133
}
107-
return List<_FirmwareEntry>.empty();
134+
return list;
108135
}
109136

110-
void updatePinecilStatus(Timer timer) {
137+
void _updatePinecilStatus(Timer timer) {
111138
final devicesList = libUSB.getDevicesList();
112139
if (_previousDevicesCount != devicesList.devices.length) {
113140
_previousDevicesCount = devicesList.devices.length;
114141
_pinecilState = _PinecilState.Disconnected;
115142
for (var device in devicesList.devices) {
116143
var descriptor = libUSB.getDeviceDescriptor(device);
117-
print(descriptor.idVendor.toRadixString(16) + " / " + descriptor.idProduct.toRadixString(16));
118144
if (descriptor.idVendor == 0x28E9 && descriptor.idProduct == 0x0189) {
119145
try {
120146
final deviceHandle = libUSB.open(device);
@@ -141,19 +167,20 @@ class _FlashPinecilPageState extends State<FlashPinecilPage> {
141167
devicesList.free();
142168
}
143169

144-
void copyLog() {
170+
void _copyLog() {
145171
String logData = _log.map((e) => e.message).join('\n');
146172
FlutterClipboard.copy(logData);
147173
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
148174
content: Text("The log has been copied"),
149175
));
150176
}
151177

152-
void flashFirmware({String? firmwarePath, String? firmwareURL}) async {
178+
void _flashFirmware(
179+
{String? firmwarePath, _FirmwareEntry? firmwareEntry}) async {
153180
String filePath = "";
154181

155182
// region Custom Firmware Check
156-
if (firmwareURL == null) {
183+
if (firmwareEntry == null) {
157184
if (firmwarePath == null) {
158185
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
159186
content: Text("You need to specify custom firmware path"),
@@ -204,14 +231,15 @@ class _FlashPinecilPageState extends State<FlashPinecilPage> {
204231
stderr.addStream(dfuUtil.stderr); */
205232

206233
// region Download Firmware
207-
if (firmwareURL != null) {
234+
if (firmwareEntry != null) {
208235
setState(() {
209236
_state = "Downloading firmware... 0%";
210-
_log.add(_LogMessage("Downloading firmware " + firmwareURL, false));
237+
_log.add(_LogMessage(
238+
"Downloading firmware " + firmwareEntry.fileName, false));
211239
});
212240
try {
213241
final firmwaresZipResponse = await _dio.get(
214-
_pinecilZipUrl!,
242+
firmwareEntry.zipUrl,
215243
onReceiveProgress: (count, total) => setState(() {
216244
_state =
217245
"Downloading firmware... ${((count / total) * 100.0).round()}%";
@@ -223,7 +251,7 @@ class _FlashPinecilPageState extends State<FlashPinecilPage> {
223251
ZipDecoder().decodeBytes(firmwaresZipResponse.data);
224252

225253
final firmwareZipFile = firmwaresZip.files
226-
.singleWhere((element) => element.name == firmwareURL);
254+
.singleWhere((element) => element.name == firmwareEntry.fileName);
227255
final firmwareFile = File("$currentWorkingDirectory/_tmpfirm.dfu");
228256
await firmwareFile.create(recursive: true);
229257
await firmwareFile.writeAsBytes(firmwareZipFile.content);
@@ -324,7 +352,7 @@ class _FlashPinecilPageState extends State<FlashPinecilPage> {
324352
});
325353
}
326354

327-
Step connectStep(BuildContext context) {
355+
Step _connectStep(BuildContext context) {
328356
return Step(
329357
isActive: _currentStep == 0,
330358
state: _currentStep > 0 ? StepState.complete : StepState.indexed,
@@ -350,7 +378,7 @@ class _FlashPinecilPageState extends State<FlashPinecilPage> {
350378
);
351379
}
352380

353-
Step selectFirmwareStep(BuildContext context) {
381+
Step _selectFirmwareStep(BuildContext context) {
354382
return Step(
355383
isActive: _currentStep == 1,
356384
state: _currentStep > 1 ? StepState.complete : StepState.indexed,
@@ -365,26 +393,26 @@ class _FlashPinecilPageState extends State<FlashPinecilPage> {
365393
return Row(
366394
children: [
367395
Expanded(
368-
child: DropdownButtonFormField<String>(
369-
value: _firmwareType,
396+
child: DropdownButtonFormField<_FirmwareEntry?>(
397+
value: _firmwareToFlash,
370398
isDense: true,
371399
items: [
372-
const DropdownMenuItem<String>(
400+
const DropdownMenuItem<_FirmwareEntry?>(
373401
child: Text("Custom"),
374402
value: null,
375403
),
376404
if (snapshot.hasData)
377405
...snapshot.data!
378-
.map<DropdownMenuItem<String>>(
379-
(e) => DropdownMenuItem<String>(
406+
.map<DropdownMenuItem<_FirmwareEntry?>>(
407+
(e) => DropdownMenuItem<_FirmwareEntry?>(
380408
child: Text(e.name),
381-
value: e.url,
409+
value: e,
382410
),
383411
)
384412
.toList()
385413
],
386414
onChanged: (item) =>
387-
setState(() => _firmwareType = item),
415+
setState(() => _firmwareToFlash = item),
388416
decoration:
389417
const InputDecoration(labelText: 'Firmware Type'),
390418
),
@@ -408,15 +436,15 @@ class _FlashPinecilPageState extends State<FlashPinecilPage> {
408436
children: [
409437
Flexible(
410438
child: TextField(
411-
enabled: _firmwareType == null,
439+
enabled: _firmwareToFlash == null,
412440
controller: _firmwarePathController,
413441
decoration:
414442
const InputDecoration(labelText: 'Path to the firmware'),
415443
),
416444
),
417445
const SizedBox(width: 8),
418446
ElevatedButton(
419-
onPressed: _firmwareType == null
447+
onPressed: _firmwareToFlash == null
420448
? () async {
421449
var result = await FilePicker.platform.pickFiles(
422450
type: FileType.custom,
@@ -435,9 +463,9 @@ class _FlashPinecilPageState extends State<FlashPinecilPage> {
435463
),
436464
const SizedBox(height: 18),
437465
ElevatedButton(
438-
onPressed: () => flashFirmware(
466+
onPressed: () => _flashFirmware(
439467
firmwarePath: _firmwarePathController.text,
440-
firmwareURL: _firmwareType,
468+
firmwareEntry: _firmwareToFlash,
441469
),
442470
child: const Text('Update'),
443471
),
@@ -446,7 +474,7 @@ class _FlashPinecilPageState extends State<FlashPinecilPage> {
446474
);
447475
}
448476

449-
Step updateStep(BuildContext context) {
477+
Step _updateStep(BuildContext context) {
450478
return Step(
451479
isActive: _currentStep == 2,
452480
state: _currentStep > 2 ? StepState.complete : StepState.indexed,
@@ -479,7 +507,7 @@ class _FlashPinecilPageState extends State<FlashPinecilPage> {
479507
headerBuilder: (context, isExpanded) => ListTile(
480508
title: Text('Log'),
481509
trailing: IconButton(
482-
onPressed: copyLog,
510+
onPressed: _copyLog,
483511
icon: Icon(Icons.copy),
484512
tooltip: 'Copy log to clipboard',
485513
),
@@ -515,7 +543,7 @@ class _FlashPinecilPageState extends State<FlashPinecilPage> {
515543
);
516544
}
517545

518-
Step unplugStep(BuildContext context) {
546+
Step _unplugStep(BuildContext context) {
519547
return Step(
520548
isActive: _currentStep == 3,
521549
title: const Text('Unplug'),
@@ -524,7 +552,8 @@ class _FlashPinecilPageState extends State<FlashPinecilPage> {
524552
);
525553
}
526554

527-
Widget stepperControlsBuilder(BuildContext context, ControlsDetails details) {
555+
Widget _stepperControlsBuilder(
556+
BuildContext context, ControlsDetails details) {
528557
if (details.currentStep == 0) {
529558
if (_pinecilState == _PinecilState.Error) {
530559
return const Text(
@@ -545,12 +574,12 @@ class _FlashPinecilPageState extends State<FlashPinecilPage> {
545574
? StepperType.vertical
546575
: StepperType.horizontal,
547576
currentStep: _currentStep,
548-
controlsBuilder: stepperControlsBuilder,
577+
controlsBuilder: _stepperControlsBuilder,
549578
steps: [
550-
connectStep(context),
551-
selectFirmwareStep(context),
552-
updateStep(context),
553-
unplugStep(context)
579+
_connectStep(context),
580+
_selectFirmwareStep(context),
581+
_updateStep(context),
582+
_unplugStep(context)
554583
],
555584
),
556585
);

0 commit comments

Comments
 (0)