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

View File

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

View File

@ -1,22 +1,93 @@
import 'package:flutter/cupertino.dart';
extension NavigatorStateExtension on NavigatorState {
void pushNamedIfNotCurrent( String routeName, {Object? arguments} ) {
void pushNamedIfNotCurrent(String routeName, {Object? arguments}) {
if (!isCurrent(routeName)) {
pushNamed( routeName, arguments: arguments );
pushNamed(routeName, arguments: arguments);
}
}
bool isCurrent( String routeName ) {
bool isCurrent(String routeName) {
bool isCurrent = false;
popUntil( (route) {
popUntil((route) {
if (route.settings.name == routeName) {
isCurrent = true;
}
return true;
} );
});
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/alert_data.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/network/request.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/marquee_text.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/material.dart';
import 'package:flutter/services.dart';
@ -421,7 +421,7 @@ class _AiChatPageState extends State<AiChatPage> {
FilesModel? file = message.fileLocal ??
(message.file == null
? null
: FilesModel(message.file.toString(),
: FilesModel(message.file.toString().replaceAll(' ', ''),
duration: message.duration != null
? Duration(seconds: message.duration!)
: null));
@ -540,18 +540,30 @@ class _AiChatPageState extends State<AiChatPage> {
totalDuration: file.duration,
),
)
: file.isImage()
: file.isVideo()
? Padding(
padding: const EdgeInsets.all(8.0),
child: messageImage(file),
padding: const EdgeInsets.fromLTRB(
16, 16, 16, 0),
child: ClipRRect(
borderRadius:
DesignConfig.lowBorderRadius,
child: ChatVideoPlayer(
src: file.path,
)),
)
: Padding(
padding: const EdgeInsets.all(
8.0,
),
child: messageFile(
context, message, state),
),
: file.isImage()
? Padding(
padding:
const EdgeInsets.all(8.0),
child: messageImage(file),
)
: Padding(
padding: const EdgeInsets.all(
8.0,
),
child: messageFile(
context, message, state),
),
if (message.text != null &&
message.text!.isNotEmpty &&
((message.audio == null ||

View File

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

View File

@ -376,17 +376,19 @@ class _AiMessageBarState extends State<AiMessageBar> {
Expanded(
child: state.file != null && state.file!.isRecorded
? audioContainer()
: Form(
child: TextFormField(
: TextFormField(
textInputAction: TextInputAction.newline,
style: Theme.of(context).textTheme.bodyMedium,
minLines: 1,
maxLines: 6, // Set this
keyboardType: TextInputType.multiline,
controller: state.message,
enabled: !(state.file != null &&
widget.bot.attachment == 1),
decoration: InputDecoration(
contentPadding:
const EdgeInsets.fromLTRB(12, 12, 12, 0),
border: InputBorder.none,
hintText: 'بنویسید...',
hintStyle: Theme.of(context)
@ -412,7 +414,7 @@ class _AiMessageBarState extends State<AiMessageBar> {
state.message.text = value;
state.update();
},
)),
),
),
Padding(
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"
description:
name: video_player
sha256: e30df0d226c4ef82e2c150ebf6834b3522cf3f654d8e2f9419d376cdc071425d
sha256: "4a8c3492d734f7c39c2588a3206707a05ee80cef52e8c7f3b2078d430c84bc17"
url: "https://pub.dev"
source: hosted
version: "2.9.1"
version: "2.9.2"
video_player_android:
dependency: transitive
description:

View File

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