Skip to content

Commit 536412e

Browse files
author
gopi2401
committed
Merge Downloads Queue + History and Remove Diagnostics
1 parent 3bd05bd commit 536412e

File tree

2 files changed

+266
-231
lines changed

2 files changed

+266
-231
lines changed

lib/features/downloader/download_queue_page.dart

Lines changed: 114 additions & 207 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
import 'dart:convert';
21
import 'dart:io';
32

43
import 'package:flutter/material.dart';
54
import 'package:get/get.dart';
65
import 'package:share_plus/share_plus.dart';
76

87
import 'package:insta/features/downloader/download_job_model.dart';
9-
import 'package:insta/core/services/analytics_service.dart';
108
import 'package:insta/features/downloader/download_queue_controller.dart';
119
import 'package:insta/core/services/notification_service.dart';
1210
import 'package:insta/features/status_saver/video_player_page.dart';
@@ -21,22 +19,6 @@ class DownloadQueueScreen extends StatefulWidget {
2119
class _DownloadQueueScreenState extends State<DownloadQueueScreen> {
2220
final queue = DownloadQueueService.to;
2321
final searchController = TextEditingController();
24-
List<String> analyticsRows = const [];
25-
26-
@override
27-
void initState() {
28-
super.initState();
29-
_loadAnalytics();
30-
}
31-
32-
Future<void> _loadAnalytics() async {
33-
if (!Get.isRegistered<AnalyticsService>()) return;
34-
final rows = await AnalyticsService.to.readRecentEvents(limit: 100);
35-
if (!mounted) return;
36-
setState(() {
37-
analyticsRows = rows;
38-
});
39-
}
4022

4123
@override
4224
void dispose() {
@@ -110,219 +92,144 @@ class _DownloadQueueScreenState extends State<DownloadQueueScreen> {
11092

11193
@override
11294
Widget build(BuildContext context) {
113-
return DefaultTabController(
114-
length: 3,
115-
child: Scaffold(
116-
appBar: AppBar(
117-
title: const Text('Downloads'),
118-
bottom: const TabBar(
119-
tabs: [
120-
Tab(text: 'Queue'),
121-
Tab(text: 'History'),
122-
Tab(text: 'Diagnostics'),
123-
],
95+
return Scaffold(
96+
appBar: AppBar(
97+
title: const Text('Downloads'),
98+
),
99+
body: Column(
100+
children: [
101+
Padding(
102+
padding: const EdgeInsets.all(12),
103+
child: TextField(
104+
controller: searchController,
105+
decoration: const InputDecoration(
106+
labelText: 'Search downloads',
107+
border: OutlineInputBorder(),
108+
prefixIcon: Icon(Icons.search),
109+
),
110+
onChanged: (_) => setState(() {}),
111+
),
124112
),
125-
),
126-
body: TabBarView(
127-
children: [
128-
_buildQueueTab(),
129-
_buildHistoryTab(),
130-
_buildDiagnosticsTab(),
131-
],
132-
),
113+
Expanded(child: _buildMergedDownloadsList()),
114+
],
133115
),
134116
);
135117
}
136118

137-
Widget _buildQueueTab() {
119+
int _statusPriority(DownloadStatus status) {
120+
switch (status) {
121+
case DownloadStatus.running:
122+
return 0;
123+
case DownloadStatus.paused:
124+
return 1;
125+
case DownloadStatus.queued:
126+
return 2;
127+
case DownloadStatus.failed:
128+
return 3;
129+
case DownloadStatus.canceled:
130+
return 4;
131+
case DownloadStatus.success:
132+
return 5;
133+
}
134+
}
135+
136+
Widget _buildMergedDownloadsList() {
138137
return Obx(() {
139-
final queueJobs = queue.jobs.where((j) {
140-
return j.status == DownloadStatus.queued ||
141-
j.status == DownloadStatus.running ||
142-
j.status == DownloadStatus.paused;
138+
final query = searchController.text.trim().toLowerCase();
139+
final indexedJobs = queue.jobs.asMap().entries.toList();
140+
141+
final filtered = indexedJobs.where((entry) {
142+
if (query.isEmpty) return true;
143+
final job = entry.value;
144+
return job.fileName.toLowerCase().contains(query) ||
145+
NotificationService.to.getTypeLabel(job.type).toLowerCase().contains(query) ||
146+
job.status.name.toLowerCase().contains(query);
143147
}).toList();
144148

145-
if (queueJobs.isEmpty) {
146-
return const Center(child: Text('No active downloads'));
147-
}
149+
filtered.sort((a, b) {
150+
final byPriority = _statusPriority(a.value.status).compareTo(
151+
_statusPriority(b.value.status),
152+
);
153+
if (byPriority != 0) return byPriority;
154+
return a.key.compareTo(b.key);
155+
});
148156

149-
return ReorderableListView.builder(
150-
itemCount: queueJobs.length,
151-
onReorder: (oldIndex, newIndex) async {
152-
final oldJob = queueJobs[oldIndex];
153-
final oldGlobal = queue.jobs.indexWhere((j) => j.id == oldJob.id);
154-
if (oldGlobal == -1) return;
157+
if (queue.jobs.isEmpty) {
158+
return const Center(child: Text('No downloads yet'));
159+
}
160+
if (filtered.isEmpty) {
161+
return const Center(child: Text('No matching downloads'));
162+
}
155163

156-
final adjusted = newIndex > oldIndex ? newIndex - 1 : newIndex;
157-
final targetJob = queueJobs[adjusted.clamp(0, queueJobs.length - 1)];
158-
final newGlobal = queue.jobs.indexWhere((j) => j.id == targetJob.id);
159-
if (newGlobal == -1) return;
160-
await queue.reorder(oldGlobal, newGlobal);
161-
},
164+
return ListView.separated(
165+
itemCount: filtered.length,
166+
separatorBuilder: (_, _) => const Divider(height: 1),
162167
itemBuilder: (context, index) {
163-
final job = queueJobs[index];
168+
final job = filtered[index].value;
164169
return ListTile(
165170
key: ValueKey(job.id),
166-
title: Text(job.fileName, maxLines: 1, overflow: TextOverflow.ellipsis),
167-
subtitle: Text('${job.status.name} - ${job.progress}%'),
168-
trailing: Row(
169-
mainAxisSize: MainAxisSize.min,
170-
children: [
171-
if (job.status == DownloadStatus.running)
172-
IconButton(
173-
icon: const Icon(Icons.pause),
174-
onPressed: () => queue.pause(job.id),
175-
),
176-
if (job.status == DownloadStatus.paused)
177-
IconButton(
178-
icon: const Icon(Icons.play_arrow),
179-
onPressed: () => queue.resume(job.id),
180-
),
181-
IconButton(
182-
icon: const Icon(Icons.cancel),
183-
onPressed: () => queue.cancel(job.id),
184-
),
185-
],
171+
title: Text(
172+
job.fileName,
173+
maxLines: 1,
174+
overflow: TextOverflow.ellipsis,
175+
),
176+
subtitle: Text(
177+
'${NotificationService.to.getTypeLabel(job.type)} - ${job.status.name} - ${job.progress}%',
186178
),
179+
trailing: _buildTrailingActions(job),
187180
);
188181
},
189182
);
190183
});
191184
}
192185

193-
Widget _buildHistoryTab() {
194-
return Column(
186+
Widget _buildTrailingActions(DownloadJob job) {
187+
final isActive = job.status == DownloadStatus.queued ||
188+
job.status == DownloadStatus.running ||
189+
job.status == DownloadStatus.paused;
190+
191+
return Row(
192+
mainAxisSize: MainAxisSize.min,
195193
children: [
196-
Padding(
197-
padding: const EdgeInsets.all(12),
198-
child: TextField(
199-
controller: searchController,
200-
decoration: const InputDecoration(
201-
labelText: 'Search history',
202-
border: OutlineInputBorder(),
203-
prefixIcon: Icon(Icons.search),
204-
),
205-
onChanged: (_) => setState(() {}),
194+
if (job.status == DownloadStatus.running)
195+
IconButton(
196+
tooltip: 'Pause',
197+
icon: const Icon(Icons.pause),
198+
onPressed: () => queue.pause(job.id),
206199
),
207-
),
208-
Expanded(
209-
child: Obx(() {
210-
final query = searchController.text.trim().toLowerCase();
211-
final history = queue.jobs.where((j) {
212-
final done = j.status == DownloadStatus.success ||
213-
j.status == DownloadStatus.failed ||
214-
j.status == DownloadStatus.canceled;
215-
if (!done) return false;
216-
if (query.isEmpty) return true;
217-
return j.fileName.toLowerCase().contains(query) ||
218-
NotificationService.to.getTypeLabel(j.type).toLowerCase().contains(query);
219-
}).toList();
220-
221-
if (history.isEmpty) {
222-
return const Center(child: Text('No history found'));
223-
}
224-
225-
return ListView.separated(
226-
itemCount: history.length,
227-
separatorBuilder: (_, _) => const Divider(height: 1),
228-
itemBuilder: (context, index) {
229-
final job = history[index];
230-
return ListTile(
231-
title: Text(job.fileName, maxLines: 1, overflow: TextOverflow.ellipsis),
232-
subtitle: Text(
233-
'${NotificationService.to.getTypeLabel(job.type)} - ${job.status.name}',
234-
),
235-
trailing: Row(
236-
mainAxisSize: MainAxisSize.min,
237-
children: [
238-
if (job.status == DownloadStatus.success && job.filePath != null)
239-
IconButton(
240-
tooltip: 'Share',
241-
icon: const Icon(Icons.share),
242-
onPressed: () => _shareDownloadedFile(job),
243-
),
244-
if (job.status == DownloadStatus.success &&
245-
job.filePath != null &&
246-
_isPlayableVideo(job.filePath!))
247-
IconButton(
248-
tooltip: 'Play',
249-
icon: const Icon(Icons.play_circle),
250-
onPressed: () => _playDownloadedVideo(job),
251-
),
252-
if (job.status == DownloadStatus.failed)
253-
IconButton(
254-
icon: const Icon(Icons.refresh),
255-
onPressed: () => queue.retry(job.id),
256-
),
257-
],
258-
),
259-
);
260-
},
261-
);
262-
}),
263-
),
264-
],
265-
);
266-
}
267-
268-
Widget _buildDiagnosticsTab() {
269-
return RefreshIndicator(
270-
onRefresh: _loadAnalytics,
271-
child: ListView(
272-
padding: const EdgeInsets.all(12),
273-
children: [
274-
const Text(
275-
'Failed Jobs',
276-
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
200+
if (job.status == DownloadStatus.paused)
201+
IconButton(
202+
tooltip: 'Resume',
203+
icon: const Icon(Icons.play_arrow),
204+
onPressed: () => queue.resume(job.id),
277205
),
278-
const SizedBox(height: 8),
279-
Obx(() {
280-
final failed = queue.jobs.where((j) => j.status == DownloadStatus.failed).toList();
281-
if (failed.isEmpty) {
282-
return const Padding(
283-
padding: EdgeInsets.symmetric(vertical: 12),
284-
child: Text('No failed jobs'),
285-
);
286-
}
287-
return Column(
288-
children: failed
289-
.map(
290-
(j) => Card(
291-
child: ListTile(
292-
title: Text(j.fileName, maxLines: 1, overflow: TextOverflow.ellipsis),
293-
subtitle: Text(j.errorCode ?? 'unknown'),
294-
trailing: IconButton(
295-
icon: const Icon(Icons.refresh),
296-
onPressed: () => queue.retry(j.id),
297-
),
298-
),
299-
),
300-
)
301-
.toList(),
302-
);
303-
}),
304-
const SizedBox(height: 16),
305-
const Text(
306-
'Recent Events',
307-
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
206+
if (job.status == DownloadStatus.failed)
207+
IconButton(
208+
tooltip: 'Retry',
209+
icon: const Icon(Icons.refresh),
210+
onPressed: () => queue.retry(job.id),
308211
),
309-
const SizedBox(height: 8),
310-
if (analyticsRows.isEmpty)
311-
const Text('No analytics events yet')
312-
else
313-
...analyticsRows.map((line) {
314-
String title = line;
315-
try {
316-
final json = jsonDecode(line) as Map<String, dynamic>;
317-
title = '${json['event']} @ ${json['ts']}';
318-
} catch (_) {}
319-
return Padding(
320-
padding: const EdgeInsets.only(bottom: 6),
321-
child: Text(title, style: const TextStyle(fontSize: 12)),
322-
);
323-
}),
324-
],
325-
),
212+
if (job.status == DownloadStatus.success && job.filePath != null)
213+
IconButton(
214+
tooltip: 'Share',
215+
icon: const Icon(Icons.share),
216+
onPressed: () => _shareDownloadedFile(job),
217+
),
218+
if (job.status == DownloadStatus.success &&
219+
job.filePath != null &&
220+
_isPlayableVideo(job.filePath!))
221+
IconButton(
222+
tooltip: 'Play',
223+
icon: const Icon(Icons.play_circle),
224+
onPressed: () => _playDownloadedVideo(job),
225+
),
226+
if (isActive)
227+
IconButton(
228+
tooltip: 'Cancel',
229+
icon: const Icon(Icons.cancel),
230+
onPressed: () => queue.cancel(job.id),
231+
),
232+
],
326233
);
327234
}
328235
}

0 commit comments

Comments
 (0)