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
|
## <a id="v1.8.9"></a>[v1.8.9] - 2023-06-04
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- About: data usage
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- target Android 14 (API 34)
|
- 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.model.FieldMap
|
||||||
import deckers.thibault.aves.utils.PermissionManager
|
import deckers.thibault.aves.utils.PermissionManager
|
||||||
import deckers.thibault.aves.utils.StorageUtils
|
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.getPrimaryVolumePath
|
||||||
import deckers.thibault.aves.utils.StorageUtils.getVolumePaths
|
import deckers.thibault.aves.utils.StorageUtils.getVolumePaths
|
||||||
import io.flutter.plugin.common.MethodCall
|
import io.flutter.plugin.common.MethodCall
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
||||||
|
import io.flutter.util.PathUtils
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
@ -25,6 +27,7 @@ class StorageHandler(private val context: Context) : MethodCallHandler {
|
||||||
|
|
||||||
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
|
"getDataUsage" -> ioScope.launch { safe(call, result, ::getDataUsage) }
|
||||||
"getStorageVolumes" -> ioScope.launch { safe(call, result, ::getStorageVolumes) }
|
"getStorageVolumes" -> ioScope.launch { safe(call, result, ::getStorageVolumes) }
|
||||||
"getVaultRoot" -> ioScope.launch { safe(call, result, ::getVaultRoot) }
|
"getVaultRoot" -> ioScope.launch { safe(call, result, ::getVaultRoot) }
|
||||||
"getFreeSpace" -> ioScope.launch { safe(call, result, ::getFreeSpace) }
|
"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) {
|
private fun getStorageVolumes(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
|
||||||
val volumes = ArrayList<Map<String, Any>>()
|
val volumes = ArrayList<Map<String, Any>>()
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
|
|
@ -716,6 +716,18 @@ object StorageUtils {
|
||||||
|
|
||||||
// convenience methods
|
// 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 {
|
fun ensureTrailingSeparator(dirPath: String): String {
|
||||||
return if (dirPath.endsWith(File.separator)) dirPath else dirPath + File.separator
|
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",
|
"aboutBugReportInstruction": "Report on GitHub with the logs and system information",
|
||||||
"aboutBugReportButton": "Report",
|
"aboutBugReportButton": "Report",
|
||||||
|
|
||||||
|
"aboutDataUsageSectionTitle": "Data Usage",
|
||||||
|
"aboutDataUsageData": "Data",
|
||||||
|
"aboutDataUsageCache": "Cache",
|
||||||
|
"aboutDataUsageDatabase": "Database",
|
||||||
|
"aboutDataUsageMisc": "Misc",
|
||||||
|
"aboutDataUsageInternal": "Internal",
|
||||||
|
"aboutDataUsageExternal": "External",
|
||||||
|
|
||||||
"aboutCreditsSectionTitle": "Credits",
|
"aboutCreditsSectionTitle": "Credits",
|
||||||
"aboutCreditsWorldAtlas1": "This app uses a TopoJSON file from",
|
"aboutCreditsWorldAtlas1": "This app uses a TopoJSON file from",
|
||||||
"aboutCreditsWorldAtlas2": "under ISC License.",
|
"aboutCreditsWorldAtlas2": "under ISC License.",
|
||||||
|
|
|
@ -8,6 +8,8 @@ import 'package:flutter/services.dart';
|
||||||
import 'package:streams_channel/streams_channel.dart';
|
import 'package:streams_channel/streams_channel.dart';
|
||||||
|
|
||||||
abstract class StorageService {
|
abstract class StorageService {
|
||||||
|
Future<Map<String, int>> getDataUsage();
|
||||||
|
|
||||||
Future<Set<StorageVolume>> getStorageVolumes();
|
Future<Set<StorageVolume>> getStorageVolumes();
|
||||||
|
|
||||||
Future<String> getVaultRoot();
|
Future<String> getVaultRoot();
|
||||||
|
@ -45,6 +47,17 @@ class PlatformStorageService implements StorageService {
|
||||||
static const _platform = MethodChannel('deckers.thibault/aves/storage');
|
static const _platform = MethodChannel('deckers.thibault/aves/storage');
|
||||||
static final _stream = StreamsChannel('deckers.thibault/aves/activity_result_stream');
|
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
|
@override
|
||||||
Future<Set<StorageVolume>> getStorageVolumes() async {
|
Future<Set<StorageVolume>> getStorageVolumes() async {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:aves/widgets/about/app_ref.dart';
|
import 'package:aves/widgets/about/app_ref.dart';
|
||||||
import 'package:aves/widgets/about/bug_report.dart';
|
import 'package:aves/widgets/about/bug_report.dart';
|
||||||
import 'package:aves/widgets/about/credits.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/licenses.dart';
|
||||||
import 'package:aves/widgets/about/translators.dart';
|
import 'package:aves/widgets/about/translators.dart';
|
||||||
import 'package:aves/widgets/common/basic/insets.dart';
|
import 'package:aves/widgets/common/basic/insets.dart';
|
||||||
|
@ -31,6 +32,8 @@ class AboutMobilePage extends StatelessWidget {
|
||||||
const Divider(),
|
const Divider(),
|
||||||
const BugReport(),
|
const BugReport(),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
|
const AboutDataUsage(),
|
||||||
|
const Divider(),
|
||||||
const AboutCredits(),
|
const AboutCredits(),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
const AboutTranslators(),
|
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;
|
if (free == null) return true;
|
||||||
|
|
||||||
late int needed;
|
late int needed;
|
||||||
int sumSize(sum, entry) => sum + (entry.sizeBytes ?? 0);
|
int sumSize(int sum, AvesEntry entry) => sum + (entry.sizeBytes ?? 0);
|
||||||
switch (moveType) {
|
switch (moveType) {
|
||||||
case MoveType.copy:
|
case MoveType.copy:
|
||||||
case MoveType.export:
|
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/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/colors.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
|
||||||
import 'package:aves/utils/mime_utils.dart';
|
import 'package:aves/utils/mime_utils.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.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: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:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class MimeDonut extends StatefulWidget {
|
class MimeDonut extends StatelessWidget {
|
||||||
final IconData icon;
|
final IconData icon;
|
||||||
final Map<String, int> byMimeTypes;
|
final Map<String, int> byMimeTypes;
|
||||||
final Duration animationDuration;
|
final Duration animationDuration;
|
||||||
|
@ -29,157 +22,21 @@ class MimeDonut extends StatefulWidget {
|
||||||
required this.onFilterSelection,
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
super.build(context);
|
final locale = context.l10n.localeName;
|
||||||
|
|
||||||
if (byMimeTypes.isEmpty) return const SizedBox();
|
|
||||||
|
|
||||||
final l10n = context.l10n;
|
|
||||||
final locale = l10n.localeName;
|
|
||||||
final numberFormat = NumberFormat.decimalPattern(locale);
|
final numberFormat = NumberFormat.decimalPattern(locale);
|
||||||
|
|
||||||
final sum = byMimeTypes.values.sum;
|
|
||||||
|
|
||||||
final colors = context.watch<AvesColorsData>();
|
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 = [
|
String formatKey(d) => MimeUtils.displayType(d.key);
|
||||||
charts.Series<EntryByMimeDatum, String>(
|
return AvesDonut(
|
||||||
id: 'mime',
|
title: Icon(icon),
|
||||||
colorFn: (d, i) => charts.ColorUtil.fromDartColor(d.color),
|
byTypes: byMimeTypes,
|
||||||
domainFn: (d, i) => d.displayText,
|
animationDuration: animationDuration,
|
||||||
measureFn: (d, i) => d.entryCount,
|
formatKey: formatKey,
|
||||||
data: seriesData,
|
formatValue: numberFormat.format,
|
||||||
labelAccessorFn: (d, _) => '${d.displayText}: ${d.entryCount}',
|
colorize: (d) => colors.fromString(formatKey(d)),
|
||||||
),
|
onTap: (d) => onFilterSelection(MimeFilter(d.key)),
|
||||||
];
|
|
||||||
|
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
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",
|
"aboutBugCopyInfoButton",
|
||||||
"aboutBugReportInstruction",
|
"aboutBugReportInstruction",
|
||||||
"aboutBugReportButton",
|
"aboutBugReportButton",
|
||||||
|
"aboutDataUsageSectionTitle",
|
||||||
|
"aboutDataUsageData",
|
||||||
|
"aboutDataUsageCache",
|
||||||
|
"aboutDataUsageDatabase",
|
||||||
|
"aboutDataUsageMisc",
|
||||||
|
"aboutDataUsageInternal",
|
||||||
|
"aboutDataUsageExternal",
|
||||||
"aboutCreditsSectionTitle",
|
"aboutCreditsSectionTitle",
|
||||||
"aboutCreditsWorldAtlas1",
|
"aboutCreditsWorldAtlas1",
|
||||||
"aboutCreditsWorldAtlas2",
|
"aboutCreditsWorldAtlas2",
|
||||||
|
@ -902,6 +909,13 @@
|
||||||
"aboutBugCopyInfoButton",
|
"aboutBugCopyInfoButton",
|
||||||
"aboutBugReportInstruction",
|
"aboutBugReportInstruction",
|
||||||
"aboutBugReportButton",
|
"aboutBugReportButton",
|
||||||
|
"aboutDataUsageSectionTitle",
|
||||||
|
"aboutDataUsageData",
|
||||||
|
"aboutDataUsageCache",
|
||||||
|
"aboutDataUsageDatabase",
|
||||||
|
"aboutDataUsageMisc",
|
||||||
|
"aboutDataUsageInternal",
|
||||||
|
"aboutDataUsageExternal",
|
||||||
"aboutCreditsSectionTitle",
|
"aboutCreditsSectionTitle",
|
||||||
"aboutCreditsWorldAtlas1",
|
"aboutCreditsWorldAtlas1",
|
||||||
"aboutCreditsWorldAtlas2",
|
"aboutCreditsWorldAtlas2",
|
||||||
|
@ -1503,6 +1517,13 @@
|
||||||
"aboutBugCopyInfoButton",
|
"aboutBugCopyInfoButton",
|
||||||
"aboutBugReportInstruction",
|
"aboutBugReportInstruction",
|
||||||
"aboutBugReportButton",
|
"aboutBugReportButton",
|
||||||
|
"aboutDataUsageSectionTitle",
|
||||||
|
"aboutDataUsageData",
|
||||||
|
"aboutDataUsageCache",
|
||||||
|
"aboutDataUsageDatabase",
|
||||||
|
"aboutDataUsageMisc",
|
||||||
|
"aboutDataUsageInternal",
|
||||||
|
"aboutDataUsageExternal",
|
||||||
"aboutCreditsSectionTitle",
|
"aboutCreditsSectionTitle",
|
||||||
"aboutCreditsWorldAtlas1",
|
"aboutCreditsWorldAtlas1",
|
||||||
"aboutCreditsWorldAtlas2",
|
"aboutCreditsWorldAtlas2",
|
||||||
|
@ -1854,7 +1875,54 @@
|
||||||
"cs": [
|
"cs": [
|
||||||
"editorActionTransform",
|
"editorActionTransform",
|
||||||
"cropAspectRatioFree",
|
"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": [
|
"fa": [
|
||||||
|
@ -2030,6 +2098,13 @@
|
||||||
"aboutBugSaveLogInstruction",
|
"aboutBugSaveLogInstruction",
|
||||||
"aboutBugCopyInfoInstruction",
|
"aboutBugCopyInfoInstruction",
|
||||||
"aboutBugReportInstruction",
|
"aboutBugReportInstruction",
|
||||||
|
"aboutDataUsageSectionTitle",
|
||||||
|
"aboutDataUsageData",
|
||||||
|
"aboutDataUsageCache",
|
||||||
|
"aboutDataUsageDatabase",
|
||||||
|
"aboutDataUsageMisc",
|
||||||
|
"aboutDataUsageInternal",
|
||||||
|
"aboutDataUsageExternal",
|
||||||
"aboutCreditsSectionTitle",
|
"aboutCreditsSectionTitle",
|
||||||
"aboutCreditsWorldAtlas1",
|
"aboutCreditsWorldAtlas1",
|
||||||
"aboutCreditsWorldAtlas2",
|
"aboutCreditsWorldAtlas2",
|
||||||
|
@ -2361,6 +2436,16 @@
|
||||||
"filePickerUseThisFolder"
|
"filePickerUseThisFolder"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"fr": [
|
||||||
|
"aboutDataUsageSectionTitle",
|
||||||
|
"aboutDataUsageData",
|
||||||
|
"aboutDataUsageCache",
|
||||||
|
"aboutDataUsageDatabase",
|
||||||
|
"aboutDataUsageMisc",
|
||||||
|
"aboutDataUsageInternal",
|
||||||
|
"aboutDataUsageExternal"
|
||||||
|
],
|
||||||
|
|
||||||
"gl": [
|
"gl": [
|
||||||
"columnCount",
|
"columnCount",
|
||||||
"saveCopyButtonLabel",
|
"saveCopyButtonLabel",
|
||||||
|
@ -2549,6 +2634,13 @@
|
||||||
"aboutBugCopyInfoButton",
|
"aboutBugCopyInfoButton",
|
||||||
"aboutBugReportInstruction",
|
"aboutBugReportInstruction",
|
||||||
"aboutBugReportButton",
|
"aboutBugReportButton",
|
||||||
|
"aboutDataUsageSectionTitle",
|
||||||
|
"aboutDataUsageData",
|
||||||
|
"aboutDataUsageCache",
|
||||||
|
"aboutDataUsageDatabase",
|
||||||
|
"aboutDataUsageMisc",
|
||||||
|
"aboutDataUsageInternal",
|
||||||
|
"aboutDataUsageExternal",
|
||||||
"aboutCreditsSectionTitle",
|
"aboutCreditsSectionTitle",
|
||||||
"aboutCreditsWorldAtlas1",
|
"aboutCreditsWorldAtlas1",
|
||||||
"aboutCreditsWorldAtlas2",
|
"aboutCreditsWorldAtlas2",
|
||||||
|
@ -3214,6 +3306,13 @@
|
||||||
"aboutBugCopyInfoButton",
|
"aboutBugCopyInfoButton",
|
||||||
"aboutBugReportInstruction",
|
"aboutBugReportInstruction",
|
||||||
"aboutBugReportButton",
|
"aboutBugReportButton",
|
||||||
|
"aboutDataUsageSectionTitle",
|
||||||
|
"aboutDataUsageData",
|
||||||
|
"aboutDataUsageCache",
|
||||||
|
"aboutDataUsageDatabase",
|
||||||
|
"aboutDataUsageMisc",
|
||||||
|
"aboutDataUsageInternal",
|
||||||
|
"aboutDataUsageExternal",
|
||||||
"aboutCreditsSectionTitle",
|
"aboutCreditsSectionTitle",
|
||||||
"aboutCreditsWorldAtlas1",
|
"aboutCreditsWorldAtlas1",
|
||||||
"aboutCreditsWorldAtlas2",
|
"aboutCreditsWorldAtlas2",
|
||||||
|
@ -3859,6 +3958,13 @@
|
||||||
"aboutBugCopyInfoButton",
|
"aboutBugCopyInfoButton",
|
||||||
"aboutBugReportInstruction",
|
"aboutBugReportInstruction",
|
||||||
"aboutBugReportButton",
|
"aboutBugReportButton",
|
||||||
|
"aboutDataUsageSectionTitle",
|
||||||
|
"aboutDataUsageData",
|
||||||
|
"aboutDataUsageCache",
|
||||||
|
"aboutDataUsageDatabase",
|
||||||
|
"aboutDataUsageMisc",
|
||||||
|
"aboutDataUsageInternal",
|
||||||
|
"aboutDataUsageExternal",
|
||||||
"aboutCreditsSectionTitle",
|
"aboutCreditsSectionTitle",
|
||||||
"aboutCreditsWorldAtlas1",
|
"aboutCreditsWorldAtlas1",
|
||||||
"aboutCreditsWorldAtlas2",
|
"aboutCreditsWorldAtlas2",
|
||||||
|
@ -4207,6 +4313,26 @@
|
||||||
"filePickerUseThisFolder"
|
"filePickerUseThisFolder"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"hu": [
|
||||||
|
"aboutDataUsageSectionTitle",
|
||||||
|
"aboutDataUsageData",
|
||||||
|
"aboutDataUsageCache",
|
||||||
|
"aboutDataUsageDatabase",
|
||||||
|
"aboutDataUsageMisc",
|
||||||
|
"aboutDataUsageInternal",
|
||||||
|
"aboutDataUsageExternal"
|
||||||
|
],
|
||||||
|
|
||||||
|
"id": [
|
||||||
|
"aboutDataUsageSectionTitle",
|
||||||
|
"aboutDataUsageData",
|
||||||
|
"aboutDataUsageCache",
|
||||||
|
"aboutDataUsageDatabase",
|
||||||
|
"aboutDataUsageMisc",
|
||||||
|
"aboutDataUsageInternal",
|
||||||
|
"aboutDataUsageExternal"
|
||||||
|
],
|
||||||
|
|
||||||
"it": [
|
"it": [
|
||||||
"saveCopyButtonLabel",
|
"saveCopyButtonLabel",
|
||||||
"applyTooltip",
|
"applyTooltip",
|
||||||
|
@ -4216,7 +4342,14 @@
|
||||||
"cropAspectRatioFree",
|
"cropAspectRatioFree",
|
||||||
"cropAspectRatioOriginal",
|
"cropAspectRatioOriginal",
|
||||||
"cropAspectRatioSquare",
|
"cropAspectRatioSquare",
|
||||||
"widgetTapUpdateWidget"
|
"widgetTapUpdateWidget",
|
||||||
|
"aboutDataUsageSectionTitle",
|
||||||
|
"aboutDataUsageData",
|
||||||
|
"aboutDataUsageCache",
|
||||||
|
"aboutDataUsageDatabase",
|
||||||
|
"aboutDataUsageMisc",
|
||||||
|
"aboutDataUsageInternal",
|
||||||
|
"aboutDataUsageExternal"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ja": [
|
"ja": [
|
||||||
|
@ -4246,6 +4379,13 @@
|
||||||
"vaultBinUsageDialogMessage",
|
"vaultBinUsageDialogMessage",
|
||||||
"exportEntryDialogQuality",
|
"exportEntryDialogQuality",
|
||||||
"exportEntryDialogWriteMetadata",
|
"exportEntryDialogWriteMetadata",
|
||||||
|
"aboutDataUsageSectionTitle",
|
||||||
|
"aboutDataUsageData",
|
||||||
|
"aboutDataUsageCache",
|
||||||
|
"aboutDataUsageDatabase",
|
||||||
|
"aboutDataUsageMisc",
|
||||||
|
"aboutDataUsageInternal",
|
||||||
|
"aboutDataUsageExternal",
|
||||||
"stateEmpty",
|
"stateEmpty",
|
||||||
"placeEmpty",
|
"placeEmpty",
|
||||||
"searchStatesSectionTitle",
|
"searchStatesSectionTitle",
|
||||||
|
@ -4270,6 +4410,16 @@
|
||||||
"tagEditorDiscardDialogMessage"
|
"tagEditorDiscardDialogMessage"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"ko": [
|
||||||
|
"aboutDataUsageSectionTitle",
|
||||||
|
"aboutDataUsageData",
|
||||||
|
"aboutDataUsageCache",
|
||||||
|
"aboutDataUsageDatabase",
|
||||||
|
"aboutDataUsageMisc",
|
||||||
|
"aboutDataUsageInternal",
|
||||||
|
"aboutDataUsageExternal"
|
||||||
|
],
|
||||||
|
|
||||||
"lt": [
|
"lt": [
|
||||||
"columnCount",
|
"columnCount",
|
||||||
"saveCopyButtonLabel",
|
"saveCopyButtonLabel",
|
||||||
|
@ -4319,6 +4469,13 @@
|
||||||
"exportEntryDialogQuality",
|
"exportEntryDialogQuality",
|
||||||
"exportEntryDialogWriteMetadata",
|
"exportEntryDialogWriteMetadata",
|
||||||
"tooManyItemsErrorDialogMessage",
|
"tooManyItemsErrorDialogMessage",
|
||||||
|
"aboutDataUsageSectionTitle",
|
||||||
|
"aboutDataUsageData",
|
||||||
|
"aboutDataUsageCache",
|
||||||
|
"aboutDataUsageDatabase",
|
||||||
|
"aboutDataUsageMisc",
|
||||||
|
"aboutDataUsageInternal",
|
||||||
|
"aboutDataUsageExternal",
|
||||||
"drawerPlacePage",
|
"drawerPlacePage",
|
||||||
"statePageTitle",
|
"statePageTitle",
|
||||||
"stateEmpty",
|
"stateEmpty",
|
||||||
|
@ -4667,6 +4824,13 @@
|
||||||
"aboutBugCopyInfoButton",
|
"aboutBugCopyInfoButton",
|
||||||
"aboutBugReportInstruction",
|
"aboutBugReportInstruction",
|
||||||
"aboutBugReportButton",
|
"aboutBugReportButton",
|
||||||
|
"aboutDataUsageSectionTitle",
|
||||||
|
"aboutDataUsageData",
|
||||||
|
"aboutDataUsageCache",
|
||||||
|
"aboutDataUsageDatabase",
|
||||||
|
"aboutDataUsageMisc",
|
||||||
|
"aboutDataUsageInternal",
|
||||||
|
"aboutDataUsageExternal",
|
||||||
"aboutCreditsSectionTitle",
|
"aboutCreditsSectionTitle",
|
||||||
"aboutCreditsWorldAtlas1",
|
"aboutCreditsWorldAtlas1",
|
||||||
"aboutCreditsWorldAtlas2",
|
"aboutCreditsWorldAtlas2",
|
||||||
|
@ -5037,6 +5201,13 @@
|
||||||
"patternDialogEnter",
|
"patternDialogEnter",
|
||||||
"patternDialogConfirm",
|
"patternDialogConfirm",
|
||||||
"exportEntryDialogQuality",
|
"exportEntryDialogQuality",
|
||||||
|
"aboutDataUsageSectionTitle",
|
||||||
|
"aboutDataUsageData",
|
||||||
|
"aboutDataUsageCache",
|
||||||
|
"aboutDataUsageDatabase",
|
||||||
|
"aboutDataUsageMisc",
|
||||||
|
"aboutDataUsageInternal",
|
||||||
|
"aboutDataUsageExternal",
|
||||||
"statePageTitle",
|
"statePageTitle",
|
||||||
"stateEmpty",
|
"stateEmpty",
|
||||||
"searchStatesSectionTitle",
|
"searchStatesSectionTitle",
|
||||||
|
@ -5096,6 +5267,13 @@
|
||||||
"exportEntryDialogQuality",
|
"exportEntryDialogQuality",
|
||||||
"exportEntryDialogWriteMetadata",
|
"exportEntryDialogWriteMetadata",
|
||||||
"tooManyItemsErrorDialogMessage",
|
"tooManyItemsErrorDialogMessage",
|
||||||
|
"aboutDataUsageSectionTitle",
|
||||||
|
"aboutDataUsageData",
|
||||||
|
"aboutDataUsageCache",
|
||||||
|
"aboutDataUsageDatabase",
|
||||||
|
"aboutDataUsageMisc",
|
||||||
|
"aboutDataUsageInternal",
|
||||||
|
"aboutDataUsageExternal",
|
||||||
"drawerPlacePage",
|
"drawerPlacePage",
|
||||||
"statePageTitle",
|
"statePageTitle",
|
||||||
"stateEmpty",
|
"stateEmpty",
|
||||||
|
@ -5135,6 +5313,13 @@
|
||||||
"authenticateToUnlockVault",
|
"authenticateToUnlockVault",
|
||||||
"viewDialogSortSectionTitle",
|
"viewDialogSortSectionTitle",
|
||||||
"viewDialogReverseSortOrder",
|
"viewDialogReverseSortOrder",
|
||||||
|
"aboutDataUsageSectionTitle",
|
||||||
|
"aboutDataUsageData",
|
||||||
|
"aboutDataUsageCache",
|
||||||
|
"aboutDataUsageDatabase",
|
||||||
|
"aboutDataUsageMisc",
|
||||||
|
"aboutDataUsageInternal",
|
||||||
|
"aboutDataUsageExternal",
|
||||||
"aboutCreditsSectionTitle",
|
"aboutCreditsSectionTitle",
|
||||||
"aboutLicensesBanner",
|
"aboutLicensesBanner",
|
||||||
"aboutLicensesAndroidLibrariesSectionTitle",
|
"aboutLicensesAndroidLibrariesSectionTitle",
|
||||||
|
@ -5432,6 +5617,13 @@
|
||||||
"aboutBugCopyInfoButton",
|
"aboutBugCopyInfoButton",
|
||||||
"aboutBugReportInstruction",
|
"aboutBugReportInstruction",
|
||||||
"aboutBugReportButton",
|
"aboutBugReportButton",
|
||||||
|
"aboutDataUsageSectionTitle",
|
||||||
|
"aboutDataUsageData",
|
||||||
|
"aboutDataUsageCache",
|
||||||
|
"aboutDataUsageDatabase",
|
||||||
|
"aboutDataUsageMisc",
|
||||||
|
"aboutDataUsageInternal",
|
||||||
|
"aboutDataUsageExternal",
|
||||||
"aboutCreditsWorldAtlas1",
|
"aboutCreditsWorldAtlas1",
|
||||||
"aboutCreditsWorldAtlas2",
|
"aboutCreditsWorldAtlas2",
|
||||||
"aboutLicensesSectionTitle",
|
"aboutLicensesSectionTitle",
|
||||||
|
@ -5751,6 +5943,16 @@
|
||||||
"filePickerUseThisFolder"
|
"filePickerUseThisFolder"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"pl": [
|
||||||
|
"aboutDataUsageSectionTitle",
|
||||||
|
"aboutDataUsageData",
|
||||||
|
"aboutDataUsageCache",
|
||||||
|
"aboutDataUsageDatabase",
|
||||||
|
"aboutDataUsageMisc",
|
||||||
|
"aboutDataUsageInternal",
|
||||||
|
"aboutDataUsageExternal"
|
||||||
|
],
|
||||||
|
|
||||||
"pt": [
|
"pt": [
|
||||||
"saveCopyButtonLabel",
|
"saveCopyButtonLabel",
|
||||||
"applyTooltip",
|
"applyTooltip",
|
||||||
|
@ -5760,7 +5962,14 @@
|
||||||
"cropAspectRatioFree",
|
"cropAspectRatioFree",
|
||||||
"cropAspectRatioOriginal",
|
"cropAspectRatioOriginal",
|
||||||
"cropAspectRatioSquare",
|
"cropAspectRatioSquare",
|
||||||
"widgetTapUpdateWidget"
|
"widgetTapUpdateWidget",
|
||||||
|
"aboutDataUsageSectionTitle",
|
||||||
|
"aboutDataUsageData",
|
||||||
|
"aboutDataUsageCache",
|
||||||
|
"aboutDataUsageDatabase",
|
||||||
|
"aboutDataUsageMisc",
|
||||||
|
"aboutDataUsageInternal",
|
||||||
|
"aboutDataUsageExternal"
|
||||||
],
|
],
|
||||||
|
|
||||||
"ro": [
|
"ro": [
|
||||||
|
@ -5778,6 +5987,13 @@
|
||||||
"videoResumptionModeAlways",
|
"videoResumptionModeAlways",
|
||||||
"widgetTapUpdateWidget",
|
"widgetTapUpdateWidget",
|
||||||
"exportEntryDialogQuality",
|
"exportEntryDialogQuality",
|
||||||
|
"aboutDataUsageSectionTitle",
|
||||||
|
"aboutDataUsageData",
|
||||||
|
"aboutDataUsageCache",
|
||||||
|
"aboutDataUsageDatabase",
|
||||||
|
"aboutDataUsageMisc",
|
||||||
|
"aboutDataUsageInternal",
|
||||||
|
"aboutDataUsageExternal",
|
||||||
"settingsAskEverytime",
|
"settingsAskEverytime",
|
||||||
"settingsVideoPlaybackTile",
|
"settingsVideoPlaybackTile",
|
||||||
"settingsVideoPlaybackPageTitle",
|
"settingsVideoPlaybackPageTitle",
|
||||||
|
@ -5786,6 +6002,16 @@
|
||||||
"tagEditorDiscardDialogMessage"
|
"tagEditorDiscardDialogMessage"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"ru": [
|
||||||
|
"aboutDataUsageSectionTitle",
|
||||||
|
"aboutDataUsageData",
|
||||||
|
"aboutDataUsageCache",
|
||||||
|
"aboutDataUsageDatabase",
|
||||||
|
"aboutDataUsageMisc",
|
||||||
|
"aboutDataUsageInternal",
|
||||||
|
"aboutDataUsageExternal"
|
||||||
|
],
|
||||||
|
|
||||||
"sk": [
|
"sk": [
|
||||||
"itemCount",
|
"itemCount",
|
||||||
"columnCount",
|
"columnCount",
|
||||||
|
@ -5891,6 +6117,13 @@
|
||||||
"aboutBugCopyInfoButton",
|
"aboutBugCopyInfoButton",
|
||||||
"aboutBugReportInstruction",
|
"aboutBugReportInstruction",
|
||||||
"aboutBugReportButton",
|
"aboutBugReportButton",
|
||||||
|
"aboutDataUsageSectionTitle",
|
||||||
|
"aboutDataUsageData",
|
||||||
|
"aboutDataUsageCache",
|
||||||
|
"aboutDataUsageDatabase",
|
||||||
|
"aboutDataUsageMisc",
|
||||||
|
"aboutDataUsageInternal",
|
||||||
|
"aboutDataUsageExternal",
|
||||||
"aboutCreditsSectionTitle",
|
"aboutCreditsSectionTitle",
|
||||||
"aboutCreditsWorldAtlas1",
|
"aboutCreditsWorldAtlas1",
|
||||||
"aboutCreditsWorldAtlas2",
|
"aboutCreditsWorldAtlas2",
|
||||||
|
@ -6294,6 +6527,13 @@
|
||||||
"editEntryDateDialogShift",
|
"editEntryDateDialogShift",
|
||||||
"removeEntryMetadataDialogTitle",
|
"removeEntryMetadataDialogTitle",
|
||||||
"tooManyItemsErrorDialogMessage",
|
"tooManyItemsErrorDialogMessage",
|
||||||
|
"aboutDataUsageSectionTitle",
|
||||||
|
"aboutDataUsageData",
|
||||||
|
"aboutDataUsageCache",
|
||||||
|
"aboutDataUsageDatabase",
|
||||||
|
"aboutDataUsageMisc",
|
||||||
|
"aboutDataUsageInternal",
|
||||||
|
"aboutDataUsageExternal",
|
||||||
"collectionActionShowTitleSearch",
|
"collectionActionShowTitleSearch",
|
||||||
"collectionActionHideTitleSearch",
|
"collectionActionHideTitleSearch",
|
||||||
"collectionActionAddShortcut",
|
"collectionActionAddShortcut",
|
||||||
|
@ -6671,6 +6911,13 @@
|
||||||
"vaultBinUsageDialogMessage",
|
"vaultBinUsageDialogMessage",
|
||||||
"exportEntryDialogQuality",
|
"exportEntryDialogQuality",
|
||||||
"exportEntryDialogWriteMetadata",
|
"exportEntryDialogWriteMetadata",
|
||||||
|
"aboutDataUsageSectionTitle",
|
||||||
|
"aboutDataUsageData",
|
||||||
|
"aboutDataUsageCache",
|
||||||
|
"aboutDataUsageDatabase",
|
||||||
|
"aboutDataUsageMisc",
|
||||||
|
"aboutDataUsageInternal",
|
||||||
|
"aboutDataUsageExternal",
|
||||||
"drawerPlacePage",
|
"drawerPlacePage",
|
||||||
"statePageTitle",
|
"statePageTitle",
|
||||||
"stateEmpty",
|
"stateEmpty",
|
||||||
|
@ -6693,6 +6940,16 @@
|
||||||
"tagPlaceholderState"
|
"tagPlaceholderState"
|
||||||
],
|
],
|
||||||
|
|
||||||
|
"uk": [
|
||||||
|
"aboutDataUsageSectionTitle",
|
||||||
|
"aboutDataUsageData",
|
||||||
|
"aboutDataUsageCache",
|
||||||
|
"aboutDataUsageDatabase",
|
||||||
|
"aboutDataUsageMisc",
|
||||||
|
"aboutDataUsageInternal",
|
||||||
|
"aboutDataUsageExternal"
|
||||||
|
],
|
||||||
|
|
||||||
"zh": [
|
"zh": [
|
||||||
"saveCopyButtonLabel",
|
"saveCopyButtonLabel",
|
||||||
"chipActionGoToPlacePage",
|
"chipActionGoToPlacePage",
|
||||||
|
@ -6725,6 +6982,13 @@
|
||||||
"exportEntryDialogQuality",
|
"exportEntryDialogQuality",
|
||||||
"exportEntryDialogWriteMetadata",
|
"exportEntryDialogWriteMetadata",
|
||||||
"tooManyItemsErrorDialogMessage",
|
"tooManyItemsErrorDialogMessage",
|
||||||
|
"aboutDataUsageSectionTitle",
|
||||||
|
"aboutDataUsageData",
|
||||||
|
"aboutDataUsageCache",
|
||||||
|
"aboutDataUsageDatabase",
|
||||||
|
"aboutDataUsageMisc",
|
||||||
|
"aboutDataUsageInternal",
|
||||||
|
"aboutDataUsageExternal",
|
||||||
"drawerPlacePage",
|
"drawerPlacePage",
|
||||||
"statePageTitle",
|
"statePageTitle",
|
||||||
"stateEmpty",
|
"stateEmpty",
|
||||||
|
@ -6748,5 +7012,15 @@
|
||||||
"settingsAccessibilityShowPinchGestureAlternatives",
|
"settingsAccessibilityShowPinchGestureAlternatives",
|
||||||
"statsTopStatesSectionTitle",
|
"statsTopStatesSectionTitle",
|
||||||
"tagPlaceholderState"
|
"tagPlaceholderState"
|
||||||
|
],
|
||||||
|
|
||||||
|
"zh_Hant": [
|
||||||
|
"aboutDataUsageSectionTitle",
|
||||||
|
"aboutDataUsageData",
|
||||||
|
"aboutDataUsageCache",
|
||||||
|
"aboutDataUsageDatabase",
|
||||||
|
"aboutDataUsageMisc",
|
||||||
|
"aboutDataUsageInternal",
|
||||||
|
"aboutDataUsageExternal"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue