From 23ef5870153740c5b5d9d500e399a4fe93b2d2b1 Mon Sep 17 00:00:00 2001 From: klockky Date: Sat, 11 Apr 2026 00:21:31 +0300 Subject: [PATCH] feat(spoof): port session spoofing screen with device presets --- lib/backend/api.dart | 53 +- lib/core/config/device_presets.dart | 1013 +++++++++++++++++ lib/core/config/spoof_data.dart | 31 - lib/core/storage/spoofing_service.dart | 27 + lib/frontend/screens/auth/login_screen.dart | 4 +- .../screens/auth/spoff_redacted_screen.dart | 217 ---- .../screens/profile/settings_tab.dart | 3 +- .../screens/profile/spoof_screen.dart | 980 ++++++++++++---- lib/l10n/app_en.arb | 48 +- lib/l10n/app_localizations.dart | 255 ++++- lib/l10n/app_localizations_en.dart | 127 ++- lib/l10n/app_localizations_ru.dart | 133 ++- lib/l10n/app_ru.arb | 48 +- 13 files changed, 2398 insertions(+), 541 deletions(-) create mode 100644 lib/core/config/device_presets.dart delete mode 100644 lib/core/config/spoof_data.dart create mode 100644 lib/core/storage/spoofing_service.dart delete mode 100644 lib/frontend/screens/auth/spoff_redacted_screen.dart diff --git a/lib/backend/api.dart b/lib/backend/api.dart index 87fd3d6..0ff3ace 100644 --- a/lib/backend/api.dart +++ b/lib/backend/api.dart @@ -5,6 +5,7 @@ import '../core/config/config.dart'; import '../core/config/countries.dart'; import '../core/protocol/opcode_map.dart'; import '../core/protocol/packet.dart'; +import '../core/storage/spoofing_service.dart'; import '../core/transport/connection.dart'; import '../core/transport/dispatcher.dart'; import '../core/transport/receiver.dart'; @@ -112,7 +113,7 @@ class Api { Future sendHandshake() async { final deviceInfo = DeviceInfoPlugin(); - final deviceType = (Platform.isLinux || Platform.isWindows) + String deviceType = (Platform.isLinux || Platform.isWindows) ? 'DESKTOP' : (Platform.isAndroid) ? 'ANDROID' @@ -120,10 +121,16 @@ class Api { String osVersion = ''; String deviceName = 'Unknown'; String architecture = 'arm64'; + String appVersion = SpoofingService.hardcodedAppVersion; + int buildNumber = SpoofingService.hardcodedBuildNumber; + String screen = '1920x1080'; tz.initializeTimeZones(); final timeZoneName = await FlutterTimezone.getLocalTimezone(); - final timezone = timeZoneName.identifier; + String timezone = timeZoneName.identifier; + String locale = 'ru'; + String deviceLocale = Platform.localeName.substring(0, 2); + String deviceId = 'a1b2c3d4e5f6a7b8'; if (Platform.isLinux) { final linuxInfo = await deviceInfo.linuxInfo; @@ -150,24 +157,54 @@ class Api { ); } + final spoofed = await SpoofingService.getSpoofedSessionData(); + if (spoofed != null) { + deviceType = (spoofed['device_type'] as String?) ?? deviceType; + final sDeviceName = spoofed['device_name'] as String?; + if (sDeviceName != null && sDeviceName.isNotEmpty) { + deviceName = sDeviceName; + } + final sOsVersion = spoofed['os_version'] as String?; + if (sOsVersion != null && sOsVersion.isNotEmpty) osVersion = sOsVersion; + final sScreen = spoofed['screen'] as String?; + if (sScreen != null && sScreen.isNotEmpty) screen = sScreen; + final sTimezone = spoofed['timezone'] as String?; + if (sTimezone != null && sTimezone.isNotEmpty) timezone = sTimezone; + final sLocale = spoofed['locale'] as String?; + if (sLocale != null && sLocale.isNotEmpty) { + locale = sLocale; + deviceLocale = sLocale.split(RegExp(r'[-_]')).first; + } + final sDeviceId = spoofed['device_id'] as String?; + if (sDeviceId != null && sDeviceId.isNotEmpty) deviceId = sDeviceId; + appVersion = (spoofed['app_version'] as String?) ?? appVersion; + architecture = (spoofed['arch'] as String?) ?? architecture; + final sBuild = spoofed['build_number']; + if (sBuild is int) { + buildNumber = sBuild; + } else if (sBuild is String) { + buildNumber = int.tryParse(sBuild) ?? buildNumber; + } + } + _userAgent = { 'deviceType': deviceType, - 'locale': 'ru', - 'deviceLocale': Platform.localeName.substring(0, 2), + 'locale': locale, + 'deviceLocale': deviceLocale, 'osVersion': osVersion, 'deviceName': deviceName, - 'appVersion': '26.8.1', - 'screen': '1920x1080', + 'appVersion': appVersion, + 'screen': screen, 'timezone': timezone, 'pushDeviceType': 'GCM', 'arch': architecture, - 'buildNumber': 6606, + 'buildNumber': buildNumber, }; final payload = { 'mt_instanceid': '550e8400-e29b-41d4-a716-446655440000', 'clientSessionId': 42, - 'deviceId': 'a1b2c3d4e5f6a7b8', + 'deviceId': deviceId, 'userAgent': _userAgent, }; diff --git a/lib/core/config/device_presets.dart b/lib/core/config/device_presets.dart new file mode 100644 index 0000000..de7506e --- /dev/null +++ b/lib/core/config/device_presets.dart @@ -0,0 +1,1013 @@ +class DevicePreset { + final String deviceType; + final String userAgent; + final String deviceName; + final String osVersion; + final String screen; + final String timezone; + final String locale; + + DevicePreset({ + required this.deviceType, + required this.userAgent, + required this.deviceName, + required this.osVersion, + required this.screen, + required this.timezone, + required this.locale, + }); +} + +final List devicePresets = [ + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 14; SM-S928B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36', + deviceName: 'Samsung Galaxy S24 Ultra', + osVersion: 'Android 14', + screen: 'xxhdpi 450dpi 1440x3120', + timezone: 'Europe/Berlin', + locale: 'de-DE', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 14; Pixel 8 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36', + deviceName: 'Google Pixel 8 Pro', + osVersion: 'Android 14', + screen: 'xxhdpi 430dpi 1344x2992', + timezone: 'America/New_York', + locale: 'en-US', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; 23021RAA2Y) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36', + deviceName: 'Xiaomi 13 Pro', + osVersion: 'Android 13', + screen: 'xxhdpi 460dpi 1440x3200', + timezone: 'Asia/Shanghai', + locale: 'zh-CN', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 14; CPH2521) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36', + deviceName: 'OnePlus 12', + osVersion: 'Android 14', + screen: 'xxhdpi 450dpi 1440x3168', + timezone: 'Asia/Kolkata', + locale: 'en-IN', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; SM-G998B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Mobile Safari/537.36', + deviceName: 'Samsung Galaxy S21 Ultra', + osVersion: 'Android 13', + screen: 'xxhdpi 460dpi 1440x3200', + timezone: 'Europe/London', + locale: 'en-GB', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36', + deviceName: 'Google Pixel 6', + osVersion: 'Android 12', + screen: 'xxhdpi 420dpi 1080x2400', + timezone: 'America/Chicago', + locale: 'en-US', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; RMX3371) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Mobile Safari/537.36', + deviceName: 'Realme GT Master Edition', + osVersion: 'Android 13', + screen: 'xxhdpi 400dpi 1080x2400', + timezone: 'Asia/Dubai', + locale: 'ar-AE', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 11; M2101K6G) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Mobile Safari/537.36', + deviceName: 'Poco F3', + osVersion: 'Android 11', + screen: 'xxhdpi 420dpi 1080x2400', + timezone: 'Europe/Madrid', + locale: 'es-ES', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 14; SO-51D) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36', + deviceName: 'Sony Xperia 1 V', + osVersion: 'Android 14', + screen: 'xxxhdpi 560dpi 1644x3840', + timezone: 'Asia/Tokyo', + locale: 'ja-JP', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; XT2201-2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36', + deviceName: 'Motorola Edge 30 Pro', + osVersion: 'Android 13', + screen: 'xxhdpi 400dpi 1080x2400', + timezone: 'America/Sao_Paulo', + locale: 'pt-BR', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 14; SM-A546E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36', + deviceName: 'Samsung Galaxy A54', + osVersion: 'Android 14', + screen: 'xxhdpi 400dpi 1080x2340', + timezone: 'Australia/Sydney', + locale: 'en-AU', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 12; 2201116SG) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36', + deviceName: 'Redmi Note 11 Pro', + osVersion: 'Android 12', + screen: 'xxhdpi 420dpi 1080x2400', + timezone: 'Europe/Rome', + locale: 'it-IT', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; ZS676KS) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Mobile Safari/537.36', + deviceName: 'Asus ROG Phone 6', + osVersion: 'Android 13', + screen: 'xxhdpi 420dpi 1080x2448', + timezone: 'Asia/Taipei', + locale: 'zh-TW', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 10; TA-1021) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Mobile Safari/537.36', + deviceName: 'Nokia 8', + osVersion: 'Android 10', + screen: 'xxhdpi 380dpi 1440x2560', + timezone: 'Europe/Helsinki', + locale: 'fi-FI', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; PGT-N19) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36', + deviceName: 'Huawei P60 Pro', + osVersion: 'Android 13 (EMUI)', + screen: 'xxhdpi 430dpi 1220x2700', + timezone: 'Europe/Paris', + locale: 'fr-FR', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 9; LM-G710) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Mobile Safari/537.36', + deviceName: 'LG G7 ThinQ', + osVersion: 'Android 9', + screen: 'xxhdpi 450dpi 1440x3120', + timezone: 'Asia/Seoul', + locale: 'ko-KR', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 14; Nothing Phone (2)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36', + deviceName: 'Nothing Phone (2)', + osVersion: 'Android 14', + screen: 'xxhdpi 400dpi 1080x2412', + timezone: 'America/Toronto', + locale: 'en-CA', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; SM-F936U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Mobile Safari/537.36', + deviceName: 'Samsung Galaxy Z Fold 4', + osVersion: 'Android 13', + screen: 'xhdpi 350dpi 1812x2176', + timezone: 'America/Denver', + locale: 'en-US', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 12; LE2113) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Mobile Safari/537.36', + deviceName: 'OnePlus 9', + osVersion: 'Android 12', + screen: 'xxhdpi 420dpi 1080x2400', + timezone: 'Europe/Stockholm', + locale: 'sv-SE', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 14; Pixel 7a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36', + deviceName: 'Google Pixel 7a', + osVersion: 'Android 14', + screen: 'xxhdpi 400dpi 1080x2400', + timezone: 'Europe/Amsterdam', + locale: 'nl-NL', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 14; SM-S911B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36', + deviceName: 'Samsung Galaxy S24', + osVersion: 'Android 14', + screen: 'xxhdpi 450dpi 1440x3120', + timezone: 'America/Vancouver', + locale: 'en-CA', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; M2101K6C) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Mobile Safari/537.36', + deviceName: 'Poco F3 GT', + osVersion: 'Android 13', + screen: 'xxhdpi 420dpi 1080x2400', + timezone: 'Asia/Kolkata', + locale: 'en-IN', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 14; V29) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36', + deviceName: 'Vivo V29', + osVersion: 'Android 14', + screen: 'xxhdpi 460dpi 1440x3200', + timezone: 'Asia/Bangkok', + locale: 'th-TH', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; K30 Ultra) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Mobile Safari/537.36', + deviceName: 'Xiaomi K30 Ultra', + osVersion: 'Android 13', + screen: 'xxhdpi 450dpi 1440x3200', + timezone: 'Asia/Shanghai', + locale: 'zh-CN', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 14; P80) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36', + deviceName: 'Oppo Find N3', + osVersion: 'Android 14', + screen: 'xxhdpi 430dpi 1440x3168', + timezone: 'Europe/Paris', + locale: 'fr-FR', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; SM-G916B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Mobile Safari/537.36', + deviceName: 'Samsung Galaxy S20 FE', + osVersion: 'Android 13', + screen: 'xxhdpi 400dpi 1080x2400', + timezone: 'Europe/Berlin', + locale: 'de-DE', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 12; CPH2135) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36', + deviceName: 'OnePlus 8 Pro', + osVersion: 'Android 12', + screen: 'xxhdpi 450dpi 1440x3168', + timezone: 'America/New_York', + locale: 'en-US', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 14; S24E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36', + deviceName: 'Samsung Galaxy S24 Edge', + osVersion: 'Android 14', + screen: 'xxhdpi 460dpi 1440x3200', + timezone: 'Asia/Tokyo', + locale: 'ja-JP', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; LE2120) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36', + deviceName: 'OnePlus 9 Pro', + osVersion: 'Android 13', + screen: 'xxhdpi 460dpi 1440x3216', + timezone: 'America/Toronto', + locale: 'en-CA', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 14; A14 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36', + deviceName: 'Google Pixel 9 Pro', + osVersion: 'Android 14', + screen: 'xxhdpi 430dpi 1344x2992', + timezone: 'Europe/London', + locale: 'en-GB', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; 21091116AC) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Mobile Safari/537.36', + deviceName: 'Xiaomi 12T', + osVersion: 'Android 13', + screen: 'xxhdpi 460dpi 1440x3200', + timezone: 'Europe/Rome', + locale: 'it-IT', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 12; SM-F711B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36', + deviceName: 'Samsung Galaxy Z Flip 3', + osVersion: 'Android 12', + screen: 'xhdpi 370dpi 1080x2640', + timezone: 'America/Mexico_City', + locale: 'es-MX', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; XT2201-3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36', + deviceName: 'Motorola Edge 40', + osVersion: 'Android 13', + screen: 'xxhdpi 400dpi 1080x2400', + timezone: 'Asia/Dubai', + locale: 'ar-AE', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 14; 23088RA9AC) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36', + deviceName: 'Xiaomi 14 Ultra', + osVersion: 'Android 14', + screen: 'xxhdpi 450dpi 1440x3200', + timezone: 'Europe/Moscow', + locale: 'ru-RU', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; CPH2487) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Mobile Safari/537.36', + deviceName: 'OnePlus 11', + osVersion: 'Android 13', + screen: 'xxhdpi 450dpi 1440x3216', + timezone: 'Australia/Sydney', + locale: 'en-AU', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 12; M2004J19C) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Mobile Safari/537.36', + deviceName: 'Xiaomi Mi 10T Pro', + osVersion: 'Android 12', + screen: 'xxhdpi 460dpi 1440x3200', + timezone: 'America/Sao_Paulo', + locale: 'pt-BR', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 14; SM-A546B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Mobile Safari/537.36', + deviceName: 'Samsung Galaxy A55', + osVersion: 'Android 14', + screen: 'xxhdpi 460dpi 1440x3200', + timezone: 'Europe/Madrid', + locale: 'es-ES', + ), + DevicePreset( + deviceType: 'ANDROID', + userAgent: + 'Mozilla/5.0 (Linux; Android 13; RMX3761) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36', + deviceName: 'Realme GT Neo 3', + osVersion: 'Android 13', + screen: 'xxhdpi 460dpi 1440x3200', + timezone: 'Asia/Hong_Kong', + locale: 'zh-HK', + ), + + DevicePreset( + deviceType: 'IOS', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1', + deviceName: 'iPhone 15 Pro Max', + osVersion: 'iOS 17.5.1', + screen: '1290x2796 3.0x', + timezone: 'America/Los_Angeles', + locale: 'en-US', + ), + DevicePreset( + deviceType: 'IOS', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1', + deviceName: 'iPhone 13', + osVersion: 'iOS 16.7', + screen: '1170x2532 3.0x', + timezone: 'Europe/London', + locale: 'en-GB', + ), + DevicePreset( + deviceType: 'IOS', + userAgent: + 'Mozilla/5.0 (iPad; CPU OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/124.0.6367.88 Mobile/15E148 Safari/604.1', + deviceName: 'iPad Pro 11-inch', + osVersion: 'iPadOS 17.5', + screen: '1668x2388 2.0x', + timezone: 'Europe/Paris', + locale: 'fr-FR', + ), + DevicePreset( + deviceType: 'IOS', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/125.0 Mobile/15E148', + deviceName: 'iPhone 14 Pro', + osVersion: 'iOS 17.4.1', + screen: '1179x2556 3.0x', + timezone: 'Europe/Berlin', + locale: 'de-DE', + ), + DevicePreset( + deviceType: 'IOS', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_8 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6.3 Mobile/15E148 Safari/604.1', + deviceName: 'iPhone SE (2020)', + osVersion: 'iOS 15.8', + screen: '750x1334 2.0x', + timezone: 'Australia/Melbourne', + locale: 'en-AU', + ), + DevicePreset( + deviceType: 'IOS', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) OPR/55.0.2519.144889 Mobile/15E148', + deviceName: 'iPhone 15', + osVersion: 'iOS 17.1', + screen: '1179x2556 3.0x', + timezone: 'Asia/Tokyo', + locale: 'ja-JP', + ), + DevicePreset( + deviceType: 'IOS', + userAgent: + 'Mozilla/5.0 (iPad; CPU OS 16_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Mobile/15E148 Safari/604.1', + deviceName: 'iPad Air 5th Gen', + osVersion: 'iPadOS 16.5', + screen: '1640x2360 2.0x', + timezone: 'America/Toronto', + locale: 'en-CA', + ), + DevicePreset( + deviceType: 'IOS', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1', + deviceName: 'iPhone 15 Pro', + osVersion: 'iOS 17.5', + screen: '1179x2556 3.0x', + timezone: 'Asia/Singapore', + locale: 'en-SG', + ), + DevicePreset( + deviceType: 'IOS', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1', + deviceName: 'iPhone 14', + osVersion: 'iOS 17.4', + screen: '1170x2532 3.0x', + timezone: 'Europe/Stockholm', + locale: 'sv-SE', + ), + DevicePreset( + deviceType: 'IOS', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1', + deviceName: 'iPhone 12 Pro Max', + osVersion: 'iOS 16.6', + screen: '1284x2778 3.0x', + timezone: 'Asia/Bangkok', + locale: 'th-TH', + ), + DevicePreset( + deviceType: 'IOS', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 15_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6 Mobile/15E148 Safari/604.1', + deviceName: 'iPhone 11 Pro', + osVersion: 'iOS 15.7', + screen: '1125x2436 3.0x', + timezone: 'Europe/Istanbul', + locale: 'tr-TR', + ), + DevicePreset( + deviceType: 'IOS', + userAgent: + 'Mozilla/5.0 (iPad; CPU OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1', + deviceName: 'iPad Mini 6th Gen', + osVersion: 'iPadOS 17.5', + screen: '1488x2266 2.0x', + timezone: 'America/Mexico_City', + locale: 'es-MX', + ), + DevicePreset( + deviceType: 'IOS', + userAgent: + 'Mozilla/5.0 (iPad; CPU OS 16_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.7 Mobile/15E148 Safari/604.1', + deviceName: 'iPad 10th Gen', + osVersion: 'iPadOS 16.7', + screen: '1620x2160 2.0x', + timezone: 'Asia/Hong_Kong', + locale: 'zh-HK', + ), + DevicePreset( + deviceType: 'IOS', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.3 Mobile/15E148 Safari/604.1', + deviceName: 'iPhone 13 Pro', + osVersion: 'iOS 17.3', + screen: '1170x2532 3.0x', + timezone: 'Europe/Dublin', + locale: 'en-IE', + ), + DevicePreset( + deviceType: 'IOS', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5 Mobile/15E148 Safari/604.1', + deviceName: 'iPhone 12', + osVersion: 'iOS 16.5', + screen: '1125x2436 3.0x', + timezone: 'Asia/Mumbai', + locale: 'en-IN', + ), + DevicePreset( + deviceType: 'IOS', + userAgent: + 'Mozilla/5.0 (iPad; CPU OS 15_8 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6 Mobile/15E148 Safari/604.1', + deviceName: 'iPad Pro 12.9-inch', + osVersion: 'iPadOS 15.8', + screen: '2048x2732 2.0x', + timezone: 'Europe/Vienna', + locale: 'de-AT', + ), + DevicePreset( + deviceType: 'IOS', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_8 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Mobile/15E148 Safari/604.1', + deviceName: 'iPhone XS Max', + osVersion: 'iOS 14.8', + screen: '1125x2436 3.0x', + timezone: 'America/Los_Angeles', + locale: 'en-US', + ), + DevicePreset( + deviceType: 'IOS', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1', + deviceName: 'iPhone 13 mini', + osVersion: 'iOS 17.2', + screen: '1080x2340 3.0x', + timezone: 'America/Miami', + locale: 'en-US', + ), + DevicePreset( + deviceType: 'IOS', + userAgent: + 'Mozilla/5.0 (iPad; CPU OS 14_8 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1', + deviceName: 'iPad Air 4th Gen', + osVersion: 'iPadOS 14.8', + screen: '1640x2360 2.0x', + timezone: 'Europe/Zurich', + locale: 'de-CH', + ), + DevicePreset( + deviceType: 'IOS', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Mobile/15E148 Safari/604.1', + deviceName: 'iPhone 11', + osVersion: 'iOS 16.4', + screen: '828x1792 2.0x', + timezone: 'America/Argentina/Buenos_Aires', + locale: 'es-AR', + ), + DevicePreset( + deviceType: 'IOS', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Mobile/15E148 Safari/604.1', + deviceName: 'iPhone 12 mini', + osVersion: 'iOS 17.1', + screen: '1080x2340 3.0x', + timezone: 'Europe/Brussels', + locale: 'nl-BE', + ), + DevicePreset( + deviceType: 'IOS', + userAgent: + 'Mozilla/5.0 (iPad; CPU OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1', + deviceName: 'iPad Air Pro 11-inch', + osVersion: 'iPadOS 17.4', + screen: '2388x1668 2.0x', + timezone: 'Asia/Bangkok', + locale: 'en-TH', + ), + DevicePreset( + deviceType: 'IOS', + userAgent: + 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.3 Mobile/15E148 Safari/604.1', + deviceName: 'iPhone XR', + osVersion: 'iOS 13.7', + screen: '828x1792 2.0x', + timezone: 'Europe/Lisbon', + locale: 'pt-PT', + ), + DevicePreset( + deviceType: 'IOS', + userAgent: + 'Mozilla/5.0 (iPad; CPU OS 17_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.3 Mobile/15E148 Safari/604.1', + deviceName: 'iPad (9th generation)', + osVersion: 'iPadOS 17.3', + screen: '1620x2160 2.0x', + timezone: 'Europe/Prague', + locale: 'cs-CZ', + ), + + DevicePreset( + deviceType: 'DESKTOP', + userAgent: + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + deviceName: 'Windows PC', + osVersion: 'Windows 11', + screen: '1920x1080 1.25x', + timezone: 'Europe/Moscow', + locale: 'ru-RU', + ), + DevicePreset( + deviceType: 'DESKTOP', + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + deviceName: 'MacBook Pro', + osVersion: 'macOS 14.5 Sonoma', + screen: '1728x1117 2.0x', + timezone: 'America/New_York', + locale: 'en-US', + ), + DevicePreset( + deviceType: 'DESKTOP', + userAgent: + 'Mozilla/5.0 (X11; Linux x86_64; rv:125.0) Gecko/20100101 Firefox/125.0', + deviceName: 'Linux PC', + osVersion: 'Ubuntu 24.04 LTS', + screen: '2560x1440 1.0x', + timezone: 'UTC', + locale: 'en-GB', + ), + DevicePreset( + deviceType: 'DESKTOP', + userAgent: + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0', + deviceName: 'Windows PC (Firefox)', + osVersion: 'Windows 10', + screen: '1536x864 1.0x', + timezone: 'Europe/Paris', + locale: 'fr-FR', + ), + DevicePreset( + deviceType: 'DESKTOP', + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Safari/605.1.15', + deviceName: 'iMac (Safari)', + osVersion: 'macOS 13.6 Ventura', + screen: '3840x2160 1.5x', + timezone: 'America/Los_Angeles', + locale: 'en-US', + ), + + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36', + deviceName: 'Chrome', + osVersion: 'Windows ', + screen: '1920x1080', + timezone: 'Europe/Berlin', + locale: 'de-DE', + ), + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + deviceName: 'Chrome', + osVersion: 'Windows', + screen: '2560x1440', + timezone: 'America/New_York', + locale: 'en-US', + ), + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.2420.97', + deviceName: 'Edge', + osVersion: 'Windows', + screen: '1536x864', + timezone: 'Europe/London', + locale: 'en-GB', + ), + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36', + deviceName: 'Chrome', + osVersion: 'Windows', + screen: '1920x1200', + timezone: 'Europe/Paris', + locale: 'fr-FR', + ), + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + deviceName: 'Chrome', + osVersion: 'Windows', + screen: '1366x768', + timezone: 'Europe/Madrid', + locale: 'es-ES', + ), + + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0', + deviceName: 'Firefox', + osVersion: 'Windows', + screen: '1920x1080', + timezone: 'Europe/Rome', + locale: 'it-IT', + ), + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0', + deviceName: 'Firefox', + osVersion: 'Windows', + screen: '1440x900', + timezone: 'Europe/Amsterdam', + locale: 'nl-NL', + ), + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', + deviceName: 'Firefox', + osVersion: 'Windows', + screen: '1600x900', + timezone: 'Europe/Warsaw', + locale: 'pl-PL', + ), + + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.2535.51', + deviceName: 'Edge', + osVersion: 'Windows', + screen: '1920x1080', + timezone: 'America/Chicago', + locale: 'en-US', + ), + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.2478.109', + deviceName: 'Edge', + osVersion: 'Windows', + screen: '1366x768', + timezone: 'America/Sao_Paulo', + locale: 'pt-BR', + ), + + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36', + deviceName: 'Chrome', + osVersion: 'macOS 14.5', + screen: '2560x1440', + timezone: 'America/Los_Angeles', + locale: 'en-US', + ), + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + deviceName: 'Chrome', + osVersion: 'macOS 13.6', + screen: '1440x900', + timezone: 'America/Toronto', + locale: 'en-CA', + ), + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_7_10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', + deviceName: 'Chrome', + osVersion: 'macOS 11.7', + screen: '1728x1117', + timezone: 'Australia/Sydney', + locale: 'en-AU', + ), + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36', + deviceName: 'Chrome', + osVersion: 'macOS 12.5', + screen: '2048x1152', + timezone: 'Europe/London', + locale: 'en-GB', + ), + + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0', + deviceName: 'Firefox', + osVersion: 'macOS 14.5', + screen: '1920x1080', + timezone: 'America/New_York', + locale: 'en-US', + ), + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:125.0) Gecko/20100101 Firefox/125.0', + deviceName: 'Firefox', + osVersion: 'macOS 13.0', + screen: '1680x1050', + timezone: 'Europe/Berlin', + locale: 'de-DE', + ), + + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15', + deviceName: 'Safari', + osVersion: 'macOS 14.5', + screen: '1440x900', + timezone: 'America/New_York', + locale: 'en-US', + ), + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Safari/605.1.15', + deviceName: 'Safari', + osVersion: 'macOS 13.6', + screen: '2560x1600', + timezone: 'Europe/Paris', + locale: 'fr-FR', + ), + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6.1 Safari/605.1.15', + deviceName: 'Safari', + osVersion: 'macOS 10.14', + screen: '1280x800', + timezone: 'Asia/Tokyo', + locale: 'ja-JP', + ), + + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36', + deviceName: 'Chrome', + osVersion: 'Linux', + screen: '1920x1080', + timezone: 'Europe/Moscow', + locale: 'ru-RU', + ), + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36', + deviceName: 'Chrome', + osVersion: 'Linux', + screen: '1366x768', + timezone: 'Asia/Kolkata', + locale: 'en-IN', + ), + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (X11; CrOS x86_64 14541.0.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + deviceName: 'Chrome', + osVersion: 'Chrome OS', + screen: '1920x1080', + timezone: 'America/Mexico_City', + locale: 'es-MX', + ), + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36', + deviceName: 'Chrome', + osVersion: 'Linux', + screen: '1600x900', + timezone: 'Asia/Shanghai', + locale: 'zh-CN', + ), + + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:126.0) Gecko/20100101 Firefox/126.0', + deviceName: 'Firefox', + osVersion: 'Linux', + screen: '1920x1080', + timezone: 'UTC', + locale: 'en-GB', + ), + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:125.0) Gecko/20100101 Firefox/125.0', + deviceName: 'Firefox', + osVersion: 'Linux', + screen: '2560x1440', + timezone: 'America/Denver', + locale: 'en-US', + ), + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0', + deviceName: 'Firefox', + osVersion: 'Linux', + screen: '1366x768', + timezone: 'Asia/Dubai', + locale: 'ar-AE', + ), + + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 OPR/110.0.0.0', + deviceName: 'Opera', + osVersion: 'Windows', + screen: '1920x1080', + timezone: 'Europe/Oslo', + locale: 'no-NO', + ), + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Vivaldi/6.5.3206.63', + deviceName: 'Vivaldi', + osVersion: 'macOS 14.0', + screen: '1440x900', + timezone: 'Europe/Stockholm', + locale: 'sv-SE', + ), + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (Windows NT 10.0; rv:102.0) Gecko/20100101 Firefox/102.0', + deviceName: 'Firefox', + osVersion: 'Windows', + screen: '1280x720', + timezone: 'Asia/Seoul', + locale: 'ko-KR', + ), + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36', + deviceName: 'Chrome', + osVersion: 'Linux', + screen: '1920x1080', + timezone: 'Europe/Helsinki', + locale: 'fi-FI', + ), + DevicePreset( + deviceType: 'WEB', + userAgent: + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.2 Safari/605.1.15', + deviceName: 'Safari', + osVersion: 'macOS 10.13', + screen: '1280x800', + timezone: 'America/Vancouver', + locale: 'en-CA', + ), +]; diff --git a/lib/core/config/spoof_data.dart b/lib/core/config/spoof_data.dart deleted file mode 100644 index f60c3ff..0000000 --- a/lib/core/config/spoof_data.dart +++ /dev/null @@ -1,31 +0,0 @@ -class SpoofData { - static const List deviceNames = [ - 'Samsung Galaxy S23', - 'Xiaomi 13 Pro', - 'Google Pixel 7', - 'OnePlus 11', - ]; - - static const List osVersions = ['12', '13', '14']; - - static const List resolutions = [ - '1080x2400', - '1440x3200', - '720x1600', - ]; - - static const List deviceIds = [ - 'a1b2c3d4e5f6', - 'f8e7d6c5b4a3', - '9876543210ab', - '1234567890cd', - ]; - - static const List architectures = ['arm64-v8a', 'armeabi-v7a']; - - static const String deviceType = 'android'; - static const String timezone = 'Europe/Moscow'; - static const String locale = 'ru_RU'; - static const String appVersion = '26.10.1'; - static const String buildNumber = '6728'; -} diff --git a/lib/core/storage/spoofing_service.dart b/lib/core/storage/spoofing_service.dart new file mode 100644 index 0000000..21a766d --- /dev/null +++ b/lib/core/storage/spoofing_service.dart @@ -0,0 +1,27 @@ +import 'package:shared_preferences/shared_preferences.dart'; + +class SpoofingService { + static const String hardcodedAppVersion = '26.8.1'; + static const int hardcodedBuildNumber = 6606; + + static Future?> getSpoofedSessionData() async { + final prefs = await SharedPreferences.getInstance(); + + final isEnabled = prefs.getBool('spoofing_enabled') ?? false; + if (!isEnabled) return null; + + return { + 'device_name': prefs.getString('spoof_devicename'), + 'os_version': prefs.getString('spoof_osversion'), + 'screen': prefs.getString('spoof_screen'), + 'timezone': prefs.getString('spoof_timezone'), + 'locale': prefs.getString('spoof_locale'), + 'device_id': prefs.getString('spoof_deviceid'), + 'device_type': prefs.getString('spoof_devicetype'), + 'app_version': prefs.getString('spoof_appversion') ?? hardcodedAppVersion, + 'arch': prefs.getString('spoof_arch') ?? 'arm64-v8a', + 'build_number': + prefs.getInt('spoof_buildnumber') ?? hardcodedBuildNumber, + }; + } +} diff --git a/lib/frontend/screens/auth/login_screen.dart b/lib/frontend/screens/auth/login_screen.dart index d5b7784..c64d751 100644 --- a/lib/frontend/screens/auth/login_screen.dart +++ b/lib/frontend/screens/auth/login_screen.dart @@ -12,7 +12,7 @@ import 'code_confirmation_screen.dart'; import 'select_country_screen.dart'; import 'proxy_settings_sheet.dart'; import 'server_settings_sheet.dart'; -import 'spoff_redacted_screen.dart'; +import '../profile/spoof_screen.dart'; import '../../widgets/custom_notification.dart'; import '../../../main.dart'; @@ -522,7 +522,7 @@ class _LoginScreenState extends State { Navigator.push( context, MaterialPageRoute( - builder: (context) => const SpoffRedactedScreen(), + builder: (context) => const SpoofScreen(), ), ); }, diff --git a/lib/frontend/screens/auth/spoff_redacted_screen.dart b/lib/frontend/screens/auth/spoff_redacted_screen.dart deleted file mode 100644 index a553a59..0000000 --- a/lib/frontend/screens/auth/spoff_redacted_screen.dart +++ /dev/null @@ -1,217 +0,0 @@ -import 'dart:math'; -import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:komet/core/config/spoof_data.dart'; -import 'package:komet/frontend/widgets/custom_notification.dart'; - -class SpoffRedactedScreen extends StatefulWidget { - const SpoffRedactedScreen({super.key}); - - @override - State createState() => _SpoffRedactedScreenState(); -} - -class _SpoffRedactedScreenState extends State { - final TextEditingController _deviceNameController = TextEditingController(); - final TextEditingController _osVersionController = TextEditingController(); - final TextEditingController _resolutionController = TextEditingController(); - final TextEditingController _deviceIdController = TextEditingController(); - final TextEditingController _architectureController = TextEditingController(); - - @override - void initState() { - super.initState(); - _loadSettings(); - } - - Future _loadSettings() async { - final prefs = await SharedPreferences.getInstance(); - - final random = Random(); - - setState(() { - _deviceNameController.text = - prefs.getString('spoof_device_name') ?? - SpoofData.deviceNames[random.nextInt(SpoofData.deviceNames.length)]; - _osVersionController.text = - prefs.getString('spoof_os_version') ?? - SpoofData.osVersions[random.nextInt(SpoofData.osVersions.length)]; - _resolutionController.text = - prefs.getString('spoof_resolution') ?? - SpoofData.resolutions[random.nextInt(SpoofData.resolutions.length)]; - _deviceIdController.text = - prefs.getString('spoof_device_id') ?? - SpoofData.deviceIds[random.nextInt(SpoofData.deviceIds.length)]; - _architectureController.text = - prefs.getString('spoof_architecture') ?? - SpoofData.architectures[random.nextInt( - SpoofData.architectures.length, - )]; - }); - } - - Future _saveSettings() async { - final prefs = await SharedPreferences.getInstance(); - await prefs.setString('spoof_device_name', _deviceNameController.text); - await prefs.setString('spoof_os_version', _osVersionController.text); - await prefs.setString('spoof_resolution', _resolutionController.text); - await prefs.setString('spoof_device_id', _deviceIdController.text); - await prefs.setString('spoof_architecture', _architectureController.text); - - if (mounted) { - showCustomNotification(context, 'Настройки сохранены'); - } - } - - @override - void dispose() { - _deviceNameController.dispose(); - _osVersionController.dispose(); - _resolutionController.dispose(); - _deviceIdController.dispose(); - _architectureController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final cs = Theme.of(context).colorScheme; - return Scaffold( - backgroundColor: cs.surface, - appBar: AppBar( - iconTheme: IconThemeData(color: cs.onSurface), - title: Text( - 'Подделка спуфа', - style: GoogleFonts.inter( - color: cs.onSurface, - fontWeight: FontWeight.w500, - ), - ), - backgroundColor: cs.surface, - actions: [ - IconButton( - icon: Icon(Icons.check, color: cs.primary), - onPressed: _saveSettings, - ), - ], - ), - body: SingleChildScrollView( - padding: const EdgeInsets.all(16.0), - child: Column( - children: [ - _buildTextField( - controller: TextEditingController(text: SpoofData.deviceType), - label: 'Тип устройства', - cs: cs, - readOnly: true, - ), - const SizedBox(height: 16), - _buildTextField( - controller: _deviceNameController, - label: 'Имя устройства', - cs: cs, - ), - const SizedBox(height: 16), - _buildTextField( - controller: _osVersionController, - label: 'Версия ОС', - cs: cs, - ), - const SizedBox(height: 16), - _buildTextField( - controller: _resolutionController, - label: 'Разрешение экрана', - cs: cs, - ), - const SizedBox(height: 16), - _buildTextField( - controller: TextEditingController(text: SpoofData.timezone), - label: 'Часовой пояс', - cs: cs, - readOnly: true, - ), - const SizedBox(height: 16), - _buildTextField( - controller: TextEditingController(text: SpoofData.locale), - label: 'Локаль', - cs: cs, - readOnly: true, - ), - const SizedBox(height: 16), - _buildTextField( - controller: _deviceIdController, - label: 'ID устройства', - cs: cs, - ), - const SizedBox(height: 16), - _buildTextField( - controller: TextEditingController(text: SpoofData.appVersion), - label: 'Версия приложения', - cs: cs, - readOnly: true, - ), - const SizedBox(height: 16), - _buildTextField( - controller: TextEditingController(text: SpoofData.buildNumber), - label: 'Build Number', - cs: cs, - readOnly: true, - ), - const SizedBox(height: 16), - _buildTextField( - controller: _architectureController, - label: 'Архитектура', - cs: cs, - ), - const SizedBox(height: 32), - ], - ), - ), - ); - } - - Widget _buildTextField({ - required TextEditingController controller, - required String label, - required ColorScheme cs, - bool readOnly = false, - }) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - label, - style: GoogleFonts.inter( - color: cs.onSurfaceVariant, - fontWeight: FontWeight.w500, - fontSize: 14, - ), - ), - const SizedBox(height: 8), - TextField( - controller: controller, - readOnly: readOnly, - style: GoogleFonts.inter( - color: readOnly ? cs.onSurfaceVariant : cs.onSurface, - fontSize: 15, - ), - decoration: InputDecoration( - filled: true, - fillColor: readOnly - ? cs.surfaceContainerHighest - : cs.surfaceContainerHigh, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide.none, - ), - contentPadding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 14, - ), - ), - ), - ], - ); - } -} diff --git a/lib/frontend/screens/profile/settings_tab.dart b/lib/frontend/screens/profile/settings_tab.dart index ddaba8e..2389838 100644 --- a/lib/frontend/screens/profile/settings_tab.dart +++ b/lib/frontend/screens/profile/settings_tab.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:package_info_plus/package_info_plus.dart'; import '../../../core/storage/app_database.dart'; +import '../../../l10n/app_localizations.dart'; import '../auth/proxy_settings_sheet.dart'; import 'debug_menu_screen.dart'; import 'devices_screen.dart'; @@ -143,7 +144,7 @@ class _SettingsTabState extends State { ), _SettingsItem( icon: Symbols.shield_lock, - label: 'Подделка данных', + label: AppLocalizations.of(context)!.profileMenuSpoof, onTap: () { Navigator.push( context, diff --git a/lib/frontend/screens/profile/spoof_screen.dart b/lib/frontend/screens/profile/spoof_screen.dart index accabc4..b5ca254 100644 --- a/lib/frontend/screens/profile/spoof_screen.dart +++ b/lib/frontend/screens/profile/spoof_screen.dart @@ -1,9 +1,18 @@ +import 'dart:async'; +import 'dart:io'; import 'dart:math'; + +import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/material.dart'; -import 'package:material_symbols_icons/symbols.dart'; +import 'package:flutter_timezone/flutter_timezone.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import '../../../core/config/spoof_data.dart'; -import '../../widgets/custom_notification.dart'; + +import '../../../core/config/device_presets.dart'; +import '../../../core/storage/spoofing_service.dart'; +import '../../../l10n/app_localizations.dart'; +import '../../../main.dart'; + +enum SpoofingMethod { partial, full } class SpoofScreen extends StatefulWidget { const SpoofScreen({super.key}); @@ -13,158 +22,400 @@ class SpoofScreen extends StatefulWidget { } class _SpoofScreenState extends State { - final _deviceTypeController = TextEditingController(); + static const String _hardcodedVersion = SpoofingService.hardcodedAppVersion; + static const int _hardcodedBuildNumber = + SpoofingService.hardcodedBuildNumber; + + final _random = Random(); final _deviceNameController = TextEditingController(); final _osVersionController = TextEditingController(); final _screenController = TextEditingController(); final _timezoneController = TextEditingController(); final _localeController = TextEditingController(); - final _deviceLocaleController = TextEditingController(); final _deviceIdController = TextEditingController(); final _appVersionController = TextEditingController(); final _buildNumberController = TextEditingController(); - final _architectureController = TextEditingController(); - final _pushDeviceTypeController = TextEditingController(); - final _mtInstanceIdController = TextEditingController(); - final _clientSessionIdController = TextEditingController(); + String _selectedDeviceType = 'ANDROID'; + String _selectedArch = 'arm64-v8a'; + SpoofingMethod _selectedMethod = SpoofingMethod.partial; bool _isLoading = true; @override void initState() { super.initState(); - _loadSettings(); + _loadInitialData(); } - Future _loadSettings() async { - final prefs = await SharedPreferences.getInstance(); - final random = Random(); + String _generateDeviceId() { + final bytes = List.generate(8, (_) => _random.nextInt(256)); + return bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join(); + } + Future _loadInitialData() async { + setState(() => _isLoading = true); + final prefs = await SharedPreferences.getInstance(); + final isSpoofingEnabled = prefs.getBool('spoofing_enabled') ?? false; + + if (isSpoofingEnabled) { + _deviceNameController.text = prefs.getString('spoof_devicename') ?? ''; + _osVersionController.text = prefs.getString('spoof_osversion') ?? ''; + _screenController.text = prefs.getString('spoof_screen') ?? ''; + _timezoneController.text = prefs.getString('spoof_timezone') ?? ''; + _localeController.text = prefs.getString('spoof_locale') ?? ''; + _deviceIdController.text = prefs.getString('spoof_deviceid') ?? ''; + _appVersionController.text = + prefs.getString('spoof_appversion') ?? _hardcodedVersion; + _selectedArch = prefs.getString('spoof_arch') ?? 'arm64-v8a'; + _buildNumberController.text = + prefs.getInt('spoof_buildnumber')?.toString() ?? + '$_hardcodedBuildNumber'; + + String savedType = prefs.getString('spoof_devicetype') ?? 'ANDROID'; + if (savedType == 'WEB') savedType = 'ANDROID'; + _selectedDeviceType = savedType; + if (mounted) setState(() => _isLoading = false); + } else { + await _loadDeviceData(); + } + } + + Future _loadDeviceData() async { + setState(() => _isLoading = true); + + final deviceInfo = DeviceInfoPlugin(); + final pixelRatio = View.of(context).devicePixelRatio; + final size = View.of(context).physicalSize; + + _appVersionController.text = _hardcodedVersion; + _localeController.text = Platform.localeName.split('_').first; + + final dpi = (160 * pixelRatio).round(); + String densityBucket; + if (dpi >= 560) { + densityBucket = 'xxxhdpi'; + } else if (dpi >= 380) { + densityBucket = 'xxhdpi'; + } else if (dpi >= 280) { + densityBucket = 'xhdpi'; + } else if (dpi >= 200) { + densityBucket = 'hdpi'; + } else if (dpi >= 140) { + densityBucket = 'mdpi'; + } else { + densityBucket = 'ldpi'; + } + _screenController.text = + '$densityBucket ${dpi}dpi ${size.width.round()}x${size.height.round()}'; + + final prefs = await SharedPreferences.getInstance(); + var realDeviceId = prefs.getString('real_device_id'); + if (realDeviceId == null || realDeviceId.isEmpty) { + realDeviceId = _generateDeviceId(); + await prefs.setString('real_device_id', realDeviceId); + } + _deviceIdController.text = realDeviceId; + + try { + final timezoneInfo = await FlutterTimezone.getLocalTimezone(); + _timezoneController.text = timezoneInfo.identifier; + } catch (_) { + _timezoneController.text = 'Europe/Moscow'; + } + + if (Platform.isAndroid) { + final androidInfo = await deviceInfo.androidInfo; + _deviceNameController.text = + '${androidInfo.manufacturer} ${androidInfo.model}'; + _osVersionController.text = 'Android ${androidInfo.version.release}'; + _selectedDeviceType = 'ANDROID'; + _selectedArch = androidInfo.supportedAbis.isNotEmpty + ? androidInfo.supportedAbis.first + : 'arm64-v8a'; + _buildNumberController.text = '$_hardcodedBuildNumber'; + } else if (Platform.isIOS) { + final iosInfo = await deviceInfo.iosInfo; + _deviceNameController.text = iosInfo.name; + _osVersionController.text = + '${iosInfo.systemName} ${iosInfo.systemVersion}'; + _selectedDeviceType = 'IOS'; + _selectedArch = 'arm64'; + _buildNumberController.text = '$_hardcodedBuildNumber'; + } else { + await _applyGeneratedData(); + } + + if (mounted) setState(() => _isLoading = false); + } + + Future _applyGeneratedData() async { + final filteredPresets = devicePresets + .where( + (p) => p.deviceType != 'WEB' && p.deviceType == _selectedDeviceType, + ) + .toList(); + + if (filteredPresets.isEmpty) return; + + final preset = filteredPresets[_random.nextInt(filteredPresets.length)]; + await _applyPreset(preset); + } + + Future _applyPreset(DevicePreset preset) async { setState(() { - _deviceTypeController.text = prefs.getString('spoof_device_type') ?? SpoofData.deviceType; - _deviceNameController.text = prefs.getString('spoof_device_name') ?? - SpoofData.deviceNames[random.nextInt(SpoofData.deviceNames.length)]; - _osVersionController.text = prefs.getString('spoof_os_version') ?? - 'Android ${SpoofData.osVersions[random.nextInt(SpoofData.osVersions.length)]}'; - _screenController.text = prefs.getString('spoof_screen') ?? - SpoofData.resolutions[random.nextInt(SpoofData.resolutions.length)]; - _timezoneController.text = prefs.getString('spoof_timezone') ?? SpoofData.timezone; - _localeController.text = prefs.getString('spoof_locale') ?? SpoofData.locale; - _deviceLocaleController.text = prefs.getString('spoof_device_locale') ?? 'ru'; - _deviceIdController.text = prefs.getString('spoof_device_id') ?? - SpoofData.deviceIds[random.nextInt(SpoofData.deviceIds.length)]; - _appVersionController.text = prefs.getString('spoof_app_version') ?? SpoofData.appVersion; - _buildNumberController.text = prefs.getString('spoof_build_number') ?? SpoofData.buildNumber; - _architectureController.text = prefs.getString('spoof_architecture') ?? - SpoofData.architectures[random.nextInt(SpoofData.architectures.length)]; - _pushDeviceTypeController.text = prefs.getString('spoof_push_device_type') ?? 'GCM'; - _mtInstanceIdController.text = prefs.getString('spoof_mt_instanceid') ?? - '550e8400-e29b-41d4-a716-446655440000'; - _clientSessionIdController.text = prefs.getString('spoof_client_session_id') ?? '42'; - _isLoading = false; + _deviceNameController.text = preset.deviceName; + _osVersionController.text = preset.osVersion; + _screenController.text = preset.screen; + _appVersionController.text = _hardcodedVersion; + _deviceIdController.text = _generateDeviceId(); + + _selectedDeviceType = preset.deviceType; + + if (preset.deviceType == 'ANDROID') { + _selectedArch = 'arm64-v8a'; + } else if (preset.deviceType == 'IOS') { + _selectedArch = 'arm64'; + } else { + _selectedArch = 'x86_64'; + } + _buildNumberController.text = '$_hardcodedBuildNumber'; + + if (_selectedMethod == SpoofingMethod.full) { + _timezoneController.text = preset.timezone; + _localeController.text = preset.locale; + } }); + + if (_selectedMethod == SpoofingMethod.partial) { + String timezone; + try { + final timezoneInfo = await FlutterTimezone.getLocalTimezone(); + timezone = timezoneInfo.identifier; + } catch (_) { + timezone = 'Europe/Moscow'; + } + final locale = Platform.localeName.split('_').first; + + if (mounted) { + setState(() { + _timezoneController.text = timezone; + _localeController.text = locale; + }); + } + } } - Future _saveSettings() async { + Future _saveSpoofingSettings() async { + if (!mounted) return; + final prefs = await SharedPreferences.getInstance(); - await prefs.setString('spoof_device_type', _deviceTypeController.text); - await prefs.setString('spoof_device_name', _deviceNameController.text); - await prefs.setString('spoof_os_version', _osVersionController.text); + + final oldValues = { + 'device_name': prefs.getString('spoof_devicename') ?? '', + 'os_version': prefs.getString('spoof_osversion') ?? '', + 'screen': prefs.getString('spoof_screen') ?? '', + 'timezone': prefs.getString('spoof_timezone') ?? '', + 'locale': prefs.getString('spoof_locale') ?? '', + 'device_id': prefs.getString('spoof_deviceid') ?? '', + 'device_type': prefs.getString('spoof_devicetype') ?? 'ANDROID', + 'arch': prefs.getString('spoof_arch') ?? '', + 'build_number': prefs.getInt('spoof_buildnumber')?.toString() ?? '', + }; + + final newValues = { + 'device_name': _deviceNameController.text, + 'os_version': _osVersionController.text, + 'screen': _screenController.text, + 'timezone': _timezoneController.text, + 'locale': _localeController.text, + 'device_id': _deviceIdController.text, + 'device_type': _selectedDeviceType, + 'arch': _selectedArch, + 'build_number': _buildNumberController.text, + }; + + final oldAppVersion = + prefs.getString('spoof_appversion') ?? _hardcodedVersion; + final newAppVersion = _appVersionController.text; + + bool otherDataChanged = false; + for (final key in oldValues.keys) { + if (oldValues[key] != newValues[key]) { + otherDataChanged = true; + break; + } + } + + final appVersionChanged = oldAppVersion != newAppVersion; + final isChangingAwayFromHardcoded = newAppVersion != _hardcodedVersion; + + if (appVersionChanged && isChangingAwayFromHardcoded) { + if (!mounted) return; + final l10n = AppLocalizations.of(context)!; + final confirmed = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(l10n.spoofDialogUnsureTitle), + content: Text(l10n.spoofDialogUnsureContent), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: Text(l10n.spoofDialogCancel), + ), + FilledButton( + onPressed: () => Navigator.of(context).pop(true), + child: Text(l10n.spoofDialogYes), + ), + ], + ), + ); + if (confirmed != true) return; + } + + if (appVersionChanged && !otherDataChanged) { + await _saveAllData(prefs); + if (mounted) Navigator.of(context).pop(); + return; + } + + if (!mounted) return; + final l10n = AppLocalizations.of(context)!; + final confirmed = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(l10n.spoofDialogApplyTitle), + content: Text(l10n.spoofDialogApplyContent), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: Text(l10n.spoofDialogApplyDeny), + ), + FilledButton( + onPressed: () => Navigator.of(context).pop(true), + child: Text(l10n.spoofDialogApplyConfirm), + ), + ], + ), + ); + + if (confirmed != true || !mounted) return; + + await _saveAllData(prefs); + + try { + await api.disconnect(); + await api.connect(); + if (mounted) Navigator.of(context).pop(); + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + AppLocalizations.of(context)!.spoofErrorApplyFailed(e.toString()), + ), + backgroundColor: Theme.of(context).colorScheme.error, + ), + ); + } + } + } + + Future _saveAllData(SharedPreferences prefs) async { + await prefs.setBool('spoofing_enabled', true); + await prefs.setString('spoof_devicename', _deviceNameController.text); + await prefs.setString('spoof_osversion', _osVersionController.text); await prefs.setString('spoof_screen', _screenController.text); await prefs.setString('spoof_timezone', _timezoneController.text); await prefs.setString('spoof_locale', _localeController.text); - await prefs.setString('spoof_device_locale', _deviceLocaleController.text); - await prefs.setString('spoof_device_id', _deviceIdController.text); - await prefs.setString('spoof_app_version', _appVersionController.text); - await prefs.setString('spoof_build_number', _buildNumberController.text); - await prefs.setString('spoof_architecture', _architectureController.text); - await prefs.setString('spoof_push_device_type', _pushDeviceTypeController.text); - await prefs.setString('spoof_mt_instanceid', _mtInstanceIdController.text); - await prefs.setString('spoof_client_session_id', _clientSessionIdController.text); - - if (mounted) { - showCustomNotification(context, 'Настройки сохранены'); - } + await prefs.setString('spoof_deviceid', _deviceIdController.text); + await prefs.setString('spoof_devicetype', _selectedDeviceType); + await prefs.setString('spoof_appversion', _appVersionController.text); + await prefs.setString('spoof_arch', _selectedArch); + await prefs.setInt( + 'spoof_buildnumber', + int.tryParse(_buildNumberController.text) ?? _hardcodedBuildNumber, + ); } - Future _randomizeAll() async { - final random = Random(); + void _generateNewDeviceId() { setState(() { - _deviceNameController.text = SpoofData.deviceNames[random.nextInt(SpoofData.deviceNames.length)]; - _osVersionController.text = 'Android ${SpoofData.osVersions[random.nextInt(SpoofData.osVersions.length)]}'; - _screenController.text = SpoofData.resolutions[random.nextInt(SpoofData.resolutions.length)]; - _deviceIdController.text = SpoofData.deviceIds[random.nextInt(SpoofData.deviceIds.length)]; - _architectureController.text = SpoofData.architectures[random.nextInt(SpoofData.architectures.length)]; + _deviceIdController.text = _generateDeviceId(); }); - await _saveSettings(); - if (mounted) { - showCustomNotification(context, 'Данные рандомизированы'); - } } @override void dispose() { - _deviceTypeController.dispose(); _deviceNameController.dispose(); _osVersionController.dispose(); _screenController.dispose(); _timezoneController.dispose(); _localeController.dispose(); - _deviceLocaleController.dispose(); _deviceIdController.dispose(); _appVersionController.dispose(); _buildNumberController.dispose(); - _architectureController.dispose(); - _pushDeviceTypeController.dispose(); - _mtInstanceIdController.dispose(); - _clientSessionIdController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { - final cs = Theme.of(context).colorScheme; - - if (_isLoading) { - return Scaffold( - backgroundColor: cs.surface, - body: const Center(child: CircularProgressIndicator()), - ); - } - + final l10n = AppLocalizations.of(context)!; return Scaffold( - backgroundColor: cs.surface, - body: SafeArea( - bottom: false, - child: CustomScrollView( - physics: const BouncingScrollPhysics(), - slivers: [ - SliverToBoxAdapter(child: _buildAppBar(context, cs)), - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.fromLTRB(16, 8, 16, 0), - child: _buildSection(cs, [ - _buildField(cs, 'Тип устройства', _deviceTypeController), - _buildField(cs, 'Имя устройства', _deviceNameController), - _buildField(cs, 'Версия ОС', _osVersionController), - _buildField(cs, 'Разрешение экрана', _screenController), - _buildField(cs, 'Архитектура', _architectureController), - _buildField(cs, 'ID устройства', _deviceIdController), - _buildField(cs, 'Часовой пояс', _timezoneController), - _buildField(cs, 'Локаль', _localeController), - _buildField(cs, 'Локаль устройства', _deviceLocaleController), - _buildField(cs, 'Версия приложения', _appVersionController), - _buildField(cs, 'Build Number', _buildNumberController), - _buildField(cs, 'Push Device Type', _pushDeviceTypeController), - _buildField(cs, 'MT Instance ID', _mtInstanceIdController), - _buildField(cs, 'Client Session ID', _clientSessionIdController, isLast: true), - ]), + appBar: AppBar( + title: Text(l10n.spoofScreenTitle), + centerTitle: true, + ), + body: _isLoading + ? const Center(child: CircularProgressIndicator()) + : SingleChildScrollView( + padding: const EdgeInsets.fromLTRB(16, 8, 16, 120), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _buildInfoCard(), + const SizedBox(height: 16), + _buildSpoofingMethodCard(), + const SizedBox(height: 16), + _buildDeviceTypeCard(), + const SizedBox(height: 24), + _buildMainDataCard(), + const SizedBox(height: 16), + _buildRegionalDataCard(), + const SizedBox(height: 16), + _buildIdentifiersCard(), + ], ), ), - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.fromLTRB(16, 16, 16, 120), - child: _buildActionButtons(cs), + floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, + floatingActionButton: _buildFloatingActionButtons(), + ); + } + + Widget _buildInfoCard() { + final l10n = AppLocalizations.of(context)!; + return Card( + color: Theme.of( + context, + ).colorScheme.secondaryContainer.withValues(alpha: 0.5), + elevation: 0, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.touch_app, + size: 18, + color: Theme.of(context).colorScheme.onSecondaryContainer, + ), + const SizedBox(width: 8), + Flexible( + child: Text( + l10n.spoofInfoHint, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.onSecondaryContainer, + ), ), ), ], @@ -173,161 +424,388 @@ class _SpoofScreenState extends State { ); } - Widget _buildAppBar(BuildContext context, ColorScheme cs) { + Widget _buildSpoofingMethodCard() { + final theme = Theme.of(context); + final l10n = AppLocalizations.of(context)!; + Widget descriptionWidget; + + if (_selectedMethod == SpoofingMethod.partial) { + descriptionWidget = _buildDescriptionTile( + icon: Icons.check_circle_outline, + color: Colors.green.shade700, + text: l10n.spoofMethodPartialDescription, + ); + } else { + descriptionWidget = _buildDescriptionTile( + icon: Icons.warning_amber_rounded, + color: theme.colorScheme.error, + text: l10n.spoofMethodFullDescription, + ); + } + + return Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + children: [ + Text(l10n.spoofMethodTitle, style: theme.textTheme.titleMedium), + const SizedBox(height: 12), + SegmentedButton( + style: SegmentedButton.styleFrom(shape: const StadiumBorder()), + segments: [ + ButtonSegment( + value: SpoofingMethod.partial, + label: Text(l10n.spoofMethodPartial), + icon: const Icon(Icons.security_outlined), + ), + ButtonSegment( + value: SpoofingMethod.full, + label: Text(l10n.spoofMethodFull), + icon: const Icon(Icons.public_outlined), + ), + ], + selected: {_selectedMethod}, + onSelectionChanged: (s) => + setState(() => _selectedMethod = s.first), + ), + const SizedBox(height: 12), + descriptionWidget, + ], + ), + ), + ); + } + + Widget _buildDeviceTypeCard() { + final theme = Theme.of(context); + final l10n = AppLocalizations.of(context)!; + return Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(l10n.spoofDeviceTypeTitle, style: theme.textTheme.titleMedium), + const SizedBox(height: 12), + _buildDescriptionTile( + icon: Icons.info_outline, + color: theme.colorScheme.primary, + text: l10n.spoofDeviceTypeDescription, + ), + const SizedBox(height: 12), + _buildChipSelector( + options: const [ + _ChipOption('ANDROID', 'ANDROID', Icons.android_outlined), + _ChipOption('IOS', 'iOS', Icons.phone_iphone_outlined), + _ChipOption( + 'DESKTOP', + 'Desktop', + Icons.desktop_windows_outlined, + ), + ], + selected: _selectedDeviceType, + onSelected: (value) { + setState(() { + _selectedDeviceType = value; + if (value == 'ANDROID') { + _selectedArch = 'arm64-v8a'; + } else if (value == 'IOS') { + _selectedArch = 'arm64'; + } else { + _selectedArch = 'x86_64'; + } + }); + }, + ), + ], + ), + ), + ); + } + + Widget _buildDescriptionTile({ + required IconData icon, + required Color color, + required String text, + }) { + return ListTile( + leading: Icon(icon, color: color), + contentPadding: EdgeInsets.zero, + title: Text( + text, + style: TextStyle( + fontSize: 13, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ); + } + + Widget _buildSectionHeader(BuildContext context, String title) { return Padding( - padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4), + padding: const EdgeInsets.only(bottom: 16.0, top: 8.0), + child: Text( + title, + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.w600, + ), + ), + ); + } + + Widget _buildMainDataCard() { + final l10n = AppLocalizations.of(context)!; + return Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildSectionHeader(context, l10n.spoofMainSectionTitle), + TextField( + controller: _deviceNameController, + decoration: _inputDecoration( + l10n.spoofFieldDeviceName, + Icons.smartphone_outlined, + ), + ), + const SizedBox(height: 16), + TextField( + controller: _osVersionController, + decoration: _inputDecoration( + l10n.spoofFieldOsVersion, + Icons.layers_outlined, + ), + ), + ], + ), + ), + ); + } + + Widget _buildRegionalDataCard() { + final l10n = AppLocalizations.of(context)!; + return Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildSectionHeader(context, l10n.spoofRegionalSectionTitle), + TextField( + controller: _screenController, + decoration: _inputDecoration( + l10n.spoofFieldScreen, + Icons.fullscreen_outlined, + ), + ), + const SizedBox(height: 16), + TextField( + controller: _timezoneController, + enabled: _selectedMethod == SpoofingMethod.full, + decoration: _inputDecoration( + l10n.spoofFieldTimezone, + Icons.public_outlined, + ), + ), + const SizedBox(height: 16), + TextField( + controller: _localeController, + enabled: _selectedMethod == SpoofingMethod.full, + decoration: _inputDecoration( + l10n.spoofFieldLocale, + Icons.language_outlined, + ), + ), + ], + ), + ), + ); + } + + Widget _buildIdentifiersCard() { + final l10n = AppLocalizations.of(context)!; + return Card( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildSectionHeader(context, l10n.spoofIdentifiersSectionTitle), + _buildDescriptionTile( + icon: Icons.info_outline, + color: Theme.of(context).colorScheme.tertiary, + text: l10n.spoofIdentifiersDescription, + ), + const SizedBox(height: 12), + TextField( + controller: _deviceIdController, + decoration: + _inputDecoration(l10n.spoofFieldDeviceId, Icons.tag_outlined) + .copyWith( + suffixIcon: IconButton( + icon: const Icon(Icons.autorenew_outlined), + tooltip: l10n.spoofRegenerateIdTooltip, + onPressed: _generateNewDeviceId, + ), + ), + ), + const SizedBox(height: 16), + TextField( + controller: _appVersionController, + decoration: _inputDecoration( + l10n.spoofFieldAppVersion, + Icons.info_outline_rounded, + ), + ), + const SizedBox(height: 16), + TextField( + controller: _buildNumberController, + keyboardType: TextInputType.number, + decoration: _inputDecoration( + l10n.spoofFieldBuildNumber, + Icons.numbers_outlined, + ), + ), + const SizedBox(height: 16), + Padding( + padding: const EdgeInsets.only(left: 4, bottom: 8), + child: Text( + l10n.spoofFieldArchitecture, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ), + _buildChipSelector( + options: const [ + _ChipOption('arm64-v8a', 'arm64-v8a', Icons.memory_outlined), + _ChipOption( + 'armeabi-v7a', + 'armeabi-v7a', + Icons.memory_outlined, + ), + _ChipOption('x86', 'x86', Icons.memory_outlined), + _ChipOption('x86_64', 'x86_64', Icons.memory_outlined), + _ChipOption('arm64', 'arm64', Icons.memory_outlined), + ], + selected: _selectedArch, + onSelected: (value) => + setState(() => _selectedArch = value), + ), + ], + ), + ), + ); + } + + InputDecoration _inputDecoration(String label, IconData icon) { + return InputDecoration( + labelText: label, + prefixIcon: Icon(icon), + border: OutlineInputBorder(borderRadius: BorderRadius.circular(16)), + filled: true, + fillColor: Theme.of(context).colorScheme.surfaceContainerHighest, + ); + } + + Widget _buildChipSelector({ + required List<_ChipOption> options, + required T selected, + required ValueChanged onSelected, + }) { + final cs = Theme.of(context).colorScheme; + return Wrap( + spacing: 8, + runSpacing: 8, + children: options.map((opt) { + final isSelected = opt.value == selected; + return ChoiceChip( + label: Text(opt.label), + avatar: isSelected + ? Icon(Icons.check, size: 18, color: cs.onSecondaryContainer) + : (opt.icon != null + ? Icon(opt.icon, size: 18, color: cs.onSurfaceVariant) + : null), + selected: isSelected, + showCheckmark: false, + onSelected: (_) => onSelected(opt.value), + labelStyle: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w500, + color: isSelected ? cs.onSecondaryContainer : cs.onSurface, + ), + backgroundColor: cs.surfaceContainerHighest, + selectedColor: cs.secondaryContainer, + side: BorderSide( + color: isSelected ? Colors.transparent : cs.outlineVariant, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + ); + }).toList(), + ); + } + + Widget _buildFloatingActionButtons() { + final l10n = AppLocalizations.of(context)!; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Row( children: [ - IconButton( - icon: Icon(Symbols.arrow_back, color: cs.onSurface, size: 24, weight: 400), - onPressed: () => Navigator.pop(context), + Expanded( + flex: 1, + child: FilledButton.tonal( + onPressed: _applyGeneratedData, + onLongPress: _loadDeviceData, + style: FilledButton.styleFrom( + padding: const EdgeInsets.symmetric( + vertical: 16, + horizontal: 16, + ), + shape: const StadiumBorder(), + ), + child: Text(l10n.spoofButtonGenerate), + ), ), - const SizedBox(width: 4), - Text( - 'Подделка данных', - style: TextStyle( - color: cs.onSurface, - fontSize: 20, - fontWeight: FontWeight.w700, - fontFamily: 'Outfit', + const SizedBox(width: 12), + Expanded( + flex: 1, + child: FilledButton( + onPressed: _saveSpoofingSettings, + style: FilledButton.styleFrom( + padding: const EdgeInsets.symmetric( + vertical: 16, + horizontal: 16, + ), + shape: const StadiumBorder(), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.save_alt_outlined), + const SizedBox(width: 8), + Text(l10n.spoofButtonApply), + ], + ), ), ), ], ), ); } +} +class _ChipOption { + final T value; + final String label; + final IconData? icon; - - Widget _buildSection(ColorScheme cs, List children) { - return Container( - decoration: BoxDecoration( - color: cs.surfaceContainerHigh, - borderRadius: BorderRadius.circular(20), - ), - child: Column(children: children), - ); - } - - Widget _buildField( - ColorScheme cs, - String label, - TextEditingController controller, { - bool isLast = false, - }) { - return Column( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), - child: Row( - children: [ - SizedBox( - width: 140, - child: Text( - label, - style: TextStyle( - color: cs.onSurfaceVariant, - fontSize: 14, - fontWeight: FontWeight.w500, - ), - ), - ), - const SizedBox(width: 12), - Expanded( - child: TextField( - controller: controller, - style: TextStyle( - color: cs.onSurface, - fontSize: 14, - fontWeight: FontWeight.w400, - ), - decoration: InputDecoration( - filled: true, - fillColor: cs.surfaceContainerHighest, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - borderSide: BorderSide.none, - ), - contentPadding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 10, - ), - isDense: true, - ), - ), - ), - ], - ), - ), - if (!isLast) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Divider( - height: 1, - thickness: 1, - color: cs.outlineVariant.withValues(alpha: 0.35), - ), - ), - ], - ); - } - - Widget _buildActionButtons(ColorScheme cs) { - return Row( - children: [ - Expanded( - child: SizedBox( - height: 48, - child: OutlinedButton( - onPressed: _randomizeAll, - style: OutlinedButton.styleFrom( - foregroundColor: cs.onSurface, - side: BorderSide(color: cs.outline, width: 1), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(14), - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Symbols.shuffle, size: 18, weight: 400), - const SizedBox(width: 6), - const Text( - 'Рандомизировать', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - ), - ), - ], - ), - ), - ), - ), - const SizedBox(width: 12), - Expanded( - child: SizedBox( - height: 48, - child: FilledButton( - onPressed: _saveSettings, - style: FilledButton.styleFrom( - backgroundColor: cs.primary, - foregroundColor: cs.onPrimary, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(14), - ), - ), - child: const Text( - 'Сохранить', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - ), - ), - ), - ), - ), - ], - ); - } + const _ChipOption(this.value, this.label, [this.icon]); } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index ae9b610..c047aaa 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -12,7 +12,7 @@ "loginEdit": "Change", "loginDone": "Done", "loginReadTermsNotification": "Please read the terms of use first", - "loginSpoofRedacted": "Spoof redaction", + "loginSpoofRedacted": "Spoofing", "loginProxy": "Proxy", "loginChangeServer": "Change server", "serverSettingsTitle": "Server", @@ -54,5 +54,49 @@ "proxyApply": "Apply and reconnect", "proxyDisable": "Disable proxy", "proxySettingsSaved": "Proxy settings applied", - "proxyInvalidHostOrPort": "Enter a valid proxy host and port (1–65535)" + "proxyInvalidHostOrPort": "Enter a valid proxy host and port (1–65535)", + + "spoofScreenTitle": "Session spoofing", + "spoofInfoHint": "Tap \"Generate\":\n• Short tap: random preset.\n• Long press: real device data.", + "spoofMethodTitle": "Spoofing method", + "spoofMethodPartial": "Partial", + "spoofMethodFull": "Full", + "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!", + "spoofDeviceTypeTitle": "Device type", + "spoofDeviceTypeDescription": "Choose a device type for preset generation. Tapping \"Generate\" will only use presets of the selected type.", + "spoofDeviceTypeLabel": "Device type", + "spoofMainSectionTitle": "Main data", + "spoofFieldDeviceName": "Device name", + "spoofFieldOsVersion": "OS version", + "spoofRegionalSectionTitle": "Regional data", + "spoofFieldScreen": "Screen resolution", + "spoofFieldTimezone": "Timezone", + "spoofFieldLocale": "Locale", + "spoofIdentifiersSectionTitle": "Identifiers", + "spoofIdentifiersDescription": "mt_instanceid and clientSessionId are generated automatically on every app launch. Only the Device ID can be changed.", + "spoofFieldDeviceId": "Device ID", + "spoofRegenerateIdTooltip": "Generate a new ID", + "spoofFieldAppVersion": "App version", + "spoofFieldBuildNumber": "Build number", + "spoofFieldArchitecture": "Architecture", + "spoofButtonGenerate": "Generate", + "spoofButtonApply": "Apply", + "spoofDialogUnsureTitle": "Are you sure?", + "spoofDialogUnsureContent": "The app may become unstable due to API incompatibility", + "spoofDialogCancel": "Cancel", + "spoofDialogYes": "Yes", + "spoofDialogApplyTitle": "Apply settings?", + "spoofDialogApplyContent": "Need to reconnect the app, ok?", + "spoofDialogApplyDeny": "No", + "spoofDialogApplyConfirm": "Ok!", + "spoofErrorApplyFailed": "Failed to apply settings: {error}", + "@spoofErrorApplyFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "profileMenuSpoof": "Spoofing" } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 6741f70..15fbdb0 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -62,7 +62,8 @@ import 'app_localizations_ru.dart'; /// be consistent with the languages listed in the AppLocalizations.supportedLocales /// property. abstract class AppLocalizations { - AppLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + AppLocalizations(String locale) + : localeName = intl.Intl.canonicalizedLocale(locale.toString()); final String localeName; @@ -70,7 +71,8 @@ abstract class AppLocalizations { return Localizations.of(context, AppLocalizations); } - static const LocalizationsDelegate delegate = _AppLocalizationsDelegate(); + static const LocalizationsDelegate delegate = + _AppLocalizationsDelegate(); /// A list of this localizations delegate along with the default localizations /// delegates. @@ -82,17 +84,18 @@ abstract class AppLocalizations { /// Additional delegates can be added by appending to this list in /// MaterialApp. This list does not have to be used at all if a custom list /// of delegates is preferred or required. - static const List> localizationsDelegates = >[ - delegate, - GlobalMaterialLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - ]; + static const List> localizationsDelegates = + >[ + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; /// A list of this localizations delegate's supported locales. static const List supportedLocales = [ Locale('en'), - Locale('ru') + Locale('ru'), ]; /// No description provided for @loginTitle. @@ -170,7 +173,7 @@ abstract class AppLocalizations { /// No description provided for @loginSpoofRedacted. /// /// In en, this message translates to: - /// **'Spoof redaction'** + /// **'Spoofing'** String get loginSpoofRedacted; /// No description provided for @loginProxy. @@ -376,9 +379,226 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Enter a valid proxy host and port (1–65535)'** String get proxyInvalidHostOrPort; + + /// No description provided for @spoofScreenTitle. + /// + /// In en, this message translates to: + /// **'Session spoofing'** + String get spoofScreenTitle; + + /// No description provided for @spoofInfoHint. + /// + /// In en, this message translates to: + /// **'Tap \"Generate\":\n• Short tap: random preset.\n• Long press: real device data.'** + String get spoofInfoHint; + + /// No description provided for @spoofMethodTitle. + /// + /// In en, this message translates to: + /// **'Spoofing method'** + String get spoofMethodTitle; + + /// No description provided for @spoofMethodPartial. + /// + /// In en, this message translates to: + /// **'Partial'** + String get spoofMethodPartial; + + /// No description provided for @spoofMethodFull. + /// + /// In en, this message translates to: + /// **'Full'** + String get spoofMethodFull; + + /// No description provided for @spoofMethodPartialDescription. + /// + /// In en, this message translates to: + /// **'Recommended method. Random data is used, but your real timezone and locale are kept for plausibility.'** + String get spoofMethodPartialDescription; + + /// No description provided for @spoofMethodFullDescription. + /// + /// In en, this message translates to: + /// **'All data including timezone and locale is generated randomly. Use this method at your own risk!'** + String get spoofMethodFullDescription; + + /// No description provided for @spoofDeviceTypeTitle. + /// + /// In en, this message translates to: + /// **'Device type'** + String get spoofDeviceTypeTitle; + + /// No description provided for @spoofDeviceTypeDescription. + /// + /// In en, this message translates to: + /// **'Choose a device type for preset generation. Tapping \"Generate\" will only use presets of the selected type.'** + String get spoofDeviceTypeDescription; + + /// No description provided for @spoofDeviceTypeLabel. + /// + /// In en, this message translates to: + /// **'Device type'** + String get spoofDeviceTypeLabel; + + /// No description provided for @spoofMainSectionTitle. + /// + /// In en, this message translates to: + /// **'Main data'** + String get spoofMainSectionTitle; + + /// No description provided for @spoofFieldDeviceName. + /// + /// In en, this message translates to: + /// **'Device name'** + String get spoofFieldDeviceName; + + /// No description provided for @spoofFieldOsVersion. + /// + /// In en, this message translates to: + /// **'OS version'** + String get spoofFieldOsVersion; + + /// No description provided for @spoofRegionalSectionTitle. + /// + /// In en, this message translates to: + /// **'Regional data'** + String get spoofRegionalSectionTitle; + + /// No description provided for @spoofFieldScreen. + /// + /// In en, this message translates to: + /// **'Screen resolution'** + String get spoofFieldScreen; + + /// No description provided for @spoofFieldTimezone. + /// + /// In en, this message translates to: + /// **'Timezone'** + String get spoofFieldTimezone; + + /// No description provided for @spoofFieldLocale. + /// + /// In en, this message translates to: + /// **'Locale'** + String get spoofFieldLocale; + + /// No description provided for @spoofIdentifiersSectionTitle. + /// + /// In en, this message translates to: + /// **'Identifiers'** + String get spoofIdentifiersSectionTitle; + + /// No description provided for @spoofIdentifiersDescription. + /// + /// In en, this message translates to: + /// **'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 @spoofFieldDeviceId. + /// + /// In en, this message translates to: + /// **'Device ID'** + String get spoofFieldDeviceId; + + /// No description provided for @spoofRegenerateIdTooltip. + /// + /// In en, this message translates to: + /// **'Generate a new ID'** + String get spoofRegenerateIdTooltip; + + /// No description provided for @spoofFieldAppVersion. + /// + /// In en, this message translates to: + /// **'App version'** + String get spoofFieldAppVersion; + + /// No description provided for @spoofFieldBuildNumber. + /// + /// In en, this message translates to: + /// **'Build number'** + String get spoofFieldBuildNumber; + + /// No description provided for @spoofFieldArchitecture. + /// + /// In en, this message translates to: + /// **'Architecture'** + String get spoofFieldArchitecture; + + /// No description provided for @spoofButtonGenerate. + /// + /// In en, this message translates to: + /// **'Generate'** + String get spoofButtonGenerate; + + /// No description provided for @spoofButtonApply. + /// + /// In en, this message translates to: + /// **'Apply'** + String get spoofButtonApply; + + /// No description provided for @spoofDialogUnsureTitle. + /// + /// In en, this message translates to: + /// **'Are you sure?'** + String get spoofDialogUnsureTitle; + + /// No description provided for @spoofDialogUnsureContent. + /// + /// In en, this message translates to: + /// **'The app may become unstable due to API incompatibility'** + String get spoofDialogUnsureContent; + + /// No description provided for @spoofDialogCancel. + /// + /// In en, this message translates to: + /// **'Cancel'** + String get spoofDialogCancel; + + /// No description provided for @spoofDialogYes. + /// + /// In en, this message translates to: + /// **'Yes'** + String get spoofDialogYes; + + /// No description provided for @spoofDialogApplyTitle. + /// + /// In en, this message translates to: + /// **'Apply settings?'** + String get spoofDialogApplyTitle; + + /// No description provided for @spoofDialogApplyContent. + /// + /// In en, this message translates to: + /// **'Need to reconnect the app, ok?'** + String get spoofDialogApplyContent; + + /// No description provided for @spoofDialogApplyDeny. + /// + /// In en, this message translates to: + /// **'No'** + String get spoofDialogApplyDeny; + + /// No description provided for @spoofDialogApplyConfirm. + /// + /// In en, this message translates to: + /// **'Ok!'** + String get spoofDialogApplyConfirm; + + /// No description provided for @spoofErrorApplyFailed. + /// + /// In en, this message translates to: + /// **'Failed to apply settings: {error}'** + String spoofErrorApplyFailed(String error); + + /// No description provided for @profileMenuSpoof. + /// + /// In en, this message translates to: + /// **'Spoofing'** + String get profileMenuSpoof; } -class _AppLocalizationsDelegate extends LocalizationsDelegate { +class _AppLocalizationsDelegate + extends LocalizationsDelegate { const _AppLocalizationsDelegate(); @override @@ -387,25 +607,26 @@ class _AppLocalizationsDelegate extends LocalizationsDelegate } @override - bool isSupported(Locale locale) => ['en', 'ru'].contains(locale.languageCode); + bool isSupported(Locale locale) => + ['en', 'ru'].contains(locale.languageCode); @override bool shouldReload(_AppLocalizationsDelegate old) => false; } AppLocalizations lookupAppLocalizations(Locale locale) { - - // Lookup logic when only language code is specified. switch (locale.languageCode) { - case 'en': return AppLocalizationsEn(); - case 'ru': return AppLocalizationsRu(); + case 'en': + return AppLocalizationsEn(); + case 'ru': + return AppLocalizationsRu(); } throw FlutterError( 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' 'an issue with the localizations generation tool. Please file an issue ' 'on GitHub with a reproducible sample app and the gen-l10n configuration ' - 'that was used.' + 'that was used.', ); } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 68d2d21..4422b8f 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -12,7 +12,8 @@ class AppLocalizationsEn extends AppLocalizations { String get loginTitle => 'Sign in to Komet'; @override - String get loginSubtitle => 'Check your country code and enter your\nphone number.'; + String get loginSubtitle => + 'Check your country code and enter your\nphone number.'; @override String get loginCountry => 'Country'; @@ -45,7 +46,7 @@ class AppLocalizationsEn extends AppLocalizations { String get loginReadTermsNotification => 'Please read the terms of use first'; @override - String get loginSpoofRedacted => 'Spoof redaction'; + String get loginSpoofRedacted => 'Spoofing'; @override String get loginProxy => 'Proxy'; @@ -102,7 +103,8 @@ class AppLocalizationsEn extends AppLocalizations { String get selectCountrySearchHint => 'Search countries…'; @override - String get codeConfirmationSmsSent => 'We sent an SMS with a verification code to your phone number.'; + String get codeConfirmationSmsSent => + 'We sent an SMS with a verification code to your phone number.'; @override String codeResendInSeconds(int seconds) { @@ -149,5 +151,122 @@ class AppLocalizationsEn extends AppLocalizations { String get proxySettingsSaved => 'Proxy settings applied'; @override - String get proxyInvalidHostOrPort => 'Enter a valid proxy host and port (1–65535)'; + String get proxyInvalidHostOrPort => + 'Enter a valid proxy host and port (1–65535)'; + + @override + String get spoofScreenTitle => 'Session spoofing'; + + @override + String get spoofInfoHint => + 'Tap \"Generate\":\n• Short tap: random preset.\n• Long press: real device data.'; + + @override + String get spoofMethodTitle => 'Spoofing method'; + + @override + String get spoofMethodPartial => 'Partial'; + + @override + String get spoofMethodFull => 'Full'; + + @override + String get spoofMethodPartialDescription => + 'Recommended method. Random data is used, but your real timezone and locale are kept for plausibility.'; + + @override + String get spoofMethodFullDescription => + 'All data including timezone and locale is generated randomly. Use this method at your own risk!'; + + @override + String get spoofDeviceTypeTitle => 'Device type'; + + @override + String get spoofDeviceTypeDescription => + 'Choose a device type for preset generation. Tapping \"Generate\" will only use presets of the selected type.'; + + @override + String get spoofDeviceTypeLabel => 'Device type'; + + @override + String get spoofMainSectionTitle => 'Main data'; + + @override + String get spoofFieldDeviceName => 'Device name'; + + @override + String get spoofFieldOsVersion => 'OS version'; + + @override + String get spoofRegionalSectionTitle => 'Regional data'; + + @override + String get spoofFieldScreen => 'Screen resolution'; + + @override + String get spoofFieldTimezone => 'Timezone'; + + @override + String get spoofFieldLocale => 'Locale'; + + @override + String get spoofIdentifiersSectionTitle => 'Identifiers'; + + @override + String get spoofIdentifiersDescription => + 'mt_instanceid and clientSessionId are generated automatically on every app launch. Only the Device ID can be changed.'; + + @override + String get spoofFieldDeviceId => 'Device ID'; + + @override + String get spoofRegenerateIdTooltip => 'Generate a new ID'; + + @override + String get spoofFieldAppVersion => 'App version'; + + @override + String get spoofFieldBuildNumber => 'Build number'; + + @override + String get spoofFieldArchitecture => 'Architecture'; + + @override + String get spoofButtonGenerate => 'Generate'; + + @override + String get spoofButtonApply => 'Apply'; + + @override + String get spoofDialogUnsureTitle => 'Are you sure?'; + + @override + String get spoofDialogUnsureContent => + 'The app may become unstable due to API incompatibility'; + + @override + String get spoofDialogCancel => 'Cancel'; + + @override + String get spoofDialogYes => 'Yes'; + + @override + String get spoofDialogApplyTitle => 'Apply settings?'; + + @override + String get spoofDialogApplyContent => 'Need to reconnect the app, ok?'; + + @override + String get spoofDialogApplyDeny => 'No'; + + @override + String get spoofDialogApplyConfirm => 'Ok!'; + + @override + String spoofErrorApplyFailed(String error) { + return 'Failed to apply settings: $error'; + } + + @override + String get profileMenuSpoof => 'Spoofing'; } diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 7f58d45..6648921 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -12,7 +12,8 @@ class AppLocalizationsRu extends AppLocalizations { String get loginTitle => 'Войдите в Komet'; @override - String get loginSubtitle => 'Проверьте код страны и введите свой\nномер телефона.'; + String get loginSubtitle => + 'Проверьте код страны и введите свой\nномер телефона.'; @override String get loginCountry => 'Страна'; @@ -42,10 +43,11 @@ class AppLocalizationsRu extends AppLocalizations { String get loginDone => 'Готово'; @override - String get loginReadTermsNotification => 'Сначала прочитайте условия использования'; + String get loginReadTermsNotification => + 'Сначала прочитайте условия использования'; @override - String get loginSpoofRedacted => 'Подделка спуфа'; + String get loginSpoofRedacted => 'Подмена данных'; @override String get loginProxy => 'Прокси'; @@ -69,7 +71,8 @@ class AppLocalizationsRu extends AppLocalizations { String get serverUseDefault => 'Сбросить к умолчанию'; @override - String get serverInvalidHostOrPort => 'Укажите корректный хост и порт (1–65535)'; + String get serverInvalidHostOrPort => + 'Укажите корректный хост и порт (1–65535)'; @override String get serverSettingsSaved => 'Настройки сервера применены'; @@ -102,7 +105,8 @@ class AppLocalizationsRu extends AppLocalizations { String get selectCountrySearchHint => 'Поиск страны…'; @override - String get codeConfirmationSmsSent => 'Мы отправили SMS с кодом подтверждения на ваш номер телефона.'; + String get codeConfirmationSmsSent => + 'Мы отправили SMS с кодом подтверждения на ваш номер телефона.'; @override String codeResendInSeconds(int seconds) { @@ -149,5 +153,122 @@ class AppLocalizationsRu extends AppLocalizations { String get proxySettingsSaved => 'Настройки прокси применены'; @override - String get proxyInvalidHostOrPort => 'Укажите корректный хост и порт прокси (1–65535)'; + String get proxyInvalidHostOrPort => + 'Укажите корректный хост и порт прокси (1–65535)'; + + @override + String get spoofScreenTitle => 'Подмена данных сессии'; + + @override + String get spoofInfoHint => + 'Нажмите \"Сгенерировать\":\n• Короткое нажатие: случайный пресет.\n• Длинное нажатие: реальные данные.'; + + @override + String get spoofMethodTitle => 'Метод подмены'; + + @override + String get spoofMethodPartial => 'Частичный'; + + @override + String get spoofMethodFull => 'Полный'; + + @override + String get spoofMethodPartialDescription => + 'Рекомендуемый метод. Используются случайные данные, но ваш реальный часовой пояс и локаль для большей правдоподобности.'; + + @override + String get spoofMethodFullDescription => + 'Все данные, включая часовой пояс и локаль, генерируются случайно. Использование этого метода на ваш страх и риск!'; + + @override + String get spoofDeviceTypeTitle => 'Тип устройства'; + + @override + String get spoofDeviceTypeDescription => + 'Выберите тип устройства для генерации пресетов. При нажатии \"Сгенерировать\" будут использоваться только пресеты выбранного типа.'; + + @override + String get spoofDeviceTypeLabel => 'Тип устройства'; + + @override + String get spoofMainSectionTitle => 'Основные данные'; + + @override + String get spoofFieldDeviceName => 'Имя устройства'; + + @override + String get spoofFieldOsVersion => 'Версия ОС'; + + @override + String get spoofRegionalSectionTitle => 'Региональные данные'; + + @override + String get spoofFieldScreen => 'Разрешение экрана'; + + @override + String get spoofFieldTimezone => 'Часовой пояс'; + + @override + String get spoofFieldLocale => 'Локаль'; + + @override + String get spoofIdentifiersSectionTitle => 'Идентификаторы'; + + @override + String get spoofIdentifiersDescription => + 'mt_instanceid и clientSessionId генерируются автоматически при каждом запуске приложения. Изменить можно только Device ID.'; + + @override + String get spoofFieldDeviceId => 'ID Устройства'; + + @override + String get spoofRegenerateIdTooltip => 'Сгенерировать новый ID'; + + @override + String get spoofFieldAppVersion => 'Версия приложения'; + + @override + String get spoofFieldBuildNumber => 'Build Number'; + + @override + String get spoofFieldArchitecture => 'Архитектура'; + + @override + String get spoofButtonGenerate => 'Сгенерировать'; + + @override + String get spoofButtonApply => 'Применить'; + + @override + String get spoofDialogUnsureTitle => 'Ты уверен?'; + + @override + String get spoofDialogUnsureContent => + 'Приложение может начать работать нестабильно из-за несовместимости API'; + + @override + String get spoofDialogCancel => 'Отмена'; + + @override + String get spoofDialogYes => 'Да'; + + @override + String get spoofDialogApplyTitle => 'Применить настройки?'; + + @override + String get spoofDialogApplyContent => 'Нужно перезайти в приложение, ок?'; + + @override + String get spoofDialogApplyDeny => 'Не'; + + @override + String get spoofDialogApplyConfirm => 'Ок!'; + + @override + String spoofErrorApplyFailed(String error) { + return 'Ошибка при применении настроек: $error'; + } + + @override + String get profileMenuSpoof => 'Подмена данных'; } diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index babcc9e..2efbabb 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -12,7 +12,7 @@ "loginEdit": "Изменить", "loginDone": "Готово", "loginReadTermsNotification": "Сначала прочитайте условия использования", - "loginSpoofRedacted": "Подделка спуфа", + "loginSpoofRedacted": "Подмена данных", "loginProxy": "Прокси", "loginChangeServer": "Смена сервера", "serverSettingsTitle": "Сервер", @@ -54,5 +54,49 @@ "proxyApply": "Применить и переподключиться", "proxyDisable": "Отключить прокси", "proxySettingsSaved": "Настройки прокси применены", - "proxyInvalidHostOrPort": "Укажите корректный хост и порт прокси (1–65535)" + "proxyInvalidHostOrPort": "Укажите корректный хост и порт прокси (1–65535)", + + "spoofScreenTitle": "Подмена данных сессии", + "spoofInfoHint": "Нажмите \"Сгенерировать\":\n• Короткое нажатие: случайный пресет.\n• Длинное нажатие: реальные данные.", + "spoofMethodTitle": "Метод подмены", + "spoofMethodPartial": "Частичный", + "spoofMethodFull": "Полный", + "spoofMethodPartialDescription": "Рекомендуемый метод. Используются случайные данные, но ваш реальный часовой пояс и локаль для большей правдоподобности.", + "spoofMethodFullDescription": "Все данные, включая часовой пояс и локаль, генерируются случайно. Использование этого метода на ваш страх и риск!", + "spoofDeviceTypeTitle": "Тип устройства", + "spoofDeviceTypeDescription": "Выберите тип устройства для генерации пресетов. При нажатии \"Сгенерировать\" будут использоваться только пресеты выбранного типа.", + "spoofDeviceTypeLabel": "Тип устройства", + "spoofMainSectionTitle": "Основные данные", + "spoofFieldDeviceName": "Имя устройства", + "spoofFieldOsVersion": "Версия ОС", + "spoofRegionalSectionTitle": "Региональные данные", + "spoofFieldScreen": "Разрешение экрана", + "spoofFieldTimezone": "Часовой пояс", + "spoofFieldLocale": "Локаль", + "spoofIdentifiersSectionTitle": "Идентификаторы", + "spoofIdentifiersDescription": "mt_instanceid и clientSessionId генерируются автоматически при каждом запуске приложения. Изменить можно только Device ID.", + "spoofFieldDeviceId": "ID Устройства", + "spoofRegenerateIdTooltip": "Сгенерировать новый ID", + "spoofFieldAppVersion": "Версия приложения", + "spoofFieldBuildNumber": "Build Number", + "spoofFieldArchitecture": "Архитектура", + "spoofButtonGenerate": "Сгенерировать", + "spoofButtonApply": "Применить", + "spoofDialogUnsureTitle": "Ты уверен?", + "spoofDialogUnsureContent": "Приложение может начать работать нестабильно из-за несовместимости API", + "spoofDialogCancel": "Отмена", + "spoofDialogYes": "Да", + "spoofDialogApplyTitle": "Применить настройки?", + "spoofDialogApplyContent": "Нужно перезайти в приложение, ок?", + "spoofDialogApplyDeny": "Не", + "spoofDialogApplyConfirm": "Ок!", + "spoofErrorApplyFailed": "Ошибка при применении настроек: {error}", + "@spoofErrorApplyFailed": { + "placeholders": { + "error": { + "type": "String" + } + } + }, + "profileMenuSpoof": "Подмена данных" }