♻️ Improved the files screen
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
@@ -12,6 +11,8 @@ import 'package:island/widgets/alert.dart';
|
|||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
import 'package:island/widgets/content/file_info_sheet.dart';
|
import 'package:island/widgets/content/file_info_sheet.dart';
|
||||||
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
|
import 'package:island/widgets/usage_overview.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||||
@@ -106,7 +107,23 @@ class FileListScreen extends HookConsumerWidget {
|
|||||||
}, [selectedPool.value, includeRecycled.value]);
|
}, [selectedPool.value, includeRecycled.value]);
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(title: Text('Files'), leading: const PageBackButton()),
|
isNoBackground: false,
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text('Files'),
|
||||||
|
leading: const PageBackButton(),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Symbols.bar_chart),
|
||||||
|
onPressed:
|
||||||
|
() => _showUsageSheet(
|
||||||
|
context,
|
||||||
|
usageAsync.value,
|
||||||
|
quotaAsync.value,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
],
|
||||||
|
),
|
||||||
body: usageAsync.when(
|
body: usageAsync.when(
|
||||||
data:
|
data:
|
||||||
(usage) => quotaAsync.when(
|
(usage) => quotaAsync.when(
|
||||||
@@ -137,86 +154,6 @@ class FileListScreen extends HookConsumerWidget {
|
|||||||
if (usage == null) return const SizedBox.shrink();
|
if (usage == null) return const SizedBox.shrink();
|
||||||
return CustomScrollView(
|
return CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
const SliverGap(8),
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: _buildStatCard(
|
|
||||||
'All Uploads',
|
|
||||||
'${((usage['total_usage_bytes'] as num) / (1024 * 1024 * 1024)).toStringAsFixed(3)} GiB',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: _buildStatCard(
|
|
||||||
'All Files',
|
|
||||||
'${usage['total_file_count']}',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: _buildStatCard(
|
|
||||||
'Quota',
|
|
||||||
'${usage['total_quota']} MiB',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: _buildStatCard(
|
|
||||||
'Used Quota',
|
|
||||||
'${((usage['used_quota'] as num) / (usage['total_quota'] as num) * 100).toStringAsFixed(2)}%',
|
|
||||||
progress:
|
|
||||||
(usage['used_quota'] as num) /
|
|
||||||
(usage['total_quota'] as num),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padding(horizontal: 8),
|
|
||||||
),
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Card(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
const Text('Pool Usage'),
|
|
||||||
SizedBox(
|
|
||||||
height: 200,
|
|
||||||
child: PieChart(_buildPoolChartData(usage)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Card(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
const Text('Verbose Quota'),
|
|
||||||
SizedBox(
|
|
||||||
height: 200,
|
|
||||||
child: PieChart(_buildQuotaChartData(quota)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
).padding(horizontal: 8),
|
|
||||||
),
|
|
||||||
const SliverGap(8),
|
const SliverGap(8),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: _buildFilters(ref, selectedPool, includeRecycled),
|
child: _buildFilters(ref, selectedPool, includeRecycled),
|
||||||
@@ -299,67 +236,6 @@ class FileListScreen extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
PieChartData _buildPoolChartData(Map<String, dynamic> usage) {
|
|
||||||
final pools = usage['pool_usages'] as List<dynamic>;
|
|
||||||
final colors = [
|
|
||||||
Colors.blue,
|
|
||||||
Colors.green,
|
|
||||||
Colors.orange,
|
|
||||||
Colors.red,
|
|
||||||
Colors.purple,
|
|
||||||
];
|
|
||||||
return PieChartData(
|
|
||||||
sections:
|
|
||||||
pools.asMap().entries.map((entry) {
|
|
||||||
final pool = entry.value as Map<String, dynamic>;
|
|
||||||
final title = pool['pool_name'] as String;
|
|
||||||
final truncatedTitle =
|
|
||||||
title.length > 8 ? '${title.substring(0, 8)}...' : title;
|
|
||||||
return PieChartSectionData(
|
|
||||||
value: (pool['usage_bytes'] as num).toDouble(),
|
|
||||||
title: truncatedTitle,
|
|
||||||
color: colors[entry.key % colors.length],
|
|
||||||
radius: 60,
|
|
||||||
titleStyle: const TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
PieChartData _buildQuotaChartData(Map<String, dynamic>? quota) {
|
|
||||||
if (quota == null) return PieChartData(sections: []);
|
|
||||||
return PieChartData(
|
|
||||||
sections: [
|
|
||||||
PieChartSectionData(
|
|
||||||
value: (quota['based_quota'] as num).toDouble(),
|
|
||||||
title: 'Base',
|
|
||||||
color: Colors.green,
|
|
||||||
radius: 60,
|
|
||||||
titleStyle: const TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
PieChartSectionData(
|
|
||||||
value: (quota['extra_quota'] as num).toDouble(),
|
|
||||||
title: 'Extra',
|
|
||||||
color: Colors.orange,
|
|
||||||
radius: 60,
|
|
||||||
titleStyle: const TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildFilters(
|
Widget _buildFilters(
|
||||||
WidgetRef ref,
|
WidgetRef ref,
|
||||||
ValueNotifier<String?> selectedPool,
|
ValueNotifier<String?> selectedPool,
|
||||||
@@ -519,38 +395,19 @@ class FileListScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStatCard(String label, String value, {double? progress}) {
|
void _showUsageSheet(
|
||||||
return Card(
|
BuildContext context,
|
||||||
child: Padding(
|
Map<String, dynamic>? usage,
|
||||||
padding: const EdgeInsets.all(16),
|
Map<String, dynamic>? quota,
|
||||||
child: Column(
|
) {
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
showModalBottomSheet(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
context: context,
|
||||||
children: [
|
isScrollControlled: true,
|
||||||
Text(label, style: const TextStyle(fontSize: 14)),
|
builder:
|
||||||
Row(
|
(context) => SheetScaffold(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
titleText: 'Usage Overview',
|
||||||
children: [
|
child: UsageOverviewWidget(usage: usage, quota: quota),
|
||||||
Text(
|
),
|
||||||
value,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (progress != null) ...[
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
SizedBox(
|
|
||||||
width: 28,
|
|
||||||
height: 28,
|
|
||||||
child: CircularProgressIndicator(value: progress),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
197
lib/widgets/usage_overview.dart
Normal file
197
lib/widgets/usage_overview.dart
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
|
class UsageOverviewWidget extends StatelessWidget {
|
||||||
|
final Map<String, dynamic>? usage;
|
||||||
|
final Map<String, dynamic>? quota;
|
||||||
|
|
||||||
|
const UsageOverviewWidget({
|
||||||
|
super.key,
|
||||||
|
required this.usage,
|
||||||
|
required this.quota,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (usage == null) return const SizedBox.shrink();
|
||||||
|
final nonNullUsage = usage!;
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _buildStatCard(
|
||||||
|
'All Uploads',
|
||||||
|
'${((nonNullUsage['total_usage_bytes'] as num) / (1024 * 1024 * 1024)).toStringAsFixed(3)} GiB',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _buildStatCard(
|
||||||
|
'All Files',
|
||||||
|
'${nonNullUsage['total_file_count']}',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _buildStatCard(
|
||||||
|
'Quota',
|
||||||
|
'${nonNullUsage['total_quota']} MiB',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _buildStatCard(
|
||||||
|
'Used Quota',
|
||||||
|
'${((nonNullUsage['used_quota'] as num) / (nonNullUsage['total_quota'] as num) * 100).toStringAsFixed(2)}%',
|
||||||
|
progress:
|
||||||
|
(nonNullUsage['used_quota'] as num) /
|
||||||
|
(nonNullUsage['total_quota'] as num),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 8),
|
||||||
|
const Gap(8),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Text('Pool Usage'),
|
||||||
|
SizedBox(
|
||||||
|
height: 200,
|
||||||
|
child: PieChart(_buildPoolChartData(nonNullUsage)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const Text('Verbose Quota'),
|
||||||
|
SizedBox(
|
||||||
|
height: 200,
|
||||||
|
child: PieChart(_buildQuotaChartData(quota)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 8),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
PieChartData _buildPoolChartData(Map<String, dynamic> usage) {
|
||||||
|
final pools = usage['pool_usages'] as List<dynamic>;
|
||||||
|
final colors = [
|
||||||
|
Colors.blue,
|
||||||
|
Colors.green,
|
||||||
|
Colors.orange,
|
||||||
|
Colors.red,
|
||||||
|
Colors.purple,
|
||||||
|
];
|
||||||
|
return PieChartData(
|
||||||
|
sections:
|
||||||
|
pools.asMap().entries.map((entry) {
|
||||||
|
final pool = entry.value as Map<String, dynamic>;
|
||||||
|
final title = pool['pool_name'] as String;
|
||||||
|
final truncatedTitle =
|
||||||
|
title.length > 8 ? '${title.substring(0, 8)}...' : title;
|
||||||
|
return PieChartSectionData(
|
||||||
|
value: (pool['usage_bytes'] as num).toDouble(),
|
||||||
|
title: truncatedTitle,
|
||||||
|
color: colors[entry.key % colors.length],
|
||||||
|
radius: 60,
|
||||||
|
titleStyle: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
PieChartData _buildQuotaChartData(Map<String, dynamic>? quota) {
|
||||||
|
if (quota == null) return PieChartData(sections: []);
|
||||||
|
return PieChartData(
|
||||||
|
sections: [
|
||||||
|
PieChartSectionData(
|
||||||
|
value: (quota['based_quota'] as num).toDouble(),
|
||||||
|
title: 'Base',
|
||||||
|
color: Colors.green,
|
||||||
|
radius: 60,
|
||||||
|
titleStyle: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
PieChartSectionData(
|
||||||
|
value: (quota['extra_quota'] as num).toDouble(),
|
||||||
|
title: 'Extra',
|
||||||
|
color: Colors.orange,
|
||||||
|
radius: 60,
|
||||||
|
titleStyle: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildStatCard(String label, String value, {double? progress}) {
|
||||||
|
return Card(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Text(label, style: const TextStyle(fontSize: 14)),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (progress != null) ...[
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
SizedBox(
|
||||||
|
width: 28,
|
||||||
|
height: 28,
|
||||||
|
child: CircularProgressIndicator(value: progress),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user