"Updated BotsModel and FilesModel with new properties, added extensions to String, modified AiChatPage and AiChatState, and updated dependencies in pubspec.yaml and pubspec.lock."

This commit is contained in:
OkaykOrhmn 2024-11-11 16:59:26 +03:30
parent d0cb260e14
commit 3f6957ae15
11 changed files with 475 additions and 30 deletions

View File

@ -2,6 +2,7 @@ class BotsModel {
int? id; int? id;
String? name; String? name;
String? image; String? image;
String? responseType;
String? description; String? description;
List<String>? attachmentType; List<String>? attachmentType;
int? attachment; int? attachment;
@ -22,6 +23,7 @@ class BotsModel {
} }
attachment = json['attachment']; attachment = json['attachment'];
editable = json['editable']; editable = json['editable'];
responseType = json['responseType'];
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
@ -35,6 +37,7 @@ class BotsModel {
} }
data['attachment'] = attachment; data['attachment'] = attachment;
data['editable'] = editable; data['editable'] = editable;
data['responseType'] = responseType;
return data; return data;
} }
} }

View File

@ -12,6 +12,7 @@ class FilesModel {
final bool isRecorded; final bool isRecorded;
final bool? audio; final bool? audio;
final bool? image; final bool? image;
final bool? video;
final bool? network; final bool? network;
final Uint8List? bytes; final Uint8List? bytes;
final Duration? duration; final Duration? duration;
@ -22,6 +23,7 @@ class FilesModel {
this.isRecorded = false, this.isRecorded = false,
this.audio, this.audio,
this.image, this.image,
this.video,
this.network, this.network,
this.bytes, this.bytes,
this.duration, this.duration,
@ -39,7 +41,7 @@ class FilesModel {
bool isAudio() { bool isAudio() {
return audio ?? return audio ??
(lookupMimeType(path)?.startsWith('audio/') ?? false) || (lookupMimeType(path)?.startsWith('audio/') ?? false) ||
(lookupMimeType(path)?.startsWith('video/') ?? false); path.contains(".mp3");
} }
bool isImage() { bool isImage() {
@ -48,6 +50,12 @@ class FilesModel {
false || path.contains(".png"); false || path.contains(".png");
} }
bool isVideo() {
return video ??
lookupMimeType(path)?.startsWith('video/') ??
false || path.contains(".mp4");
}
bool isNetwork() { bool isNetwork() {
return network ?? path.startsWith('blob:') || path.startsWith('/uploads'); return network ?? path.startsWith('blob:') || path.startsWith('/uploads');
} }

View File

@ -1,7 +1,6 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
extension NavigatorStateExtension on NavigatorState { extension NavigatorStateExtension on NavigatorState {
void pushNamedIfNotCurrent(String routeName, {Object? arguments}) { void pushNamedIfNotCurrent(String routeName, {Object? arguments}) {
if (!isCurrent(routeName)) { if (!isCurrent(routeName)) {
pushNamed(routeName, arguments: arguments); pushNamed(routeName, arguments: arguments);
@ -18,5 +17,77 @@ extension NavigatorStateExtension on NavigatorState {
}); });
return isCurrent; return isCurrent;
} }
}
extension StringUrl on String {
bool startsWithEnglish() {
// Regular expression to check if the first character is an English letter
return RegExp(r'^[A-Za-z]').hasMatch(this);
}
bool startsWithPersian() {
// Regular expression to check if the first character is a Persian letter
return RegExp(r'^[\u0600-\u06FF]').hasMatch(this);
}
bool isImage() {
final extension = split('.').last.toLowerCase();
const imageExtensions = [
'jpg',
'jpeg',
'png',
'gif',
'bmp',
'webp',
'tiff'
];
return imageExtensions.contains(extension);
}
bool isDocument() {
final extension = split('.').last.toLowerCase();
const documentExtensions = [
'pdf',
'doc',
'docx',
'xls',
'xlsx',
'ppt',
'pptx',
'txt'
];
return documentExtensions.contains(extension);
}
bool isAudio() {
final extension = split('.').last.toLowerCase();
const audioExtensions = ['mp3', 'wav', 'aac', 'ogg', 'flac'];
return audioExtensions.contains(extension);
}
bool isVideo() {
final extension = split('.').last.toLowerCase();
const videoExtensions = ['mp4', 'avi', 'mov', 'wmv', 'mkv', 'flv'];
return videoExtensions.contains(extension);
}
String convertToEnglishNumber() {
final Map<String, String> persianToEnglishMap = {
'۰': '0',
'۱': '1',
'۲': '2',
'۳': '3',
'۴': '4',
'۵': '5',
'۶': '6',
'۷': '7',
'۸': '8',
'۹': '9',
};
return split('').map((char) {
return persianToEnglishMap[char] ??
char; // Replace with English number if exists, else keep original char
}).join('');
}
} }

View File

@ -16,7 +16,6 @@ import 'package:didvan/models/enums.dart';
import 'package:didvan/models/view/action_sheet_data.dart'; import 'package:didvan/models/view/action_sheet_data.dart';
import 'package:didvan/models/view/alert_data.dart'; import 'package:didvan/models/view/alert_data.dart';
import 'package:didvan/routes/routes.dart'; import 'package:didvan/routes/routes.dart';
import 'package:didvan/services/app_initalizer.dart';
import 'package:didvan/services/media/media.dart'; import 'package:didvan/services/media/media.dart';
import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart'; import 'package:didvan/services/network/request_helper.dart';
@ -31,6 +30,7 @@ import 'package:didvan/views/widgets/didvan/icon_button.dart';
import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/marquee_text.dart'; import 'package:didvan/views/widgets/marquee_text.dart';
import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:didvan/views/widgets/skeleton_image.dart';
import 'package:didvan/views/widgets/video/chat_video_player.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -421,7 +421,7 @@ class _AiChatPageState extends State<AiChatPage> {
FilesModel? file = message.fileLocal ?? FilesModel? file = message.fileLocal ??
(message.file == null (message.file == null
? null ? null
: FilesModel(message.file.toString(), : FilesModel(message.file.toString().replaceAll(' ', ''),
duration: message.duration != null duration: message.duration != null
? Duration(seconds: message.duration!) ? Duration(seconds: message.duration!)
: null)); : null));
@ -540,9 +540,21 @@ class _AiChatPageState extends State<AiChatPage> {
totalDuration: file.duration, totalDuration: file.duration,
), ),
) )
: file.isVideo()
? Padding(
padding: const EdgeInsets.fromLTRB(
16, 16, 16, 0),
child: ClipRRect(
borderRadius:
DesignConfig.lowBorderRadius,
child: ChatVideoPlayer(
src: file.path,
)),
)
: file.isImage() : file.isImage()
? Padding( ? Padding(
padding: const EdgeInsets.all(8.0), padding:
const EdgeInsets.all(8.0),
child: messageImage(file), child: messageImage(file),
) )
: Padding( : Padding(

View File

@ -179,7 +179,7 @@ class AiChatState extends CoreProvier {
var str = utf8.decode(value); var str = utf8.decode(value);
if (!kIsWeb) { if (!kIsWeb) {
if (bot.id == 12) { if (bot.responseType != 'text') {
responseMessgae += str.split('{{{').first; responseMessgae += str.split('{{{').first;
} }
if (str.contains('{{{')) { if (str.contains('{{{')) {
@ -202,7 +202,7 @@ class AiChatState extends CoreProvier {
} catch (e) { } catch (e) {
e.printError(); e.printError();
} }
if (bot.id == 12) { if (bot.responseType != 'text') {
responseMessgae = "${responseMessgae.split('.png').first}.png"; responseMessgae = "${responseMessgae.split('.png').first}.png";
return; return;
} }
@ -243,8 +243,8 @@ class AiChatState extends CoreProvier {
} }
messages.last.prompts.last = messages.last.prompts.last.copyWith( messages.last.prompts.last = messages.last.prompts.last.copyWith(
finished: true, finished: true,
text: bot.id == 12 ? null : responseMessgae, text: bot.responseType != 'text' ? null : responseMessgae,
file: bot.id == 12 ? responseMessgae : null, file: bot.responseType != 'text' ? responseMessgae : null,
id: aiMessageId); id: aiMessageId);
if (messages.last.prompts.length > 2) { if (messages.last.prompts.length > 2) {
messages.last.prompts[messages.last.prompts.length - 2] = messages messages.last.prompts[messages.last.prompts.length - 2] = messages

View File

@ -376,17 +376,19 @@ class _AiMessageBarState extends State<AiMessageBar> {
Expanded( Expanded(
child: state.file != null && state.file!.isRecorded child: state.file != null && state.file!.isRecorded
? audioContainer() ? audioContainer()
: Form( : TextFormField(
child: TextFormField(
textInputAction: TextInputAction.newline, textInputAction: TextInputAction.newline,
style: Theme.of(context).textTheme.bodyMedium, style: Theme.of(context).textTheme.bodyMedium,
minLines: 1, minLines: 1,
maxLines: 6, // Set this maxLines: 6, // Set this
keyboardType: TextInputType.multiline, keyboardType: TextInputType.multiline,
controller: state.message, controller: state.message,
enabled: !(state.file != null && enabled: !(state.file != null &&
widget.bot.attachment == 1), widget.bot.attachment == 1),
decoration: InputDecoration( decoration: InputDecoration(
contentPadding:
const EdgeInsets.fromLTRB(12, 12, 12, 0),
border: InputBorder.none, border: InputBorder.none,
hintText: 'بنویسید...', hintText: 'بنویسید...',
hintStyle: Theme.of(context) hintStyle: Theme.of(context)
@ -412,7 +414,7 @@ class _AiMessageBarState extends State<AiMessageBar> {
state.message.text = value; state.message.text = value;
state.update(); state.update();
}, },
)), ),
), ),
Padding( Padding(
padding: const EdgeInsets.only(bottom: 8.0), padding: const EdgeInsets.only(bottom: 8.0),

View File

@ -0,0 +1,86 @@
// ignore_for_file: deprecated_member_use
import 'package:chewie/chewie.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart';
import 'package:didvan/views/widgets/video/custome_controls.dart';
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:video_player/video_player.dart';
class ChatVideoPlayer extends StatefulWidget {
final String src;
const ChatVideoPlayer({Key? key, required this.src}) : super(key: key);
@override
State<ChatVideoPlayer> createState() => _ChatVideoPlayerState();
}
class _ChatVideoPlayerState extends State<ChatVideoPlayer> {
late VideoPlayerController _videoPlayerController;
ChewieController? _chewieController;
@override
void initState() {
super.initState();
_handleVideoPlayback();
}
Future<void> _handleVideoPlayback() async {
_videoPlayerController = VideoPlayerController.network(
RequestHelper.baseUrl + widget.src,
httpHeaders: {'Authorization': 'Bearer ${RequestService.token}'});
await _videoPlayerController.initialize().then((_) {
setState(() {
_chewieController = ChewieController(
customControls: const CustomControls(),
videoPlayerController: _videoPlayerController,
autoPlay: false,
looping: true,
showOptions: false,
allowPlaybackSpeedChanging: false,
placeholder: const CircularProgressIndicator(),
aspectRatio: 16 / 9,
materialProgressColors: ChewieProgressColors(
playedColor: Theme.of(context).colorScheme.title,
handleColor: Theme.of(context).colorScheme.title),
);
});
}).catchError((e) {
setState(() {});
});
}
@override
void dispose() {
_videoPlayerController.pause();
_videoPlayerController.dispose();
_chewieController?.dispose(); // Dispose of the ChewieController
super.dispose();
}
@override
Widget build(BuildContext context) {
return _chewieController == null
? SizedBox(
child: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: SpinKitThreeBounce(
color: Theme.of(context).colorScheme.primary,
size: 18,
),
),
),
)
: AspectRatio(
aspectRatio: _videoPlayerController.value.aspectRatio,
child: Chewie(
controller: _chewieController!,
),
);
}
}

View File

@ -0,0 +1,197 @@
import 'dart:async';
import 'package:chewie/chewie.dart';
import 'package:didvan/views/widgets/video/play_btn_animation.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class CustomControls extends StatefulWidget {
const CustomControls({super.key});
@override
State<CustomControls> createState() => _CustomControlsState();
}
class _CustomControlsState extends State<CustomControls> {
late ChewieController chewieController;
bool isAnimating = false;
double opacity = 1;
Timer? _hideControlsTimer;
ValueNotifier<Duration> position = ValueNotifier(Duration.zero);
@override
void didChangeDependencies() {
super.didChangeDependencies();
chewieController = ChewieController.of(context);
chewieController.videoPlayerController.addListener(
() {
position.value = chewieController.videoPlayerController.value.position;
},
);
}
void _startHideControlsTimer() {
_hideControlsTimer?.cancel();
_hideControlsTimer = Timer(const Duration(seconds: 3), () {
setState(() {
opacity = 0;
});
});
}
@override
void dispose() {
_hideControlsTimer?.cancel(); // Clean up the timer
super.dispose();
}
@override
Widget build(BuildContext context) {
return Directionality(
textDirection: TextDirection.ltr,
child: AnimatedOpacity(
duration: const Duration(milliseconds: 400),
opacity: opacity,
child: InkWell(
onTap: () {
setState(() {
opacity = 1;
});
_startHideControlsTimer(); // Restart the timer on tap
},
child: Stack(
children: [
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
padding: const EdgeInsets.only(bottom: 12),
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: [
Colors.black,
Colors.black26,
Color.fromARGB(10, 0, 0, 0)
])),
child: Row(
children: [
// Positioned.fill(
// child: Container(
// decoration: BoxDecoration(
// color: !chewieController.isPlaying
// ? Colors.black.withOpacity(0.4)
// : Colors.transparent),
// )),
_buildPlayPause(),
_buildProgressIndicator(),
_buildFullScreenToggle(),
],
),
),
),
],
),
),
),
);
}
Widget _buildPlayPause() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: InkWell(
onTap: () {
setState(() {
if (chewieController.isPlaying) {
chewieController.pause();
opacity = 1;
} else {
chewieController.play();
opacity = 0;
}
isAnimating = true;
});
_startHideControlsTimer(); // Restart the timer on tap
},
child: PlayBtnAnimation(
alwaysAnimate: true,
isAnimating: isAnimating,
onEnd: () => setState(
() => isAnimating = false,
),
child: Icon(
chewieController.isPlaying
? CupertinoIcons.pause_fill
: CupertinoIcons.play_fill,
color: Colors.white,
size: 24,
),
),
),
);
}
Widget _buildProgressIndicator() {
return Expanded(
child: ValueListenableBuilder<Duration>(
valueListenable: position,
builder: (context, p, _) {
Duration duration =
chewieController.videoPlayerController.value.duration;
return 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(),
onChanged: (value) async {
await chewieController.pause();
position.value = Duration(milliseconds: value.round());
_startHideControlsTimer();
},
onChangeEnd: (value) async {
await chewieController
.seekTo(Duration(milliseconds: value.round()));
await chewieController.play();
setState(() {});
},
),
);
},
),
);
}
Widget _buildFullScreenToggle() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: InkWell(
onTap: () => setState(() {
chewieController.toggleFullScreen();
_startHideControlsTimer(); // Restart the timer on tap
}),
child: Icon(
chewieController.isFullScreen
? Icons.fullscreen_exit
: Icons.fullscreen,
color: Colors.white,
size: 30,
),
),
);
}
}

View File

@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
class PlayBtnAnimation extends StatefulWidget {
final Widget child;
final bool isAnimating;
final bool alwaysAnimate;
final Duration duration;
final Function()? onEnd;
const PlayBtnAnimation(
{Key? key,
required this.child,
required this.isAnimating,
this.duration = const Duration(milliseconds: 150),
this.onEnd,
this.alwaysAnimate = false})
: super(key: key);
@override
State<PlayBtnAnimation> createState() => _PlayBtnAnimationState();
}
class _PlayBtnAnimationState extends State<PlayBtnAnimation>
with SingleTickerProviderStateMixin {
late AnimationController controller;
late Animation<double> scale;
@override
void initState() {
super.initState();
final halfDuration = widget.duration.inMilliseconds ~/ 2;
controller = AnimationController(
vsync: this, duration: Duration(milliseconds: halfDuration));
scale = Tween<double>(begin: 1, end: 1.2).animate(controller);
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
void didUpdateWidget(covariant PlayBtnAnimation oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.isAnimating != oldWidget.isAnimating) {
doAnimation();
}
}
Future doAnimation() async {
if (widget.isAnimating) {
await controller.forward();
await controller.reverse();
await Future.delayed(const Duration(milliseconds: 400));
widget.onEnd?.call();
}
}
@override
Widget build(BuildContext context) {
return ScaleTransition(scale: scale, child: widget.child);
}
}

View File

@ -1374,10 +1374,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: video_player name: video_player
sha256: e30df0d226c4ef82e2c150ebf6834b3522cf3f654d8e2f9419d376cdc071425d sha256: "4a8c3492d734f7c39c2588a3206707a05ee80cef52e8c7f3b2078d430c84bc17"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.9.1" version: "2.9.2"
video_player_android: video_player_android:
dependency: transitive dependency: transitive
description: description:

View File

@ -84,7 +84,7 @@ dependencies:
get: ^4.6.6 get: ^4.6.6
# firebase_auth: ^4.19.6 # firebase_auth: ^4.19.6
just_audio: ^0.9.11 just_audio: ^0.9.11
video_player: ^2.8.7 video_player: ^2.9.2
chewie: ^1.8.3 chewie: ^1.8.3
typewritertext: ^3.0.8 typewritertext: ^3.0.8
flutter_markdown: ^0.7.3+1 flutter_markdown: ^0.7.3+1