"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:
parent
d0cb260e14
commit
3f6957ae15
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
extension NavigatorStateExtension on NavigatorState {
|
||||
|
||||
void pushNamedIfNotCurrent(String routeName, {Object? arguments}) {
|
||||
if (!isCurrent(routeName)) {
|
||||
pushNamed(routeName, arguments: arguments);
|
||||
|
|
@ -18,5 +17,77 @@ extension NavigatorStateExtension on NavigatorState {
|
|||
});
|
||||
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('');
|
||||
}
|
||||
}
|
||||
|
|
@ -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,9 +540,21 @@ class _AiChatPageState extends State<AiChatPage> {
|
|||
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()
|
||||
? Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
padding:
|
||||
const EdgeInsets.all(8.0),
|
||||
child: messageImage(file),
|
||||
)
|
||||
: Padding(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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!,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue