swot category added

This commit is contained in:
mohamadmahdi jebeli 2025-07-01 14:54:20 +03:30
parent 399374530e
commit 75241cf93e
35 changed files with 1173 additions and 1137 deletions

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@ -0,0 +1,3 @@
<svg width="29" height="29" viewBox="0 0 29 29" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.7429 26.6555C22.0896 26.5238 23.4338 26.3678 24.7749 26.1875C25.2102 26.1304 25.6142 25.9303 25.9234 25.6186C26.2326 25.3069 26.4294 24.9012 26.4829 24.4655C26.6429 23.1795 26.8009 21.8415 26.9289 20.4715M9.01291 2.28748C7.66619 2.41911 6.32197 2.57513 4.98091 2.75548C4.54552 2.81201 4.14127 3.01165 3.83173 3.32301C3.52218 3.63436 3.3249 4.03977 3.27091 4.47548C3.10196 5.80513 2.95328 7.13729 2.82491 8.47148M20.7429 2.28748C22.1229 2.41948 23.4729 2.58348 24.7749 2.75548C25.2099 2.81242 25.6137 3.01225 25.9229 3.32356C26.232 3.63488 26.429 4.04006 26.4829 4.47548C26.6429 5.76348 26.8009 7.10148 26.9289 8.47148M9.01291 26.6555C7.66632 26.5226 6.32212 26.3666 4.98091 26.1875C4.54522 26.1308 4.14075 25.9309 3.83117 25.6192C3.52159 25.3074 3.32449 24.9015 3.27091 24.4655C3.10198 23.1365 2.9533 21.805 2.82491 20.4715M14.8769 10.7955V8.22548M14.8769 10.7955C16.4509 10.7955 18.0849 10.7955 19.5349 10.9835C20.1429 11.0593 20.7095 11.3317 21.1485 11.7591C21.5875 12.1866 21.8749 12.7457 21.9669 13.3515C22.0849 14.1575 22.0849 14.9755 22.0849 16.3675C22.0849 17.7615 22.0849 18.5795 21.9649 19.3835C21.8731 19.9887 21.5863 20.5473 21.1481 20.9747C20.7099 21.4021 20.1442 21.6748 19.5369 21.7515C18.0869 21.9415 16.4529 21.9415 14.8789 21.9415C13.3069 21.9415 11.6729 21.9415 10.2229 21.7515C9.6149 21.6756 9.0483 21.4033 8.60931 20.9758C8.17031 20.5484 7.88294 19.9892 7.79091 19.3835C7.67291 18.5795 7.67291 17.7615 7.67291 16.3675C7.67291 14.9735 7.67291 14.1575 7.79091 13.3515C7.88294 12.7457 8.17031 12.1866 8.60931 11.7591C9.0483 11.3317 9.6149 11.0593 10.2229 10.9835C11.6729 10.7955 13.3049 10.7955 14.8769 10.7955ZM17.4709 16.7555V15.9795M12.2769 16.7555V15.9795M14.8769 8.19948C16.1089 8.19948 16.8029 7.50548 16.8029 6.27348C16.8029 5.04148 16.1089 4.35148 14.8769 4.35148C13.6449 4.35148 12.9529 5.04548 12.9529 6.27548C12.9529 7.50548 13.6469 8.19948 14.8769 8.19948Z" stroke="#195D80" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -18,6 +18,7 @@ import 'package:didvan/services/media/media.dart';
import 'package:didvan/services/notification/firebase_api.dart';
import 'package:didvan/services/notification/notification_service.dart';
import 'package:didvan/utils/my_custom_scroll_behavior.dart';
import 'package:didvan/views/ai/ai_chat_state.dart';
import 'package:didvan/views/ai/ai_state.dart';
import 'package:didvan/views/ai/bot_assistants_state.dart';
import 'package:didvan/views/ai/history_ai_chat_state.dart';
@ -35,24 +36,6 @@ import 'package:sentry_flutter/sentry_flutter.dart';
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
// @pragma('vm:entry-point')
// Future _initPushNotification(RemoteMessage message) async {
// if (!kIsWeb) {
// await NotificationService.startListeningNotificationEvents();
// }
// await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
// // if (kDebugMode) {
// print("background: ${NotificationData.fromJson(message.data).toJson()}");
// // }
// try {
// NotificationService.showFirebaseNotification(message);
// } catch (e) {
// // if (kDebugMode) {
// print(e);
// // }
// }
// }
@pragma('vm:entry-point')
Future<void> _backgroundCallbackHomeWidget(Uri? uri) async {
await HomeWidget.saveWidgetData("uri", uri!.host);
@ -76,9 +59,9 @@ void main() async {
if (Platform.isAndroid) {
await FlutterDownloader.initialize(
debug:
true, // optional: set to false to disable printing logs to console (default: true)
true,
ignoreSsl:
true // option: set to false to disable working with http links (default: false)
true
);
}
} catch (e) {
@ -86,7 +69,6 @@ void main() async {
}
}
// FirebaseMessaging.onBackgroundMessage(_initPushNotification);
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform);
await FirebaseApi().initNotification();
@ -98,11 +80,7 @@ void main() async {
(options) {
options.dsn =
'https://a4cfcaa7d67471240d295c25c968d91d@o4508585857384448.ingest.de.sentry.io/4508585886548048';
// Set tracesSampleRate to 1.0 to capture 100% of transactions for tracing.
// We recommend adjusting this value in production.
options.tracesSampleRate = 1.0;
// The sampling rate for profiling is relative to tracesSampleRate
// Setting to 1.0 will profile 100% of sampled transactions:
options.profilesSampleRate = 1.0;
},
appRunner: () => runApp(const Didvan()),
@ -124,19 +102,13 @@ class _DidvanState extends State<Didvan> with WidgetsBindingObserver {
@override
void initState() {
// if (!kIsWeb) {
// NotificationService.startListeningNotificationEvents();
// }
WidgetsBinding.instance.addObserver(this);
// NotificationService.startListeningNotificationEvents();
super.initState();
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
// MediaService.audioPlayer.dispose();
if (MediaService.currentPodcast != null) {
MediaService.audioPlayer.dispose();
}
@ -151,7 +123,7 @@ class _DidvanState extends State<Didvan> with WidgetsBindingObserver {
if (state == AppLifecycleState.resumed) {
var r = await HomeWidget.getWidgetData("cRoute", defaultValue: '');
if (r!.toString() != Routes.splash) {
await HomeWidgetRepository.decideWhereToGo();
await HomeWidgetRepository.decideWhereToGo();
NotificationMessage? data = HomeWidgetRepository.data;
@ -188,12 +160,12 @@ class _DidvanState extends State<Didvan> with WidgetsBindingObserver {
ChangeNotifierProvider<AiState>(
create: (context) => AiState(),
),
ChangeNotifierProvider<AiChatState>(
create: (context) => AiChatState(),
),
ChangeNotifierProvider<BotAssistantsState>(
create: (context) => BotAssistantsState(),
),
// ChangeNotifierProvider<StoryViewerState>(
// create: (context) => StoryViewerState(),
// ),
],
child: Consumer<ThemeProvider>(
builder: (context, themeProvider, child) => Container(
@ -223,7 +195,6 @@ class _DidvanState extends State<Didvan> with WidgetsBindingObserver {
onGenerateRoute: (settings) =>
RouteGenerator.generateRoute(settings),
builder: BotToastInit(),
//1. call BotToastInit
navigatorObservers: [BotToastNavigatorObserver()],
initialRoute: "/",
localizationsDelegates: const [
@ -240,4 +211,4 @@ class _DidvanState extends State<Didvan> with WidgetsBindingObserver {
),
);
}
}
}

View File

@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
enum MediaType { image, video, gif }
// Represents a single story item (image, gif, or video)
class StoryItem {
final int id;
final String url;
@ -38,8 +37,6 @@ class StoryItem {
id: json['id'],
url: json['mediaUrl'],
media: mediaType,
// API doesn't provide duration for images/gifs, so we use a default.
// For videos, the player controller will determine the duration.
duration: const Duration(seconds: 10),
);
}
@ -57,9 +54,8 @@ class User {
});
}
// This class will wrap the API response and fit it into the UI's expected structure.
class UserStories {
final int id; // The main story ID from the API
final int id;
final User user;
final List<StoryItem> stories;
@ -76,14 +72,13 @@ class UserStories {
return UserStories(
id: json['id'],
user: User(
name: json['title'], // Using title directly from the API
name: json['title'],
profileImageUrl:
_mapCategoryToIcon(json['category']), // Mapping category to an icon
_mapCategoryToIcon(json['category']),
createdAt: json['createdAt'],
),
stories: items.map((item) {
final storyItem = StoryItem.fromJson(item);
// Check if this story item has been viewed.
if (completedIds.contains(storyItem.id)) {
storyItem.isViewed.value = true;
}

View File

@ -1,7 +1,6 @@
// ignore_for_file: depend_on_referenced_packages, avoid_web_libraries_in_flutter
import 'dart:async';
import 'dart:convert';
import 'package:didvan/models/ai/files_model.dart';
import 'package:didvan/services/storage/storage.dart';

View File

@ -20,7 +20,7 @@ class RequestHelper {
static const String _baseHomeUrl = '$baseUrl/home';
static const String _baseNewStats = '$baseUrl/home/statistic';
static const String _baseNewStatsSearch = '$baseUrl/home/statistic/search';
static const String checkHasPassword = '$_baseUserUrl/checkHasPassword';
static const String checkHasPassword = '$_baseUserUrl/checkHasPassword';
static const String mainPageContent = _baseHomeUrl;
static String searchAll({

View File

@ -206,7 +206,9 @@ class ActionSheetUtils {
data.backgroundColor ?? Theme.of(context).colorScheme.surface,
),
width: mediaQueryData.size.width * 0.8,
padding: const EdgeInsets.all(24.0),
// BEGIN: EDIT THIS LINE
padding: const EdgeInsets.fromLTRB(24, 16, 24, 24), // پدینگ بالا از 24 به 16 تغییر کرد
// END: EDIT THIS LINE
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
@ -397,8 +399,8 @@ class ActionSheetUtils {
),
),
Positioned(
right: 24,
top: 24,
right: 16,
top: 0,
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
@ -446,4 +448,4 @@ class ActionSheetUtils {
DesignConfig.updateSystemUiOverlayStyle();
Navigator.of(context).pop();
}
}
}

View File

@ -1,375 +1,309 @@
import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/constants/assets.dart';
import 'package:didvan/models/ai/ai_chat_args.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/utils/action_sheet.dart';
import 'package:didvan/views/ai/ai_state.dart';
import 'package:didvan/views/ai/history_ai_chat_state.dart';
import 'package:didvan/views/ai/tool_screen.dart';
import 'package:didvan/views/ai/widgets/message_bar_btn.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/shimmer_placeholder.dart';
import 'package:didvan/views/widgets/skeleton_image.dart';
import 'package:didvan/views/widgets/state_handlers/empty_connection.dart';
import 'package:didvan/views/widgets/state_handlers/empty_state.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// lib/views/ai/ai.dart
class Ai extends StatefulWidget {
const Ai({Key? key}) : super(key: key);
import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/constants/assets.dart';
import 'package:didvan/models/ai/ai_chat_args.dart';
import 'package:didvan/views/ai/ai_state.dart';
import 'package:didvan/views/ai/history_ai_chat_state.dart';
import 'package:didvan/views/ai/tool_screen.dart';
import 'package:didvan/views/ai/widgets/message_bar_btn.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/shimmer_placeholder.dart';
import 'package:didvan/views/widgets/state_handlers/empty_connection.dart';
import 'package:didvan/views/widgets/state_handlers/empty_state.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
@override
// ignore: library_private_types_in_public_api
_AiState createState() => _AiState();
}
class Ai extends StatefulWidget {
const Ai({Key? key}) : super(key: key);
class _AiState extends State<Ai> {
@override
void initState() {
super.initState();
context.read<AiState>().getTools();
@override
_AiState createState() => _AiState();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
Consumer<AiState>(
builder: (BuildContext context, state, Widget? child) {
switch (state.page) {
case 1:
return const ToolScreen();
case 0:
default:
return Consumer<HistoryAiChatState>(
builder: (context, state, child) {
if (state.bots.isEmpty) {
return Center(
child: Image.asset(
Assets.loadingAnimation,
width: 60,
height: 60,
),
);
}
final bot = state.bot!;
return Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Expanded(
child: SingleChildScrollView(
child: Padding(
padding:
const EdgeInsets.only(bottom: 24, top: 70),
child: Column(
children: [
Consumer<AiState>(
builder: (BuildContext context,
AiState state, Widget? child) {
final tools = state.tools;
if (tools != null && tools.isEmpty) {
return EmptyState(
asset: Assets.emptyResult,
title: 'لیست خالی است',
);
}
if (state.loading) {
return toolsPlaceHolder();
}
if (tools == null) {
return Padding(
padding: EdgeInsets.only(
top: MediaQuery.sizeOf(context)
.height *
0.1),
child: Center(
child: EmptyConnection(
onRetry: () {
context
.read<AiState>()
.getTools();
},
),
),
);
}
class _AiState extends State<Ai> {
@override
void initState() {
super.initState();
context.read<AiState>().getTools();
context.read<HistoryAiChatState>().getBots();
}
return const SizedBox();
},
)
],
@override
Widget build(BuildContext context) {
return Stack(
children: [
Consumer<AiState>(
builder: (BuildContext context, state, Widget? child) {
switch (state.page) {
case 1:
return const ToolScreen();
case 0:
default:
return Consumer<HistoryAiChatState>(
builder: (context, historyState, child) {
if (historyState.bots.isEmpty) {
return Center(
child: Image.asset(
Assets.loadingAnimation,
width: 60,
height: 60,
),
);
}
final bot = historyState.bot!;
return Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Expanded(
child: SingleChildScrollView(
child: Padding(
padding:
const EdgeInsets.only(bottom: 24, top: 70),
child: Column(
children: [
Consumer<AiState>(
builder: (BuildContext context,
AiState aiState, Widget? child) {
final tools = aiState.tools;
if (tools != null && tools.isEmpty) {
return EmptyState(
asset: Assets.emptyResult,
title: 'لیست خالی است',
);
}
if (aiState.loading) {
return toolsPlaceHolder();
}
if (tools == null) {
return Padding(
padding: EdgeInsets.only(
top: MediaQuery.sizeOf(context)
.height *
0.1),
child: Center(
child: EmptyConnection(
onRetry: () {
context
.read<AiState>()
.getTools();
},
),
),
);
}
return const SizedBox();
},
)
],
),
),
),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 12),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
InkWell(
onTap: () {
final historyState =
context.read<HistoryAiChatState>();
if (historyState.bots.isEmpty) {
historyState.getBots();
}
ActionSheetUtils(context)
.botsDialogSelect(context: context);
},
borderRadius: BorderRadius.circular(12),
child: const SizedBox(),
// Padding(
// padding: const EdgeInsets.symmetric(
// vertical: 8.0, horizontal: 12.0),
// child: Row(
// mainAxisSize: MainAxisSize.min,
// crossAxisAlignment:
// CrossAxisAlignment.center,
// mainAxisAlignment: MainAxisAlignment.start,
// children: [
// Icon(DidvanIcons.angle_down_light,
// size: 16,
// color: Theme.of(context)
// .colorScheme
// .title),
// const SizedBox(
// width: 8,
// ),
// DidvanText(
// bot.name.toString(),
// color: Theme.of(context)
// .colorScheme
// .title,
// style: const TextStyle(
// fontWeight: FontWeight.bold),
// ),
// const SizedBox(
// width: 12,
// ),
// SkeletonImage(
// width: 28,
// height: 28,
// imageUrl: bot.image.toString(),
// borderRadius:
// BorderRadius.circular(360),
// ),
// ],
// ),
// ),
),
InkWell(
onTap: () => Navigator.of(context)
.pushNamed(Routes.aiChat,
arguments: AiChatArgs(
bot: bot,
)),
child: Column(
children: [
Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.surface,
border: Border.all(
color: const Color.fromARGB(255, 0, 126, 167),width: 1.5),
borderRadius: BorderRadius.circular(50)),
child: Row(
children: [
Expanded(
child: Padding(
padding:
const EdgeInsets
.symmetric(
horizontal: 8.0,
),
child: Form(
child: Row(
children: [
const MessageBarBtn(
enable: true,
icon: DidvanIcons
.mic_regular),
const SizedBox(
width: 8,
),
Expanded(
child:
TextFormField(
textInputAction:
TextInputAction
.newline,
style: Theme.of(
context)
.textTheme
.bodyMedium,
minLines: 1,
enabled:
false,
decoration:
InputDecoration(
border:
InputBorder
.none,
hintText:
'بنویسید یا پیام صوتی بگذارید...',
hintStyle: Theme.of(
Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 12),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
InkWell(
onTap: () =>
context.read<AiState>().startChat(
AiChatArgs(
bot: bot,
isTool: historyState.bots)),
child: Column(
children: [
Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.surface,
border: Border.all(
color:
const Color.fromARGB(
255, 0, 126, 167),
width: 1.5),
borderRadius:
BorderRadius.circular(
50)),
child: Row(
children: [
Expanded(
child: Padding(
padding:
const EdgeInsets
.symmetric(
horizontal: 8.0,
),
child: Form(
child: Row(
children: [
const MessageBarBtn(
enable: true,
icon: DidvanIcons
.mic_regular),
const SizedBox(
width: 8,
),
Expanded(
child:
TextFormField(
textInputAction:
TextInputAction
.newline,
style: Theme.of(
context)
.textTheme
.bodySmall!
.copyWith(
color:
Theme.of(context).colorScheme.disabledText),
.bodyMedium,
minLines: 1,
enabled:
false,
decoration:
InputDecoration(
border:
InputBorder
.none,
hintText:
'بنویسید یا پیام صوتی بگذارید...',
hintStyle: Theme.of(
context)
.textTheme
.bodySmall!
.copyWith(
color:
Theme.of(context).colorScheme.disabledText),
),
),
),
),
const SizedBox(
width: 8,
),
MessageBarBtn(
click: () {
Navigator.of(context).pushNamed(
Routes
.aiChat,
arguments: AiChatArgs(
const SizedBox(
width: 8,
),
MessageBarBtn(
click: () {
context
.read<
AiState>()
.startChat(
AiChatArgs(
bot:
bot,
attach:
true));
},
enable: false,
icon: Icons
.add),
],
))))
],
true,
));
},
enable: false,
icon: Icons
.add),
],
))))
],
),
),
),
),
],
),
const Padding(
padding:
EdgeInsets.fromLTRB(8, 8, 8, 4),
child: FittedBox(
child: DidvanText(
'مدل‌های هوش مصنوعی می‌توانند اشتباه کنند، صحت اطلاعات مهم را بررسی کنید.',style: TextStyle(fontWeight: FontWeight.w600),
),
],
),
)
],
)),
],
const Padding(
padding:
EdgeInsets.fromLTRB(8, 8, 8, 4),
child: FittedBox(
child: DidvanText(
'مدل‌های هوش مصنوعی می‌توانند اشتباه کنند، صحت اطلاعات مهم را بررسی کنید.',
style: TextStyle(
fontWeight: FontWeight.w600),
),
),
)
],
)),
],
),
),
),
],
);
},
);
}
},
),
Consumer<HistoryAiChatState>(builder: (context, state, child) {
if (state.bots.isEmpty) return const SizedBox();
return Positioned(
top: 32,
right: 0,
child: InkWell(
onTap: () => Scaffold.of(context).openDrawer(),
child: Container(
width: 46,
height: 46,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(12),
bottomLeft: Radius.circular(12)),
boxShadow: DesignConfig.defaultShadow),
child: Icon(
DidvanIcons.angle_left_light,
color: Theme.of(context).colorScheme.title,
),
)),
);
})
],
);
}
],
);
},
);
}
},
),
],
);
}
ListView toolsPlaceHolder() {
return ListView.builder(
itemCount: 10,
shrinkWrap: true,
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 24),
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return Container(
decoration: BoxDecoration(
borderRadius: DesignConfig.lowBorderRadius,
border: Border.all(
color: const Color(0xffB8B8B8),
ListView toolsPlaceHolder() {
return ListView.builder(
itemCount: 10,
shrinkWrap: true,
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 24),
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return Container(
decoration: BoxDecoration(
borderRadius: DesignConfig.lowBorderRadius,
border: Border.all(
color: const Color(0xffB8B8B8),
),
),
),
padding: const EdgeInsets.all(24),
margin: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
ShimmerPlaceholder(
width: 75,
height: 75,
borderRadius: BorderRadius.circular(360),
),
const SizedBox(
width: 8,
),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ShimmerPlaceholder(
width: 120,
height: 24,
borderRadius: DesignConfig.mediumBorderRadius,
),
ShimmerPlaceholder(
width: 60,
height: 24,
borderRadius: DesignConfig.mediumBorderRadius,
),
],
),
SizedBox(
height: 8,
),
ShimmerPlaceholder(
width: 120,
height: 24,
borderRadius: DesignConfig.mediumBorderRadius,
),
SizedBox(
height: 8,
),
ShimmerPlaceholder(
width: 240,
height: 46,
borderRadius: DesignConfig.mediumBorderRadius,
),
],
padding: const EdgeInsets.all(24),
margin: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
ShimmerPlaceholder(
width: 75,
height: 75,
borderRadius: BorderRadius.circular(360),
),
)
],
),
);
},
);
}
}
const SizedBox(
width: 8,
),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ShimmerPlaceholder(
width: 120,
height: 24,
borderRadius: DesignConfig.mediumBorderRadius,
),
ShimmerPlaceholder(
width: 60,
height: 24,
borderRadius: DesignConfig.mediumBorderRadius,
),
],
),
SizedBox(
height: 8,
),
ShimmerPlaceholder(
width: 120,
height: 24,
borderRadius: DesignConfig.mediumBorderRadius,
),
SizedBox(
height: 8,
),
ShimmerPlaceholder(
width: 240,
height: 46,
borderRadius: DesignConfig.mediumBorderRadius,
),
],
),
)
],
),
);
},
);
}
}

