From 575243e959e02c7a9299d71dc489a205a5d9d802 Mon Sep 17 00:00:00 2001 From: "Mr.Jebelli" Date: Sat, 14 Feb 2026 09:46:34 +0330 Subject: [PATCH] first final test --- android/app/build.gradle | 5 + android/app/google-services.json | 102 +- android/app/src/main/AndroidManifest.xml | 9 + assets/icon/outline/folder-add.svg | 5 + lib/core/gen/assets.gen.dart | 128 +- lib/core/routes/route_generator.dart | 25 +- lib/core/services/firebase/firebase_api.dart | 62 +- lib/data/model/ai/credit_model.dart | 7 +- lib/firebase_options.dart | 16 + lib/main.dart | 62 +- lib/ui/screens/auth/auth_page.dart | 10 +- .../screens/auth/code/gift_code_screen.dart | 4 +- .../verification/verification_screen.dart | 2 +- lib/ui/screens/chat/bloc/messages_bloc.dart | 3 + lib/ui/screens/chat/chat_page.dart | 1223 ++++++++++------- .../chat/cubit/receive_message_cubit.dart | 25 +- .../screens/gmedia/chats/photo_chat_page.dart | 266 +++- .../screens/gmedia/chats/video_chat_page.dart | 3 +- .../main/home/bloc/cubit/posts_cubit.dart | 2 +- lib/ui/screens/main/home/home_screen.dart | 1 - lib/ui/screens/splash/splash_page.dart | 5 + lib/ui/widgets/family_banner.dart | 19 +- 22 files changed, 1315 insertions(+), 669 deletions(-) create mode 100644 assets/icon/outline/folder-add.svg diff --git a/android/app/build.gradle b/android/app/build.gradle index f1f35d8..4f6b947 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -76,9 +76,14 @@ android { shrinkResources false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } + + debug { + signingConfig signingConfigs.release + } } } + flutter { source '../..' } diff --git a/android/app/google-services.json b/android/app/google-services.json index f0f7ae1..7d519c1 100644 --- a/android/app/google-services.json +++ b/android/app/google-services.json @@ -7,17 +7,113 @@ "client": [ { "client_info": { - "mobilesdk_app_id": "1:581103504002:android:896e4d41aad9180318829b", + "mobilesdk_app_id": "1:581103504002:android:d12a150d3d54570418829b", + "android_client_info": { + "package_name": "com.example.hoshan" + } + }, + "oauth_client": [ + { + "client_id": "581103504002-kb0klg3vds4qcnt5cc348d402l803jh8.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.example.hoshan", + "certificate_hash": "4fea29f5e9872705f5d86af453b1d77fd4134260" + } + }, + { + "client_id": "581103504002-dhn875p11up9ae7k6l5r1kdqs71ap3qf.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyDi2WRiOSEws1alpLitxX0zsX14rT71aPk" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "581103504002-dhn875p11up9ae7k6l5r1kdqs71ap3qf.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "581103504002-2e454pgr7fes6b94ptbt82rpfldehedq.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.example.hoshan" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:581103504002:android:8983465e8b4f637d18829b", "android_client_info": { "package_name": "com.houshan.basa" } }, + "oauth_client": [ + { + "client_id": "581103504002-mugog6e0d224snhmoen4t01g91m4corc.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.houshan.basa", + "certificate_hash": "5f7bdab9d1978fa6ba4bb89ba17cfddb41aa7bdb" + } + }, + { + "client_id": "581103504002-qc5f48octc7274mr3n4hgam3fr5pejd3.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.houshan.basa", + "certificate_hash": "df8e828db30cabed5c68362c0a67f760610046ee" + } + }, + { + "client_id": "581103504002-dhn875p11up9ae7k6l5r1kdqs71ap3qf.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyDi2WRiOSEws1alpLitxX0zsX14rT71aPk" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "581103504002-dhn875p11up9ae7k6l5r1kdqs71ap3qf.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "581103504002-2e454pgr7fes6b94ptbt82rpfldehedq.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.example.hoshan" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:581103504002:android:896e4d41aad9180318829b", + "android_client_info": { + "package_name": "com.houshan.hoshan" + } + }, "oauth_client": [ { "client_id": "581103504002-jqptfhs4cpgqngckh8g6kisd25jhfp35.apps.googleusercontent.com", "client_type": 1, "android_info": { - "package_name": "com.houshan.basa", + "package_name": "com.houshan.hoshan", "certificate_hash": "b64c77112af5000772d60fbe05138e873f6fc118" } }, @@ -25,7 +121,7 @@ "client_id": "581103504002-t6a9nuq2nnbdjj71s2inc70p4fikl921.apps.googleusercontent.com", "client_type": 1, "android_info": { - "package_name": "com.houshan.basa", + "package_name": "com.houshan.hoshan", "certificate_hash": "5f7bdab9d1978fa6ba4bb89ba17cfddb41aa7bdb" } }, diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 0aba8f6..5fe11fd 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -35,6 +35,15 @@ + + + + + + + + + diff --git a/assets/icon/outline/folder-add.svg b/assets/icon/outline/folder-add.svg new file mode 100644 index 0000000..0ad8c40 --- /dev/null +++ b/assets/icon/outline/folder-add.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lib/core/gen/assets.gen.dart b/lib/core/gen/assets.gen.dart index f453401..ec3c9fc 100644 --- a/lib/core/gen/assets.gen.dart +++ b/lib/core/gen/assets.gen.dart @@ -362,6 +362,10 @@ class $AssetsIconBulkGen { class $AssetsIconGifGen { const $AssetsIconGifGen(); + /// File path: assets/icon/gif/33.gif + AssetGenImage get a33 => + const AssetGenImage('assets/icon/gif/33.gif', size: Size(640.0, 360.0)); + /// File path: assets/icon/gif/alpha.gif AssetGenImage get alpha => const AssetGenImage('assets/icon/gif/alpha.gif'); @@ -445,6 +449,7 @@ class $AssetsIconGifGen { /// List of all assets List get values => [ + a33, alpha, bell, beta, @@ -534,6 +539,18 @@ class $AssetsIconNavbarsGen { class $AssetsIconOutlineGen { const $AssetsIconOutlineGen(); + /// File path: assets/icon/outline/Houshan features.svg + SvgGenImage get houshanFeatures => const SvgGenImage( + 'assets/icon/outline/Houshan features.svg', + size: Size(32.0, 33.0), + ); + + /// File path: assets/icon/outline/add family.svg + SvgGenImage get addFamily => const SvgGenImage( + 'assets/icon/outline/add family.svg', + size: Size(32.0, 33.0), + ); + /// File path: assets/icon/outline/add.svg SvgGenImage get add => const SvgGenImage('assets/icon/outline/add.svg', size: Size(24.0, 24.0)); @@ -550,6 +567,12 @@ class $AssetsIconOutlineGen { size: Size(24.0, 24.0), ); + /// File path: assets/icon/outline/arrow-left.svg + SvgGenImage get arrowLeft => const SvgGenImage( + 'assets/icon/outline/arrow-left.svg', + size: Size(24.0, 24.0), + ); + /// File path: assets/icon/outline/arrow-right.svg SvgGenImage get arrowRight => const SvgGenImage( 'assets/icon/outline/arrow-right.svg', @@ -596,6 +619,12 @@ class $AssetsIconOutlineGen { size: Size(19.0, 19.0), ); + /// File path: assets/icon/outline/carbon_pedestrian-family.svg + SvgGenImage get carbonPedestrianFamily => const SvgGenImage( + 'assets/icon/outline/carbon_pedestrian-family.svg', + size: Size(32.0, 32.0), + ); + /// File path: assets/icon/outline/card-add.svg SvgGenImage get cardAdd => const SvgGenImage( 'assets/icon/outline/card-add.svg', @@ -630,6 +659,12 @@ class $AssetsIconOutlineGen { size: Size(16.0, 17.0), ); + /// File path: assets/icon/outline/close-circle.svg + SvgGenImage get closeCircle => const SvgGenImage( + 'assets/icon/outline/close-circle.svg', + size: Size(24.0, 24.0), + ); + /// File path: assets/icon/outline/coin.svg SvgGenImage get coin => const SvgGenImage('assets/icon/outline/coin.svg', size: Size(25.0, 25.0)); @@ -692,6 +727,10 @@ class $AssetsIconOutlineGen { size: Size(16.0, 16.0), ); + /// File path: assets/icon/outline/edit.svg + SvgGenImage get edit => + const SvgGenImage('assets/icon/outline/edit.svg', size: Size(24.0, 24.0)); + /// File path: assets/icon/outline/element-plus.svg SvgGenImage get elementPlus => const SvgGenImage( 'assets/icon/outline/element-plus.svg', @@ -722,6 +761,18 @@ class $AssetsIconOutlineGen { size: Size(16.0, 17.0), ); + /// File path: assets/icon/outline/family.svg + SvgGenImage get family => const SvgGenImage( + 'assets/icon/outline/family.svg', + size: Size(328.0, 115.0), + ); + + /// File path: assets/icon/outline/familyMembers.svg + SvgGenImage get familyMembers => const SvgGenImage( + 'assets/icon/outline/familyMembers.svg', + size: Size(30.0, 29.0), + ); + /// File path: assets/icon/outline/filter.svg SvgGenImage get filter => const SvgGenImage( 'assets/icon/outline/filter.svg', @@ -734,6 +785,12 @@ class $AssetsIconOutlineGen { size: Size(16.0, 16.0), ); + /// File path: assets/icon/outline/folder-add.svg + SvgGenImage get folderAdd => const SvgGenImage( + 'assets/icon/outline/folder-add.svg', + size: Size(18.0, 18.0), + ); + /// File path: assets/icon/outline/gallery-add.svg SvgGenImage get galleryAdd => const SvgGenImage( 'assets/icon/outline/gallery-add.svg', @@ -746,6 +803,12 @@ class $AssetsIconOutlineGen { size: Size(18.0, 18.0), ); + /// File path: assets/icon/outline/gift 2.svg + SvgGenImage get gift2 => const SvgGenImage( + 'assets/icon/outline/gift 2.svg', + size: Size(86.0, 88.0), + ); + /// File path: assets/icon/outline/gift.svg SvgGenImage get gift => const SvgGenImage('assets/icon/outline/gift.svg', size: Size(24.0, 24.0)); @@ -786,6 +849,12 @@ class $AssetsIconOutlineGen { size: Size(16.0, 16.0), ); + /// File path: assets/icon/outline/ion_infinite.svg + SvgGenImage get ionInfinite => const SvgGenImage( + 'assets/icon/outline/ion_infinite.svg', + size: Size(32.0, 32.0), + ); + /// File path: assets/icon/outline/lamp-charge.svg SvgGenImage get lampCharge => const SvgGenImage( 'assets/icon/outline/lamp-charge.svg', @@ -918,6 +987,10 @@ class $AssetsIconOutlineGen { size: Size(24.0, 24.0), ); + /// File path: assets/icon/outline/send.svg + SvgGenImage get send => + const SvgGenImage('assets/icon/outline/send.svg', size: Size(16.0, 16.0)); + /// File path: assets/icon/outline/setting.svg SvgGenImage get setting => const SvgGenImage( 'assets/icon/outline/setting.svg', @@ -952,6 +1025,12 @@ class $AssetsIconOutlineGen { SvgGenImage get stop => const SvgGenImage('assets/icon/outline/stop.svg', size: Size(24.0, 24.0)); + /// File path: assets/icon/outline/streamline_coins-stack.svg + SvgGenImage get streamlineCoinsStack => const SvgGenImage( + 'assets/icon/outline/streamline_coins-stack.svg', + size: Size(32.0, 32.0), + ); + /// File path: assets/icon/outline/sun.svg SvgGenImage get sun => const SvgGenImage('assets/icon/outline/sun.svg', size: Size(18.0, 18.0)); @@ -992,6 +1071,12 @@ class $AssetsIconOutlineGen { size: Size(16.0, 16.0), ); + /// File path: assets/icon/outline/trashPopup.svg + SvgGenImage get trashPopup => const SvgGenImage( + 'assets/icon/outline/trashPopup.svg', + size: Size(48.0, 48.0), + ); + /// File path: assets/icon/outline/verify.svg SvgGenImage get verify => const SvgGenImage( 'assets/icon/outline/verify.svg', @@ -1010,17 +1095,32 @@ class $AssetsIconOutlineGen { size: Size(16.0, 16.0), ); + /// File path: assets/icon/outline/wallet.svg + SvgGenImage get wallet => const SvgGenImage( + 'assets/icon/outline/wallet.svg', + size: Size(32.0, 33.0), + ); + /// File path: assets/icon/outline/warning-2.svg SvgGenImage get warning2 => const SvgGenImage( 'assets/icon/outline/warning-2.svg', size: Size(24.0, 25.0), ); + /// File path: assets/icon/outline/warning.svg + SvgGenImage get warning => const SvgGenImage( + 'assets/icon/outline/warning.svg', + size: Size(32.0, 33.0), + ); + /// List of all assets List get values => [ + houshanFeatures, + addFamily, add, archiveTick, arrowFlashRight, + arrowLeft, arrowRight, assistant, bitcoinRefresh, @@ -1029,12 +1129,14 @@ class $AssetsIconOutlineGen { calendarEdit, call, camera, + carbonPedestrianFamily, cardAdd, cardPos, characters, chart, chat, clock, + closeCircle, coin, copy, courthouse, @@ -1046,15 +1148,20 @@ class $AssetsIconOutlineGen { documentText, download, edit2, + edit, elementPlus, emojiHappy, emptyWalletTick, emptyWallet, eraser, + family, + familyMembers, filter, flag2, + folderAdd, galleryAdd, ghost, + gift2, gift, globalSearch, hat, @@ -1063,6 +1170,7 @@ class $AssetsIconOutlineGen { idea, import, infoCircle, + ionInfinite, lampCharge, library, like, @@ -1087,12 +1195,14 @@ class $AssetsIconOutlineGen { profileUserDoual, profile, searchNormal, + send, setting, share, shieldTick, smsTracking, stopCircle, stop, + streamlineCoinsStack, sun, tickCircle, tickSquare, @@ -1100,10 +1210,13 @@ class $AssetsIconOutlineGen { toolBox, translate, trash, + trashPopup, verify, videoPlay, voiceCricle, + wallet, warning2, + warning, ]; } @@ -1130,6 +1243,12 @@ class $AssetsIconSocialGen { class $AssetsImageBoardingsGen { const $AssetsImageBoardingsGen(); + /// File path: assets/image/boardings/AI Houshan.svg + SvgGenImage get aIHoushan => const SvgGenImage( + 'assets/image/boardings/AI Houshan.svg', + size: Size(328.0, 88.0), + ); + /// File path: assets/image/boardings/drei.png AssetGenImage get drei => const AssetGenImage( 'assets/image/boardings/drei.png', @@ -1167,7 +1286,7 @@ class $AssetsImageBoardingsGen { ); /// List of all assets - List get values => [drei, eins, funf, sechs, vier, zwei]; + List get values => [aIHoushan, drei, eins, funf, sechs, vier, zwei]; } class $AssetsImageEmptyGen { @@ -1191,6 +1310,12 @@ class $AssetsImageEmptyGen { size: Size(347.0, 433.0), ); + /// File path: assets/image/empty/empty state 1 1.png + AssetGenImage get emptyState11 => const AssetGenImage( + 'assets/image/empty/empty state 1 1.png', + size: Size(328.0, 351.0), + ); + /// File path: assets/image/empty/empty-text-underline.svg SvgGenImage get emptyTextUnderline => const SvgGenImage( 'assets/image/empty/empty-text-underline.svg', @@ -1226,6 +1351,7 @@ class $AssetsImageEmptyGen { amount, assistant, connection, + emptyState11, emptyTextUnderline, inbox, messages, diff --git a/lib/core/routes/route_generator.dart b/lib/core/routes/route_generator.dart index 9a3c827..73d5fba 100644 --- a/lib/core/routes/route_generator.dart +++ b/lib/core/routes/route_generator.dart @@ -180,20 +180,20 @@ class Routes { navigatorKey: navigatorKey, redirect: (context, state) async { final uri = state.uri; - if (uri.scheme == 'houshan' && uri.host == 'auth') { - final token = uri.queryParameters['token']; - if (token != null && token.isNotEmpty) { + + final token = uri.queryParameters['token']; + final hasToken = token != null && token.isNotEmpty; + + final isCustomScheme = uri.scheme == 'houshan' && uri.host == 'auth'; + + if (hasToken) { + if (isCustomScheme || kIsWeb || uri.path == '/') { if (kDebugMode) { - print('Deep link token received: ${token.substring(0, 20)}...'); + print('✅ Login Token Detected: ${token.substring(0, 10)}...'); } await AuthTokenStorage.setToken(token); - await OnBoardingStorage - .setAsSeen(); + await OnBoardingStorage.setAsSeen(); showOnBoarding = false; - if (kDebugMode) { - print('Token saved. Redirecting to main...'); - } - return main; } } @@ -211,7 +211,7 @@ class Routes { return auth; } - if (state.matchedLocation == auth) { + if (state.uri.path == auth) { if (context.read().state is UserInfoFail) return null; if (AuthTokenStorage.getToken().isNotEmpty || (UserInfoCubit.userInfoModel.login != null && @@ -219,7 +219,7 @@ class Routes { return main; } } - if (state.matchedLocation == home && kIsWeb) { + if (state.uri.path == home && kIsWeb) { if (UserInfoCubit.userInfoModel.login == null) { return main; } else { @@ -250,7 +250,6 @@ class Routes { try { showOnBoarding = state.extra as bool; } catch (e) { - // Check if onboarding was already seen showOnBoarding = !OnBoardingStorage.hasSeen(); if (kDebugMode) { print('Error get Args: $e'); diff --git a/lib/core/services/firebase/firebase_api.dart b/lib/core/services/firebase/firebase_api.dart index 3cb8cad..d7051ae 100644 --- a/lib/core/services/firebase/firebase_api.dart +++ b/lib/core/services/firebase/firebase_api.dart @@ -1,5 +1,8 @@ +// ignore_for_file: avoid_print + import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:go_router/go_router.dart'; import 'package:hoshan/core/routes/route_generator.dart'; @@ -16,27 +19,44 @@ class FirebasApi { static Future initialNotifications() async { await refreshToken(); + try { + await _firebaseMessaging.subscribeToTopic('all_users').timeout( + const Duration(seconds: 10), + onTimeout: () { + print('⏰ Subscribe to topic timed out - continuing anyway'); + }, + ); + print('✅ Subscribed to topic: all_users'); + } catch (e) { + print('❌ Failed to subscribe to topic (app will continue): $e'); + } + initPushNotification(); } static Future refreshToken() async { - await _firebaseMessaging.requestPermission(); + try { + print('🔄 Start getting FCM Token...'); + await _firebaseMessaging.requestPermission(); - fcmToken = await _firebaseMessaging.getToken(); - - final packageInfo = await PackageInfo.fromPlatform(); - final packageName = packageInfo.packageName; + fcmToken = await _firebaseMessaging.getToken().timeout( + const Duration(seconds: 10), + onTimeout: () { + print('⏰ FCM Token request timed out - continuing without token'); + return null; + }, + ); + + final packageInfo = await PackageInfo.fromPlatform(); + final packageName = packageInfo.packageName; - if (kDebugMode) { print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); print('📦 Package Name: $packageName'); - print('📱 FCM Token for Firebase:'); - print('$fcmToken'); - print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); - print(''); - print('✅ Copy this token and use it in Firebase Console:'); - print(' Cloud Messaging → Send test message'); + print('🔥 Firebase Initialized Successfully'); + print('📱 FCM Token: $fcmToken'); print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); + } catch (e) { + print('❌ ERROR Getting Token (app will continue): $e'); } } @@ -67,5 +87,23 @@ class FirebasApi { static Future initPushNotification() async { FirebaseMessaging.instance.getInitialMessage().then(handleMessage); FirebaseMessaging.onMessageOpenedApp.listen(handleMessage); + FirebaseMessaging.onMessage.listen((RemoteMessage message) { + if (message.notification != null) { + if (navigatorKey.currentContext != null) { + ScaffoldMessenger.of(navigatorKey.currentContext!).showSnackBar( + SnackBar( + content: Text( + message.notification!.title != null + ? '${message.notification!.title}\n${message.notification!.body}' + : '${message.notification!.body}', + textDirection: TextDirection.rtl, + ), + behavior: SnackBarBehavior.floating, + showCloseIcon: true, + ), + ); + } + } + }); } } diff --git a/lib/data/model/ai/credit_model.dart b/lib/data/model/ai/credit_model.dart index d506673..dcc0f9e 100644 --- a/lib/data/model/ai/credit_model.dart +++ b/lib/data/model/ai/credit_model.dart @@ -1,16 +1,19 @@ class CreditModel { final int? credit; final int? freeCredit; - - CreditModel({this.credit, this.freeCredit}); + final int? giftCredit; + + CreditModel({this.credit, this.freeCredit, this.giftCredit}); CreditModel copyWith({ int? credit, int? freeCredit, + int? giftCredit, }) { return CreditModel( credit: credit ?? this.credit, freeCredit: freeCredit ?? this.freeCredit, + giftCredit: giftCredit ?? this.giftCredit, ); } } diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart index f0ead28..ebb48bc 100644 --- a/lib/firebase_options.dart +++ b/lib/firebase_options.dart @@ -57,6 +57,22 @@ class DefaultFirebaseOptions { storageBucket: 'hoshan-42d9f.appspot.com', ); + static const FirebaseOptions androidHoushan = FirebaseOptions( + apiKey: 'AIzaSyDi2WRiOSEws1alpLitxX0zsX14rT71aPk', + appId: '1:581103504002:android:8983465e8b4f637d18829b', + messagingSenderId: '581103504002', + projectId: 'hoshan-42d9f', + storageBucket: 'hoshan-42d9f.appspot.com', + ); + + static const FirebaseOptions androidHoushanMain = FirebaseOptions( + apiKey: 'AIzaSyDi2WRiOSEws1alpLitxX0zsX14rT71aPk', + appId: '1:581103504002:android:896e4d41aad9180318829b', + messagingSenderId: '581103504002', + projectId: 'hoshan-42d9f', + storageBucket: 'hoshan-42d9f.appspot.com', + ); + static const FirebaseOptions ios = FirebaseOptions( apiKey: 'AIzaSyAENpjFJ2VeVQEt3_Iud9gfqLvuffb7EUI', appId: '1:581103504002:ios:4e51253f750a0fc818829b', diff --git a/lib/main.dart b/lib/main.dart index 0793dac..a00a3e0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,5 @@ +// ignore_for_file: avoid_print + import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/foundation.dart'; @@ -30,50 +32,88 @@ import 'package:hoshan/ui/screens/tools/bloc/tools_bloc.dart'; import 'package:hoshan/ui/theme/cubit/theme_mode_cubit.dart'; import 'package:hoshan/ui/theme/my_custom_scroll_behavior.dart'; import 'package:hoshan/ui/theme/theme.dart'; +import 'package:package_info_plus/package_info_plus.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:url_strategy/url_strategy.dart'; @pragma('vm:entry-point') -Future _onBackgroundMessage(RemoteMessage message) async {} +Future _onBackgroundMessage(RemoteMessage message) async { + debugPrint("Handling a background message: ${message.messageId}"); +} void main() async { setPathUrlStrategy(); WidgetsFlutterBinding.ensureInitialized(); - // 1. Wrap SharedPreferences in try-catch try { await SharedPreferencesHelper.initial(); } catch (e) { debugPrint("Error initializing SharedPreferences: $e"); } - // 2. Wrap Tapsell in try-catch (High probability of crash here) try { await TapsellService.initialize(); } catch (e) { debugPrint("Error initializing Tapsell: $e"); } - // 3. Firebase Initialization + print('🚀 Starting Firebase Initialization...'); try { FirebaseMessaging.onBackgroundMessage(_onBackgroundMessage); - await Firebase.initializeApp( - options: DefaultFirebaseOptions.currentPlatform); - FirebasApi.initialNotifications(); - } catch (e) { - debugPrint('Error initializing Firebase: $e'); + + FirebaseOptions options = DefaultFirebaseOptions.currentPlatform; + + if (defaultTargetPlatform == TargetPlatform.android) { + print('🤖 Platform is Android, checking package info...'); + final packageInfo = await PackageInfo.fromPlatform(); + print("📦 RUNNING WITH PACKAGE: ${packageInfo.packageName}"); + + if (packageInfo.packageName == 'com.houshan.basa') { + options = DefaultFirebaseOptions.androidHoushan; + print("🔥 Using 'androidHoushan' options (Basa)"); + } else if (packageInfo.packageName == 'com.houshan.hoshan') { + options = DefaultFirebaseOptions.androidHoushanMain; + print("🔥 Using 'androidHoushanMain' options (Main)"); + } else { + print("⚠️ Unknown package name, using default options"); + } + } + + if (Firebase.apps.isEmpty) { + print('⏳ Calling Firebase.initializeApp...'); + try { + await Firebase.initializeApp(options: options); + print('✅ Firebase.initializeApp completed.'); + } catch (e) { + print('⚠️ Firebase.initializeApp failed: $e'); + if (e.toString().contains('duplicate-app')) { + print('ℹ️ Ignoring duplicate-app error and continuing...'); + } else { + rethrow; + } + } + } else { + print('ℹ️ Firebase already initialized. Skipping initialization.'); + } + + print('⏳ Calling FirebasApi.initialNotifications()...'); + await FirebasApi.initialNotifications(); + } catch (e, stack) { + print('❌❌ Error initializing Firebase: $e'); + print(stack); } - // 4. Downloader Initialization try { if (!kIsWeb) { await FlutterDownloader.initialize(debug: true, ignoreSsl: true); + print('✅ FlutterDownloader initialized'); } } catch (e) { debugPrint('Error initializing FlutterDownloader: $e'); } // Run App + print('✅✅ Main initialization completed! Starting app...'); if (kReleaseMode) { await SentryFlutter.init((options) { options.dsn = @@ -81,11 +121,11 @@ void main() async { options.sendDefaultPii = true; }, - // Init your App. appRunner: () => runApp(mainApp())); } else { runApp(mainApp()); } + print('🚀🚀 App started successfully!'); } MultiBlocProvider mainApp() { diff --git a/lib/ui/screens/auth/auth_page.dart b/lib/ui/screens/auth/auth_page.dart index fea54fc..4eaeccf 100644 --- a/lib/ui/screens/auth/auth_page.dart +++ b/lib/ui/screens/auth/auth_page.dart @@ -51,11 +51,17 @@ class _AuthPageState extends State { if (!kIsWeb) { final appLinks = AppLinks(); _linkSubscription = appLinks.uriLinkStream.listen((uri) async { - if (uri.scheme == 'houshan' && uri.host == 'auth') { + print('🔔 AuthPage Listener Received URI: $uri'); // Debug Log + + final isCustomScheme = uri.scheme == 'houshan' && uri.host == 'auth'; + final isWebLink = (uri.scheme == 'https' || uri.scheme == 'http') && + uri.host == 'basaweb.houshan.ai'; + + if (isCustomScheme || isWebLink) { final token = uri.queryParameters['token']; if (token != null && token.isNotEmpty) { if (kDebugMode) { - print('Deep link received while app open: $token'); + print('Deep link received: $uri'); } await AuthTokenStorage.setToken(token); await OnBoardingStorage.setAsSeen(); diff --git a/lib/ui/screens/auth/code/gift_code_screen.dart b/lib/ui/screens/auth/code/gift_code_screen.dart index 1a85bcf..b41f1c1 100644 --- a/lib/ui/screens/auth/code/gift_code_screen.dart +++ b/lib/ui/screens/auth/code/gift_code_screen.dart @@ -28,7 +28,7 @@ class _GiftCodeScreenState extends State { listener: (context, state) async { if (state is CheckCodeSucceess) { await context.read().getUserInfo(); - context.go(Routes.giftCredit, + context.go(Routes.home, extra: HomeArgs(message: state.message, freeCredit: 50)); } }, @@ -105,7 +105,7 @@ class _GiftCodeScreenState extends State { onPressed: () async { if (state is CheckCodeLoading) return; await context.read().getUserInfo(); - context.go(Routes.giftCredit, + context.go(Routes.home, extra: HomeArgs(freeCredit: 50)); }, child: Text( diff --git a/lib/ui/screens/auth/verification/verification_screen.dart b/lib/ui/screens/auth/verification/verification_screen.dart index e27d92c..4ce736e 100644 --- a/lib/ui/screens/auth/verification/verification_screen.dart +++ b/lib/ui/screens/auth/verification/verification_screen.dart @@ -96,7 +96,7 @@ class _VerificationScreenState extends State { context.read().changeState(AuthScreens.code); } else { await context.read().getUserInfo(); - context.go(Routes.giftCredit); + context.go(Routes.home); } } }, diff --git a/lib/ui/screens/chat/bloc/messages_bloc.dart b/lib/ui/screens/chat/bloc/messages_bloc.dart index ac02740..a74b4e1 100644 --- a/lib/ui/screens/chat/bloc/messages_bloc.dart +++ b/lib/ui/screens/chat/bloc/messages_bloc.dart @@ -58,6 +58,9 @@ class MessagesBloc extends Bloc { if (event is AddMessage) { final updatedList = List.from(state.messages); updatedList.add(event.message); + if (kDebugMode) { + print('📨 AddMessage: Total messages = ${updatedList.length}, Role = ${event.message.role}, File = ${event.message.file?.name}'); + } emit(MessagesSuccess(messages: updatedList)); // Copy the current state } diff --git a/lib/ui/screens/chat/chat_page.dart b/lib/ui/screens/chat/chat_page.dart index 9ff7da6..d4370b4 100644 --- a/lib/ui/screens/chat/chat_page.dart +++ b/lib/ui/screens/chat/chat_page.dart @@ -1,4 +1,4 @@ -// ignore_for_file: deprecated_member_use_from_same_package, use_build_context_synchronously +// ignore_for_file: deprecated_member_use_from_same_package, use_build_context_synchronously, avoid_print import 'dart:math'; @@ -74,6 +74,8 @@ class _ChatPageState extends State { final ValueNotifier selectedFile = ValueNotifier(null); final TextEditingController messageText = TextEditingController(); ValueNotifier maxLines = ValueNotifier(5); + late final ValueNotifier selectedBotId = + ValueNotifier(widget.chatArgs.bot.id ?? 1); void sendRequest( {required final String? message, @@ -82,9 +84,13 @@ class _ChatPageState extends State { final bool withOutNewMessage = false}) { final creditState = CreditModel( credit: UserInfoCubit.userInfoModel.credit ?? 0, - freeCredit: UserInfoCubit.userInfoModel.freeCredit ?? 0); - int credit = (creditState.freeCredit ?? 0) + (creditState.credit ?? 0); + freeCredit: UserInfoCubit.userInfoModel.freeCredit ?? 0, + giftCredit: UserInfoCubit.userInfoModel.gift_credit ?? 0); + int credit = (creditState.freeCredit ?? 0) + + (creditState.credit ?? 0) + + (creditState.giftCredit ?? 0); if (credit < widget.chatArgs.bot.cost!) { + print("crdit= $credit, cost=${widget.chatArgs.bot.cost}"); DialogHandler(context: context).showUpgradeCredit(); messageText.text = message ?? ''; @@ -92,14 +98,26 @@ class _ChatPageState extends State { } if (!withOutNewMessage) { + if (kDebugMode) { + print( + '📤 Sending message: text="${message ?? ""}", file=${file?.name}'); + } + + final messageContent = []; + + if (message != null && message.isNotEmpty) { + messageContent.add(Content(type: 'text', text: message)); + } + + if (file != null && (message == null || message.isEmpty)) { + messageContent.add(Content(type: 'text', text: '')); + } + context.read().add(AddMessage( message: Messages( role: 'human', id: 'hero', - content: [ - if (message != null && message.isNotEmpty) - Content(type: 'text', text: message) - ], + content: messageContent, query: message, file: file, retry: retry))); @@ -107,7 +125,7 @@ class _ChatPageState extends State { context.read().execute( request: SendMessageModel( - botId: bot.id, + botId: selectedBotId.value, file: file, id: chatId, messageId: 'hero', @@ -474,69 +492,209 @@ class _ChatPageState extends State { ? const Radius.circular(0) : const Radius.circular(10))), padding: const EdgeInsets.all(8), - child: message.content != null - ? Column( - crossAxisAlignment: - CrossAxisAlignment.end, - children: [ - if (message.file != null) - message.file!.name.isVideo() - ? GestureDetector( - onTap: () => DialogHandler( - context: context) - .showVideoHero( - url: message - .file!.path), - child: Container( - constraints: - const BoxConstraints( - maxWidth: double.infinity, - ), - child: VideoThumbnailWidget( - videoUrl: - message.file!.path), - ), - ) - : message.file!.name.isImage() - ? GestureDetector( - onTap: () => - DialogHandler( + child: + (message.content != null && + message.content!.isNotEmpty) || + message.file != null + ? Column( + crossAxisAlignment: + CrossAxisAlignment.end, + children: [ + Builder( + builder: (context) { + if (kDebugMode && + !message.fromBot!) { + print( + '👤 User message: id=${message.id}, file=${message.file?.name}, content=${message.content?.length}'); + } + return const SizedBox.shrink(); + }, + ), + if (message.file != null) + Stack( + children: [ + message.file!.name.isVideo() + ? GestureDetector( + onTap: () => DialogHandler( context: context) - .showImageHero( - image: message + .showVideoHero( + url: message .file! .path), - child: AspectRatio( - aspectRatio: 1 / 1, - child: Container( + child: Container( + constraints: + const BoxConstraints( + maxWidth: double + .infinity, + ), + child: VideoThumbnailWidget( + videoUrl: + message + .file! + .path), + ), + ) + : message.file!.name + .isImage() + ? GestureDetector( + onTap: () => DialogHandler( + context: + context) + .showImageHero( + image: message + .file! + .path), + child: + AspectRatio( + aspectRatio: + 1 / 1, + child: + Container( + constraints: + const BoxConstraints( + maxWidth: double + .infinity, + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(8), + child: CustomeImage( + src: message + .file! + .path, + fit: BoxFit + .cover, + )), + ), + ), + ) + : message.file!.name + .isAudio() + ? Player( + fileUrl: + message + .file! + .path, + inMessages: + true, + ) + : Container( + decoration: BoxDecoration( + color: context + .read< + ThemeModeCubit>() + .isDark() + ? AppColors.black[ + 900] + : AppColors + .gray + .defaultShade, + borderRadius: + BorderRadius.circular( + 10)), + padding: + const EdgeInsets + .all( + 8), + constraints: + const BoxConstraints( + minHeight: + 64), + child: Row( + children: [ + SizedBox( + child: message.file!.name.isDocument() + ? const Icon(CupertinoIcons.doc) + : const SizedBox.shrink(), + ), + Expanded( + child: + Padding( + padding: const EdgeInsets + .symmetric( + horizontal: + 12.0), + child: + Text( + message + .file! + .name, + textDirection: message.file!.name.startsWithEnglish() + ? TextDirection.ltr + : TextDirection.rtl, + style: + const TextStyle(fontSize: 16), + overflow: + TextOverflow.ellipsis, + maxLines: + 2, + ), + )), + ], + ), + ), + if (message.id == 'hero') + Positioned.fill( + child: Container( + decoration: + BoxDecoration( + color: Colors.black + .withOpacity(0.5), + borderRadius: + BorderRadius + .circular(8), + ), + child: Center( + child: + CircularProgressIndicator( + color: Colors.white, + ), + ), + ), + ), + ], + ), + if (message.content != null && + message.content!.isNotEmpty) + ...List.generate( + message.content!.length, + (index) { + final content = + message.content![index]; + return Column( + children: [ + if (content.audioUrl != + null) + Player( + fileUrl: content + .audioUrl! + .url ?? + '', + inMessages: true, + ), + if (content.imageUrl != + null) + Container( constraints: const BoxConstraints( maxWidth: double.infinity, ), - child: ClipRRect( - borderRadius: - BorderRadius - .circular( - 8), - child: - CustomeImage( - src: message - .file!.path, - fit: BoxFit - .cover, - )), + child: AspectRatio( + aspectRatio: 1 / 1, + child: ImageNetwork( + url: content + .imageUrl + ?.url ?? + '', + showHero: true, + radius: 10, + ), + ), ), - ), - ) - : message.file!.name.isAudio() - ? Player( - fileUrl: message - .file!.path, - inMessages: true, - ) - : Container( + if (content.pdfUrl != + null) + Container( decoration: BoxDecoration( color: context .read< @@ -561,16 +719,10 @@ class _ChatPageState extends State { 64), child: Row( children: [ - SizedBox( - child: message - .file! - .name - .isDocument() - ? const Icon( - CupertinoIcons - .doc) - : const SizedBox - .shrink(), + const SizedBox( + child: Icon( + CupertinoIcons + .doc), ), Expanded( child: @@ -580,11 +732,14 @@ class _ChatPageState extends State { horizontal: 12.0), child: Text( - message.file! - .name, - textDirection: message - .file! - .name + content.pdfUrl! + .url + ?.split( + '/') + .last ?? + '', + textDirection: (content.pdfUrl!.url?.split('/').last ?? + '') .startsWithEnglish() ? TextDirection .ltr @@ -602,307 +757,86 @@ class _ChatPageState extends State { ], ), ), - ...List.generate( - message.content!.length, - (index) { - final content = - message.content![index]; - return Column( - children: [ - if (content.audioUrl != null) - Player( - fileUrl: - content.audioUrl!.url ?? - '', - inMessages: true, - ), - if (content.imageUrl != null) - Container( - constraints: - const BoxConstraints( - maxWidth: double.infinity, - ), - child: AspectRatio( - aspectRatio: 1 / 1, - child: ImageNetwork( - url: content.imageUrl - ?.url ?? - '', - showHero: true, - radius: 10, - ), - ), - ), - if (content.pdfUrl != null) - Container( - decoration: BoxDecoration( - color: context - .read< - ThemeModeCubit>() - .isDark() - ? AppColors - .black[900] - : AppColors.gray - .defaultShade, - borderRadius: - BorderRadius - .circular(10)), - padding: - const EdgeInsets.all(8), - constraints: - const BoxConstraints( - minHeight: 64), - child: Row( - children: [ - const SizedBox( - child: Icon( - CupertinoIcons - .doc), - ), - Expanded( - child: Padding( + if (content.audioUrl == null && + content.imageUrl == + null && + content.pdfUrl == + null) + Padding( padding: const EdgeInsets .symmetric( horizontal: - 12.0), - child: Text( - content.pdfUrl!.url - ?.split('/') - .last ?? - '', - textDirection: (content - .pdfUrl! - .url - ?.split( - '/') - .last ?? - '') - .startsWithEnglish() - ? TextDirection - .ltr - : TextDirection - .rtl, - style: - const TextStyle( - fontSize: - 16), - overflow: - TextOverflow - .ellipsis, - maxLines: 2, - ), - )), - ], - ), - ), - if (content.audioUrl == null && - content.imageUrl == null && - content.pdfUrl == null) - Padding( - padding: const EdgeInsets - .symmetric( - horizontal: 8.0), - child: Builder( - builder: (context) { - directionName - .value = content - .text != - null && - content.text! - .startsWithEnglish() - ? "LTR" - : 'RTL'; - return DefaultMarkdownText( - key: markdownKey, - text: content.text ?? - '', - fromBot: - message.fromBot!, - color: message - .fromBot! - ? Theme.of( - context) - .colorScheme - .onSurface - : Colors.white); - }), - ) - ], - ); - }, - ), - if (message.fromBot!) - Padding( - padding: const EdgeInsets.only( - top: 8.0, left: 8.0), - child: Row( - mainAxisAlignment: - MainAxisAlignment - .spaceBetween, - children: [ - ValueListenableBuilder( - valueListenable: - directionName, - builder: (context, dir, _) { - return InkWell( - onTap: () { - directionName - .value = markdownKey - .currentState - ?.changeDirection() ?? - 'RTL'; - }, - child: Container( - padding: - const EdgeInsets - .symmetric( - horizontal: - 12, - vertical: 4), - decoration: BoxDecoration( - borderRadius: - BorderRadius - .circular( - 4), - color: context - .read< - ThemeModeCubit>() - .isDark() - ? AppColors - .black[ - 900] - : AppColors - .primaryColor[ - 50]), - child: Text( - dir, - style: TextStyle( - color: Theme.of( - context) - .colorScheme - .primary), - ), - ), - ); - }), - Row( - mainAxisSize: - MainAxisSize.min, + 8.0), + child: Builder( + builder: + (context) { + directionName + .value = content + .text != + null && + content + .text! + .startsWithEnglish() + ? "LTR" + : 'RTL'; + return DefaultMarkdownText( + key: + markdownKey, + text: content + .text ?? + '', + fromBot: message + .fromBot!, + color: message + .fromBot! + ? Theme.of( + context) + .colorScheme + .onSurface + : Colors + .white); + }), + ) + ], + ); + }, + ), + if (message.fromBot!) + Padding( + padding: const EdgeInsets.only( + top: 8.0, left: 8.0), + child: Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, children: [ - BlocProvider< - LikeMessageCubit>( - create: (context) => - LikeMessageCubit() - ..getLike( - like: message - .like), - child: BlocBuilder< - LikeMessageCubit, - LikeMessageState>( + ValueListenableBuilder( + valueListenable: + directionName, builder: - (context, state) { - return DefaultPlaceHolder( - enabled: state - is LikeMessageLoading, - child: Row( - children: [ - GestureDetector( - onTap: - () async { - await context.read().setLike( - like: state - is LikeMessageLiked - ? null - : true, - chatId: - chatId!, - messageId: - message.id!); - }, - child: - Padding( - padding: const EdgeInsets - .only( - right: - 20.0), - child: - SizedBox( - width: 16, - height: - 16, - child: state - is LikeMessageLiked - ? Assets - .icon - .bold - .like - .svg(color: AppColors.green.defaultShade) - : Assets.icon.outline.like.svg(color: Theme.of(context).colorScheme.primary), - ), - ), - ), - GestureDetector( - onTap: - () async { - await context.read().setLike( - like: state - is LikeMessageDisLiked - ? null - : false, - chatId: - chatId!, - messageId: - message.id!); - }, - child: - Padding( - padding: const EdgeInsets - .only( - right: - 20.0), - child: - SizedBox( - width: 16, - height: - 16, - child: state - is LikeMessageDisLiked - ? Assets - .icon - .bold - .dislike - .svg(color: AppColors.red.defaultShade) - : Assets.icon.outline.dislike.svg(color: Theme.of(context).colorScheme.primary), - ), - ), - ), - ], - ), - ); - }, - ), - ), - if (widget.chatArgs.bot - .tool != - null && - !widget - .chatArgs.bot.tool!) - MorePopupMenuHandler( - context: context) - .morePopUpMenu( - child: Padding( - padding: - const EdgeInsets - .only( - right: - 20.0), - child: - CircleIconBtn( - icon: Assets - .icon - .outline - .magicpen, + (context, dir, _) { + return InkWell( + onTap: () { + directionName + .value = markdownKey + .currentState + ?.changeDirection() ?? + 'RTL'; + }, + child: Container( + padding: + const EdgeInsets + .symmetric( + horizontal: + 12, + vertical: + 4), + decoration: BoxDecoration( + borderRadius: + BorderRadius + .circular( + 4), color: context .read< ThemeModeCubit>() @@ -911,166 +845,290 @@ class _ChatPageState extends State { .black[ 900] : AppColors - .primaryColor[50], - iconColor: Theme.of( - context) - .colorScheme - .primary, - size: 28, - iconPadding: - const EdgeInsets - .all( - 6), - ), + .primaryColor[ + 50]), + child: Text( + dir, + style: TextStyle( + color: Theme.of( + context) + .colorScheme + .primary), ), - items: [ - PopUpMenuItemModel( - click: () { - try { - refreshQuestions - .value = - false; - sendRequest( - file: message - .file, - message: - 'خلاصه‌تر بنویس'); - } catch (e) { - if (kDebugMode) { - print( - 'Error is: $e'); - } - } - }, - popupMenuItem: PopupMenuItem( - value: 0, - child: MorePopupMenuHandler.morePopUpItem( + ), + ); + }), + Row( + mainAxisSize: + MainAxisSize.min, + children: [ + BlocProvider< + LikeMessageCubit>( + create: (context) => + LikeMessageCubit() + ..getLike( + like: message + .like), + child: BlocBuilder< + LikeMessageCubit, + LikeMessageState>( + builder: (context, + state) { + return DefaultPlaceHolder( + enabled: state + is LikeMessageLoading, + child: Row( + children: [ + GestureDetector( + onTap: + () async { + await context.read().setLike( + like: state is LikeMessageLiked + ? null + : true, + chatId: + chatId!, + messageId: + message.id!); + }, + child: + Padding( + padding: const EdgeInsets + .only( + right: + 20.0), + child: + SizedBox( + width: + 16, + height: + 16, + child: state is LikeMessageLiked + ? Assets.icon.bold.like.svg(color: AppColors.green.defaultShade) + : Assets.icon.outline.like.svg(color: Theme.of(context).colorScheme.primary), + ), + ), + ), + GestureDetector( + onTap: + () async { + await context.read().setLike( + like: state is LikeMessageDisLiked + ? null + : false, + chatId: + chatId!, + messageId: + message.id!); + }, + child: + Padding( + padding: const EdgeInsets + .only( + right: + 20.0), + child: + SizedBox( + width: + 16, + height: + 16, + child: state is LikeMessageDisLiked + ? Assets.icon.bold.dislike.svg(color: AppColors.red.defaultShade) + : Assets.icon.outline.dislike.svg(color: Theme.of(context).colorScheme.primary), + ), + ), + ), + ], + ), + ); + }, + ), + ), + if (widget.chatArgs.bot + .tool != + null && + !widget.chatArgs.bot + .tool!) + MorePopupMenuHandler( + context: + context) + .morePopUpMenu( + child: + Padding( + padding: const EdgeInsets + .only( + right: + 20.0), + child: + CircleIconBtn( icon: Assets .icon .outline - .eraser, - title: - 'خلاصه‌تر بنویس')), - ), - PopUpMenuItemModel( - click: () { - try { - refreshQuestions - .value = - false; - sendRequest( - file: message - .file, - message: - 'کامل بنویس'); - } catch (e) { - if (kDebugMode) { - print( - 'Error is: $e'); - } - } - }, - popupMenuItem: PopupMenuItem( - value: 1, - child: MorePopupMenuHandler.morePopUpItem( - icon: Assets - .icon - .outline - .edit2, - title: - 'کامل بنویس')), - ), - PopUpMenuItemModel( - popupMenuItem: PopupMenuItem( - value: 2, - child: MorePopupMenuHandler.morePopUpItem( - icon: Assets - .icon - .outline - .voiceCricle, - title: - 'لحن نوشته را تغییر بده')), - click: () async { - await BottomSheetHandler( - context) - .showStringList( - onSelect: - (value) { - try { - refreshQuestions.value = - false; - sendRequest( - file: message.file, - message: 'لحن نوشته را تغییر بده به $value'); - } catch (e) { - if (kDebugMode) { - print('Error is: $e'); - } - } - }, - title: 'انتخاب لحن نوشته', - values: [ - 'رسمی', - 'عامیانه', - 'دوستانه', - 'حرفه ای', - 'محاوره ای', - 'طنز', - 'جدی' - ]); - }, - ), - PopUpMenuItemModel( - popupMenuItem: PopupMenuItem( - value: 3, - child: MorePopupMenuHandler.morePopUpItem( - icon: Assets - .icon - .outline - .translate, - title: - 'ترجمه کن')), - click: () async { - await BottomSheetHandler( - context) - .showStringList( - onSelect: - (value) { - try { - refreshQuestions.value = - false; - sendRequest( - file: message.file, - message: 'زبان نوشته را تغییر بده به $value'); - } catch (e) { - if (kDebugMode) { - print('Error is: $e'); - } - } - }, - title: - 'انتخاب زبان', - values: [ - '🇮🇷 فارسی', - 'Arabic 🇸🇦', - 'Bengali 🇧🇩', - 'English 🇬🇧', - 'French 🇫🇷', - 'German 🇩🇪', - 'Hindi 🇮🇳', - 'Italian 🇮🇹' - ]); - }, - ), - ]), + .magicpen, + color: context + .read< + ThemeModeCubit>() + .isDark() + ? AppColors.black[ + 900] + : AppColors + .primaryColor[50], + iconColor: Theme.of( + context) + .colorScheme + .primary, + size: 28, + iconPadding: + const EdgeInsets + .all( + 6), + ), + ), + items: [ + PopUpMenuItemModel( + click: () { + try { + refreshQuestions + .value = + false; + sendRequest( + file: message + .file, + message: + 'خلاصه‌تر بنویس'); + } catch (e) { + if (kDebugMode) { + print( + 'Error is: $e'); + } + } + }, + popupMenuItem: PopupMenuItem( + value: 0, + child: MorePopupMenuHandler.morePopUpItem( + icon: Assets + .icon + .outline + .eraser, + title: + 'خلاصه‌تر بنویس')), + ), + PopUpMenuItemModel( + click: () { + try { + refreshQuestions + .value = + false; + sendRequest( + file: message + .file, + message: + 'کامل بنویس'); + } catch (e) { + if (kDebugMode) { + print( + 'Error is: $e'); + } + } + }, + popupMenuItem: PopupMenuItem( + value: 1, + child: MorePopupMenuHandler.morePopUpItem( + icon: Assets + .icon + .outline + .edit2, + title: + 'کامل بنویس')), + ), + PopUpMenuItemModel( + popupMenuItem: PopupMenuItem( + value: 2, + child: MorePopupMenuHandler.morePopUpItem( + icon: Assets + .icon + .outline + .voiceCricle, + title: + 'لحن نوشته را تغییر بده')), + click: + () async { + await BottomSheetHandler( + context) + .showStringList( + onSelect: + (value) { + try { + refreshQuestions.value = false; + sendRequest(file: message.file, message: 'لحن نوشته را تغییر بده به $value'); + } catch (e) { + if (kDebugMode) { + print('Error is: $e'); + } + } + }, + title: 'انتخاب لحن نوشته', + values: [ + 'رسمی', + 'عامیانه', + 'دوستانه', + 'حرفه ای', + 'محاوره ای', + 'طنز', + 'جدی' + ]); + }, + ), + PopUpMenuItemModel( + popupMenuItem: PopupMenuItem( + value: 3, + child: MorePopupMenuHandler.morePopUpItem( + icon: Assets + .icon + .outline + .translate, + title: + 'ترجمه کن')), + click: + () async { + await BottomSheetHandler( + context) + .showStringList( + onSelect: + (value) { + try { + refreshQuestions.value = false; + sendRequest(file: message.file, message: 'زبان نوشته را تغییر بده به $value'); + } catch (e) { + if (kDebugMode) { + print('Error is: $e'); + } + } + }, + title: + 'انتخاب زبان', + values: [ + '🇮🇷 فارسی', + 'Arabic 🇸🇦', + 'Bengali 🇧🇩', + 'English 🇬🇧', + 'French 🇫🇷', + 'German 🇩🇪', + 'Hindi 🇮🇳', + 'Italian 🇮🇹' + ]); + }, + ), + ]), + ], + ), ], ), - ], - ), - ) - ], - ) - : null, + ) + ], + ) + : null, ), const SizedBox( width: 4, @@ -1178,6 +1236,9 @@ class _ChatPageState extends State { Widget aNewMessage() { return BlocConsumer( builder: (context, state) { + if (kDebugMode) { + print('🔄 aNewMessage state: ${state.runtimeType}'); + } if (state is ReceiveMessageOnResponsing) { return Container( alignment: Alignment.centerLeft, @@ -1999,6 +2060,114 @@ class _ChatPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.end, children: [ + if (bot.tool == null || !bot.tool!) + ValueListenableBuilder( + valueListenable: selectedBotId, + builder: (context, currentBotId, _) { + String botName = currentBotId == 1 + ? 'ChatGPT' + : currentBotId == 2 + ? 'Gemini' + : currentBotId == 4 + ? 'Grok' + : 'Claude'; + + return PopupMenuButton( + color: + Theme.of(context).colorScheme.surface, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + offset: const Offset(0, -10), + onSelected: (int value) { + print('🤖 Bot selected: $value'); + selectedBotId.value = value; + }, + itemBuilder: (BuildContext context) => + >[ + PopupMenuItem( + value: 1, + child: Text( + 'ChatGPT', + style: AppTextStyles.body5.copyWith( + fontWeight: currentBotId == 1 + ? FontWeight.bold + : FontWeight.normal, + ), + ), + ), + PopupMenuItem( + value: 2, + child: Text( + 'Gemini', + style: AppTextStyles.body5.copyWith( + fontWeight: currentBotId == 2 + ? FontWeight.bold + : FontWeight.normal, + ), + ), + ), + PopupMenuItem( + value: 4, + child: Text( + 'Grok', + style: AppTextStyles.body5.copyWith( + fontWeight: currentBotId == 4 + ? FontWeight.bold + : FontWeight.normal, + ), + ), + ), + PopupMenuItem( + value: 9, + child: Text( + 'Claude', + style: AppTextStyles.body5.copyWith( + fontWeight: currentBotId == 9 + ? FontWeight.bold + : FontWeight.normal, + ), + ), + ), + ], + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Theme.of(context) + .colorScheme + .primary + .withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + botName, + style: AppTextStyles.body6.copyWith( + color: Theme.of(context) + .colorScheme + .primary, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(width: 4), + Icon( + Icons.keyboard_arrow_up, + size: 16, + color: Theme.of(context) + .colorScheme + .primary, + ), + ], + ), + ), + ); + }, + ), + if (bot.tool == null || !bot.tool!) + const SizedBox(width: 12), ValueListenableBuilder( valueListenable: webSearch, builder: (context, isWebSearchEnabled, _) { @@ -2163,8 +2332,8 @@ class _ChatPageState extends State { const EdgeInsets.only( right: 8.0), child: CircleIconBtn( - icon: Assets - .icon.outline.cardAdd, + icon: Assets.icon.outline + .folderAdd, color: Theme.of(context) .colorScheme .primary, diff --git a/lib/ui/screens/chat/cubit/receive_message_cubit.dart b/lib/ui/screens/chat/cubit/receive_message_cubit.dart index 69906bb..d578366 100644 --- a/lib/ui/screens/chat/cubit/receive_message_cubit.dart +++ b/lib/ui/screens/chat/cubit/receive_message_cubit.dart @@ -54,7 +54,6 @@ class ReceiveMessageCubit extends Cubit { AiResponseModel aiResponseModel = AiResponseModel(); await scrollToEnd(); try { - // Call your streaming message function and yield states accordingly await for (String message in request.tool! ? ChatbotRepository.sendMessageTool(request) : ChatbotRepository.sendMessage(request)) { @@ -78,7 +77,7 @@ class ReceiveMessageCubit extends Cubit { for (String json in jsonStrings) { json = json.replaceAll( - RegExp(r'[{}"]'), ''); // Remove braces and quotes + RegExp(r'[{}"]'), ''); List pairs = json.split(', '); for (String pair in pairs) { if (pair.contains('chat_id:')) { @@ -94,10 +93,10 @@ class ReceiveMessageCubit extends Cubit { pair.replaceAll('human_message_id: ', ''); } else if (pair.contains('credit:')) { jsonMap['credit'] = pair.replaceAll('credit: ', ''); - } else if (pair.contains('credit:')) { - jsonMap['credit'] = pair.replaceAll('credit: ', ''); } else if (pair.contains('free:')) { jsonMap['free'] = pair.replaceAll('free: ', ''); + } else if (pair.contains('gift:')) { + jsonMap['gift'] = pair.replaceAll('gift: ', ''); } else if (pair.contains('human_message_created_at:')) { jsonMap['human_message_created_at'] = pair.replaceAll('human_message_created_at: ', ''); @@ -122,8 +121,16 @@ class ReceiveMessageCubit extends Cubit { final res = AiResponseModel.fromJson(jsonMap); if (res.content != null) { - result += res.content ?? ''; // Add each content to the list - if (!(res.content!.startsWith('http'))) { + result += res.content ?? ''; + if (res.content!.startsWith('http')) { + if (res.content!.isImage()) { + emit(ReceiveMessageOnResponsing(text: 'در حال آماده‌سازی تصویر')); + } else if (res.content!.isAudio()) { + emit(ReceiveMessageOnResponsing(text: 'در حال آماده‌سازی صوت')); + } else if (res.content!.isDocument()) { + emit(ReceiveMessageOnResponsing(text: 'در حال آماده‌سازی فایل')); + } + } else { emit(ReceiveMessageOnResponsing(text: result)); } } @@ -138,10 +145,14 @@ class ReceiveMessageCubit extends Cubit { content: result.isEmpty ? res.content : result, freeCredit: res.freeCredit, humanMessageId: res.humanMessageId); - // Yield the received message line by line } await scrollToEnd(); + if (aiResponseModel.content != null && + aiResponseModel.content!.startsWith('http')) { + await Future.delayed(const Duration(milliseconds: 500)); + } + if (aiResponseModel.error ?? true) { emit(ReceiveMessageOnFail( oldHumanMessageId: request.messageId ?? '', diff --git a/lib/ui/screens/gmedia/chats/photo_chat_page.dart b/lib/ui/screens/gmedia/chats/photo_chat_page.dart index bcb1427..afd631c 100644 --- a/lib/ui/screens/gmedia/chats/photo_chat_page.dart +++ b/lib/ui/screens/gmedia/chats/photo_chat_page.dart @@ -126,6 +126,20 @@ class _PhotoChatPageState extends State { .state is MediaGResponseLoading) { return; } + + context.read().add(AddMessage( + message: Messages( + file: image.xFile, + content: [ + Content( + imageUrl: FileUrl( + url: image.xFile.path)) + ], + createdAt: DateTime.now() + .toIso8601String(), + id: 'hero', + role: 'user'))); + context.read().request( SendMessageModel( ghost: isGhost.value, @@ -164,11 +178,30 @@ class _PhotoChatPageState extends State { .state is MediaGResponseLoading) { return; } + + final query = _query.text; + + if (query.isNotEmpty) { + context.read().add( + AddMessage( + message: Messages( + query: query, + content: [ + Content( + type: 'text', + text: query) + ], + createdAt: DateTime.now() + .toIso8601String(), + id: 'hero', + role: 'user'))); + } + context.read().request( SendMessageModel( messageId: DateTime.now() .toIso8601String(), - query: _query.text, + query: query, botId: widget.chatArgs.bot.id, ghost: isGhost.value, id: chatId)); @@ -255,22 +288,50 @@ class _PhotoChatPageState extends State { child: CircularProgressIndicator(), ); } - // if (state is MessagesSuccess) { final m = mState.messages; List> allMessages = groupMessages(m); - // فیلتر کردن گروه‌هایی که حداقل یک پیام با image_url دارند - List> messages = allMessages.where((group) { - return group.any((msg) => - msg.content?.any((c) => c.imageUrl != null) ?? false); - }).toList(); + + List> messages = ptp + ? allMessages.where((group) { + return group.any((msg) => + msg.content?.any((c) => c.imageUrl != null) ?? + false); + }).toList() + : allMessages; return BlocConsumer( listener: (context, state) async { if (state is MediaGResponseSucess) { + final messages = + context.read().state.messages; + final heroMessage = messages.firstWhere( + (m) => m.id == 'hero', + orElse: () => Messages(id: null, role: 'user'), + ); + + if (heroMessage.id != null) { + context + .read() + .add(DeleteMessageWithId(messageId: 'hero')); + } + context.read().add(AddMessage( message: Messages( query: state.query, file: state.file, + content: state.file != null + ? [ + Content( + imageUrl: + FileUrl(url: state.file!.path)) + ] + : state.query != null && + state.query!.isNotEmpty + ? [ + Content( + type: 'text', text: state.query) + ] + : null, createdAt: DateTime.now().toIso8601String(), error: state.response.error, id: state.response.humanMessageId, @@ -300,11 +361,16 @@ class _PhotoChatPageState extends State { chatId = state.response.chatId; } + + if (state is MediaGResponseFail) { + context + .read() + .add(DeleteMessageWithId(messageId: 'hero')); + } }, builder: (context, state) { final newMaxSize = messages.length + 1; - // استفاده از postFrameCallback برای جلوگیری از setState در build if (maxSize.value != newMaxSize) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { @@ -318,7 +384,6 @@ class _PhotoChatPageState extends State { return ListView( physics: const BouncingScrollPhysics(), children: [ - // Bot info section Column( children: [ const SizedBox( @@ -497,7 +562,6 @@ class _PhotoChatPageState extends State { ), ], ), - // Messages list ...messages.asMap().entries.map((entry) { final index = entry.key; final ms = entry.value; @@ -545,30 +609,59 @@ class _PhotoChatPageState extends State { 16)), child: AspectRatio( aspectRatio: 3 / 4, - child: user!.file != null - ? ClipRRect( - borderRadius: - BorderRadius - .circular( - 12), - child: CustomeImage( - src: user - .file!.path, - fit: BoxFit.cover, - )) - : ImageNetwork( - width: - double.infinity, - height: - double.infinity, - radius: 12, - showHero: true, - url: user - .content - ?.first - .imageUrl - ?.url ?? - ''), + child: Stack( + fit: StackFit.expand, + children: [ + user!.file != null + ? ClipRRect( + borderRadius: + BorderRadius + .circular( + 12), + child: + CustomeImage( + src: user + .file! + .path, + fit: BoxFit + .cover, + )) + : ImageNetwork( + width: double + .infinity, + height: double + .infinity, + radius: 12, + showHero: true, + url: user + .content + ?.first + .imageUrl + ?.url ?? + ''), + if (user.id == 'hero') + Container( + decoration: + BoxDecoration( + color: Colors + .black + .withOpacity( + 0.5), + borderRadius: + BorderRadius + .circular( + 12), + ), + child: Center( + child: + CircularProgressIndicator( + color: Colors + .white, + ), + ), + ), + ], + ), ), ), after: Container( @@ -684,52 +777,83 @@ class _PhotoChatPageState extends State { ?.url ?? ''), ) - : Padding( - padding: - const EdgeInsets.all( - 16.0), - child: Directionality( - textDirection: - TextDirection.rtl, - child: Scrollbar( - thumbVisibility: true, - trackVisibility: true, - interactive: true, - controller: - yourScrollController, - radius: const Radius - .circular(16), - child: - SingleChildScrollView( - controller: - yourScrollController, - physics: - const BouncingScrollPhysics(), - child: Padding( - padding: - const EdgeInsets - .only( - left: - 8.0), + : Stack( + children: [ + Padding( + padding: + const EdgeInsets + .all(16.0), + child: Directionality( + textDirection: + TextDirection + .rtl, + child: Scrollbar( + thumbVisibility: + true, + trackVisibility: + true, + interactive: true, + controller: + yourScrollController, + radius: + const Radius + .circular( + 16), child: - SelectableText( - user.query ?? - '', + SingleChildScrollView( + controller: + yourScrollController, + physics: + const BouncingScrollPhysics(), + child: Padding( + padding: + const EdgeInsets + .only( + left: + 8.0), + child: + SelectableText( + user.query ?? + '', - style: - AppTextStyles + style: AppTextStyles .body4 .copyWith( - color: Colors - .white, + color: Colors + .white, + ), + // overflow: TextOverflow.ellipsis, + // textAlign: TextAlign.justify, + ), ), - // overflow: TextOverflow.ellipsis, - // textAlign: TextAlign.justify, ), ), ), ), - ), + if (user.id == 'hero') + Positioned.fill( + child: Container( + decoration: + BoxDecoration( + color: Colors + .black + .withOpacity( + 0.5), + borderRadius: + BorderRadius + .circular( + 12), + ), + child: Center( + child: + CircularProgressIndicator( + color: Colors + .white, + ), + ), + ), + ), + ], ), ), ], diff --git a/lib/ui/screens/gmedia/chats/video_chat_page.dart b/lib/ui/screens/gmedia/chats/video_chat_page.dart index 024b38a..4016a96 100644 --- a/lib/ui/screens/gmedia/chats/video_chat_page.dart +++ b/lib/ui/screens/gmedia/chats/video_chat_page.dart @@ -316,7 +316,6 @@ class _TtaPageState extends State { 1 + (state is MediaGResponseLoading ? 1 : 0); - // استفاده از postFrameCallback برای جلوگیری از setState در build if (maxSize.value != newMaxSize) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { @@ -653,7 +652,7 @@ class _TtaPageState extends State { BorderRadius.circular( 12), child: ChatVideoPlayer( - src: ai?.content?.first + src: ai.content?.first .videoUrl?.url ?? '', custome: diff --git a/lib/ui/screens/main/home/bloc/cubit/posts_cubit.dart b/lib/ui/screens/main/home/bloc/cubit/posts_cubit.dart index bb69811..bdffde2 100644 --- a/lib/ui/screens/main/home/bloc/cubit/posts_cubit.dart +++ b/lib/ui/screens/main/home/bloc/cubit/posts_cubit.dart @@ -15,7 +15,7 @@ class PostsCubit extends Cubit { // Disabled enabled.json check - always show posts // try { // final check = await CoursesServices.dio.get('/enabled.json'); - // print('🟢 enabled.json: ${check.data}'); + // print('� enabled.json: ${check.data}'); // if (check.data == false) { // emit(PostsFail()); // return; diff --git a/lib/ui/screens/main/home/home_screen.dart b/lib/ui/screens/main/home/home_screen.dart index 5c0740f..5348f66 100644 --- a/lib/ui/screens/main/home/home_screen.dart +++ b/lib/ui/screens/main/home/home_screen.dart @@ -32,7 +32,6 @@ import 'package:hoshan/ui/theme/colors.dart'; import 'package:hoshan/ui/theme/cubit/theme_mode_cubit.dart'; import 'package:hoshan/ui/theme/responsive.dart'; import 'package:hoshan/ui/theme/text.dart'; -import 'package:hoshan/ui/widgets/ai_Banner.dart'; import 'package:hoshan/ui/widgets/components/bot/bot_grid_card.dart'; import 'package:hoshan/ui/widgets/components/bot/bot_grid_card_placeholder.dart'; import 'package:hoshan/ui/widgets/components/bot/tool_card.dart'; diff --git a/lib/ui/screens/splash/splash_page.dart b/lib/ui/screens/splash/splash_page.dart index d268c84..d7dff26 100644 --- a/lib/ui/screens/splash/splash_page.dart +++ b/lib/ui/screens/splash/splash_page.dart @@ -31,8 +31,10 @@ class _SplashPageState extends State { @override void initState() { super.initState(); + print('🎯 SplashPage: initState called'); WidgetsBinding.instance.addPostFrameCallback((_) async { + print('🎯 SplashPage: PostFrameCallback started'); bool hasDeepLink = false; if (!kIsWeb) { try { @@ -92,11 +94,14 @@ class _SplashPageState extends State { if (!hasDeepLink) { String authToken = AuthTokenStorage.getToken(); + print('🎯 SplashPage: Auth token = ${authToken.isEmpty ? "EMPTY" : "EXISTS"}'); if (mounted) { if (authToken.isEmpty) { + print('🎯 SplashPage: Navigating to auth page'); FirebasApi.deleteToken(); context.go(Routes.auth); } else { + print('🎯 SplashPage: Getting user info'); context.read().getUserInfo(); } } diff --git a/lib/ui/widgets/family_banner.dart b/lib/ui/widgets/family_banner.dart index be464af..0adfddb 100644 --- a/lib/ui/widgets/family_banner.dart +++ b/lib/ui/widgets/family_banner.dart @@ -10,23 +10,16 @@ class FamilyBanner extends StatelessWidget { @override Widget build(BuildContext context) { + final hasParent = UserInfoCubit.userInfoModel.parent != null; + + if (hasParent) { + return const SizedBox.shrink(); + } + return Padding( padding: const EdgeInsets.all(16.0), child: GestureDetector( onTap: () { - final hasParent = UserInfoCubit.userInfoModel.parent != null; - - if (hasParent) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text( - 'شما عضو یک خانواده هستید و نمی‌توانید خانواده جدید اضافه کنید', - ), - ), - ); - return; - } - Navigator.push( context, MaterialPageRoute(