about: data usage

This commit is contained in:
Thibault Deckers 2023-06-18 23:23:31 +02:00
parent f08632dda2
commit edb131363b
11 changed files with 712 additions and 160 deletions

View file

@ -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)

View file

@ -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) {

View file

@ -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
} }

View file

@ -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.",

View file

@ -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 {

View file

@ -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(),

View 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);
},
);
}
}

View file

@ -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:

View 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,
});
}

View file

@ -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,
});
} }

View file

@ -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"
] ]
} }