View File

@ -1,12 +1,14 @@
// lib/views/ai/ai_chat_page.dart
// ignore_for_file: library_private_types_in_public_api, deprecated_member_use, depend_on_referenced_packages, unnecessary_import
import 'dart:io' show Platform;
import 'package:didvan/views/widgets/state_handlers/state_handler.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/constants/assets.dart';
import 'package:didvan/main.dart';
import 'package:didvan/models/ai/ai_chat_args.dart';
import 'package:didvan/models/ai/chats_model.dart';
@ -28,7 +30,6 @@ import 'package:didvan/views/ai/widgets/audio_wave.dart';
import 'package:didvan/views/ai/widgets/hoshan_drawer.dart';
import 'package:didvan/views/widgets/didvan/didvan_markdown.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/hoshan_app_bar.dart';
import 'package:didvan/views/widgets/marquee_text.dart';
import 'package:didvan/views/widgets/skeleton_image.dart';
import 'package:didvan/views/widgets/video/chat_video_player.dart';
@ -52,6 +53,34 @@ class _AiChatPageState extends State<AiChatPage> {
final GlobalKey<ScaffoldState> scaffKey = GlobalKey<ScaffoldState>();
FocusNode focusNode = FocusNode();
@override
void didUpdateWidget(covariant AiChatPage oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.args.bot.id != oldWidget.args.bot.id) {
final state = context.read<AiChatState>();
state.clearChat();
if (widget.args.chat != null) {
state.chatId = widget.args.chat!.id!;
state.chat = widget.args.chat;
state.getAllMessages(state.chatId!);
}
if (widget.args.prompts != null) {
state.messages.add(MessageModel(
dateTime: DateTime.now()
.subtract(const Duration(minutes: 210))
.toIso8601String(),
prompts: [widget.args.prompts!]));
state.message.clear();
state.update();
state.postMessage(
widget.args.bot, widget.args.assistantsName != null);
}
}
}
@override
void initState() {
final state = context.read<AiChatState>();
@ -59,6 +88,8 @@ class _AiChatPageState extends State<AiChatPage> {
state.chatId = widget.args.chat!.id!;
state.chat = widget.args.chat;
}
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (state.chatId != null) {
state.getAllMessages(state.chatId!).then((value) => Future.delayed(
@ -68,6 +99,7 @@ class _AiChatPageState extends State<AiChatPage> {
() => focusNode.requestFocus(),
));
} else {
state.appState = AppState.idle;
Future.delayed(
const Duration(
milliseconds: 100,
@ -103,145 +135,141 @@ class _AiChatPageState extends State<AiChatPage> {
},
child: Consumer<AiChatState>(
builder: (context, state, child) => Scaffold(
appBar: HoshanAppBar(
onBack: () {
Navigator.pop(context);
},
withActions: false,
),
// appBar: HoshanAppBar(
// onBack: () {
// Navigator.pop(context);
// },
// withActions: false,
// ),
key: scaffKey,
resizeToAvoidBottomInset: true,
drawer: HoshanDrawer(
scaffKey: scaffKey,
),
body: state.loading
? Center(
child: Image.asset(
Assets.loadingAnimation,
width: 60,
height: 60,
),
)
: Stack(
children: [
SingleChildScrollView(
reverse: true,
controller: state.scrollController,
child: Column(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(
height: 24,
),
SkeletonImage(
width: 75,
height: 75,
imageUrl: widget.args.bot.image.toString(),
borderRadius: BorderRadius.circular(360),
),
const SizedBox(
height: 12,
),
DidvanText(
widget.args.assistantsName ??
widget.args.bot.name.toString(),
fontSize: 17,
fontWeight: FontWeight.bold,
),
if (state.messages.isEmpty)
Column(
body: StateHandler<AiChatState>(
state: state,
onRetry: () {
if (state.chatId != null) {
state.getAllMessages(state.chatId!);
}
},
builder: (context, state) {
return Stack(
children: [
SingleChildScrollView(
reverse: true,
controller: state.scrollController,
child: Column(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(
height: 24,
),
// SkeletonImage(
// width: 75,
// height: 75,
// imageUrl: widget.args.bot.image.toString(),
// borderRadius: BorderRadius.circular(360),
// ),
// const SizedBox(
// height: 12,
// ),
// DidvanText(
// widget.args.assistantsName ??
// widget.args.bot.name.toString(),
// fontSize: 17,
// fontWeight: FontWeight.bold,
// ),
if (state.messages.isEmpty)
Column(
children: [
const SizedBox(
height: 16,
),
Padding(
padding: const EdgeInsets.only(left: 60,right: 60),
child: Center(
child: DidvanText(
widget.args.bot.description ?? '',
fontSize: 12,
color: Theme.of(context)
.colorScheme
.caption,
textAlign: TextAlign.justify,
)),
),
const SizedBox(
height: 100,
),
],
)
],
),
if (state.messages.isNotEmpty)
ListView.builder(
itemCount: state.messages.length,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.only(
bottom: state.file != null &&
!(state.file!.isRecorded)
? 180
: 100),
itemBuilder: (context, mIndex) {
final prompts =
state.messages[mIndex].prompts;
final time =
state.messages[mIndex].dateTime;
return Column(
children: [
const SizedBox(
height: 16,
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20.0),
child: Center(
child: DidvanText(
widget.args.bot.description ?? '',
fontSize: 12,
color: Theme.of(context)
.colorScheme
.caption,
textAlign: TextAlign.justify,
)),
),
const SizedBox(
height: 100,
timeLabel(context, time),
ListView.builder(
itemCount: prompts.length,
shrinkWrap: true,
physics:
const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
final message = prompts[index];
return messageBubble(message,
context, state, index, mIndex);
},
),
],
)
],
),
if (state.messages.isNotEmpty)
ListView.builder(
itemCount: state.messages.length,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.only(
bottom: state.file != null &&
!(state.file!.isRecorded)
? 180
: 100),
itemBuilder: (context, mIndex) {
final prompts =
state.messages[mIndex].prompts;
final time =
state.messages[mIndex].dateTime;
return Column(
children: [
timeLabel(context, time),
ListView.builder(
itemCount: prompts.length,
shrinkWrap: true,
physics:
const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
final message = prompts[index];
return messageBubble(message,
context, state, index, mIndex);
},
),
],
);
}),
],
),
);
}),
],
),
Positioned(
top: 32,
right: 0,
child: InkWell(
onTap: () => scaffKey.currentState!.openDrawer(),
child: Container(
width: 46,
height: 46,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(12),
bottomLeft: Radius.circular(12)),
boxShadow: DesignConfig.defaultShadow),
child: Icon(
DidvanIcons.angle_left_light,
color: Theme.of(context).colorScheme.title,
),
)),
)
],
),
),
Positioned(
top: 32,
right: 0,
child: InkWell(
onTap: () => scaffKey.currentState!.openDrawer(),
child: Container(
width: 46,
height: 46,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(12),
bottomLeft: Radius.circular(12)),
boxShadow: DesignConfig.defaultShadow),
child: Icon(
DidvanIcons.angle_left_light,
color: Theme.of(context).colorScheme.title,
),
)),
)
],
);
},
),
bottomSheet: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Platform.isIOS
// ? AiMessageBarIOS(
// bot: widget.args.bot,
// )
// :
AiMessageBar(
bot: widget.args.bot,
attch: widget.args.attach,
@ -362,9 +390,7 @@ class _AiChatPageState extends State<AiChatPage> {
: Column(
children: [
if (file != null)
(file.isAudio()
// && (!kIsWeb && !Platform.isIOS)
)
(file.isAudio())
? Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
@ -428,104 +454,105 @@ class _AiChatPageState extends State<AiChatPage> {
.toString()
.contains('user') &&
widget.args.assistantsName == null)
// PopupMenuButton(
// offset: const Offset(0, 46),
// onSelected: (value) async {
// navigatorKey.currentState!.pushNamed(
// Routes.aiChat,
// arguments: AiChatArgs(
// bot: value,
// prompts: message,
// isTool: widget.args.isTool ??
// context
// .read<
// HistoryAiChatState>()
// .bots));
// },
// itemBuilder: (BuildContext context) {
// final bots = widget.args.isTool ??
// context
// .read<HistoryAiChatState>()
// .bots;
// return <PopupMenuEntry>[
// ...List.generate(
// bots.length,
// (index) => PopupMenuItem(
// value: bots[index],
// height: 72,
// child: Container(
// constraints:
// const BoxConstraints(
// maxWidth: 200),
// child: Row(
// children: [
// SkeletonImage(
// imageUrl: bots[index]
// .image
// .toString(),
// width: 42,
// height: 42,
// borderRadius:
// BorderRadius
// .circular(360),
// ),
// const SizedBox(width: 12),
// Expanded(
// child: Directionality(
// textDirection:
// TextDirection.ltr,
// child: DidvanText(
// bots[index]
// .name
// .toString(),
// maxLines: 1,
// overflow:
// TextOverflow
// .ellipsis,
// ),
// ),
// ),
// ],
// ),
// ),
// ),
// )
// ];
// },
// // child: Container(
// // alignment: Alignment.center,
// // margin: const EdgeInsets.all(8),
// // padding: const EdgeInsets.symmetric(
// // horizontal: 8),
// // constraints: const BoxConstraints(
// // maxWidth: 100),
// // decoration: BoxDecoration(
// // borderRadius:
// // DesignConfig.lowBorderRadius,
// // border: Border.all(
// // color: Theme.of(context)
// // .colorScheme
// // .title)),
// // child: Row(
// // children: [
// // Expanded(
// // child: Directionality(
// // textDirection:
// // TextDirection.ltr,
// // child: DidvanText(
// // '${widget.args.assistantsName ?? widget.args.bot.name}',
// // maxLines: 1,
// // overflow:
// // TextOverflow.ellipsis,
// // ),
// // ),
// // ),
// // const Icon(
// // DidvanIcons.angle_down_light),
// // ],
// // ),
// // )
// ),
PopupMenuButton(
offset: const Offset(0, 46),
onSelected: (value) async {
navigatorKey.currentState!.pushNamed(
Routes.aiChat,
arguments: AiChatArgs(
bot: value,
prompts: message,
isTool: widget.args.isTool ??
context
.read<
HistoryAiChatState>()
.bots));
},
itemBuilder: (BuildContext context) {
final bots = widget.args.isTool ??
context
.read<HistoryAiChatState>()
.bots;
return <PopupMenuEntry>[
...List.generate(
bots.length,
(index) => PopupMenuItem(
value: bots[index],
height: 72,
child: Container(
constraints:
const BoxConstraints(
maxWidth: 200),
child: Row(
children: [
SkeletonImage(
imageUrl: bots[index]
.image
.toString(),
width: 42,
height: 42,
borderRadius:
BorderRadius
.circular(360),
),
const SizedBox(width: 12),
Expanded(
child: Directionality(
textDirection:
TextDirection.ltr,
child: DidvanText(
bots[index]
.name
.toString(),
maxLines: 1,
overflow:
TextOverflow
.ellipsis,
),
),
),
],
),
),
),
)
];
},
child: const SizedBox(),
// Container(
// alignment: Alignment.center,
// margin: const EdgeInsets.all(8),
// padding: const EdgeInsets.symmetric(
// horizontal: 8),
// constraints: const BoxConstraints(
// maxWidth: 100),
// decoration: BoxDecoration(
// borderRadius:
// DesignConfig.lowBorderRadius,
// border: Border.all(
// color: Theme.of(context)
// .colorScheme
// .title)),
// child: Row(
// children: [
// Expanded(
// child: Directionality(
// textDirection:
// TextDirection.ltr,
// child: DidvanText(
// '${widget.args.assistantsName ?? widget.args.bot.name}',
// maxLines: 1,
// overflow:
// TextOverflow.ellipsis,
// ),
// ),
// ),
// const Icon(
// DidvanIcons.angle_down_light),
// ],
// ),
// )
),
if (message.role
.toString()
.contains('user') &&
@ -691,7 +718,6 @@ class _AiChatPageState extends State<AiChatPage> {
children: [
DidvanText(
DateTimeUtils.timeWithAmPm(message.createdAt.toString()),
// DateTimeUtils.timeWithAmPm(message.createdAt),
style: Theme.of(context).textTheme.labelSmall,
color: Theme.of(context).colorScheme.caption,
),
@ -733,18 +759,6 @@ class _AiChatPageState extends State<AiChatPage> {
: TextDirection.rtl,
),
),
// if (state.file != null && !kIsWeb)
// FutureBuilder(
// future: state.file!.main.length(),
// builder: (context, snapshot) {
// if (!snapshot.hasData) {
// return const SizedBox();
// }
// return DidvanText(
// 'File Size ${(snapshot.data! / 1000).round()} KB',
// fontSize: 12,
// );
// })
],
),
)
@ -772,4 +786,4 @@ class _AiChatPageState extends State<AiChatPage> {
child: Image.file(file.main)),
);
}
}
}

