about: data usage
This commit is contained in:
parent
f08632dda2
commit
edb131363b
11 changed files with 712 additions and 160 deletions
|
@ -6,6 +6,10 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
## <a id="v1.8.9"></a>[v1.8.9] - 2023-06-04
|
||||
|
||||
### Added
|
||||
|
||||
- About: data usage
|
||||
|
||||
### Changed
|
||||
|
||||
- target Android 14 (API 34)
|
||||
|
|
|
@ -9,11 +9,13 @@ import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
|
|||
import deckers.thibault.aves.model.FieldMap
|
||||
import deckers.thibault.aves.utils.PermissionManager
|
||||
import deckers.thibault.aves.utils.StorageUtils
|
||||
import deckers.thibault.aves.utils.StorageUtils.getFolderSize
|
||||
import deckers.thibault.aves.utils.StorageUtils.getPrimaryVolumePath
|
||||
import deckers.thibault.aves.utils.StorageUtils.getVolumePaths
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
||||
import io.flutter.util.PathUtils
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
|
@ -25,6 +27,7 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
|
|||
|
||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||
when (call.method) {
|
||||
"getDataUsage" -> ioScope.launch { safe(call, result, ::getDataUsage) }
|
||||
"getStorageVolumes" -> ioScope.launch { safe(call, result, ::getStorageVolumes) }
|
||||
"getVaultRoot" -> ioScope.launch { safe(call, result, ::getVaultRoot) }
|
||||
"getFreeSpace" -> ioScope.launch { safe(call, result, ::getFreeSpace) }
|
||||
|
@ -39,6 +42,37 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
|
|||
}
|
||||
}
|
||||
|
||||
private fun getDataUsage(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
|
||||
var internalCache = getFolderSize(context.cacheDir)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
internalCache += getFolderSize(context.codeCacheDir)
|
||||
}
|
||||
val externalCache = context.externalCacheDirs.map(::getFolderSize).sum()
|
||||
|
||||
val dataDir = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) context.dataDir else File(context.applicationInfo.dataDir)
|
||||
|
||||
val database = getFolderSize(File(dataDir, "databases"))
|
||||
val flutter = getFolderSize(File(PathUtils.getDataDirectory(context)))
|
||||
val vaults = getFolderSize(File(StorageUtils.getVaultRoot(context)))
|
||||
val trash = context.getExternalFilesDirs(null).mapNotNull { StorageUtils.trashDirFor(context, it.path) }.map(::getFolderSize).sum()
|
||||
|
||||
val internalData = getFolderSize(dataDir) - internalCache
|
||||
val externalData = context.getExternalFilesDirs(null).map(::getFolderSize).sum()
|
||||
val miscData = internalData + externalData - (database + flutter + vaults + trash)
|
||||
|
||||
result.success(
|
||||
hashMapOf(
|
||||
"database" to database,
|
||||
"flutter" to flutter,
|
||||
"vaults" to vaults,
|
||||
"trash" to trash,
|
||||
"miscData" to miscData,
|
||||
"internalCache" to internalCache,
|
||||
"externalCache" to externalCache,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun getStorageVolumes(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
|
||||
val volumes = ArrayList<Map<String, Any>>()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
|
|
|
@ -716,6 +716,18 @@ object StorageUtils {
|
|||
|
||||
// convenience methods
|
||||
|
||||
fun getFolderSize(f: File): Long {
|
||||
var size: Long = 0
|
||||
if (f.isDirectory) {
|
||||
for (file in f.listFiles()!!) {
|
||||
size += getFolderSize(file)
|
||||
}
|
||||
} else {
|
||||
size = f.length()
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
fun ensureTrailingSeparator(dirPath: String): String {
|
||||
return if (dirPath.endsWith(File.separator)) dirPath else dirPath + File.separator
|
||||
}
|
||||
|
|
|
@ -533,6 +533,14 @@
|
|||
"aboutBugReportInstruction": "Report on GitHub with the logs and system information",
|
||||
"aboutBugReportButton": "Report",
|
||||
|
||||
"aboutDataUsageSectionTitle": "Data Usage",
|
||||
"aboutDataUsageData": "Data",
|
||||
"aboutDataUsageCache": "Cache",
|
||||
"aboutDataUsageDatabase": "Database",
|
||||
"aboutDataUsageMisc": "Misc",
|
||||
"aboutDataUsageInternal": "Internal",
|
||||
"aboutDataUsageExternal": "External",
|
||||
|
||||
"aboutCreditsSectionTitle": "Credits",
|
||||
"aboutCreditsWorldAtlas1": "This app uses a TopoJSON file from",
|
||||
"aboutCreditsWorldAtlas2": "under ISC License.",
|
||||
|
|
|
@ -8,6 +8,8 @@ import 'package:flutter/services.dart';
|
|||
import 'package:streams_channel/streams_channel.dart';
|
||||
|
||||
abstract class StorageService {
|
||||
Future<Map<String, int>> getDataUsage();
|
||||
|
||||
Future<Set<StorageVolume>> getStorageVolumes();
|
||||
|
||||
Future<String> getVaultRoot();
|
||||
|
@ -45,6 +47,17 @@ class PlatformStorageService implements StorageService {
|
|||
static const _platform = MethodChannel('deckers.thibault/aves/storage');
|
||||
static final _stream = StreamsChannel('deckers.thibault/aves/activity_result_stream');
|
||||
|
||||
@override
|
||||
Future<Map<String, int>> getDataUsage() async {
|
||||
try {
|
||||
final result = await _platform.invokeMethod('getDataUsage');
|
||||
if (result != null) return (result as Map).cast<String, int>();
|
||||
} on PlatformException catch (e, stack) {
|
||||
await reportService.recordError(e, stack);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Set<StorageVolume>> getStorageVolumes() async {
|
||||
try {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:aves/widgets/about/app_ref.dart';
|
||||
import 'package:aves/widgets/about/bug_report.dart';
|
||||
import 'package:aves/widgets/about/credits.dart';
|
||||
import 'package:aves/widgets/about/data_usage.dart';
|
||||
import 'package:aves/widgets/about/licenses.dart';
|
||||
import 'package:aves/widgets/about/translators.dart';
|
||||
import 'package:aves/widgets/common/basic/insets.dart';
|
||||
|
@ -31,6 +32,8 @@ class AboutMobilePage extends StatelessWidget {
|
|||
const Divider(),
|
||||
const BugReport(),
|
||||
const Divider(),
|
||||
const AboutDataUsage(),
|
||||
const Divider(),
|
||||
const AboutCredits(),
|
||||
const Divider(),
|
||||
const AboutTranslators(),
|
||||
|
|
161
lib/widgets/about/data_usage.dart
Normal file
161
lib/widgets/about/data_usage.dart
Normal file
|
@ -0,0 +1,161 @@
|
|||
import 'package:aves/ref/brand_colors.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/theme/colors.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/theme/styles.dart';
|
||||
import 'package:aves/utils/file_utils.dart';
|
||||
import 'package:aves/widgets/common/action_mixins/feedback.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_donut.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class AboutDataUsage extends StatefulWidget {
|
||||
const AboutDataUsage({super.key});
|
||||
|
||||
@override
|
||||
State<AboutDataUsage> createState() => _AboutDataUsageState();
|
||||
}
|
||||
|
||||
class _AboutDataUsageState extends State<AboutDataUsage> with FeedbackMixin {
|
||||
late Future<Map<String, int>> _loader;
|
||||
bool _isExpanded = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loader = storageService.getDataUsage();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final animationDuration = context.select<DurationsData, Duration>((v) => v.expansionTileAnimation);
|
||||
return ExpansionPanelList(
|
||||
expansionCallback: (index, isExpanded) {
|
||||
setState(() => _isExpanded = !isExpanded);
|
||||
},
|
||||
animationDuration: animationDuration,
|
||||
expandedHeaderPadding: EdgeInsets.zero,
|
||||
elevation: 0,
|
||||
children: [
|
||||
ExpansionPanel(
|
||||
headerBuilder: (context, isExpanded) => ConstrainedBox(
|
||||
constraints: const BoxConstraints(minHeight: kMinInteractiveDimension),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: Text(l10n.aboutDataUsageSectionTitle, style: AStyles.knownTitleText),
|
||||
),
|
||||
),
|
||||
body: FutureBuilder<Map<String, int>>(
|
||||
future: _loader,
|
||||
builder: (context, snapshot) {
|
||||
final data = snapshot.data;
|
||||
if (data == null) return const SizedBox();
|
||||
|
||||
final dataMap = {
|
||||
DataUsageDonut.bin: data['trash'] ?? 0,
|
||||
DataUsageDonut.database: data['database'] ?? 0,
|
||||
DataUsageDonut.vaults: data['vaults'] ?? 0,
|
||||
DataUsageDonut.misc: data['miscData'] ?? 0,
|
||||
};
|
||||
final flutter = data['flutter'] ?? 0;
|
||||
if (flutter > 0) {
|
||||
dataMap[DataUsageDonut.flutter] = flutter;
|
||||
}
|
||||
final cacheMap = {
|
||||
DataUsageDonut.internal: data['internalCache'] ?? 0,
|
||||
DataUsageDonut.external: data['externalCache'] ?? 0,
|
||||
};
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
DataUsageDonut(
|
||||
title: l10n.aboutDataUsageData,
|
||||
byTypes: dataMap,
|
||||
animationDuration: animationDuration,
|
||||
),
|
||||
DataUsageDonut(
|
||||
title: l10n.aboutDataUsageCache,
|
||||
byTypes: cacheMap,
|
||||
animationDuration: animationDuration,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
isExpanded: _isExpanded,
|
||||
canTapOnHeader: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DataUsageDonut extends StatelessWidget {
|
||||
final String title;
|
||||
final Map<String, int> byTypes;
|
||||
final Duration animationDuration;
|
||||
|
||||
// data
|
||||
static const String bin = 'bin';
|
||||
static const String database = 'database';
|
||||
static const String flutter = 'flutter';
|
||||
static const String vaults = 'vaults';
|
||||
static const String misc = 'misc';
|
||||
|
||||
// cache
|
||||
static const String internal = 'internal';
|
||||
static const String external = 'external';
|
||||
|
||||
const DataUsageDonut({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.byTypes,
|
||||
required this.animationDuration,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final locale = l10n.localeName;
|
||||
final colors = context.watch<AvesColorsData>();
|
||||
|
||||
return AvesDonut(
|
||||
title: Text(title),
|
||||
byTypes: byTypes,
|
||||
animationDuration: animationDuration,
|
||||
formatKey: (d) {
|
||||
switch (d.key) {
|
||||
case bin:
|
||||
return l10n.binPageTitle;
|
||||
case database:
|
||||
return l10n.aboutDataUsageDatabase;
|
||||
case flutter:
|
||||
return 'Flutter';
|
||||
case vaults:
|
||||
return l10n.albumTierVaults;
|
||||
case misc:
|
||||
return l10n.aboutDataUsageMisc;
|
||||
case internal:
|
||||
return l10n.aboutDataUsageInternal;
|
||||
case external:
|
||||
return l10n.aboutDataUsageExternal;
|
||||
default:
|
||||
return d.key;
|
||||
}
|
||||
},
|
||||
formatValue: (v) => formatFileSize(locale, v, round: 0),
|
||||
colorize: (d) {
|
||||
Color? color;
|
||||
switch (d.key) {
|
||||
case flutter:
|
||||
color = colors.fromBrandColor(BrandColors.flutter);
|
||||
}
|
||||
return color ?? colors.fromString(d.key);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -30,7 +30,7 @@ mixin SizeAwareMixin {
|
|||
if (free == null) return true;
|
||||
|
||||
late int needed;
|
||||
int sumSize(sum, entry) => sum + (entry.sizeBytes ?? 0);
|
||||
int sumSize(int sum, AvesEntry entry) => sum + (entry.sizeBytes ?? 0);
|
||||
switch (moveType) {
|
||||
case MoveType.copy:
|
||||
case MoveType.export:
|
||||
|
|
186
lib/widgets/common/identity/aves_donut.dart
Normal file
186
lib/widgets/common/identity/aves_donut.dart
Normal file
|
@ -0,0 +1,186 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:aves/model/settings/enums/accessibility_animations.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:charts_flutter/flutter.dart' as charts;
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
typedef DatumKeyFormatter = String Function(AvesDonutDatum d);
|
||||
typedef DatumValueFormatter = String Function(int d);
|
||||
typedef DatumColorizer = Color Function(AvesDonutDatum d);
|
||||
typedef DatumCallback = void Function(AvesDonutDatum d);
|
||||
|
||||
class AvesDonut extends StatefulWidget {
|
||||
final Widget title;
|
||||
final Map<String, int> byTypes;
|
||||
final Duration animationDuration;
|
||||
final DatumKeyFormatter formatKey;
|
||||
final DatumValueFormatter formatValue;
|
||||
final DatumColorizer colorize;
|
||||
final DatumCallback? onTap;
|
||||
|
||||
const AvesDonut({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.byTypes,
|
||||
required this.animationDuration,
|
||||
required this.formatKey,
|
||||
required this.formatValue,
|
||||
required this.colorize,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
State<AvesDonut> createState() => _AvesDonutState();
|
||||
}
|
||||
|
||||
class _AvesDonutState extends State<AvesDonut> with AutomaticKeepAliveClientMixin {
|
||||
Map<String, int> get byTypes => widget.byTypes;
|
||||
|
||||
DatumKeyFormatter get formatKey => widget.formatKey;
|
||||
|
||||
DatumValueFormatter get formatValue => widget.formatValue;
|
||||
|
||||
DatumColorizer get colorize => widget.colorize;
|
||||
|
||||
static const avesDonutMinWidth = 124.0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
|
||||
if (byTypes.isEmpty) return const SizedBox();
|
||||
|
||||
final sum = byTypes.values.sum;
|
||||
|
||||
final seriesData = byTypes.entries.map((kv) {
|
||||
final type = kv.key;
|
||||
return AvesDonutDatum(
|
||||
key: type,
|
||||
value: kv.value,
|
||||
);
|
||||
}).toList();
|
||||
seriesData.sort((d1, d2) {
|
||||
final c = d2.value.compareTo(d1.value);
|
||||
return c != 0 ? c : compareAsciiUpperCase(formatKey(d1), formatKey(d2));
|
||||
});
|
||||
|
||||
final series = [
|
||||
charts.Series<AvesDonutDatum, String>(
|
||||
id: 'type',
|
||||
colorFn: (d, i) => charts.ColorUtil.fromDartColor(colorize(d)),
|
||||
domainFn: (d, i) => formatKey(d),
|
||||
measureFn: (d, i) => d.value,
|
||||
data: seriesData,
|
||||
labelAccessorFn: (d, _) => '${formatKey(d)}: ${d.value}',
|
||||
),
|
||||
];
|
||||
|
||||
return LayoutBuilder(builder: (context, constraints) {
|
||||
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
|
||||
final minWidth = avesDonutMinWidth * textScaleFactor;
|
||||
final availableWidth = constraints.maxWidth;
|
||||
final dim = max(minWidth, availableWidth / (availableWidth > 4 * minWidth ? 4 : (availableWidth > 2 * minWidth ? 2 : 1)));
|
||||
|
||||
final donut = SizedBox(
|
||||
width: dim,
|
||||
height: dim,
|
||||
child: Stack(
|
||||
children: [
|
||||
charts.PieChart(
|
||||
series,
|
||||
animate: context.select<Settings, bool>((v) => v.accessibilityAnimations.animate),
|
||||
animationDuration: widget.animationDuration,
|
||||
defaultRenderer: charts.ArcRendererConfig<String>(
|
||||
arcWidth: 16,
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
widget.title,
|
||||
Text(
|
||||
formatValue(sum),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
final onTap = widget.onTap;
|
||||
final legend = SizedBox(
|
||||
width: dim,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: seriesData
|
||||
.map((d) => InkWell(
|
||||
onTap: onTap != null ? () => onTap(d) : null,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(123)),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(AIcons.disc, color: colorize(d)),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: Text(
|
||||
formatKey(d),
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: false,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
formatValue(d.value),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).textTheme.bodySmall!.color,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
final children = [
|
||||
donut,
|
||||
legend,
|
||||
];
|
||||
return availableWidth > minWidth * 2
|
||||
? Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: children,
|
||||
)
|
||||
: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: children,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
}
|
||||
|
||||
@immutable
|
||||
class AvesDonutDatum extends Equatable {
|
||||
final String key;
|
||||
final int value;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [key, value];
|
||||
|
||||
const AvesDonutDatum({
|
||||
required this.key,
|
||||
required this.value,
|
||||
});
|
||||
}
|
|
@ -1,21 +1,14 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:aves/model/filters/mime.dart';
|
||||
import 'package:aves/model/settings/enums/accessibility_animations.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/theme/colors.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/utils/mime_utils.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_donut.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
||||
import 'package:charts_flutter/flutter.dart' as charts;
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class MimeDonut extends StatefulWidget {
|
||||
class MimeDonut extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final Map<String, int> byMimeTypes;
|
||||
final Duration animationDuration;
|
||||
|
@ -29,157 +22,21 @@ class MimeDonut extends StatefulWidget {
|
|||
required this.onFilterSelection,
|
||||
});
|
||||
|
||||
@override
|
||||
State<MimeDonut> createState() => _MimeDonutState();
|
||||
}
|
||||
|
||||
class _MimeDonutState extends State<MimeDonut> with AutomaticKeepAliveClientMixin {
|
||||
Map<String, int> get byMimeTypes => widget.byMimeTypes;
|
||||
|
||||
static const mimeDonutMinWidth = 124.0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
|
||||
if (byMimeTypes.isEmpty) return const SizedBox();
|
||||
|
||||
final l10n = context.l10n;
|
||||
final locale = l10n.localeName;
|
||||
final locale = context.l10n.localeName;
|
||||
final numberFormat = NumberFormat.decimalPattern(locale);
|
||||
|
||||
final sum = byMimeTypes.values.sum;
|
||||
|
||||
final colors = context.watch<AvesColorsData>();
|
||||
final seriesData = byMimeTypes.entries.map((kv) {
|
||||
final mimeType = kv.key;
|
||||
final displayText = MimeUtils.displayType(mimeType);
|
||||
return EntryByMimeDatum(
|
||||
mimeType: mimeType,
|
||||
displayText: displayText,
|
||||
color: colors.fromString(displayText),
|
||||
entryCount: kv.value,
|
||||
);
|
||||
}).toList();
|
||||
seriesData.sort((d1, d2) {
|
||||
final c = d2.entryCount.compareTo(d1.entryCount);
|
||||
return c != 0 ? c : compareAsciiUpperCase(d1.displayText, d2.displayText);
|
||||
});
|
||||
|
||||
final series = [
|
||||
charts.Series<EntryByMimeDatum, String>(
|
||||
id: 'mime',
|
||||
colorFn: (d, i) => charts.ColorUtil.fromDartColor(d.color),
|
||||
domainFn: (d, i) => d.displayText,
|
||||
measureFn: (d, i) => d.entryCount,
|
||||
data: seriesData,
|
||||
labelAccessorFn: (d, _) => '${d.displayText}: ${d.entryCount}',
|
||||
),
|
||||
];
|
||||
|
||||
return LayoutBuilder(builder: (context, constraints) {
|
||||
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
|
||||
final minWidth = mimeDonutMinWidth * textScaleFactor;
|
||||
final availableWidth = constraints.maxWidth;
|
||||
final dim = max(minWidth, availableWidth / (availableWidth > 4 * minWidth ? 4 : (availableWidth > 2 * minWidth ? 2 : 1)));
|
||||
|
||||
final donut = SizedBox(
|
||||
width: dim,
|
||||
height: dim,
|
||||
child: Stack(
|
||||
children: [
|
||||
charts.PieChart(
|
||||
series,
|
||||
animate: context.select<Settings, bool>((v) => v.accessibilityAnimations.animate),
|
||||
animationDuration: widget.animationDuration,
|
||||
defaultRenderer: charts.ArcRendererConfig<String>(
|
||||
arcWidth: 16,
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(widget.icon),
|
||||
Text(
|
||||
numberFormat.format(sum),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
String formatKey(d) => MimeUtils.displayType(d.key);
|
||||
return AvesDonut(
|
||||
title: Icon(icon),
|
||||
byTypes: byMimeTypes,
|
||||
animationDuration: animationDuration,
|
||||
formatKey: formatKey,
|
||||
formatValue: numberFormat.format,
|
||||
colorize: (d) => colors.fromString(formatKey(d)),
|
||||
onTap: (d) => onFilterSelection(MimeFilter(d.key)),
|
||||
);
|
||||
final legend = SizedBox(
|
||||
width: dim,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: seriesData
|
||||
.map((d) => InkWell(
|
||||
onTap: () => widget.onFilterSelection(MimeFilter(d.mimeType)),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(123)),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(AIcons.disc, color: d.color),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: Text(
|
||||
d.displayText,
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: false,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
numberFormat.format(d.entryCount),
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).textTheme.bodySmall!.color,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
final children = [
|
||||
donut,
|
||||
legend,
|
||||
];
|
||||
return availableWidth > minWidth * 2
|
||||
? Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: children,
|
||||
)
|
||||
: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: children,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
}
|
||||
|
||||
@immutable
|
||||
class EntryByMimeDatum extends Equatable {
|
||||
final String mimeType, displayText;
|
||||
final Color color;
|
||||
final int entryCount;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [mimeType, displayText, color, entryCount];
|
||||
|
||||
const EntryByMimeDatum({
|
||||
required this.mimeType,
|
||||
required this.displayText,
|
||||
required this.color,
|
||||
required this.entryCount,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -298,6 +298,13 @@
|
|||
"aboutBugCopyInfoButton",
|
||||
"aboutBugReportInstruction",
|
||||
"aboutBugReportButton",
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal",
|
||||
"aboutCreditsSectionTitle",
|
||||
"aboutCreditsWorldAtlas1",
|
||||
"aboutCreditsWorldAtlas2",
|
||||
|
@ -902,6 +909,13 @@
|
|||
"aboutBugCopyInfoButton",
|
||||
"aboutBugReportInstruction",
|
||||
"aboutBugReportButton",
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal",
|
||||
"aboutCreditsSectionTitle",
|
||||
"aboutCreditsWorldAtlas1",
|
||||
"aboutCreditsWorldAtlas2",
|
||||
|
@ -1503,6 +1517,13 @@
|
|||
"aboutBugCopyInfoButton",
|
||||
"aboutBugReportInstruction",
|
||||
"aboutBugReportButton",
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal",
|
||||
"aboutCreditsSectionTitle",
|
||||
"aboutCreditsWorldAtlas1",
|
||||
"aboutCreditsWorldAtlas2",
|
||||
|
@ -1854,7 +1875,54 @@
|
|||
"cs": [
|
||||
"editorActionTransform",
|
||||
"cropAspectRatioFree",
|
||||
"cropAspectRatioSquare"
|
||||
"cropAspectRatioSquare",
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal"
|
||||
],
|
||||
|
||||
"de": [
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal"
|
||||
],
|
||||
|
||||
"el": [
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal"
|
||||
],
|
||||
|
||||
"es": [
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal"
|
||||
],
|
||||
|
||||
"eu": [
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal"
|
||||
],
|
||||
|
||||
"fa": [
|
||||
|
@ -2030,6 +2098,13 @@
|
|||
"aboutBugSaveLogInstruction",
|
||||
"aboutBugCopyInfoInstruction",
|
||||
"aboutBugReportInstruction",
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal",
|
||||
"aboutCreditsSectionTitle",
|
||||
"aboutCreditsWorldAtlas1",
|
||||
"aboutCreditsWorldAtlas2",
|
||||
|
@ -2361,6 +2436,16 @@
|
|||
"filePickerUseThisFolder"
|
||||
],
|
||||
|
||||
"fr": [
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal"
|
||||
],
|
||||
|
||||
"gl": [
|
||||
"columnCount",
|
||||
"saveCopyButtonLabel",
|
||||
|
@ -2549,6 +2634,13 @@
|
|||
"aboutBugCopyInfoButton",
|
||||
"aboutBugReportInstruction",
|
||||
"aboutBugReportButton",
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal",
|
||||
"aboutCreditsSectionTitle",
|
||||
"aboutCreditsWorldAtlas1",
|
||||
"aboutCreditsWorldAtlas2",
|
||||
|
@ -3214,6 +3306,13 @@
|
|||
"aboutBugCopyInfoButton",
|
||||
"aboutBugReportInstruction",
|
||||
"aboutBugReportButton",
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal",
|
||||
"aboutCreditsSectionTitle",
|
||||
"aboutCreditsWorldAtlas1",
|
||||
"aboutCreditsWorldAtlas2",
|
||||
|
@ -3859,6 +3958,13 @@
|
|||
"aboutBugCopyInfoButton",
|
||||
"aboutBugReportInstruction",
|
||||
"aboutBugReportButton",
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal",
|
||||
"aboutCreditsSectionTitle",
|
||||
"aboutCreditsWorldAtlas1",
|
||||
"aboutCreditsWorldAtlas2",
|
||||
|
@ -4207,6 +4313,26 @@
|
|||
"filePickerUseThisFolder"
|
||||
],
|
||||
|
||||
"hu": [
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal"
|
||||
],
|
||||
|
||||
"id": [
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal"
|
||||
],
|
||||
|
||||
"it": [
|
||||
"saveCopyButtonLabel",
|
||||
"applyTooltip",
|
||||
|
@ -4216,7 +4342,14 @@
|
|||
"cropAspectRatioFree",
|
||||
"cropAspectRatioOriginal",
|
||||
"cropAspectRatioSquare",
|
||||
"widgetTapUpdateWidget"
|
||||
"widgetTapUpdateWidget",
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal"
|
||||
],
|
||||
|
||||
"ja": [
|
||||
|
@ -4246,6 +4379,13 @@
|
|||
"vaultBinUsageDialogMessage",
|
||||
"exportEntryDialogQuality",
|
||||
"exportEntryDialogWriteMetadata",
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal",
|
||||
"stateEmpty",
|
||||
"placeEmpty",
|
||||
"searchStatesSectionTitle",
|
||||
|
@ -4270,6 +4410,16 @@
|
|||
"tagEditorDiscardDialogMessage"
|
||||
],
|
||||
|
||||
"ko": [
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal"
|
||||
],
|
||||
|
||||
"lt": [
|
||||
"columnCount",
|
||||
"saveCopyButtonLabel",
|
||||
|
@ -4319,6 +4469,13 @@
|
|||
"exportEntryDialogQuality",
|
||||
"exportEntryDialogWriteMetadata",
|
||||
"tooManyItemsErrorDialogMessage",
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal",
|
||||
"drawerPlacePage",
|
||||
"statePageTitle",
|
||||
"stateEmpty",
|
||||
|
@ -4667,6 +4824,13 @@
|
|||
"aboutBugCopyInfoButton",
|
||||
"aboutBugReportInstruction",
|
||||
"aboutBugReportButton",
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal",
|
||||
"aboutCreditsSectionTitle",
|
||||
"aboutCreditsWorldAtlas1",
|
||||
"aboutCreditsWorldAtlas2",
|
||||
|
@ -5037,6 +5201,13 @@
|
|||
"patternDialogEnter",
|
||||
"patternDialogConfirm",
|
||||
"exportEntryDialogQuality",
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal",
|
||||
"statePageTitle",
|
||||
"stateEmpty",
|
||||
"searchStatesSectionTitle",
|
||||
|
@ -5096,6 +5267,13 @@
|
|||
"exportEntryDialogQuality",
|
||||
"exportEntryDialogWriteMetadata",
|
||||
"tooManyItemsErrorDialogMessage",
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal",
|
||||
"drawerPlacePage",
|
||||
"statePageTitle",
|
||||
"stateEmpty",
|
||||
|
@ -5135,6 +5313,13 @@
|
|||
"authenticateToUnlockVault",
|
||||
"viewDialogSortSectionTitle",
|
||||
"viewDialogReverseSortOrder",
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal",
|
||||
"aboutCreditsSectionTitle",
|
||||
"aboutLicensesBanner",
|
||||
"aboutLicensesAndroidLibrariesSectionTitle",
|
||||
|
@ -5432,6 +5617,13 @@
|
|||
"aboutBugCopyInfoButton",
|
||||
"aboutBugReportInstruction",
|
||||
"aboutBugReportButton",
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal",
|
||||
"aboutCreditsWorldAtlas1",
|
||||
"aboutCreditsWorldAtlas2",
|
||||
"aboutLicensesSectionTitle",
|
||||
|
@ -5751,6 +5943,16 @@
|
|||
"filePickerUseThisFolder"
|
||||
],
|
||||
|
||||
"pl": [
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal"
|
||||
],
|
||||
|
||||
"pt": [
|
||||
"saveCopyButtonLabel",
|
||||
"applyTooltip",
|
||||
|
@ -5760,7 +5962,14 @@
|
|||
"cropAspectRatioFree",
|
||||
"cropAspectRatioOriginal",
|
||||
"cropAspectRatioSquare",
|
||||
"widgetTapUpdateWidget"
|
||||
"widgetTapUpdateWidget",
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal"
|
||||
],
|
||||
|
||||
"ro": [
|
||||
|
@ -5778,6 +5987,13 @@
|
|||
"videoResumptionModeAlways",
|
||||
"widgetTapUpdateWidget",
|
||||
"exportEntryDialogQuality",
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal",
|
||||
"settingsAskEverytime",
|
||||
"settingsVideoPlaybackTile",
|
||||
"settingsVideoPlaybackPageTitle",
|
||||
|
@ -5786,6 +6002,16 @@
|
|||
"tagEditorDiscardDialogMessage"
|
||||
],
|
||||
|
||||
"ru": [
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal"
|
||||
],
|
||||
|
||||
"sk": [
|
||||
"itemCount",
|
||||
"columnCount",
|
||||
|
@ -5891,6 +6117,13 @@
|
|||
"aboutBugCopyInfoButton",
|
||||
"aboutBugReportInstruction",
|
||||
"aboutBugReportButton",
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal",
|
||||
"aboutCreditsSectionTitle",
|
||||
"aboutCreditsWorldAtlas1",
|
||||
"aboutCreditsWorldAtlas2",
|
||||
|
@ -6294,6 +6527,13 @@
|
|||
"editEntryDateDialogShift",
|
||||
"removeEntryMetadataDialogTitle",
|
||||
"tooManyItemsErrorDialogMessage",
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal",
|
||||
"collectionActionShowTitleSearch",
|
||||
"collectionActionHideTitleSearch",
|
||||
"collectionActionAddShortcut",
|
||||
|
@ -6671,6 +6911,13 @@
|
|||
"vaultBinUsageDialogMessage",
|
||||
"exportEntryDialogQuality",
|
||||
"exportEntryDialogWriteMetadata",
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal",
|
||||
"drawerPlacePage",
|
||||
"statePageTitle",
|
||||
"stateEmpty",
|
||||
|
@ -6693,6 +6940,16 @@
|
|||
"tagPlaceholderState"
|
||||
],
|
||||
|
||||
"uk": [
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal"
|
||||
],
|
||||
|
||||
"zh": [
|
||||
"saveCopyButtonLabel",
|
||||
"chipActionGoToPlacePage",
|
||||
|
@ -6725,6 +6982,13 @@
|
|||
"exportEntryDialogQuality",
|
||||
"exportEntryDialogWriteMetadata",
|
||||
"tooManyItemsErrorDialogMessage",
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal",
|
||||
"drawerPlacePage",
|
||||
"statePageTitle",
|
||||
"stateEmpty",
|
||||
|
@ -6748,5 +7012,15 @@
|
|||
"settingsAccessibilityShowPinchGestureAlternatives",
|
||||
"statsTopStatesSectionTitle",
|
||||
"tagPlaceholderState"
|
||||
],
|
||||
|
||||
"zh_Hant": [
|
||||
"aboutDataUsageSectionTitle",
|
||||
"aboutDataUsageData",
|
||||
"aboutDataUsageCache",
|
||||
"aboutDataUsageDatabase",
|
||||
"aboutDataUsageMisc",
|
||||
"aboutDataUsageInternal",
|
||||
"aboutDataUsageExternal"
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue