From 3b5be5f4d9b86780345d98030d9befbecc4cd45a Mon Sep 17 00:00:00 2001 From: Artur Date: Sun, 24 May 2026 00:02:58 +0500 Subject: [PATCH] =?UTF-8?q?=D0=9C=D0=B5=D0=B4=D0=B8=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/app/build.gradle.kts | 11 +- android/app/proguard-rules.pro | 15 ++ .../kotlin/ru/chepuhagram/app/MainActivity.kt | 42 ++++- lib/domain/services/api_service.dart | 15 +- lib/logic/auth_provider.dart | 19 ++- lib/main.dart | 157 +++++++++--------- lib/presentation/screens/chat_screen.dart | 4 +- lib/presentation/screens/splash_screen.dart | 11 +- 8 files changed, 179 insertions(+), 95 deletions(-) create mode 100644 android/app/proguard-rules.pro diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index f73cb16..7305080 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -34,6 +34,8 @@ android { buildTypes { release { + isMinifyEnabled = true + isShrinkResources = true // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig = signingConfigs.getByName("debug") @@ -50,12 +52,3 @@ dependencies { implementation(platform("com.google.firebase:firebase-bom:34.12.0")) implementation("com.google.firebase:firebase-messaging") } - -configurations.all { - resolutionStrategy.eachDependency { - if (requested.group == "com.arthenica" && requested.name.startsWith("ffmpeg-kit")) { - useVersion("6.0.3") - because("Фикс падения сборки на версии 6.0.3+2-LTS") - } - } -} \ No newline at end of file diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 0000000..7d5d259 --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,15 @@ +# Защита ffmpeg-kit от R8 +-keep class com.arthenica.ffmpegkit.** { *; } +-keep interface com.arthenica.ffmpegkit.** { *; } + +# Защита от удаления нативных методов JNI +-keepclassmembers class * { + native ; +} + +# Если используете конкретно ваш форк, добавим и его: +-keep class com.antonkarpenko.ffmpegkit.** { *; } + +# Защита Firebase (если падает Firebase, когда включен R8) +-keep class com.google.firebase.** { *; } +-keep class com.google.android.gms.** { *; } \ No newline at end of file diff --git a/android/app/src/main/kotlin/ru/chepuhagram/app/MainActivity.kt b/android/app/src/main/kotlin/ru/chepuhagram/app/MainActivity.kt index eaacfcb..4e66f35 100644 --- a/android/app/src/main/kotlin/ru/chepuhagram/app/MainActivity.kt +++ b/android/app/src/main/kotlin/ru/chepuhagram/app/MainActivity.kt @@ -1,5 +1,45 @@ package ru.chepuhagram.app +import android.app.AlertDialog +import android.os.Bundle import io.flutter.embedding.android.FlutterFragmentActivity +import io.flutter.embedding.engine.FlutterEngine -class MainActivity : FlutterFragmentActivity() +class MainActivity : FlutterFragmentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + try { + super.onCreate(savedInstanceState) + } catch (e: UnsatisfiedLinkError) { + handleFatalStartupError("Ошибка загрузки нативных библиотек: ${e.message}") + } catch (e: Exception) { + handleFatalStartupError("Произошла системная ошибка при запуске: ${e.message}") + } + } + + // Дополнительная защита при создании движка, если инициализация происходит там + override fun provideFlutterEngine(context: android.content.Context): FlutterEngine? { + return try { + super.provideFlutterEngine(context) + } catch (e: UnsatisfiedLinkError) { + handleFatalStartupError("Ошибка инициализации движка (FFmpegKit): ${e.message}") + null // Возвращаем null, чтобы предотвратить дальнейший краш + } + } + + private fun handleFatalStartupError(message: String) { + // Мы не можем использовать стандартные диалоги из темы Activity, + // так как они могут быть повреждены, используем чистый Android AlertDialog + runOnUiThread { + AlertDialog.Builder(this) + .setTitle("Критическая ошибка") + .setMessage("Приложение не может быть запущено на данном устройстве.\n\nТехническая информация: $message") + .setPositiveButton("Закрыть") { _, _ -> + finishAffinity() // Полностью закрывает приложение + } + .setCancelable(false) + .create() + .show() + } + } +} \ No newline at end of file diff --git a/lib/domain/services/api_service.dart b/lib/domain/services/api_service.dart index 7bf47a8..7a03e1d 100644 --- a/lib/domain/services/api_service.dart +++ b/lib/domain/services/api_service.dart @@ -36,10 +36,10 @@ class ApiService extends ChangeNotifier { Future getUserByUsername(String username) async { try { // Подставляй свой эндпоинт, например: /users/by-username/ - final response = await Dio().get('/users/by-username/$username'); - + final response = await Dio().get('/users/by-username/$username'); + if (response.statusCode == 200 && response.data != null) { - // Парсим полученные данные в модель контакта. + // Парсим полученные данные в модель контакта. // Убедись, что метод Contact.fromJson или Contact.fromMap корректно обрабатывает поле public_key return Contact.fromJson(response.data); } @@ -288,7 +288,14 @@ class ApiService extends ChangeNotifier { } Future getAccessToken() async { - String? token = await _storage.read(key: 'access_token'); + String? token; + try { + token = await _storage.read(key: 'access_token'); + } catch (_) { + throw Exception( + 'Критическая ошибка инициализации внутренних библиотек.\n Приложение не может продолжить работу. \n Обратитесь к разработчику. \n Код ошибки: _apis_gat_1', + ); + } if (token != null) { bool isExpiredSoon = diff --git a/lib/logic/auth_provider.dart b/lib/logic/auth_provider.dart index d0beb3a..30705ff 100644 --- a/lib/logic/auth_provider.dart +++ b/lib/logic/auth_provider.dart @@ -94,7 +94,11 @@ class AuthProvider extends ChangeNotifier { SocketService get socketService => _socketService; - Future login(String username, String password, {String? totpCode}) async { + Future login( + String username, + String password, { + String? totpCode, + }) async { _isLoading = true; notifyListeners(); @@ -180,7 +184,14 @@ class AuthProvider extends ChangeNotifier { } Future tryAutoLogin() async { - final token = await _apiService.getAccessToken(); + String? token; + try { + token = await _apiService.getAccessToken(); + } catch (e) { + throw Exception( + '$e+_aup_tal_1', + ); + } if (token == null) return false; // Загружаем currentUserId из хранилища @@ -281,7 +292,9 @@ class AuthProvider extends ChangeNotifier { _email = data['email']?.toString(); _about = data['about']?.toString(); final avatarFileId = data['avatar_file_id']?.toString(); - _avatarUrl = avatarFileId != null ? '${AppConstants.baseUrl}/media/$avatarFileId' : null; + _avatarUrl = avatarFileId != null + ? '${AppConstants.baseUrl}/media/$avatarFileId' + : null; // Загружаем локальные настройки _avatarPath = await _storage.read(key: 'avatar_path'); diff --git a/lib/main.dart b/lib/main.dart index 89b3a93..7a253ce 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -124,94 +124,101 @@ void _navigateToChat(int senderId) { void main() async { WidgetsFlutterBinding.ensureInitialized(); - await Firebase.initializeApp(); + try { + await Firebase.initializeApp(); - await Future.delayed(const Duration(milliseconds: 500)); - initialMessage = await FirebaseMessaging.instance.getInitialMessage(); - print('Initial message from main() after delay: $initialMessage'); - // Сохраняем информацию в SharedPreferences для надежности - final prefs = await SharedPreferences.getInstance(); - if (initialMessage != null) { - print('App launched from notification: ${initialMessage!.data}'); - print('Message type: ${initialMessage!.data['type']}'); - print('Sender ID: ${initialMessage!.data['sender_id']}'); + initialMessage = await FirebaseMessaging.instance.getInitialMessage(); + print('Initial message from main() after delay: $initialMessage'); + // Сохраняем информацию в SharedPreferences для надежности + final prefs = await SharedPreferences.getInstance(); + if (initialMessage != null) { + print('App launched from notification: ${initialMessage!.data}'); + print('Message type: ${initialMessage!.data['type']}'); + print('Sender ID: ${initialMessage!.data['sender_id']}'); - final payloadString = jsonEncode(initialMessage!.data); - final lastHandled = prefs.getString( - _lastHandledNotificationLaunchPayloadKey, - ); - if (lastHandled != payloadString) { - // Сохраняем данные уведомления - await prefs.setString(_notificationLaunchKey, payloadString); - await prefs.setString( + final payloadString = jsonEncode(initialMessage!.data); + final lastHandled = prefs.getString( _lastHandledNotificationLaunchPayloadKey, - payloadString, ); - print('Saved notification data to SharedPreferences'); - } else { - print('InitialMessage payload already handled earlier, skipping'); - } - } else { - print('No initial message - app launched normally'); - // Очищаем сохраненные данные, если приложение запущено нормально - await prefs.remove(_notificationLaunchKey); - } - - // Initialize local notifications - const AndroidInitializationSettings initializationSettingsAndroid = - AndroidInitializationSettings('@mipmap/ic_launcher'); - final InitializationSettings initializationSettings = InitializationSettings( - android: initializationSettingsAndroid, - ); - await flutterLocalNotificationsPlugin.initialize( - initializationSettings, - onDidReceiveNotificationResponse: _onSelectNotification, - ); - - // Если приложение было запущено из локального уведомления, сохраним payload - final notificationAppLaunchDetails = await flutterLocalNotificationsPlugin - .getNotificationAppLaunchDetails(); - if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) { - final payload = notificationAppLaunchDetails?.notificationResponse?.payload; - print('App launched from local notification, payload: $payload'); - if (payload != null && payload.isNotEmpty) { - try { - final lastHandled = prefs.getString( + if (lastHandled != payloadString) { + // Сохраняем данные уведомления + await prefs.setString(_notificationLaunchKey, payloadString); + await prefs.setString( _lastHandledNotificationLaunchPayloadKey, + payloadString, ); - if (lastHandled != payload) { - final data = jsonDecode(payload); - await prefs.setString(_notificationLaunchKey, jsonEncode(data)); - await prefs.setString( + print('Saved notification data to SharedPreferences'); + } else { + print('InitialMessage payload already handled earlier, skipping'); + } + } else { + print('No initial message - app launched normally'); + // Очищаем сохраненные данные, если приложение запущено нормально + await prefs.remove(_notificationLaunchKey); + } + + // Initialize local notifications + const AndroidInitializationSettings initializationSettingsAndroid = + AndroidInitializationSettings('@mipmap/ic_launcher'); + final InitializationSettings initializationSettings = + InitializationSettings(android: initializationSettingsAndroid); + await flutterLocalNotificationsPlugin.initialize( + initializationSettings, + onDidReceiveNotificationResponse: _onSelectNotification, + ); + + // Если приложение было запущено из локального уведомления, сохраним payload + final notificationAppLaunchDetails = await flutterLocalNotificationsPlugin + .getNotificationAppLaunchDetails(); + if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) { + final payload = + notificationAppLaunchDetails?.notificationResponse?.payload; + print('App launched from local notification, payload: $payload'); + if (payload != null && payload.isNotEmpty) { + try { + final lastHandled = prefs.getString( _lastHandledNotificationLaunchPayloadKey, - payload, ); - print('Saved local notification launch payload to SharedPreferences'); - } else { - print('Local notification payload already handled earlier, skipping'); + if (lastHandled != payload) { + final data = jsonDecode(payload); + await prefs.setString(_notificationLaunchKey, jsonEncode(data)); + await prefs.setString( + _lastHandledNotificationLaunchPayloadKey, + payload, + ); + print( + 'Saved local notification launch payload to SharedPreferences', + ); + } else { + print( + 'Local notification payload already handled earlier, skipping', + ); + } + } catch (e) { + print('Failed to save notification launch payload: $e'); } - } catch (e) { - print('Failed to save notification launch payload: $e'); } } + + // Create notification channel for Android 8+ + const AndroidNotificationChannel channel = AndroidNotificationChannel( + 'chat_id', // id + 'Messages', // title + description: 'Chat messages notifications', // description + importance: Importance.high, + ); + + await flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin + >() + ?.createNotificationChannel(channel); + + FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); + } catch (e) { + print('Уведосления не были инициальзированы: $e'); } - // Create notification channel for Android 8+ - const AndroidNotificationChannel channel = AndroidNotificationChannel( - 'chat_id', // id - 'Messages', // title - description: 'Chat messages notifications', // description - importance: Importance.high, - ); - - await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation< - AndroidFlutterLocalNotificationsPlugin - >() - ?.createNotificationChannel(channel); - - FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); - runApp( MultiProvider( providers: [ diff --git a/lib/presentation/screens/chat_screen.dart b/lib/presentation/screens/chat_screen.dart index 15eea4f..921e22b 100644 --- a/lib/presentation/screens/chat_screen.dart +++ b/lib/presentation/screens/chat_screen.dart @@ -2149,8 +2149,8 @@ class _ChatScreenState extends State with RouteAware { previewText = rawText; } else if (hasMedia) { previewText = switch (messageType) { - MessageType.voiceNote => "[Кружок}", - MessageType.videoNote => "[Голосовое]", + MessageType.videoNote => "[Кружок]", + MessageType.voiceNote => "[Голосовое]", MessageType.image => "[Фото]", MessageType.video => "[Видео]", MessageType.file => "[Файл]", diff --git a/lib/presentation/screens/splash_screen.dart b/lib/presentation/screens/splash_screen.dart index 504f169..403dea4 100644 --- a/lib/presentation/screens/splash_screen.dart +++ b/lib/presentation/screens/splash_screen.dart @@ -67,7 +67,16 @@ class _SplashScreenState extends State { // 2. Пытаемся выполнить автологин final authProvider = context.read(); - final isLoggedIn = await authProvider.tryAutoLogin(); + bool? isLoggedIn; + try { + isLoggedIn = await authProvider.tryAutoLogin(); + } catch (e) { + setState(() { + connectError = + '$e+_sps_init_1'.replaceAll('Exception: ', ''); + }); + return; + } if (!mounted) return; bool connected = false;