View File

@ -290,4 +290,13 @@ class AiChatState extends CoreProvier {
message: 'خطا در برقراری ارتباط', aLertType: ALertType.error));
update();
}
void clearChat() {
messages.clear();
chatId = null;
chat = null;
file = null;
message.clear();
isEdite = false;
notifyListeners();
}
}

View File

@ -1,3 +1,6 @@
// lib/views/ai/ai_state.dart
import 'package:didvan/models/ai/ai_chat_args.dart';
import 'package:didvan/models/ai/tools_model.dart';
import 'package:didvan/models/enums.dart';
import 'package:didvan/providers/core.dart';
@ -7,10 +10,23 @@ import 'package:didvan/services/network/request_helper.dart';
class AiState extends CoreProvier {
int page = 0;
Tools? tool;
AiChatArgs? currentChatArgs;
bool isChatting = false;
bool loading = true;
List<Tools>? tools;
void startChat(AiChatArgs args) {
currentChatArgs = args;
isChatting = true;
notifyListeners();
}
void endChat() {
currentChatArgs = null;
isChatting = false;
notifyListeners();
}
void getTools() async {
final service = RequestService(
@ -41,4 +57,4 @@ class AiState extends CoreProvier {
this.tool = tool;
update();
}
}
}

View File

@ -1,3 +1,5 @@
// lib/views/ai/history_ai_chat_page.dart
// ignore_for_file: library_private_types_in_public_api, deprecated_member_use
import 'dart:async';
@ -62,10 +64,8 @@ class _HistoryAiChatPageState extends State<HistoryAiChatPage> {
child: DidvanScaffold(
hidePlayer: true,
physics: const BouncingScrollPhysics(),
// floatingActionButton: openAiListBtn(context),
padding: EdgeInsets.zero,
scrollController: scrollController,
showSliversFirst: false,
slivers: [
SliverAppBar(
@ -97,6 +97,7 @@ class _HistoryAiChatPageState extends State<HistoryAiChatPage> {
builder: (context, state, child) {
return SliverStateHandler(
state: state,
centerEmptyState: false,
emptyState: EmptyState(
asset: Assets.emptyResult,
title: 'لیست خالی است',
@ -106,8 +107,6 @@ class _HistoryAiChatPageState extends State<HistoryAiChatPage> {
: state.chats.isEmpty,
placeholder: const _HistoryPlaceholder(),
placeholderCount: 8,
// builder: (context, state, index) => _HistoryPlaceholder(),
builder: (context, state, index) {
final chat = archived
? state.archivedChats[index]
@ -216,20 +215,20 @@ class _HistoryAiChatPageState extends State<HistoryAiChatPage> {
},
child: InkWell(
onTap: () {
// if (state.chatsToDelete.isEmpty) {
navigatorKey.currentState!.pushNamed(Routes.aiChat,
arguments: AiChatArgs(
bot: chat.bot!,
chat: chat,
assistantsName: chat.assistantsName));
// } else {
// if (state.chatsToDelete.contains(chat.id)) {
// state.chatsToDelete.remove(chat.id!);
// } else {
// state.chatsToDelete.add(chat.id!);
// }
// }
// state.update();
if (chat.bot != null) {
navigatorKey.currentState!.pushNamed(Routes.aiChat,
arguments: AiChatArgs(
bot: chat.bot!,
chat: chat,
assistantsName: chat.assistantsName));
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'امکان باز کردن این چت وجود ندارد.'),
),
);
}
},
onLongPress: () {
if (archived) {
@ -241,11 +240,6 @@ class _HistoryAiChatPageState extends State<HistoryAiChatPage> {
state.chats[index].copyWith(isEditing: true);
}
state.update();
// if (state.chatsToDelete.isEmpty) {
// state.chatsToDelete.add(chat.id!);
// }
// state.update();
},
child: Container(
padding: const EdgeInsets.symmetric(
@ -280,7 +274,6 @@ class _HistoryAiChatPageState extends State<HistoryAiChatPage> {
chat.assistantsName ??
chat.bot!.name.toString(),
fontWeight: FontWeight.bold,
// fontSize: 18,
),
DidvanText(
DateTimeUtils.momentGenerator(
@ -377,8 +370,6 @@ class _HistoryAiChatPageState extends State<HistoryAiChatPage> {
maxLines: 1,
overflow:
TextOverflow.ellipsis,
// fontWeight: FontWeight.bold,
// fontSize: 16,
),
if (chat.prompts != null &&
chat.prompts!.isNotEmpty &&
@ -475,30 +466,6 @@ class _HistoryAiChatPageState extends State<HistoryAiChatPage> {
),
);
}
// Widget openAiListBtn(BuildContext context) {
// final watch = context.watch<HistoryAiChatState>();
// final state = context.read<HistoryAiChatState>();
// return FloatingActionButton(
// backgroundColor: watch.chatsToDelete.isEmpty
// ? Theme.of(context).colorScheme.primary
// : Theme.of(context).colorScheme.error,
// shape: const OvalBorder(),
// mini: true,
// onPressed: () {
// if (watch.chatsToDelete.isEmpty) {
// state.getBots();
// state.search = '';
// _botsDialogSelect(context);
// } else {
// state.addChatToDelete();
// }
// },
// child: watch.chatsToDelete.isEmpty
// ? const Icon(DidvanIcons.add_regular)
// : const Icon(DidvanIcons.trash_regular),
// );
// }
}
class _HistoryPlaceholder extends StatelessWidget {
@ -552,4 +519,4 @@ class _HistoryPlaceholder extends StatelessWidget {
),
);
}
}
}

View File

@ -297,11 +297,12 @@ class _AiMessageBarState extends State<AiMessageBar> {
MediaQuery.of(context).viewInsets.bottom == 0
? const Padding(
padding: EdgeInsets.fromLTRB(3, 8, 3, 4),
child: DidvanText(
'مدل‌های هوش مصنوعی می‌توانند اشتباه کنند، صحت اطلاعات مهم را بررسی کنید.',
fontSize: 11,
fontWeight: FontWeight.bold,
),
child: SizedBox(height: 12,)
// DidvanText(
// 'مدل‌های هوش مصنوعی می‌توانند اشتباه کنند، صحت اطلاعات مهم را بررسی کنید.',
// fontSize: 11,
// fontWeight: FontWeight.bold,
// ),
)
: const SizedBox(
height: 12,

View File

@ -1,3 +1,4 @@
// lib/views/ai/widgets/hoshan_drawer.dart
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/main.dart';
@ -8,6 +9,7 @@ import 'package:didvan/models/view/action_sheet_data.dart';
import 'package:didvan/models/view/alert_data.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/utils/action_sheet.dart';
import 'package:didvan/utils/date_time.dart';
import 'package:didvan/views/ai/history_ai_chat_state.dart';
import 'package:didvan/views/widgets/didvan/divider.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
@ -28,15 +30,10 @@ class HoshanDrawer extends StatefulWidget {
class _HoshanDrawerState extends State<HoshanDrawer> {
@override
void initState() {
initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
final historyState = context.read<HistoryAiChatState>();
if (historyState.chats.isEmpty && !historyState.loadinggetAll) {
historyState.getChats(archived: false);
}
});
}
@override
Widget build(BuildContext context) {
return Drawer(
@ -141,13 +138,6 @@ class _HoshanDrawerState extends State<HoshanDrawer> {
const SizedBox(
height: 12,
),
// SearchField(
// title: 'title',
// onChanged: (value) {},
// focusNode: FocusNode()),
// SizedBox(
// height: 12,
// ),
Expanded(
child: CustomScrollView(
slivers: [
@ -169,11 +159,6 @@ class _HoshanDrawerState extends State<HoshanDrawer> {
onRetry: () => state.getChats())
],
)),
// SizedBox(
// height: 12,
// ),
// Text('نمایش قدیمی‌ترها')
],
),
),
@ -315,11 +300,19 @@ class _HoshanDrawerState extends State<HoshanDrawer> {
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: InkWell(
onTap: () {
navigatorKey.currentState!.pushNamed(Routes.aiChat,
arguments: AiChatArgs(
bot: chat.bot!,
chat: chat,
assistantsName: chat.assistantsName));
if (chat.bot != null) {
navigatorKey.currentState!.pushNamed(Routes.aiChat,
arguments: AiChatArgs(
bot: chat.bot!,
chat: chat,
assistantsName: chat.assistantsName));
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('امکان باز کردن این چت وجود ندارد.'),
),
);
}
},
child: Row(
children: [
@ -440,4 +433,4 @@ class _HoshanDrawerState extends State<HoshanDrawer> {
),
);
}
}
}

View File

@ -1,17 +1,21 @@
// lib/views/ai/widgets/tool_category_view_widget.dart
import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/ai/ai_chat_args.dart';
import 'package:didvan/models/ai/tools_model.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/views/ai/ai_state.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/skeleton_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
class ToolCategoryViewWidget extends StatelessWidget {
final Tools tool;
const ToolCategoryViewWidget({Key? key, required this.tool}) : super(key: key);
const ToolCategoryViewWidget({Key? key, required this.tool})
: super(key: key);
@override
Widget build(BuildContext context) {
@ -32,15 +36,18 @@ class ToolCategoryViewWidget extends StatelessWidget {
tool.image!,
width: 64,
height: 64,
placeholderBuilder: (BuildContext context) => const SizedBox(
placeholderBuilder: (BuildContext context) =>
const SizedBox(
width: 64,
height: 64,
child: Center(child: CircularProgressIndicator(strokeWidth: 2.0)),
child: Center(
child: CircularProgressIndicator(strokeWidth: 2.0)),
),
),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
margin: const EdgeInsets.symmetric(horizontal: 16),
child: DidvanText(
tool.name ?? "ابزار هوش مصنوعی",
@ -81,19 +88,22 @@ class ToolCategoryViewWidget extends StatelessWidget {
final bot = tool.bots![index];
return InkWell(
onTap: () {
Navigator.of(context).pushNamed(Routes.aiChat,
arguments: AiChatArgs(bot: bot, isTool: tool.bots));
context
.read<AiState>()
.startChat(AiChatArgs(bot: bot, isTool: tool.bots));
},
child: Container(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: DesignConfig.highBorderRadius.topLeft,
topRight: DesignConfig.highBorderRadius.topRight,
bottomRight: DesignConfig.highBorderRadius.bottomRight,
bottomLeft: DesignConfig.highBorderRadius.bottomLeft,
),
topLeft: DesignConfig.highBorderRadius.topLeft,
topRight: DesignConfig.highBorderRadius.topRight,
bottomRight:
DesignConfig.highBorderRadius.bottomRight,
bottomLeft: DesignConfig.highBorderRadius.bottomLeft,
),
color: Theme.of(context).colorScheme.surface,
border: Border.all(color: Theme.of(context).colorScheme.border)),
border: Border.all(
color: Theme.of(context).colorScheme.border)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
@ -107,55 +117,67 @@ class ToolCategoryViewWidget extends StatelessWidget {
),
),
),
Expanded(
Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(12, 8, 12, 12),
padding:
const EdgeInsets.fromLTRB(12, 8, 12, 12),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (bot.image != null)
SkeletonImage(
imageUrl: bot.image!,
width: 70,
width: 70,
height: 70,
borderRadius: BorderRadius.circular(360),
)
else
Icon(DidvanIcons.ai_solid, size: 50, color: Theme.of(context).colorScheme.primary),
Icon(DidvanIcons.ai_solid,
size: 50,
color: Theme.of(context)
.colorScheme
.primary),
const SizedBox(height: 15),
Container(
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(12)),
borderRadius:
BorderRadius.all(Radius.circular(12)),
color: customBackgroundColor,
),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
padding: const EdgeInsets.symmetric(
horizontal: 8, vertical: 4),
child: Padding(
padding: const EdgeInsets.all(4.0),
child: DidvanText(
bot.name ?? '',
fontSize: 14,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.onPrimary,
color: Theme.of(context)
.colorScheme
.onPrimary,
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
),
),
),
if (bot.short != null && bot.short!.isNotEmpty) ...[
if (bot.short != null &&
bot.short!.isNotEmpty) ...[
const SizedBox(height: 8),
Expanded(
child: DidvanText(
bot.short!,
fontSize: 10,
color: Theme.of(context).colorScheme.caption,
maxLines: 2,
fontSize: 10,
color: Theme.of(context)
.colorScheme
.caption,
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
),
),
] else const Spacer(),
] else
const Spacer(),
],
),
),
@ -167,9 +189,11 @@ class ToolCategoryViewWidget extends StatelessWidget {
},
)
else
Padding(
Padding(
padding: const EdgeInsets.all(32.0),
child: Center(child: DidvanText("هیچ رباتی برای ابزار '${tool.name ?? "انتخابی"}' موجود نیست.")),
child: Center(
child: DidvanText(
"هیچ رباتی برای ابزار '${tool.name ?? "انتخابی"}' موجود نیست.")),
),
],
),

View File

@ -1,6 +1,11 @@
// lib/views/ai_section/ai_section_page.dart
import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/ai/ai_chat_args.dart';
import 'package:didvan/models/ai/tools_model.dart';
import 'package:didvan/models/enums.dart';
import 'package:didvan/views/ai/ai.dart';
import 'package:didvan/views/ai/ai_chat_page.dart';
import 'package:didvan/views/ai/widgets/hoshan_drawer.dart';
import 'package:didvan/views/ai_section/widgets/ai_section_bnb.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
@ -19,7 +24,7 @@ class ImageGenerationScreen extends StatelessWidget {
final aiState = context.watch<AiState>();
if (aiState.tools == null || aiState.tools!.isEmpty) {
if (aiState.loading || aiState.appState == AppState.busy) {
if (aiState.loading) {
return const Center(child: CircularProgressIndicator());
}
return const Center(
@ -37,7 +42,7 @@ class VoiceChatScreen extends StatelessWidget {
final aiState = context.watch<AiState>();
if (aiState.tools == null || aiState.tools!.isEmpty) {
if (aiState.loading || aiState.appState == AppState.busy) {
if (aiState.loading) {
return const Center(child: CircularProgressIndicator());
}
return const Center(
@ -56,7 +61,7 @@ class ChartAnalysisScreen extends StatelessWidget {
final aiState = context.watch<AiState>();
if (aiState.tools == null || aiState.tools!.isEmpty) {
if (aiState.loading || aiState.appState == AppState.busy) {
if (aiState.loading) {
return const Center(child: CircularProgressIndicator());
}
return const Center(
@ -81,7 +86,7 @@ class TranslationScreen extends StatelessWidget {
final aiState = context.watch<AiState>();
if (aiState.tools == null || aiState.tools!.isEmpty) {
if (aiState.loading || aiState.appState == AppState.busy) {
if (aiState.loading) {
return const Center(child: CircularProgressIndicator());
}
return const Center(
@ -97,7 +102,6 @@ class TranslationScreen extends StatelessWidget {
return ToolCategoryViewWidget(tool: translationToolCategory);
}
}
class AiSectionPage extends StatefulWidget {
const AiSectionPage({super.key});
@ -105,25 +109,22 @@ class AiSectionPage extends StatefulWidget {
State<AiSectionPage> createState() => _AiSectionPageState();
}
class _AiSectionPageState extends State<AiSectionPage>
with SingleTickerProviderStateMixin {
late TabController _tabController;
class _AiSectionPageState extends State<AiSectionPage> {
int _currentTabIndex = 2;
final GlobalKey<ScaffoldState> _aiSectionScaffoldKey =
GlobalKey<ScaffoldState>();
final List<Widget> _pages = const [
ImageGenerationScreen(),
VoiceChatScreen(),
Ai(),
ChartAnalysisScreen(),
TranslationScreen(),
];
@override
void initState() {
super.initState();
_tabController = TabController(length: 5, vsync: this, initialIndex: 2);
_tabController.addListener(() {
if (mounted) {
setState(() {
_currentTabIndex = _tabController.index;
});
}
});
WidgetsBinding.instance.addPostFrameCallback((_) {
final historyAiChatState = context.read<HistoryAiChatState>();
if (historyAiChatState.bots.isEmpty) {
@ -131,28 +132,64 @@ class _AiSectionPageState extends State<AiSectionPage>
}
final aiState = context.read<AiState>();
aiState.page = 0;
aiState.endChat();
if (aiState.tools == null) {
aiState.getTools();
}
});
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
void _onTabChanged(int index) {
final aiState = context.read<AiState>();
aiState.endChat();
setState(() {
_currentTabIndex = index;
});
if (aiState.tools != null && aiState.tools!.isNotEmpty) {
Tools? tool;
switch (index) {
case 0:
tool = aiState.tools![0];
break;
case 1:
tool = aiState.tools!.last;
break;
case 2:
return;
case 3:
if (aiState.tools!.length > 1) {
tool = aiState.tools![1];
}
break;
case 4:
if (aiState.tools!.length > 2) {
tool = aiState.tools![2];
}
break;
}
if (tool != null && tool.bots != null && tool.bots!.isNotEmpty) {
final firstBot = tool.bots!.first;
aiState.startChat(AiChatArgs(bot: firstBot));
}
}
}
@override
Widget build(BuildContext context) {
final aiState = context.watch<AiState>();
return Scaffold(
key: _aiSectionScaffoldKey,
appBar: HoshanAppBar(
onBack: () {
final aiState = context.read<AiState>();
if (aiState.page != 0) {
aiState.goToAi();
if (aiState.isChatting) {
aiState.endChat();
setState(() {
_currentTabIndex = 2;
});
} else {
Navigator.of(context).pop();
}
@ -160,22 +197,40 @@ class _AiSectionPageState extends State<AiSectionPage>
withActions: true,
),
drawer: HoshanDrawer(scaffKey: _aiSectionScaffoldKey),
body: TabBarView(
controller: _tabController,
physics: const NeverScrollableScrollPhysics(),
children: const [
ImageGenerationScreen(),
VoiceChatScreen(),
Ai(),
ChartAnalysisScreen(),
TranslationScreen(),
body: Stack(
children: [
aiState.isChatting
? AiChatPage(args: aiState.currentChatArgs!)
: IndexedStack(
index: _currentTabIndex,
children: _pages,
),
Positioned(
top: 32,
right: 0,
child: InkWell(
onTap: () => _aiSectionScaffoldKey.currentState?.openDrawer(),
child: Container(
width: 46,
height: 46,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(12),
bottomLeft: Radius.circular(12)),
boxShadow: DesignConfig.defaultShadow),
child: Icon(
DidvanIcons.angle_left_light,
color: Theme.of(context).colorScheme.title,
),
),
),
)
],
),
bottomNavigationBar: AiSectionBNB(
currentTabIndex: _currentTabIndex,
onTabChanged: (index) {
_tabController.animateTo(index);
},
onTabChanged: _onTabChanged,
),
);
}

View File

@ -1,14 +1,8 @@
import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/models/ai/ai_chat_args.dart';
import 'package:didvan/models/ai/tools_model.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/services/webview.dart';
import 'package:didvan/views/ai/ai_state.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
class AiSectionBNB extends StatelessWidget {
final int currentTabIndex;
@ -21,15 +15,20 @@ class AiSectionBNB extends StatelessWidget {
@override
Widget build(BuildContext context) {
const String cameraSolid = 'lib/assets/icons/houshanNav/imagegeneratorS.svg';
const String cameraRegular = 'lib/assets/icons/houshanNav/imagegeneratorU.svg';
const String micSolid = 'lib/assets/icons/houshanNav/aichatS.svg';
const String micRegular = 'lib/assets/icons/houshanNav/aichatU.svg';
const String aiSolid = 'lib/assets/icons/houshanNav/houshan.svg';
const String aiRegular = 'lib/assets/icons/houshanNav/houshan.svg';
const String cameraRegular =
'lib/assets/icons/houshanNav/imagegeneratorU.svg';
// const String micSolid = 'lib/assets/icons/houshanNav/aichatS.svg';
// const String micRegular = 'lib/assets/icons/houshanNav/aichatU.svg';
const String aiSolid =
'lib/assets/icons/houshanNav/streamline-flex_ai-scanner-robot-remix.svg';
const String aiRegular =
'lib/assets/icons/houshanNav/streamline-flex_ai-scanner-robot.svg';
const String searchSolid = 'lib/assets/icons/houshanNav/searchS.svg';
const String searchRegular = 'lib/assets/icons/houshanNav/searchU.svg';
const String translateSolid = 'lib/assets/icons/houshanNav/translateS.svg';
const String translateRegular = 'lib/assets/icons/houshanNav/translateU.svg';
const String translateSolid =
'lib/assets/icons/houshanNav/translateS.svg';
const String translateRegular =
'lib/assets/icons/houshanNav/translateU.svg';
return Container(
height: 72,
@ -50,91 +49,38 @@ class AiSectionBNB extends StatelessWidget {
children: [
_AiNavBarItem(
isSelected: currentTabIndex == 0,
title: 'عکس ساز',
title: 'تصویرساز',
selectedIcon: cameraSolid,
unselectedIcon: cameraRegular,
onTap: () {
final aiState = context.read<AiState>();
if (aiState.tools != null && aiState.tools!.isNotEmpty) {
final Tools imageGenTool = aiState.tools![0]; // ابزار عکس ساز
if (imageGenTool.bots != null && imageGenTool.bots!.isNotEmpty) {
final bot = imageGenTool.bots!.first;
Navigator.of(context).pushNamed(
Routes.aiChat,
arguments: AiChatArgs(bot: bot, isTool: imageGenTool.bots),
);
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('ابزار عکس ساز در حال بارگذاری است...')),
);
}
},
),
_AiNavBarItem(
isSelected: currentTabIndex == 1,
title: 'چت صوتی',
selectedIcon: micSolid,
unselectedIcon: micRegular,
onTap: () {
NativeWebViewLauncher.openWebView(
'https://www.aisada.ir/app/page1.html');
},
onTap: () => onTabChanged(0),
),
// _AiNavBarItem(
// isSelected: currentTabIndex == 1,
// title: 'چت صوتی',
// selectedIcon: micSolid,
// unselectedIcon: micRegular,
// onTap: () => onTabChanged(1),
// ),
_AiNavBarItem(
isSelected: currentTabIndex == 2,
title: '',
title: 'گفت‌وگو',
selectedIcon: aiSolid,
unselectedIcon: aiRegular,
onTap: () => onTabChanged(2),
),
_AiNavBarItem(
isSelected: currentTabIndex == 3,
title: 'جست و جو',
title: 'جست‌وجو',
selectedIcon: searchSolid,
unselectedIcon: searchRegular,
onTap: () {
final aiState = context.read<AiState>();
// نکته: طبق کد شما، تب جستجو (اندیس ۳) ویجت تحلیل نمودار را نمایش میدهد
// که از ابزار اندیس ۱ استفاده میکند.
if (aiState.tools != null && aiState.tools!.length > 1) {
final Tools chartAnalysisTool = aiState.tools![1];
if (chartAnalysisTool.bots != null && chartAnalysisTool.bots!.isNotEmpty) {
final bot = chartAnalysisTool.bots!.first;
Navigator.of(context).pushNamed(
Routes.aiChat,
arguments: AiChatArgs(bot: bot, isTool: chartAnalysisTool.bots),
);
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('ابزار جستجو در حال بارگذاری است...')),
);
}
},
onTap: () => onTabChanged(3),
),
_AiNavBarItem(
isSelected: currentTabIndex == 4,
title: 'ترجمه',
selectedIcon: translateSolid,
unselectedIcon: translateRegular,
onTap: () {
final aiState = context.read<AiState>();
if (aiState.tools != null && aiState.tools!.length > 2) {
final Tools translationToolCategory = aiState.tools![2];
if (translationToolCategory.bots != null && translationToolCategory.bots!.isNotEmpty) {
final gptTranslatorBot = translationToolCategory.bots!.first;
Navigator.of(context).pushNamed(
Routes.aiChat,
arguments: AiChatArgs(bot: gptTranslatorBot, isTool: translationToolCategory.bots),
);
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('ابزار ترجمه در حال بارگذاری است...')),
);
}
},
onTap: () => onTabChanged(4),
),
],
),
@ -142,7 +88,6 @@ class AiSectionBNB extends StatelessWidget {
}
}
class _AiNavBarItem extends StatelessWidget {
final VoidCallback onTap;
final bool isSelected;
@ -161,7 +106,6 @@ class _AiNavBarItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
final double iconSize = title.isEmpty ? 50.0 : 32.0;
return Expanded(

View File

@ -43,10 +43,14 @@ class AuthenticationState extends CoreProvier {
currentPageIndex = 2;
}
} else {
appState = AppState.failed;
ActionSheetUtils(navigatorKey.currentContext!)
.showAlert(AlertData(message: service.errorMessage));
appState = AppState.failed;
String message = service.errorMessage;
if (service.statusCode == 404) {
message = 'کاربر موجود نمی‌باشد.';
}
ActionSheetUtils(navigatorKey.currentContext!)
.showAlert(AlertData(message: message));
}
}
Future<String?> login(UserProvider userProvider) async {

View File

@ -9,9 +9,11 @@ import 'package:didvan/views/authentication/authentication_state.dart';
import 'package:didvan/views/authentication/widgets/authentication_layout.dart';
import 'package:didvan/views/widgets/didvan/button.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:pin_code_fields/pin_code_fields.dart';
import 'package:provider/provider.dart';
import 'package:sms_autofill/sms_autofill.dart';
class Verification extends StatefulWidget {
const Verification({Key? key}) : super(key: key);
@ -39,7 +41,23 @@ class _VerificationState extends State<Verification> {
);
_handleTimer();
super.initState();
_listenForSms();
}
Future<void> _listenForSms() async {
if (kDebugMode) {
print("<<<<< در حال تلاش برای دریافت امضای برنامه >>>>>");
}
final appSignature = await SmsAutoFill().getAppSignature;
if (kDebugMode) {
print("App Signature: $appSignature");
}
await SmsAutoFill().listenForCode();
}
@override
Widget build(BuildContext context) {

View File

@ -1,7 +1,7 @@
// ignore_for_file: deprecated_member_use
import 'package:didvan/views/authentication/widgets/authentication_app_bar.dart';
import 'package:didvan/views/widgets/logos/didvan_vertical_logo.dart';
import 'package:didvan/views/widgets/logos/didvan_horizontal_logo.dart';
import 'package:flutter/material.dart';
class AuthenticationLayout extends StatelessWidget {
@ -23,10 +23,10 @@ class AuthenticationLayout extends StatelessWidget {
toolbarHeight: 56,
backgroundColor: Theme.of(context).colorScheme.background,
flexibleSpace: Padding(
padding: EdgeInsets.only(
padding: const EdgeInsets.only(
left: 16,
right: 16,
top: MediaQuery.of(context).padding.top,
top: 7,
),
child: AuthenticationAppBar(
title: appBarTitle,
@ -41,7 +41,9 @@ class AuthenticationLayout extends StatelessWidget {
bottom: 40,
),
sliver: const SliverToBoxAdapter(
child: DidvanHorizontalLogo(),
child: DidvanVerticalLogo(
height: 145,
),
),
),
SliverPadding(

View File

@ -63,13 +63,11 @@ class _HomeState extends State<Home>
),
],
),
const SizedBox(height: 8),
const SizedBox(height: 15),
SvgPicture.asset(Assets.horizontalLogoWithText, height: 80),
const SizedBox(height: 24),
DidvanText(
'به دیدوان، چشم همیشه باز مدیران خوش آمدید',
'به سوپر اپلیکیشن دیدوان خوش آمدید',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleMedium,
),
@ -82,42 +80,47 @@ class _HomeState extends State<Home>
data: ActionSheetData(
backgroundColor: Theme.of(context).colorScheme.background,
isBackgroundDropBlur: true,
content: Column(
content: Stack(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
InkWrapper(
onPressed: () {
Future.delayed(
Duration.zero,
() => Navigator.of(context).pop(),
);
},
child: const Icon(
DidvanIcons.close_solid,
size: 24,
Padding(
padding: const EdgeInsets.only(top: 24.0),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
DidvanText(
'شخصی سازی محتوا',
style:
Theme.of(context).textTheme.displaySmall,
color: Theme.of(context).colorScheme.text,
),
],
),
),
DidvanText(
'شخصی سازی محتوا',
style: Theme.of(context).textTheme.displaySmall,
color: Theme.of(context).colorScheme.text,
),
const InkWrapper(
child: Icon(
DidvanIcons.close_regular,
size: 24,
color: Colors.transparent,
const SizedBox(
height: 12,
),
const DidvanText(
"کاربر گرامی\nلطفا جهت شخصی‌سازی و استفاده بهتر از برنامه، دسته‌بندی‌های مورد علاقه خود و زمان دریافت اعلانات را انتخاب نمایید.")
],
),
),
Positioned(
top: 0,
left: 0,
child: InkWrapper(
onPressed: () {
Future.delayed(
Duration.zero,
() => Navigator.of(context).pop(),
);
},
child: const Icon(
DidvanIcons.close_solid,
size: 24,
),
],
),
),
const SizedBox(
height: 12,
),
const DidvanText(
"کاربر گرامی\nلطفا جهت شخصی‌سازی و استفاده بهتر از برنامه، دسته‌بندی‌های مورد علاقه خود و زمان دریافت اعلانات را انتخاب نمایید.")
],
),
onConfirmed: () {
@ -141,20 +144,22 @@ class _HomeState extends State<Home>
if (widget.showDialogs ?? false) {
_showDialog(context);
}
// if (!kIsWeb) {
// NotificationService.startListeningNotificationEvents();
// }
final state = context.read<HomeState>();
DesignConfig.updateSystemUiOverlayStyle();
_tabController = TabController(length: 4, vsync: this, initialIndex: 0);
state.tabController = _tabController;
// این قسمت را اضافه یا جایگزین کنید
_tabController.addListener(() {
state.currentPageIndex = _tabController.index;
if (_tabController.index == 2) {
final state = context.read<HistoryAiChatState>();
state.getBots();
// با هر بار ورود به تب هوشان، لیست چتها ریست میشود
final historyState = context.read<HistoryAiChatState>();
historyState.chats.clear();
historyState.archivedChats.clear();
historyState.update(); // برای اطمینان از بهروزرسانی UI
historyState.getBots();
}
});
if (!kIsWeb) {

View File

@ -208,7 +208,7 @@ class _SwotSection extends StatelessWidget {
/// Swot Items Slider
DidvanSlider(
height: 300,
height: 335,
itemCount: items.length,
viewportFraction: 0.65,
itemBuilder: (context, index, realIndex) => Padding(

View File

@ -33,11 +33,9 @@ class MainPageState extends CoreProvier {
Future<void> _fetchStories() async {
try {
stories = await StoryService.getStories();
// [اضافه شود] تعداد استوری های دریافت شده را چاپ کنید
print("Fetched ${stories.length} stories.");
} catch (e) {
stories = [];
// [اضافه شود] خطای رخ داده را چاپ کنید
debugPrint("Could not fetch stories: $e");
}
}

View File

@ -3,7 +3,6 @@
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/views/home/main/widgets/banner.dart';
import 'package:didvan/views/home/widgets/categories.dart';
import 'package:didvan/views/widgets/ai_banner.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:flutter/material.dart';

View File

@ -1,7 +1,6 @@
import 'package:didvan/models/story_model.dart';
import 'package:didvan/routes/routes.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
class StorySection extends StatelessWidget {
final List<UserStories> stories;
@ -67,8 +66,8 @@ class _StoryCircle extends StatelessWidget {
valueListenable: allStoriesViewed,
builder: (context, isViewed, child) {
return Container(
width: 80.0,
height: 80.0,
width: 85.0,
height: 85.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: isViewed
@ -80,8 +79,8 @@ class _StoryCircle extends StatelessWidget {
)
: const LinearGradient(
colors: [
Color.fromARGB(255, 27, 60, 79),
Color.fromARGB(255, 27, 60, 79)
Color.fromARGB(255, 1, 35, 54),
Color.fromARGB(255, 178, 4, 54),
],
begin: Alignment.topRight,
end: Alignment.bottomLeft,

View File

@ -1,6 +1,7 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cached_network_image_platform_interface/cached_network_image_platform_interface.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/home_page_content/swot.dart';
import 'package:didvan/services/app_initalizer.dart';
import 'package:didvan/services/network/request.dart';
@ -22,6 +23,23 @@ class SwotItemCard extends StatefulWidget {
}
class _SwotItemCardState extends State<SwotItemCard> {
String _getCategoryName(String category) {
switch (category) {
case 'MACRO_TRENDS':
return 'کلان روند';
case 'INDUSTRY_ENVIRONMENT':
return 'محیط صنعت';
case 'CONCEPTS':
return 'مفاهیم';
case 'MACRO_ENVIRONMENT':
return 'محیط کلان';
case 'INVESTMENTS':
return 'سرمایه گذاری';
default:
return category;
}
}
@override
Widget build(BuildContext context) {
return GestureDetector(
@ -33,8 +51,8 @@ class _SwotItemCardState extends State<SwotItemCard> {
);
},
child: Container(
height: 500 ,
width: 250,
height: 50,
margin: const EdgeInsets.only(right: 0),
padding: const EdgeInsets.all(0),
decoration: BoxDecoration(
@ -82,7 +100,7 @@ class _SwotItemCardState extends State<SwotItemCard> {
),
),
Padding(
padding: const EdgeInsets.all(8.0),
padding: const EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 0),
child: Text(
widget.item.title,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
@ -91,7 +109,6 @@ class _SwotItemCardState extends State<SwotItemCard> {
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(height: 5),
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
@ -128,24 +145,25 @@ class _SwotItemCardState extends State<SwotItemCard> {
)
],
),
// GestureDetector(
// onTap: () {}, child: Icon(DidvanIcons.bookmark_solid)
// // Icon(
// // widget.item.marked
// // ? DidvanIcons.bookmark_solid
// // : DidvanIcons.bookmark_regular,
// // color: widget.item.marked
// // ? Theme.of(context).colorScheme.secondary
// // : Theme.of(context).colorScheme.caption,
// // ),
// ),
const SizedBox(height: 10),
Row(
children: [
const Icon(DidvanIcons.puzzle_light,size: 17,),
const SizedBox(width: 5,),
Text(
_getCategoryName(widget.item.category),
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
const SizedBox(height: 30),
],
),
),
],
),
Positioned(
bottom: 0,
bottom: 3,
left: 0,
child: BookmarkIcon(postId: widget.item.id),
)
@ -154,4 +172,4 @@ class _SwotItemCardState extends State<SwotItemCard> {
),
);
}
}
}

View File

@ -3,6 +3,7 @@
import 'package:chewie/chewie.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/assets.dart';
import 'package:didvan/models/studio_details_data.dart';
import 'package:didvan/models/view/app_bar_data.dart';
import 'package:didvan/services/media/media.dart';
import 'package:didvan/views/podcasts/studio_details/studio_details_state.dart';
@ -26,9 +27,8 @@ class StudioDetails extends StatefulWidget {
}
class _StudioDetailsState extends State<StudioDetails> {
// ignore: unused_field
int _currentlyPlayingId = 0;
late VideoPlayerController _videoPlayerController;
VideoPlayerController? _videoPlayerController;
ChewieController? _chewieController;
@override
@ -40,26 +40,9 @@ class _StudioDetailsState extends State<StudioDetails> {
Future.delayed(
Duration.zero,
() => state.getStudioDetails(widget.pageData['id']).then((_) {
if (!mounted) return;
_videoPlayerController = VideoPlayerController.network(
state.studio.link,
);
_videoPlayerController.initialize().then((_) {
_chewieController = ChewieController(
videoPlayerController: _videoPlayerController,
customControls: const PrimaryControls(),
autoPlay: true,
looping: true,
aspectRatio: 16 / 9,
materialProgressColors: ChewieProgressColors(
playedColor: Theme.of(context).colorScheme.title,
handleColor: Theme.of(context).colorScheme.title,
),
);
setState(() {
_currentlyPlayingId = state.studio.id;
});
});
if (mounted) {
_initializePlayer(state.studio);
}
}),
);
@ -72,9 +55,9 @@ class _StudioDetailsState extends State<StudioDetails> {
() => Navigator.of(context).pushNamed(
Routes.mentions,
arguments: {
'id': state.studio.id,
'id': context.read<StudioDetailsState>().studio.id,
'type': 'studio',
'title': state.studio.title,
'title': context.read<StudioDetailsState>().studio.title,
},
),
);
@ -82,99 +65,135 @@ class _StudioDetailsState extends State<StudioDetails> {
}
}
Future<void> _initializePlayer(StudioDetailsData studio) async {
// Disposing old controllers before creating new ones.
_videoPlayerController?.dispose();
_chewieController?.dispose();
_videoPlayerController = VideoPlayerController.network(studio.link);
try {
await _videoPlayerController!.initialize();
if (mounted) {
setState(() {
_chewieController = ChewieController(
videoPlayerController: _videoPlayerController!,
customControls: const PrimaryControls(),
autoPlay: true,
looping: true,
aspectRatio: 16 / 9,
materialProgressColors: ChewieProgressColors(
playedColor: Theme.of(context).colorScheme.title,
handleColor: Theme.of(context).colorScheme.title,
),
);
_currentlyPlayingId = studio.id;
});
}
} catch (e) {
debugPrint("Error initializing video player: $e");
}
}
@override
Widget build(BuildContext context) {
final d = MediaQuery.of(context);
return Consumer<StudioDetailsState>(
builder: (context, state, child) => StateHandler<StudioDetailsState>(
state: state,
onRetry: () {
try {
state.getStudioDetails(state.studio.id);
} catch (e) {
state.getStudioDetails(widget.pageData['id']);
}
},
builder: (context, state) {
if (!state.isStudioLoaded) {
return Center(
child: Image.asset(
Assets.loadingAnimation,
width: 100,
height: 100,
builder: (context, state, child) {
if (state.isStudioLoaded && _currentlyPlayingId != state.studio.id) {
Future.microtask(() => _initializePlayer(state.studio));
}
return StateHandler<StudioDetailsState>(
state: state,
onRetry: () {
try {
state.getStudioDetails(state.studio.id);
} catch (e) {
state.getStudioDetails(widget.pageData['id']);
}
},
builder: (context, state) {
if (!state.isStudioLoaded) {
return Center(
child: Image.asset(
Assets.loadingAnimation,
width: 100,
height: 100,
),
);
}
return WillPopScope(
onWillPop: () async {
if (MediaService.currentPodcast != null) {
state.studio = MediaService.currentPodcast!;
}
state.handleTracking(id: state.studio.id);
return true;
},
child: SafeArea(
child: Scaffold(
backgroundColor: Theme.of(context).colorScheme.surface,
appBar: PreferredSize(
preferredSize: const Size.fromHeight(56),
child: DidvanAppBar(
appBarData: AppBarData(
trailing: BookmarkButton(
itemId: state.studio.id,
type: 'video',
value: state.studio.marked,
onMarkChanged: (value) {
widget.pageData['onMarkChanged'](
state.studio.id, value);
},
gestureSize: 48,
),
isSmall: true,
title: state.studio.title,
),
),
),
body: SingleChildScrollView(
child: SizedBox(
height: d.size.height - d.padding.top - 56,
child: Column(
children: [
AspectRatio(
aspectRatio: 16 / 9,
child: (_chewieController != null &&
_chewieController!
.videoPlayerController.value.isInitialized)
? Chewie(controller: _chewieController!)
: Center(
child: Image.asset(
Assets.loadingAnimation,
width: 100,
height: 100,
),
),
),
Expanded(
child: StudioDetailsWidget(
onMarkChanged: (id, value) => widget
.pageData['onMarkChanged'](id, value, true),
),
),
],
),
),
),
),
),
);
}
return WillPopScope(
onWillPop: () async {
if (MediaService.currentPodcast != null) {
state.studio = MediaService.currentPodcast!;
}
state.handleTracking(id: state.studio.id);
return true;
},
child: SafeArea(
child: Scaffold(
backgroundColor: Theme.of(context).colorScheme.surface,
appBar: PreferredSize(
preferredSize: const Size.fromHeight(56),
child: DidvanAppBar(
appBarData: AppBarData(
trailing: BookmarkButton(
itemId: state.studio.id,
type: 'video',
value: state.studio.marked,
onMarkChanged: (value) {
widget.pageData['onMarkChanged'](
state.studio.id, value);
},
gestureSize: 48,
),
isSmall: true,
title: state.studio.title,
),
),
),
body: SingleChildScrollView(
child: SizedBox(
height: d.size.height - d.padding.top - 56,
child: Column(
children: [
AspectRatio(
aspectRatio: 16 / 9,
child: _chewieController != null
? Chewie(controller: _chewieController!)
: Center(
child: Image.asset(
Assets.loadingAnimation,
width: 100,
height: 100,
),
),
),
Expanded(
child: StudioDetailsWidget(
onMarkChanged: (id, value) => widget
.pageData['onMarkChanged'](id, value, true),
),
),
],
),
),
),
),
),
);
},
),
},
);
},
);
}
@override
void dispose() {
_videoPlayerController.pause();
_videoPlayerController.dispose();
_videoPlayerController?.dispose();
_chewieController?.dispose();
super.dispose();
}
}
}

View File

@ -18,11 +18,14 @@ class StudioDetailsState extends CoreProvier {
StudioRequestArgs? args;
StudioRequestArgs? podcastArgs;
final List<int> relatedQueue = [];
bool _positionListenerActivated = false;
AppState alongSideState = AppState.idle;
int _trackingTimerCounter = 0;
Timer? _trackingTimer;
// BEGIN: ADD THIS LINE
StreamSubscription? _positionSubscription;
// END: ADD THIS LINE
int _selectedDetailsIndex = 0;
Timer? timer;
int timerValue = 10;
@ -134,6 +137,8 @@ class StudioDetailsState extends CoreProvier {
}
Future<void> _handlePodcastPlayback(StudioDetailsData studio) async {
_positionSubscription?.cancel();
if (args?.type == 'podcast') {
MediaService.currentPodcast = studio;
MediaService.podcastPlaylistArgs = args;
@ -149,20 +154,21 @@ class StudioDetailsState extends CoreProvier {
}
},
);
if (nextStudio != null && !_positionListenerActivated) {
_positionListenerActivated = true;
MediaService.audioPlayer.positionStream.listen((event) {
if (MediaService.audioPlayerTag?.contains('message') == true) {
if (nextStudio != null) {
_positionSubscription =
MediaService.audioPlayer.positionStream.listen((event) {
if (this.args?.type != 'podcast' ||
MediaService.audioPlayerTag?.contains('message') == true) {
return;
}
final duration =
MediaService.duration ?? Duration(seconds: studio.duration);
if (event.compareTo(duration) > 0 && nextStudio != null) {
if (stopOnPodcastEnds) {
MediaService.duration ?? Duration(seconds: this.studio.duration);
if (event.compareTo(duration) > 0 && this.nextStudio != null) {
if (this.stopOnPodcastEnds) {
MediaService.resetAudioPlayer();
return;
}
getStudioDetails(nextStudio!.id, isForward: true);
this.getStudioDetails(this.nextStudio!.id, isForward: true);
}
});
}
@ -199,7 +205,7 @@ class StudioDetailsState extends CoreProvier {
notifyListeners();
}
Future<void> handleTracking({
Future<void> handleTracking({
required int id,
bool sendRequest = true,
}) async {
@ -207,6 +213,7 @@ class StudioDetailsState extends CoreProvier {
_trackingTimerCounter = 0;
_trackingTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
_trackingTimerCounter++;
notifyListeners();
});
return;
}
@ -224,6 +231,7 @@ class StudioDetailsState extends CoreProvier {
@override
void dispose() {
_trackingTimer?.cancel();
_positionSubscription?.cancel();
super.dispose();
}
}

View File

@ -3,11 +3,10 @@ import 'package:didvan/models/story_model.dart';
import 'package:didvan/services/story_service.dart';
import 'package:didvan/utils/date_time.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/shimmer_placeholder.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:video_player/video_player.dart';
// Main PageViewer to swipe between users
class StoryViewerPage extends StatefulWidget {
final List<UserStories> stories;
final int tappedIndex;
@ -137,7 +136,7 @@ class _UserStoryViewerState extends State<UserStoryViewer>
_animationController.duration =
_videoController!.value.duration;
_videoController!.play();
// _animationController.forward();
_animationController.forward();
} else {
print(
"Video failed to initialize or has zero duration. Skipping.");
@ -232,6 +231,7 @@ class _UserStoryViewerState extends State<UserStoryViewer>
switch (story.media) {
case MediaType.image:
return CachedNetworkImage(
placeholder: (context, url) => const ShimmerPlaceholder(),
imageUrl: story.url,
fit: BoxFit.cover,
width: double.infinity,

View File

@ -1,7 +1,5 @@
import 'package:didvan/services/webview.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_svg/svg.dart';
class AiBanner extends StatelessWidget {
const AiBanner({

View File

@ -107,48 +107,51 @@ class _DidvanTextFieldState extends State<DidvanTextField> {
textDirection: val.text.startsWithEnglish()
? TextDirection.ltr
: TextDirection.rtl,
child: TextFormField(
inputFormatters: <TextInputFormatter>[
if (!widget.acceptSpace)
FilteringTextInputFormatter.allow(
RegExp("[0-9a-zA-Z\u0600-\u06FF]")),
],
autofocus: widget.autoFocus,
obscureText: _hideContent,
textAlign: widget.textAlign ?? TextAlign.start,
keyboardType: widget.textInputType,
textInputAction: widget.textInputAction,
focusNode: _focusNode,
controller: _controller,
onFieldSubmitted: widget.onSubmitted,
onChanged: _onChanged,
validator: _validator,
maxLines: _hideContent ? 1 : widget.maxLine,
minLines: widget.minLine,
maxLength: widget.maxLength,
obscuringCharacter: '*',
buildCounter: widget.showLen
? null
: (context,
{required currentLength,
required isFocused,
required maxLength}) =>
const SizedBox(),
style: (widget.isSmall
? Theme.of(context).textTheme.bodySmall!
: Theme.of(context).textTheme.bodyMedium!)
.copyWith(
fontFamily: DesignConfig.fontFamily.padRight(3)),
decoration: InputDecoration(
suffixIcon: _suffixBuilder(),
enabled: widget.enabled,
border: InputBorder.none,
hintText: widget.hintText,
errorStyle: const TextStyle(height: 0.01),
hintStyle: (widget.isSmall
child: Padding(
padding: const EdgeInsets.fromLTRB(8,8,0,8),
child: TextFormField(
inputFormatters: <TextInputFormatter>[
if (!widget.acceptSpace)
FilteringTextInputFormatter.allow(
RegExp("[0-9a-zA-Z\u0600-\u06FF]")),
],
autofocus: widget.autoFocus,
obscureText: _hideContent,
textAlign: widget.textAlign ?? TextAlign.start,
keyboardType: widget.textInputType,
textInputAction: widget.textInputAction,
focusNode: _focusNode,
controller: _controller,
onFieldSubmitted: widget.onSubmitted,
onChanged: _onChanged,
validator: _validator,
maxLines: _hideContent ? 1 : widget.maxLine,
minLines: widget.minLine,
maxLength: widget.maxLength,
obscuringCharacter: '*',
buildCounter: widget.showLen
? null
: (context,
{required currentLength,
required isFocused,
required maxLength}) =>
const SizedBox(),
style: (widget.isSmall
? Theme.of(context).textTheme.bodySmall!
: Theme.of(context).textTheme.bodyMedium!)
.copyWith(color: Theme.of(context).colorScheme.hint),
.copyWith(
fontFamily: DesignConfig.fontFamily.padRight(3)),
decoration: InputDecoration(
suffixIcon: _suffixBuilder(),
enabled: widget.enabled,
border: InputBorder.none,
hintText: widget.hintText,
errorStyle: const TextStyle(height: 0.01),
hintStyle: (widget.isSmall
? Theme.of(context).textTheme.bodySmall!
: Theme.of(context).textTheme.bodyMedium!)
.copyWith(color: Theme.of(context).colorScheme.hint),
),
),
),
);

View File

@ -4,10 +4,8 @@ import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/views/ai/ai_state.dart';
import 'package:didvan/views/ai/bot_assistants_state.dart';
import 'package:didvan/views/widgets/didvan/icon_button.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
@ -65,24 +63,24 @@ class HoshanAppBar extends StatelessWidget implements PreferredSizeWidget {
Navigator.pushNamed(context, Routes.info);
}),
if (withActions)
Stack(
children: [
DidvanIconButton(
icon: DidvanIcons.ai_regular,
size: 32,
onPressed: () {
context.read<BotAssistantsState>().getMyAssissmant();
// Stack(
// children: [
// DidvanIconButton(
// icon: DidvanIcons.ai_regular,
// size: 32,
// onPressed: () {
// context.read<BotAssistantsState>().getMyAssissmant();
Navigator.pushNamed(context, Routes.botAssistants);
},
),
Icon(
CupertinoIcons.plus,
color: Theme.of(context).colorScheme.primary,
size: 16,
)
],
),
// Navigator.pushNamed(context, Routes.botAssistants);
// },
// ),
// Icon(
// CupertinoIcons.plus,
// color: Theme.of(context).colorScheme.primary,
// size: 16,
// )
// ],
// ),
if (context.watch<AiState>().page != 0 || !withActions)
Transform.rotate(
angle: 180 * pi / 180,

View File

@ -351,21 +351,25 @@ class _PrimaryControlsState extends State<PrimaryControls> {
Duration duration =
chewieController.videoPlayerController.value.duration;
if (duration.inSeconds == 0) {
return const SizedBox(); // Ya namayesh-e yek loading bar
}
double maxValue = duration.inMilliseconds.toDouble();
double currentValue = p.inMilliseconds.toDouble();
return Column(
children: [
SliderTheme(
data: SliderThemeData(
trackHeight: 2,
// thumbColor: Colors.transparent,
overlayShape: SliderComponentShape.noOverlay,
thumbShape: const RoundSliderThumbShape(
// elevation: 0,
// pressedElevation: 0,
enabledThumbRadius: 8)),
child: Slider(
min: 0,
max: duration.inMilliseconds.toDouble(),
value: p.inMilliseconds.toDouble(),
max: maxValue,
value: currentValue.clamp(0.0, maxValue), // Estefade az clamp
onChanged: (value) async {
await chewieController.pause();
position.value = Duration(milliseconds: value.round());

View File

@ -1101,6 +1101,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "8.0.1"
pin_input_text_field:
dependency: transitive
description:
name: pin_input_text_field
sha256: f45683032283d30b670ec343781660655e3e1953438b281a0bc6e2d358486236
url: "https://pub.dev"
source: hosted
version: "4.5.2"
platform:
dependency: transitive
description:
@ -1237,6 +1245,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.4"
shimmer:
dependency: "direct main"
description:
name: shimmer
sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
skeleton_text:
dependency: "direct main"
description:
@ -1250,6 +1266,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
sms_autofill:
dependency: "direct main"
description:
name: sms_autofill
sha256: c65836abe9c1f62ce411bb78d5546a09ece4297558070b1bd871db1db283aaf9
url: "https://pub.dev"
source: hosted
version: "2.4.1"
source_span:
dependency: transitive
description:

View File

@ -110,6 +110,8 @@ dependencies:
package_info_plus: ^8.3.0
flutter_local_notifications: ^19.1.0
flutter_inappwebview: ^6.1.5
sms_autofill: ^2.4.1
shimmer: ^3.0.0
# fading_edge_scrollview: ^4.1.1
dev_dependencies:
flutter_test: