Added Flutter v5 source code

This commit is contained in:
Anirudh Sevugan 2024-12-30 19:08:36 -06:00
parent c3d40aa684
commit 6f6246d620
36 changed files with 692 additions and 346 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -1,55 +0,0 @@
import 'package:flutter/material.dart';
import 'package:media3_exoplayer_creator/screens/video_screen.dart'; // Import other files as needed
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue, // Set primary color to blue
colorScheme: ColorScheme.fromSwatch(primarySwatch: Colors.blue).copyWith(
secondary: Colors.blue, // Set accent (secondary) color to blue accent
),
appBarTheme: AppBarTheme(
color: Colors.blue, // Set the AppBar color to blue
titleTextStyle: TextStyle(
color: Colors.white, // Set text color to white for AppBar
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: Colors.blueAccent, // Set text color to blue for TextButtons
),
),
inputDecorationTheme: InputDecorationTheme(
filled: true, // Allow filled background for text fields
fillColor: Colors.white, // White background for text fields
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), // Rounded corners for text fields
borderSide: BorderSide(
color: Colors.blueAccent, // Border color
width: 2,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: Colors.blueAccent, // Focused border color
width: 2,
),
),
),
),
home: VideoScreen(),
);
}
}

View File

@ -1,176 +0,0 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:file_picker/file_picker.dart';
import 'package:http/http.dart' as http;
import 'package:keep_screen_on/keep_screen_on.dart';
import 'package:permission_handler/permission_handler.dart'; // Add this import
import '../widgets/video_player_widget.dart';
// The VideoScreen widget class
class VideoScreen extends StatefulWidget {
const VideoScreen({super.key});
@override
_VideoScreenState createState() => _VideoScreenState();
}
// The State class for VideoScreen
class _VideoScreenState extends State<VideoScreen> {
String _videoUrl = '';
String _filePath = '';
String _subtitleUrl = ''; // Subtitle URL variable
String _subtitleFilePath = ''; // Subtitle file path
// Method to show video URL dialog
Future<void> _showVideoURLDialog() async {
final TextEditingController videoController = TextEditingController();
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Enter Video URL'),
content: TextField(
controller: videoController,
decoration: InputDecoration(hintText: 'Enter a valid video URL'),
keyboardType: TextInputType.url,
),
actions: <Widget>[
TextButton(
child: Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text('OK'),
onPressed: () {
setState(() {
_videoUrl = videoController.text;
_filePath = '';
});
Navigator.of(context).pop();
},
),
],
);
},
);
}
// Method to pick video file
Future<void> _pickFile() async {
FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.video);
if (result != null && result.files.single.path != null) {
setState(() {
_filePath = result.files.single.path!;
_videoUrl = '';
});
}
}
// Method to pick subtitle file
Future<void> _pickSubtitleFile() async {
FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: ['vtt']);
if (result != null && result.files.single.path != null) {
setState(() {
_subtitleFilePath = result.files.single.path!;
_subtitleUrl = ''; // Clear subtitle URL when a subtitle file is picked
});
}
}
// Method to enter subtitle URL
Future<void> _enterSubtitleURL() async {
final TextEditingController subtitleController = TextEditingController();
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Enter Subtitle URL'),
content: TextField(
controller: subtitleController,
decoration: InputDecoration(hintText: 'Enter a valid subtitle URL'),
keyboardType: TextInputType.url,
),
actions: <Widget>[
TextButton(
child: Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text('OK'),
onPressed: () {
setState(() {
_subtitleUrl = subtitleController.text;
});
Navigator.of(context).pop();
},
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: _videoUrl.isEmpty && _filePath.isEmpty
? AppBar(
title: Text('ExoPlayer Creator'),
)
: null, // Hide AppBar when video is playing
body: Center(
child: _videoUrl.isEmpty && _filePath.isEmpty
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: _showVideoURLDialog,
child: Text('Enter Video URL'),
),
SizedBox(width: 16),
ElevatedButton(
onPressed: _pickFile,
child: Text('Choose Video File'),
),
],
),
SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: _enterSubtitleURL,
child: Text('Enter Subtitle URL'),
),
SizedBox(width: 16),
ElevatedButton(
onPressed: _pickSubtitleFile,
child: Text('Choose Subtitle File'),
),
],
),
],
)
: VideoPlayerWidget(
videoUrl: _videoUrl,
filePath: _filePath,
subtitleUrl: _subtitleUrl, // Pass subtitle URL to VideoPlayerWidget
subtitleFilePath: _subtitleFilePath, // Pass subtitle file path to VideoPlayerWidget
),
),
);
}
}

View File

@ -1,91 +0,0 @@
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
import 'package:chewie/chewie.dart';
import 'dart:io'; // Import to use File class
import 'package:media3_exoplayer_creator/utils/permission_utils.dart'; // Import permission_utils.dart for permission handling
import 'package:media3_exoplayer_creator/utils/web_vtt.dart';
class VideoPlayerWidget extends StatefulWidget {
final String videoUrl;
final String filePath;
final String subtitleUrl;
final String subtitleFilePath;
const VideoPlayerWidget({
Key? key,
required this.videoUrl,
required this.filePath,
required this.subtitleUrl,
required this.subtitleFilePath,
}) : super(key: key);
@override
_VideoPlayerWidgetState createState() => _VideoPlayerWidgetState();
}
class _VideoPlayerWidgetState extends State<VideoPlayerWidget> {
late VideoPlayerController _videoPlayerController;
late ChewieController _chewieController;
late List<WebVttCue> _subtitles;
String? _currentSubtitle;
@override
void initState() {
super.initState();
// Request permission for subtitle files if a subtitle file path is provided
if (widget.subtitleFilePath.isNotEmpty) {
requestPermissionIfNeeded(widget.subtitleFilePath, context); // Correctly call the method here
}
// Initialize the video player controller based on video URL or file path
if (widget.filePath.isNotEmpty) {
_videoPlayerController = VideoPlayerController.file(File(widget.filePath));
} else {
_videoPlayerController = VideoPlayerController.networkUrl(Uri.parse(widget.videoUrl));
}
// Initialize the Chewie controller for video playback
_chewieController = ChewieController(
videoPlayerController: _videoPlayerController,
aspectRatio: 16 / 9,
autoPlay: true,
looping: true,
);
// Load subtitles if needed
_loadSubtitles();
}
// Method to load subtitles (you can adapt it to your subtitle parsing logic)
Future<void> _loadSubtitles() async {
// Add your subtitle loading logic here
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Chewie(controller: _chewieController), // Video player widget
if (_currentSubtitle != null && _currentSubtitle!.isNotEmpty)
Positioned(
bottom: 50,
left: 0,
right: 0,
child: Text(
_currentSubtitle!,
style: TextStyle(
fontSize: 19,
color: Colors.white,
),
textAlign: TextAlign.center,
),
),
],
),
);
}
}

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -0,0 +1,109 @@
import 'package:flutter/material.dart';
import 'package:media3_exoplayer_creator/screens/video_screen.dart'; // Import other files as needed
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool _isDarkMode = false;
void _toggleTheme() {
setState(() {
_isDarkMode = !_isDarkMode;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: _isDarkMode
? ThemeData.dark().copyWith(
primaryColor: Colors.blue,
colorScheme: ColorScheme.fromSwatch(
primarySwatch: Colors.lightBlue)
.copyWith(secondary: Colors.blue),
appBarTheme: const AppBarTheme(
color: Colors.blue,
titleTextStyle: TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: Colors.blue,
),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: Colors.white,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: Colors.blue,
width: 2,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: Colors.blue,
width: 2,
),
),
),
)
: ThemeData.light().copyWith(
primaryColor: Colors.blue,
scaffoldBackgroundColor: Colors.white,
colorScheme: ColorScheme.fromSwatch(
primarySwatch: Colors.lightBlue)
.copyWith(secondary: Colors.blue),
appBarTheme: const AppBarTheme(
color: Colors.blue,
titleTextStyle: TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: Colors.blue,
),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: Colors.white,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: Colors.blue,
width: 2,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: Colors.blue,
width: 2,
),
),
),
),
home: Scaffold(
body: VideoScreen(),
),
);
}
}

View File

@ -0,0 +1,277 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:file_picker/file_picker.dart';
import '../widgets/video_player_widget.dart';
import 'package:media3_exoplayer_creator/main.dart';
// The VideoScreen widget class
class VideoScreen extends StatefulWidget {
const VideoScreen({super.key});
@override
_VideoScreenState createState() => _VideoScreenState();
}
// The State class for VideoScreen
class _VideoScreenState extends State<VideoScreen> {
String _videoUrl = '';
String _filePath = '';
String _subtitleUrl = ''; // Subtitle URL variable
String _subtitleFilePath = ''; // Subtitle file path
bool _isDarkMode = false; // Add theme state here
// Method to show video URL dialog
Future<void> _showVideoURLDialog() async {
final TextEditingController videoController = TextEditingController();
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Enter Video URL'),
content: TextField(
controller: videoController,
decoration: InputDecoration(hintText: 'Enter a valid video URL'),
keyboardType: TextInputType.url,
),
actions: <Widget>[
TextButton(
child: Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text('OK'),
onPressed: () {
setState(() {
_videoUrl = videoController.text;
_filePath = '';
});
Navigator.of(context).pop();
},
),
],
);
},
);
}
// Method to pick video file
Future<void> _pickFile() async {
FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.video);
if (result != null && result.files.single.path != null) {
setState(() {
_filePath = result.files.single.path!;
_videoUrl = '';
});
}
}
// Method to pick subtitle file
Future<void> _pickSubtitleFile() async {
FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.custom, allowedExtensions: ['vtt']);
if (result != null && result.files.single.path != null) {
setState(() {
_subtitleFilePath = result.files.single.path!;
_subtitleUrl = ''; // Clear subtitle URL when a subtitle file is picked
});
}
}
// Method to enter subtitle URL
Future<void> _enterSubtitleURL() async {
final TextEditingController subtitleController = TextEditingController();
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Enter Subtitle URL'),
content: TextField(
controller: subtitleController,
decoration: InputDecoration(hintText: 'Enter a valid subtitle URL'),
keyboardType: TextInputType.url,
),
actions: <Widget>[
TextButton(
child: Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text('OK'),
onPressed: () {
setState(() {
_subtitleUrl = subtitleController.text;
});
Navigator.of(context).pop();
},
),
],
);
},
);
}
// Toggle the theme
void _toggleTheme() {
setState(() {
_isDarkMode = !_isDarkMode;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: _isDarkMode
? ThemeData.dark().copyWith(
primaryColor: Colors.blue,
colorScheme: ColorScheme.fromSwatch(
primarySwatch: Colors.lightBlue)
.copyWith(secondary: Colors.blue),
appBarTheme: const AppBarTheme(
color: Colors.blue,
titleTextStyle: TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: Colors.blue,
),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: Colors.white,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: Colors.blue,
width: 2,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: Colors.blue,
width: 2,
),
),
),
)
: ThemeData.light().copyWith(
primaryColor: Colors.blue,
scaffoldBackgroundColor: Colors.white,
colorScheme: ColorScheme.fromSwatch(
primarySwatch: Colors.lightBlue)
.copyWith(secondary: Colors.blue),
appBarTheme: const AppBarTheme(
color: Colors.blue,
titleTextStyle: TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: Colors.blue,
),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: Colors.white,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: Colors.blue,
width: 2,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color: Colors.blue,
width: 2,
),
),
),
dialogTheme: DialogTheme(
backgroundColor: Colors.white, // Light background for light mode
titleTextStyle: TextStyle(
color: Colors.black, // Black text for titles in light mode
fontWeight: FontWeight.bold,
),
contentTextStyle: TextStyle(
color: Colors.black, // Black text for content in light mode
),
),
),
home: Scaffold(
appBar: _videoUrl.isEmpty && _filePath.isEmpty
? AppBar(
title: const Text('ExoPlayer Creator'),
actions: [
IconButton(
icon: Icon(_isDarkMode ? Icons.light_mode : Icons.dark_mode),
onPressed: _toggleTheme,
),
],
)
: null, // Hide AppBar when video is playing
body: Center(
child: _videoUrl.isEmpty && _filePath.isEmpty
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: _showVideoURLDialog,
child: Text('Enter Video URL'),
),
SizedBox(width: 16),
ElevatedButton(
onPressed: _pickFile,
child: Text('Choose Video File'),
),
],
),
SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: _enterSubtitleURL,
child: Text('Enter Subtitle URL'),
),
SizedBox(width: 16),
ElevatedButton(
onPressed: _pickSubtitleFile,
child: Text('Choose Subtitle File'),
),
],
),
],
)
: VideoPlayerWidget(
videoUrl: _videoUrl,
filePath: _filePath,
subtitleUrl: _subtitleUrl, // Pass subtitle URL to VideoPlayerWidget
subtitleFilePath: _subtitleFilePath, // Pass subtitle file path to VideoPlayerWidget
),
),
),
);
}
}

View File

@ -10,13 +10,14 @@ class WebVttCue {
});
}
// Subtitle parsing logic (WebVTT format)
List<WebVttCue> parseWebVtt(String subtitleData) {
final cuePattern = RegExp(r'(\d{2}:\d{2}:\d{2}.\d{3}) --> (\d{2}:\d{2}:\d{2}.\d{3})\n(.*?)\n\n', dotAll: true);
final List<WebVttCue> cues = [];
for (final match in cuePattern.allMatches(subtitleData)) {
final start = parseTime(match.group(1)!);
final end = parseTime(match.group(2)!);
final start = _parseTime(match.group(1)!);
final end = _parseTime(match.group(2)!);
final text = match.group(3)!;
cues.add(WebVttCue(start: start, end: end, text: text));
}
@ -24,7 +25,8 @@ List<WebVttCue> parseWebVtt(String subtitleData) {
return cues;
}
Duration parseTime(String time) {
// Helper method to parse time string to Duration
Duration _parseTime(String time) {
final parts = time.split(':');
final secondsParts = parts[2].split('.');
return Duration(

View File

@ -0,0 +1,199 @@
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
import 'package:chewie/chewie.dart';
import 'package:http/http.dart' as http;
import 'dart:io'; // Import to use File class
import 'package:media3_exoplayer_creator/utils/permission_utils.dart'; // Import permission_utils.dart for permission handling
import 'package:keep_screen_on/keep_screen_on.dart'; // Import keep_screen_on
import 'package:flutter/services.dart'; // Import SystemChrome
import '../utils/web_vtt.dart'; // Import web_vtt.dart for subtitle handling
class VideoPlayerWidget extends StatefulWidget {
final String videoUrl;
final String filePath;
final String subtitleUrl;
final String subtitleFilePath;
const VideoPlayerWidget({
Key? key,
required this.videoUrl,
required this.filePath,
required this.subtitleUrl,
required this.subtitleFilePath,
}) : super(key: key);
@override
_VideoPlayerWidgetState createState() => _VideoPlayerWidgetState();
}
class _VideoPlayerWidgetState extends State<VideoPlayerWidget> {
late VideoPlayerController _videoPlayerController;
late ChewieController _chewieController;
late List<WebVttCue> _subtitles;
String? _currentSubtitle;
bool _isLoading = true; // Flag to track loading state of subtitles
@override
void initState() {
super.initState();
// Request permission for subtitle files if a subtitle file path is provided
if (widget.subtitleFilePath.isNotEmpty) {
requestPermissionIfNeeded(widget.subtitleFilePath, context); // Correctly call the method here
}
// Initialize the video player controller based on video URL or file path
if (widget.filePath.isNotEmpty) {
_videoPlayerController = VideoPlayerController.file(File(widget.filePath));
} else {
_videoPlayerController = VideoPlayerController.networkUrl(Uri.parse(widget.videoUrl));
}
// Initialize the Chewie controller for video playback
_chewieController = ChewieController(
videoPlayerController: _videoPlayerController,
aspectRatio: 16 / 9,
autoPlay: true,
looping: true,
);
// Load subtitles if needed
_loadSubtitles();
// Keep the screen on while the video is playing
KeepScreenOn.turnOn();
// Hide system UI (status bar and navigation bar) when the video starts
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
// Listen to video position changes to update subtitles
_videoPlayerController.addListener(_updateCurrentSubtitle);
// Initialize the video player
_initializeVideoPlayer();
}
@override
void dispose() {
// Turn off the screen stay-on feature and reset the system UI when the widget is disposed
KeepScreenOn.turnOff();
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: SystemUiOverlay.values); // Restore UI to default
_videoPlayerController.removeListener(_updateCurrentSubtitle);
_videoPlayerController.dispose();
super.dispose();
}
// Method to load subtitles (you can adapt it to your subtitle parsing logic)
Future<void> _loadSubtitles() async {
setState(() {
_isLoading = true; // Start loading subtitles
});
// Check if subtitle path is provided
if (widget.subtitleFilePath.isNotEmpty) {
// Load subtitle from local file
try {
final file = File(widget.subtitleFilePath);
final subtitleData = await file.readAsString();
setState(() {
_subtitles = parseWebVtt(subtitleData); // Use parseWebVtt from web_vtt.dart
_isLoading = false; // Subtitles loaded successfully
});
} catch (e) {
setState(() {
_isLoading = false; // Failed to load subtitles
});
print('Error loading subtitle file: $e');
}
} else if (widget.subtitleUrl.isNotEmpty) {
// Load subtitle from URL
try {
final response = await http.get(Uri.parse(widget.subtitleUrl));
if (response.statusCode == 200) {
setState(() {
_subtitles = parseWebVtt(response.body); // Use parseWebVtt from web_vtt.dart
_isLoading = false; // Subtitles loaded successfully
});
} else {
setState(() {
_isLoading = false; // Failed to load subtitles from URL
});
print('Failed to load subtitle from URL: ${response.statusCode}');
}
} catch (e) {
setState(() {
_isLoading = false; // Failed to load subtitles
});
print('Error loading subtitle from URL: $e');
}
}
}
// Method to update the current subtitle based on the video position
void _updateCurrentSubtitle() {
final currentTime = _videoPlayerController.value.position;
for (var cue in _subtitles) {
if (currentTime >= cue.start && currentTime <= cue.end) {
setState(() {
_currentSubtitle = cue.text;
});
return;
}
}
setState(() {
_currentSubtitle = '';
});
}
void _initializeVideoPlayer() async {
// Initialize the video player
await _videoPlayerController.initialize();
// Update loading state once the video is ready
setState(() {
_isLoading = false;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
// Background set to black
Container(
color: Colors.black,
child: Chewie(controller: _chewieController), // Video player widget
),
// Show a loading indicator while video or subtitles are loading
if (_isLoading)
Center(
child: CircularProgressIndicator(),
),
// Display the current subtitle if available
if (_currentSubtitle != null && _currentSubtitle!.isNotEmpty && !_isLoading)
Positioned(
bottom: 70, // Adjusted the bottom padding to be higher
left: 0,
right: 0,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
color: Colors.black.withOpacity(0.7), // Black background with transparency
child: Text(
_currentSubtitle!,
style: TextStyle(
fontSize: 19,
color: Colors.white,
),
textAlign: TextAlign.center,
),
),
),
],
),
);
}
}

View File

@ -81,6 +81,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.19.0"
cross_file:
dependency: transitive
description:
name: cross_file
sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
url: "https://pub.dev"
source: hosted
version: "0.3.4+2"
crypto:
dependency: transitive
description:
@ -133,10 +141,10 @@ packages:
dependency: "direct main"
description:
name: file_picker
sha256: be325344c1f3070354a1d84a231a1ba75ea85d413774ec4bdf444c023342e030
sha256: c904b4ab56d53385563c7c39d8e9fa9af086f91495dfc48717ad84a42c3cf204
url: "https://pub.dev"
source: hosted
version: "5.5.0"
version: "8.1.7"
flutter:
dependency: "direct main"
description: flutter
@ -324,42 +332,50 @@ packages:
dependency: "direct main"
description:
name: permission_handler
sha256: bc56bfe9d3f44c3c612d8d393bd9b174eb796d706759f9b495ac254e4294baa5
sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb"
url: "https://pub.dev"
source: hosted
version: "10.4.5"
version: "11.3.1"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: "59c6322171c29df93a22d150ad95f3aa19ed86542eaec409ab2691b8f35f9a47"
sha256: "71bbecfee799e65aff7c744761a57e817e73b738fedf62ab7afd5593da21f9f1"
url: "https://pub.dev"
source: hosted
version: "10.3.6"
version: "12.0.13"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5"
sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0
url: "https://pub.dev"
source: hosted
version: "9.1.4"
version: "9.4.5"
permission_handler_html:
dependency: transitive
description:
name: permission_handler_html
sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24"
url: "https://pub.dev"
source: hosted
version: "0.1.3+5"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: "6760eb5ef34589224771010805bea6054ad28453906936f843a8cc4d3a55c4a4"
sha256: e9c8eadee926c4532d0305dff94b85bf961f16759c3af791486613152af4b4f9
url: "https://pub.dev"
source: hosted
version: "3.12.0"
version: "4.2.3"
permission_handler_windows:
dependency: transitive
description:
name: permission_handler_windows
sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
url: "https://pub.dev"
source: hosted
version: "0.1.3"
version: "0.2.1"
petitparser:
dependency: transitive
description:
@ -453,6 +469,70 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.0"
url_launcher:
dependency: "direct main"
description:
name: url_launcher
sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
url: "https://pub.dev"
source: hosted
version: "6.3.1"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193"
url: "https://pub.dev"
source: hosted
version: "6.3.14"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626"
url: "https://pub.dev"
source: hosted
version: "6.3.2"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
url: "https://pub.dev"
source: hosted
version: "3.2.1"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2"
url: "https://pub.dev"
source: hosted
version: "3.2.2"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: "44cf3aabcedde30f2dba119a9dea3b0f2672fbe6fa96e85536251d678216b3c4"
url: "https://pub.dev"
source: hosted
version: "3.1.3"
vector_math:
dependency: transitive
description:

View File

@ -30,11 +30,12 @@ environment:
dependencies:
flutter:
sdk: flutter
video_player: ^2.3.0 # Make sure to check for the latest version
chewie: ^1.2.2 # Optional, if you want to use a higher-level video player with controls
video_player: ^2.9.2 # Make sure to check for the latest version
chewie: ^1.8.5 # Optional, if you want to use a higher-level video player with controls
keep_screen_on: ^3.0.0 # Add this line
file_picker: ^5.2.2 # Use the latest version
permission_handler: ^10.2.0
file_picker: ^8.1.7 # Use the latest version
permission_handler: ^11.3.1
url_launcher: ^6.3.1
dev_dependencies:
flutter_test:
@ -50,7 +51,7 @@ dev_dependencies:
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
flutter_icons:
flutter_launcher_icons:
android: true
ios: true
image_path: "assets/icon.png"
@ -59,14 +60,14 @@ flutter_icons:
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# included with your application, so that you can use the icons ina
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
assets:
- assets/ani.png
- assets/icon.png
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images