feat(spoof): тумблер меняет данные на лету + выбор Android/iOS

This commit is contained in:
klockky
2026-06-20 15:50:47 +00:00
parent 08d5cabae5
commit d53f974ddb
6 changed files with 42 additions and 20 deletions
+37 -15
View File
@@ -101,11 +101,11 @@ class _SpoofScreenState extends State<SpoofScreen> {
if (profile != null && profile.enabled) { if (profile != null && profile.enabled) {
_spoofingEnabled = true; _spoofingEnabled = true;
_applyProfileToControllers(profile); _applyProfileToControllers(profile);
if (mounted) setState(() => _isLoading = false);
} else { } else {
_spoofingEnabled = false; _spoofingEnabled = false;
await _loadDeviceData(); await _loadDeviceData();
} }
if (mounted) setState(() => _isLoading = false);
} }
void _applyProfileToControllers(SpoofProfile profile) { void _applyProfileToControllers(SpoofProfile profile) {
@@ -163,7 +163,6 @@ class _SpoofScreenState extends State<SpoofScreen> {
} }
Future<void> _loadDeviceData() async { Future<void> _loadDeviceData() async {
setState(() => _isLoading = true);
_userAgent = ''; _userAgent = '';
_spoofingEnabled = false; _spoofingEnabled = false;
@@ -204,6 +203,7 @@ class _SpoofScreenState extends State<SpoofScreen> {
if (Platform.isAndroid) { if (Platform.isAndroid) {
final androidInfo = await deviceInfo.androidInfo; final androidInfo = await deviceInfo.androidInfo;
_selectedDeviceType = 'ANDROID';
_deviceNameController.text = _deviceNameController.text =
'${androidInfo.manufacturer} ${androidInfo.model}'; '${androidInfo.manufacturer} ${androidInfo.model}';
_osVersionController.text = 'Android ${androidInfo.version.release}'; _osVersionController.text = 'Android ${androidInfo.version.release}';
@@ -213,28 +213,32 @@ class _SpoofScreenState extends State<SpoofScreen> {
} else if (Platform.isIOS) { } else if (Platform.isIOS) {
final iosInfo = await deviceInfo.iosInfo; final iosInfo = await deviceInfo.iosInfo;
_selectedDeviceType = 'IOS'; _selectedDeviceType = 'IOS';
_selectedArch = 'arm64';
_deviceNameController.text = iosInfo.utsname.machine; _deviceNameController.text = iosInfo.utsname.machine;
_osVersionController.text = iosInfo.systemVersion; _osVersionController.text = iosInfo.systemVersion;
} else if (Platform.isLinux) { } else if (Platform.isLinux) {
final linuxInfo = await deviceInfo.linuxInfo; final linuxInfo = await deviceInfo.linuxInfo;
_selectedDeviceType = 'ANDROID';
_deviceNameController.text = linuxInfo.prettyName; _deviceNameController.text = linuxInfo.prettyName;
_osVersionController.text = linuxInfo.name; _osVersionController.text = linuxInfo.name;
} else if (Platform.isWindows) { } else if (Platform.isWindows) {
final windowsInfo = await deviceInfo.windowsInfo; final windowsInfo = await deviceInfo.windowsInfo;
_selectedDeviceType = 'ANDROID';
_deviceNameController.text = windowsInfo.productName; _deviceNameController.text = windowsInfo.productName;
_osVersionController.text = windowsInfo.productName; _osVersionController.text = windowsInfo.productName;
} else if (Platform.isMacOS) { } else if (Platform.isMacOS) {
final macInfo = await deviceInfo.macOsInfo; final macInfo = await deviceInfo.macOsInfo;
_selectedDeviceType = 'ANDROID';
_deviceNameController.text = macInfo.model; _deviceNameController.text = macInfo.model;
_osVersionController.text = 'macOS ${macInfo.osRelease}'; _osVersionController.text = 'macOS ${macInfo.osRelease}';
} }
if (mounted) setState(() => _isLoading = false); if (mounted) setState(() {});
} }
Future<void> _applyGeneratedData() async { Future<void> _applyGeneratedData() async {
final filteredPresets = devicePresets final filteredPresets = devicePresets
.where((p) => p.deviceType == 'ANDROID' || p.deviceType == 'IOS') .where((p) => p.deviceType == _selectedDeviceType)
.toList(); .toList();
if (filteredPresets.isEmpty) return; if (filteredPresets.isEmpty) return;
@@ -243,6 +247,12 @@ class _SpoofScreenState extends State<SpoofScreen> {
await _applyPreset(preset); await _applyPreset(preset);
} }
void _onDeviceTypeChanged(String type) {
if (type == _selectedDeviceType) return;
setState(() => _selectedDeviceType = type);
if (_spoofingEnabled) _applyGeneratedData();
}
Future<void> _applyPreset(DevicePreset preset) async { Future<void> _applyPreset(DevicePreset preset) async {
setState(() { setState(() {
_deviceNameController.text = preset.deviceName; _deviceNameController.text = preset.deviceName;
@@ -470,7 +480,13 @@ class _SpoofScreenState extends State<SpoofScreen> {
: l10n.spoofEnableSubtitleOff, : l10n.spoofEnableSubtitleOff,
), ),
value: _spoofingEnabled, value: _spoofingEnabled,
onChanged: (value) => setState(() => _spoofingEnabled = value), onChanged: (value) async {
if (value) {
await _applyGeneratedData();
} else {
await _loadDeviceData();
}
},
), ),
); );
} }
@@ -586,12 +602,14 @@ class _SpoofScreenState extends State<SpoofScreen> {
text: l10n.spoofDeviceTypeDescription, text: l10n.spoofDeviceTypeDescription,
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
Row( _buildChipSelector<String>(
children: [ options: const [
_buildDisabledChip('ANDROID', Icons.android_outlined, theme), _ChipOption('ANDROID', 'Android', Icons.android_outlined),
const SizedBox(width: 8), _ChipOption('IOS', 'iOS', Icons.phone_iphone_outlined),
_buildDisabledChip('iOS', Icons.phone_iphone_outlined, theme), ],
const SizedBox(width: 8), selected: _selectedDeviceType,
onSelected: _onDeviceTypeChanged,
trailing: [
_buildDisabledChip( _buildDisabledChip(
'Desktop', 'Desktop',
Icons.desktop_windows_outlined, Icons.desktop_windows_outlined,
@@ -845,14 +863,16 @@ class _SpoofScreenState extends State<SpoofScreen> {
required List<_ChipOption<T>> options, required List<_ChipOption<T>> options,
required T selected, required T selected,
required ValueChanged<T> onSelected, required ValueChanged<T> onSelected,
List<Widget> trailing = const [],
}) { }) {
final cs = Theme.of(context).colorScheme; final cs = Theme.of(context).colorScheme;
return Wrap( return Wrap(
spacing: 8, spacing: 8,
runSpacing: 8, runSpacing: 8,
children: options.map((opt) { children: [
final isSelected = opt.value == selected; ...options.map((opt) {
return ChoiceChip( final isSelected = opt.value == selected;
return ChoiceChip(
label: Text(opt.label), label: Text(opt.label),
avatar: isSelected avatar: isSelected
? Icon(Icons.check, size: 18, color: cs.onSecondaryContainer) ? Icon(Icons.check, size: 18, color: cs.onSecondaryContainer)
@@ -878,7 +898,9 @@ class _SpoofScreenState extends State<SpoofScreen> {
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
); );
}).toList(), }),
...trailing,
],
); );
} }
+1 -1
View File
@@ -68,7 +68,7 @@
"spoofMethodPartialDescription": "Recommended method. Random data is used, but your real timezone and locale are kept for plausibility.", "spoofMethodPartialDescription": "Recommended method. Random data is used, but your real timezone and locale are kept for plausibility.",
"spoofMethodFullDescription": "All data including timezone and locale is generated randomly. Use this method at your own risk!", "spoofMethodFullDescription": "All data including timezone and locale is generated randomly. Use this method at your own risk!",
"spoofDeviceTypeTitle": "Device type", "spoofDeviceTypeTitle": "Device type",
"spoofDeviceTypeDescription": "This field is not changeable, to avoid token association issues", "spoofDeviceTypeDescription": "Controls which devices are generated: Android or iOS",
"spoofDeviceTypeLabel": "Device type", "spoofDeviceTypeLabel": "Device type",
"spoofMainSectionTitle": "Main data", "spoofMainSectionTitle": "Main data",
"spoofFieldDeviceName": "Device name", "spoofFieldDeviceName": "Device name",
+1 -1
View File
@@ -455,7 +455,7 @@ abstract class AppLocalizations {
/// No description provided for @spoofDeviceTypeDescription. /// No description provided for @spoofDeviceTypeDescription.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'This field is not changeable, to avoid token association issues'** /// **'Controls which devices are generated: Android or iOS'**
String get spoofDeviceTypeDescription; String get spoofDeviceTypeDescription;
/// No description provided for @spoofDeviceTypeLabel. /// No description provided for @spoofDeviceTypeLabel.
+1 -1
View File
@@ -196,7 +196,7 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get spoofDeviceTypeDescription => String get spoofDeviceTypeDescription =>
'This field is not changeable, to avoid token association issues'; 'Controls which devices are generated: Android or iOS';
@override @override
String get spoofDeviceTypeLabel => 'Device type'; String get spoofDeviceTypeLabel => 'Device type';
+1 -1
View File
@@ -199,7 +199,7 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get spoofDeviceTypeDescription => String get spoofDeviceTypeDescription =>
'Данное поле не изменяемое, во избежании проблем с ассоциацией токена'; 'Определяет, какие устройства генерируются: Android или iOS';
@override @override
String get spoofDeviceTypeLabel => 'Тип устройства'; String get spoofDeviceTypeLabel => 'Тип устройства';
+1 -1
View File
@@ -68,7 +68,7 @@
"spoofMethodPartialDescription": "Рекомендуемый метод. Используются случайные данные, но ваш реальный часовой пояс и локаль для большей правдоподобности.", "spoofMethodPartialDescription": "Рекомендуемый метод. Используются случайные данные, но ваш реальный часовой пояс и локаль для большей правдоподобности.",
"spoofMethodFullDescription": "Все данные, включая часовой пояс и локаль, генерируются случайно. Использование этого метода на ваш страх и риск!", "spoofMethodFullDescription": "Все данные, включая часовой пояс и локаль, генерируются случайно. Использование этого метода на ваш страх и риск!",
"spoofDeviceTypeTitle": "Тип устройства", "spoofDeviceTypeTitle": "Тип устройства",
"spoofDeviceTypeDescription": "Данное поле не изменяемое, во избежании проблем с ассоциацией токена", "spoofDeviceTypeDescription": "Определяет, какие устройства генерируются: Android или iOS",
"spoofDeviceTypeLabel": "Тип устройства", "spoofDeviceTypeLabel": "Тип устройства",
"spoofMainSectionTitle": "Основные данные", "spoofMainSectionTitle": "Основные данные",
"spoofFieldDeviceName": "Имя устройства", "spoofFieldDeviceName": "Имя устройства",