завтра доделаю
This commit is contained in:
@@ -9,6 +9,7 @@ import '../../../core/storage/token_storage.dart';
|
||||
import '../../../core/utils/haptics.dart';
|
||||
import '../../../l10n/app_localizations.dart';
|
||||
import '../../../main.dart';
|
||||
import '../../widgets/info_action_sheet.dart';
|
||||
import '../auth/login_screen.dart';
|
||||
import '../auth/proxy_settings_sheet.dart';
|
||||
import 'customization_screen.dart';
|
||||
@@ -98,6 +99,38 @@ class _SettingsTabState extends State<SettingsTab> {
|
||||
if (mounted) setState(() => _hapticsEnabled = value);
|
||||
}
|
||||
|
||||
Future<void> _openCloudStorage(BuildContext context) async {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
await showInfoActionSheet(
|
||||
context,
|
||||
headerIcon: Symbols.cloud,
|
||||
title: 'Облачное хранилище',
|
||||
subtitle: 'Через МАХ',
|
||||
items: [
|
||||
const InfoActionSheetItem(
|
||||
icon: Symbols.cloud_done,
|
||||
title: 'Работает при белых списках',
|
||||
body: 'Вы сможете передать файл даже при ограниченном интернете.',
|
||||
),
|
||||
const InfoActionSheetItem(
|
||||
icon: Symbols.inventory_2,
|
||||
title: 'Файлы до 4ГБ, безлимитное количество.',
|
||||
body: 'Можете хранить массивный обьем информации.',
|
||||
),
|
||||
InfoActionSheetItem(
|
||||
icon: Symbols.gpp_maybe,
|
||||
title: 'Не обеспечивается конфединциальность файлов',
|
||||
body:
|
||||
'Облачное хранилище работает через ваш аккаунт на сервере МАХ, '
|
||||
'нужные люди всё равно могут его посмотреть.',
|
||||
titleColor: cs.error,
|
||||
),
|
||||
],
|
||||
confirmLabel: 'ОК',
|
||||
confirmDelay: const Duration(seconds: 3),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _confirmLogout() async {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
final confirmed = await showModalBottomSheet<bool>(
|
||||
@@ -277,6 +310,11 @@ child: _buildSection(
|
||||
toggleValue: _hapticsEnabled,
|
||||
onToggle: _setHaptics,
|
||||
),
|
||||
_SettingsItem(
|
||||
icon: Symbols.cloud,
|
||||
label: 'Облачное хранилище [BETA]',
|
||||
onTap: () => _openCloudStorage(context),
|
||||
),
|
||||
_SettingsItem(
|
||||
icon: Symbols.vpn_lock,
|
||||
label: 'Прокси',
|
||||
|
||||
@@ -8,10 +8,12 @@ import 'package:flutter_timezone/flutter_timezone.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../../../core/config/device_presets.dart';
|
||||
import '../../../core/storage/device_identity.dart';
|
||||
import '../../../core/storage/spoofing_service.dart';
|
||||
import '../../../core/storage/token_storage.dart';
|
||||
import '../../../l10n/app_localizations.dart';
|
||||
import '../../../main.dart';
|
||||
import '../../widgets/info_action_sheet.dart';
|
||||
import '../auth/login_screen.dart';
|
||||
|
||||
enum SpoofingMethod { partial, full }
|
||||
@@ -36,6 +38,10 @@ class _SpoofScreenState extends State<SpoofScreen> {
|
||||
final _deviceIdController = TextEditingController();
|
||||
final _appVersionController = TextEditingController();
|
||||
final _buildNumberController = TextEditingController();
|
||||
final _instanceIdController = TextEditingController();
|
||||
final _clientSessionIdController = TextEditingController();
|
||||
final _deviceLocaleController = TextEditingController();
|
||||
final _pushDeviceTypeController = TextEditingController(text: 'GCM');
|
||||
|
||||
String _selectedDeviceType = 'ANDROID';
|
||||
String _selectedArch = 'arm64-v8a';
|
||||
@@ -45,9 +51,34 @@ class _SpoofScreenState extends State<SpoofScreen> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_localeController.addListener(_syncDeviceLocale);
|
||||
_loadInitialData();
|
||||
}
|
||||
|
||||
void _syncDeviceLocale() {
|
||||
if (_selectedMethod == SpoofingMethod.full) return;
|
||||
final derived = _localeController.text.split(RegExp(r'[-_]')).first;
|
||||
if (_deviceLocaleController.text != derived) {
|
||||
_deviceLocaleController.text = derived;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _confirmFullSpoofing() {
|
||||
return showInfoActionSheet(
|
||||
context,
|
||||
headerIcon: Icons.warning_amber_rounded,
|
||||
title: 'Могут быть последствия.',
|
||||
subtitle: 'Меняй, только если знаешь что делаешь.',
|
||||
confirmLabel: 'ОК',
|
||||
confirmDelay: const Duration(seconds: 3),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _loadSessionIdentifiers() async {
|
||||
_instanceIdController.text = await DeviceIdentity.instanceId();
|
||||
_clientSessionIdController.text = '${DeviceIdentity.clientSessionId}';
|
||||
}
|
||||
|
||||
String _generateDeviceId() {
|
||||
final bytes = List<int>.generate(8, (_) => _random.nextInt(256));
|
||||
return bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
|
||||
@@ -55,6 +86,7 @@ class _SpoofScreenState extends State<SpoofScreen> {
|
||||
|
||||
Future<void> _loadInitialData() async {
|
||||
setState(() => _isLoading = true);
|
||||
await _loadSessionIdentifiers();
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final isSpoofingEnabled = prefs.getBool('spoofing_enabled') ?? false;
|
||||
|
||||
@@ -336,6 +368,7 @@ class _SpoofScreenState extends State<SpoofScreen> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_localeController.removeListener(_syncDeviceLocale);
|
||||
_deviceNameController.dispose();
|
||||
_osVersionController.dispose();
|
||||
_screenController.dispose();
|
||||
@@ -344,6 +377,10 @@ class _SpoofScreenState extends State<SpoofScreen> {
|
||||
_deviceIdController.dispose();
|
||||
_appVersionController.dispose();
|
||||
_buildNumberController.dispose();
|
||||
_instanceIdController.dispose();
|
||||
_clientSessionIdController.dispose();
|
||||
_deviceLocaleController.dispose();
|
||||
_pushDeviceTypeController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -453,8 +490,16 @@ class _SpoofScreenState extends State<SpoofScreen> {
|
||||
),
|
||||
],
|
||||
selected: {_selectedMethod},
|
||||
onSelectionChanged: (s) =>
|
||||
setState(() => _selectedMethod = s.first),
|
||||
onSelectionChanged: (s) async {
|
||||
final next = s.first;
|
||||
if (next == _selectedMethod) return;
|
||||
if (next == SpoofingMethod.full) {
|
||||
final confirmed = await _confirmFullSpoofing();
|
||||
if (!confirmed || !mounted) return;
|
||||
}
|
||||
setState(() => _selectedMethod = next);
|
||||
_syncDeviceLocale();
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
descriptionWidget,
|
||||
@@ -604,6 +649,15 @@ class _SpoofScreenState extends State<SpoofScreen> {
|
||||
Icons.language_outlined,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: _deviceLocaleController,
|
||||
enabled: _selectedMethod == SpoofingMethod.full,
|
||||
decoration: _inputDecoration(
|
||||
l10n.spoofFieldDeviceLocale,
|
||||
Icons.translate_outlined,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -625,6 +679,24 @@ class _SpoofScreenState extends State<SpoofScreen> {
|
||||
text: l10n.spoofIdentifiersDescription,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
controller: _instanceIdController,
|
||||
enabled: _selectedMethod == SpoofingMethod.full,
|
||||
decoration: _inputDecoration(
|
||||
l10n.spoofFieldInstanceId,
|
||||
Icons.fingerprint_outlined,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: _clientSessionIdController,
|
||||
enabled: _selectedMethod == SpoofingMethod.full,
|
||||
decoration: _inputDecoration(
|
||||
l10n.spoofFieldClientSessionId,
|
||||
Icons.vpn_key_outlined,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: _deviceIdController,
|
||||
decoration:
|
||||
@@ -642,7 +714,7 @@ class _SpoofScreenState extends State<SpoofScreen> {
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: _appVersionController,
|
||||
enabled: false,
|
||||
enabled: _selectedMethod == SpoofingMethod.full,
|
||||
decoration: _inputDecoration(
|
||||
l10n.spoofFieldAppVersion,
|
||||
Icons.info_outline_rounded,
|
||||
@@ -651,7 +723,7 @@ class _SpoofScreenState extends State<SpoofScreen> {
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: _buildNumberController,
|
||||
enabled: false,
|
||||
enabled: _selectedMethod == SpoofingMethod.full,
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: _inputDecoration(
|
||||
l10n.spoofFieldBuildNumber,
|
||||
@@ -659,6 +731,15 @@ class _SpoofScreenState extends State<SpoofScreen> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: _pushDeviceTypeController,
|
||||
enabled: _selectedMethod == SpoofingMethod.full,
|
||||
decoration: _inputDecoration(
|
||||
l10n.spoofFieldPushDeviceType,
|
||||
Icons.notifications_outlined,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 4, bottom: 8),
|
||||
child: Text(
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class InfoActionSheetItem {
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final String body;
|
||||
final Color? titleColor;
|
||||
final Color? iconColor;
|
||||
|
||||
const InfoActionSheetItem({
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.body,
|
||||
this.titleColor,
|
||||
this.iconColor,
|
||||
});
|
||||
}
|
||||
|
||||
Future<bool> showInfoActionSheet(
|
||||
BuildContext context, {
|
||||
String? headerEmoji,
|
||||
IconData? headerIcon,
|
||||
required String title,
|
||||
String? subtitle,
|
||||
List<InfoActionSheetItem> items = const [],
|
||||
String confirmLabel = 'ОК',
|
||||
Duration confirmDelay = Duration.zero,
|
||||
String? seenKey,
|
||||
}) async {
|
||||
assert(
|
||||
headerEmoji == null || headerIcon == null,
|
||||
'Use either headerEmoji or headerIcon, not both.',
|
||||
);
|
||||
|
||||
if (seenKey != null) {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
if (prefs.getBool(seenKey) ?? false) return true;
|
||||
}
|
||||
|
||||
if (!context.mounted) return false;
|
||||
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
final confirmed = await showModalBottomSheet<bool>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: cs.surfaceContainerHigh,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
||||
),
|
||||
builder: (ctx) => _InfoActionSheet(
|
||||
headerEmoji: headerEmoji,
|
||||
headerIcon: headerIcon,
|
||||
title: title,
|
||||
subtitle: subtitle,
|
||||
items: items,
|
||||
confirmLabel: confirmLabel,
|
||||
confirmDelay: confirmDelay,
|
||||
),
|
||||
);
|
||||
|
||||
final ok = confirmed == true;
|
||||
if (ok && seenKey != null) {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(seenKey, true);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
class _InfoActionSheet extends StatefulWidget {
|
||||
final String? headerEmoji;
|
||||
final IconData? headerIcon;
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
final List<InfoActionSheetItem> items;
|
||||
final String confirmLabel;
|
||||
final Duration confirmDelay;
|
||||
|
||||
const _InfoActionSheet({
|
||||
this.headerEmoji,
|
||||
this.headerIcon,
|
||||
required this.title,
|
||||
this.subtitle,
|
||||
this.items = const [],
|
||||
required this.confirmLabel,
|
||||
required this.confirmDelay,
|
||||
});
|
||||
|
||||
@override
|
||||
State<_InfoActionSheet> createState() => _InfoActionSheetState();
|
||||
}
|
||||
|
||||
class _InfoActionSheetState extends State<_InfoActionSheet> {
|
||||
Timer? _ticker;
|
||||
int _remaining = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_remaining = widget.confirmDelay.inSeconds;
|
||||
if (_remaining > 0) {
|
||||
_ticker = Timer.periodic(const Duration(seconds: 1), (_) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_remaining--;
|
||||
if (_remaining == 0) {
|
||||
_ticker?.cancel();
|
||||
_ticker = null;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_ticker?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
final enabled = _remaining == 0;
|
||||
final buttonText = enabled
|
||||
? widget.confirmLabel
|
||||
: '${widget.confirmLabel}($_remaining)';
|
||||
final hasHeader = widget.headerEmoji != null || widget.headerIcon != null;
|
||||
|
||||
return SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 28, 24, 20),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (hasHeader) ...[
|
||||
_buildHeader(cs),
|
||||
const SizedBox(height: 18),
|
||||
],
|
||||
Text(
|
||||
widget.title,
|
||||
textAlign: TextAlign.center,
|
||||
style: GoogleFonts.inter(
|
||||
color: cs.onSurface,
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
if (widget.subtitle != null) ...[
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
widget.subtitle!,
|
||||
textAlign: TextAlign.center,
|
||||
style: GoogleFonts.inter(
|
||||
color: cs.onSurfaceVariant,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w400,
|
||||
),
|
||||
),
|
||||
],
|
||||
if (widget.items.isNotEmpty) ...[
|
||||
const SizedBox(height: 22),
|
||||
for (int i = 0; i < widget.items.length; i++) ...[
|
||||
_buildItem(cs, widget.items[i]),
|
||||
if (i < widget.items.length - 1)
|
||||
const SizedBox(height: 18),
|
||||
],
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 28),
|
||||
FilledButton(
|
||||
onPressed: enabled ? () => Navigator.pop(context, true) : null,
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: cs.primary,
|
||||
foregroundColor: cs.onPrimary,
|
||||
disabledBackgroundColor: cs.primary.withValues(alpha: 0.45),
|
||||
disabledForegroundColor: cs.onPrimary.withValues(alpha: 0.85),
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(28),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
buttonText,
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader(ColorScheme cs) {
|
||||
if (widget.headerEmoji != null) {
|
||||
return Center(
|
||||
child: Text(
|
||||
widget.headerEmoji!,
|
||||
style: const TextStyle(fontSize: 72, height: 1.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Center(
|
||||
child: Icon(
|
||||
widget.headerIcon,
|
||||
size: 72,
|
||||
color: cs.primary,
|
||||
weight: 400,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildItem(ColorScheme cs, InfoActionSheetItem item) {
|
||||
final titleColor = item.titleColor ?? cs.onSurface;
|
||||
final iconColor = item.iconColor ?? cs.onSurfaceVariant;
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 2),
|
||||
child: Icon(
|
||||
item.icon,
|
||||
size: 26,
|
||||
color: iconColor,
|
||||
weight: 400,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
item.title,
|
||||
style: GoogleFonts.inter(
|
||||
color: titleColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w700,
|
||||
height: 1.25,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
item.body,
|
||||
style: GoogleFonts.inter(
|
||||
color: cs.onSurfaceVariant,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 1.35,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,8 +73,12 @@
|
||||
"spoofFieldScreen": "Screen resolution",
|
||||
"spoofFieldTimezone": "Timezone",
|
||||
"spoofFieldLocale": "Locale",
|
||||
"spoofFieldDeviceLocale": "Device locale (derived)",
|
||||
"spoofIdentifiersSectionTitle": "Identifiers",
|
||||
"spoofIdentifiersDescription": "mt_instanceid and clientSessionId are generated automatically on every app launch. Only the Device ID can be changed.",
|
||||
"spoofFieldInstanceId": "mt_instanceid",
|
||||
"spoofFieldClientSessionId": "clientSessionId",
|
||||
"spoofFieldPushDeviceType": "Push device type",
|
||||
"spoofFieldDeviceId": "Device ID",
|
||||
"spoofRegenerateIdTooltip": "Generate a new ID",
|
||||
"spoofFieldAppVersion": "App version",
|
||||
|
||||
@@ -482,6 +482,12 @@ abstract class AppLocalizations {
|
||||
/// **'Locale'**
|
||||
String get spoofFieldLocale;
|
||||
|
||||
/// No description provided for @spoofFieldDeviceLocale.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Device locale (derived)'**
|
||||
String get spoofFieldDeviceLocale;
|
||||
|
||||
/// No description provided for @spoofIdentifiersSectionTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -494,6 +500,24 @@ abstract class AppLocalizations {
|
||||
/// **'mt_instanceid and clientSessionId are generated automatically on every app launch. Only the Device ID can be changed.'**
|
||||
String get spoofIdentifiersDescription;
|
||||
|
||||
/// No description provided for @spoofFieldInstanceId.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'mt_instanceid'**
|
||||
String get spoofFieldInstanceId;
|
||||
|
||||
/// No description provided for @spoofFieldClientSessionId.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'clientSessionId'**
|
||||
String get spoofFieldClientSessionId;
|
||||
|
||||
/// No description provided for @spoofFieldPushDeviceType.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Push device type'**
|
||||
String get spoofFieldPushDeviceType;
|
||||
|
||||
/// No description provided for @spoofFieldDeviceId.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
||||
@@ -209,6 +209,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get spoofFieldLocale => 'Locale';
|
||||
|
||||
@override
|
||||
String get spoofFieldDeviceLocale => 'Device locale (derived)';
|
||||
|
||||
@override
|
||||
String get spoofIdentifiersSectionTitle => 'Identifiers';
|
||||
|
||||
@@ -216,6 +219,15 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
String get spoofIdentifiersDescription =>
|
||||
'mt_instanceid and clientSessionId are generated automatically on every app launch. Only the Device ID can be changed.';
|
||||
|
||||
@override
|
||||
String get spoofFieldInstanceId => 'mt_instanceid';
|
||||
|
||||
@override
|
||||
String get spoofFieldClientSessionId => 'clientSessionId';
|
||||
|
||||
@override
|
||||
String get spoofFieldPushDeviceType => 'Push device type';
|
||||
|
||||
@override
|
||||
String get spoofFieldDeviceId => 'Device ID';
|
||||
|
||||
|
||||
@@ -211,6 +211,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
@override
|
||||
String get spoofFieldLocale => 'Локаль';
|
||||
|
||||
@override
|
||||
String get spoofFieldDeviceLocale => 'Системная локаль (производная)';
|
||||
|
||||
@override
|
||||
String get spoofIdentifiersSectionTitle => 'Идентификаторы';
|
||||
|
||||
@@ -218,6 +221,15 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
String get spoofIdentifiersDescription =>
|
||||
'mt_instanceid и clientSessionId генерируются автоматически при каждом запуске приложения. Изменить можно только Device ID.';
|
||||
|
||||
@override
|
||||
String get spoofFieldInstanceId => 'mt_instanceid';
|
||||
|
||||
@override
|
||||
String get spoofFieldClientSessionId => 'clientSessionId';
|
||||
|
||||
@override
|
||||
String get spoofFieldPushDeviceType => 'Тип push-уведомлений';
|
||||
|
||||
@override
|
||||
String get spoofFieldDeviceId => 'ID Устройства';
|
||||
|
||||
|
||||
@@ -73,8 +73,12 @@
|
||||
"spoofFieldScreen": "Разрешение экрана",
|
||||
"spoofFieldTimezone": "Часовой пояс",
|
||||
"spoofFieldLocale": "Локаль",
|
||||
"spoofFieldDeviceLocale": "Системная локаль (производная)",
|
||||
"spoofIdentifiersSectionTitle": "Идентификаторы",
|
||||
"spoofIdentifiersDescription": "mt_instanceid и clientSessionId генерируются автоматически при каждом запуске приложения. Изменить можно только Device ID.",
|
||||
"spoofFieldInstanceId": "mt_instanceid",
|
||||
"spoofFieldClientSessionId": "clientSessionId",
|
||||
"spoofFieldPushDeviceType": "Тип push-уведомлений",
|
||||
"spoofFieldDeviceId": "ID Устройства",
|
||||
"spoofRegenerateIdTooltip": "Сгенерировать новый ID",
|
||||
"spoofFieldAppVersion": "Версия приложения",
|
||||
|
||||
Reference in New Issue
Block a user