"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;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,93 @@
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isCurrent( String routeName ) {
|
bool isCurrent(String routeName) {
|
||||||
bool isCurrent = false;
|
bool isCurrent = false;
|
||||||
popUntil( (route) {
|
popUntil((route) {
|
||||||
if (route.settings.name == routeName) {
|
if (route.settings.name == routeName) {
|
||||||
isCurrent = true;
|
isCurrent = true;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} );
|
});
|
||||||
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('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,18 +540,30 @@ class _AiChatPageState extends State<AiChatPage> {
|
||||||
totalDuration: file.duration,
|
totalDuration: file.duration,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: file.isImage()
|
: file.isVideo()
|
||||||
? Padding(
|
? Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.fromLTRB(
|
||||||
child: messageImage(file),
|
16, 16, 16, 0),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius:
|
||||||
|
DesignConfig.lowBorderRadius,
|
||||||
|
child: ChatVideoPlayer(
|
||||||
|
src: file.path,
|
||||||
|
)),
|
||||||
)
|
)
|
||||||
: Padding(
|
: file.isImage()
|
||||||
padding: const EdgeInsets.all(
|
? Padding(
|
||||||
8.0,
|
padding:
|
||||||
),
|
const EdgeInsets.all(8.0),
|
||||||
child: messageFile(
|
child: messageImage(file),
|
||||||
context, message, state),
|
)
|
||||||
),
|
: Padding(
|
||||||
|
padding: const EdgeInsets.all(
|
||||||
|
8.0,
|
||||||
|
),
|
||||||
|
child: messageFile(
|
||||||
|
context, message, state),
|
||||||
|
),
|
||||||
if (message.text != null &&
|
if (message.text != null &&
|
||||||
message.text!.isNotEmpty &&
|
message.text!.isNotEmpty &&
|
||||||
((message.audio == null ||
|
((message.audio == null ||
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
|
|
||||||
|
|
@ -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"
|
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:
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue