mirror of
https://github.com/A-Star100/simpliplay-android.git
synced 2025-09-18 06:29:39 +00:00
Modified source for improvement of management
This commit is contained in:
parent
3cef0cf007
commit
9bb3bf06c6
@ -1,6 +1,6 @@
|
||||
# flutter_exoplayer_creator
|
||||
# media3_exoplayer_creator
|
||||
|
||||
Flutter version of ExoPlayer Creator
|
||||
ExoPlayer Creator that supports Media3
|
||||
|
||||
## Getting Started
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- Add Internet and storage permissions -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<!--<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> -->
|
||||
|
||||
|
@ -1,7 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
import 'package:chewie/chewie.dart';
|
||||
import 'package:keep_screen_on/keep_screen_on.dart'; // Import the keep_screen_on package
|
||||
import 'package:media3_exoplayer_creator/screens/video_screen.dart'; // Import other files as needed
|
||||
|
||||
void main() {
|
||||
runApp(MyApp());
|
||||
@ -15,162 +13,43 @@ class MyApp extends StatelessWidget {
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.blue, // Set primary color to blue
|
||||
colorScheme: ColorScheme.light(
|
||||
primary: Colors.blue, // Primary color
|
||||
secondary: Colors.blueAccent, // Accent color (replaces accentColor)
|
||||
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(
|
||||
backgroundColor: Colors.blue, // Set AppBar background to blue
|
||||
),
|
||||
buttonTheme: ButtonThemeData(
|
||||
buttonColor: Colors.blue, // Set button color to blue
|
||||
),
|
||||
// Customize other theme properties as needed
|
||||
),
|
||||
home: VideoURLScreen(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class VideoURLScreen extends StatefulWidget {
|
||||
const VideoURLScreen({super.key});
|
||||
|
||||
@override
|
||||
_VideoURLScreenState createState() => _VideoURLScreenState();
|
||||
}
|
||||
|
||||
class _VideoURLScreenState extends State<VideoURLScreen> {
|
||||
String _videoUrl = '';
|
||||
|
||||
// Show the dialog to ask for the video URL
|
||||
Future<void> _showVideoURLDialog() async {
|
||||
final TextEditingController videoController = TextEditingController();
|
||||
|
||||
return showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false, // Prevent dismissing by tapping outside
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('Enter Video URL'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
controller: videoController,
|
||||
decoration: InputDecoration(hintText: 'Enter a valid video URL'),
|
||||
keyboardType: TextInputType.url,
|
||||
),
|
||||
],
|
||||
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,
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: Text('Cancel'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text('OK'),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_videoUrl = videoController.text;
|
||||
});
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('ExoPlayer Creator (New Edition)'),
|
||||
),
|
||||
body: Center(
|
||||
child: _videoUrl.isEmpty
|
||||
? ElevatedButton(
|
||||
onPressed: _showVideoURLDialog,
|
||||
child: Text('Enter Video URL'),
|
||||
)
|
||||
: VideoPlayerWidget(videoUrl: _videoUrl),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class VideoPlayerWidget extends StatefulWidget {
|
||||
final String videoUrl;
|
||||
|
||||
const VideoPlayerWidget({super.key, required this.videoUrl});
|
||||
|
||||
@override
|
||||
_VideoPlayerWidgetState createState() => _VideoPlayerWidgetState();
|
||||
}
|
||||
|
||||
class _VideoPlayerWidgetState extends State<VideoPlayerWidget> {
|
||||
late VideoPlayerController _videoPlayerController;
|
||||
late ChewieController _chewieController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_videoPlayerController = VideoPlayerController.networkUrl(Uri.parse(widget.videoUrl)); // Convert String to Uri
|
||||
|
||||
_chewieController = ChewieController(
|
||||
videoPlayerController: _videoPlayerController,
|
||||
aspectRatio: 16 / 9,
|
||||
autoPlay: true,
|
||||
looping: true,
|
||||
);
|
||||
|
||||
// Enable keep_screen_on to prevent screen sleep
|
||||
_videoPlayerController.addListener(() {
|
||||
if (_videoPlayerController.value.isPlaying) {
|
||||
KeepScreenOn.turnOn(); // Keep the screen on while video is playing
|
||||
} else {
|
||||
KeepScreenOn.turnOff(); // Allow the screen to turn off when the video is paused or stopped
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_chewieController.dispose();
|
||||
_videoPlayerController.dispose();
|
||||
KeepScreenOn.turnOff(); // Ensure screen turns off when the widget is disposed
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Chewie(
|
||||
controller: _chewieController,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Confirm exit when back is pressed
|
||||
Future<bool> _onWillPop(BuildContext context) async {
|
||||
return await showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text('Are you sure?'),
|
||||
content: Text('If you exit now, ExoPlayer will stop playing.'),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: Text('No'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
child: Text('Yes'),
|
||||
textButtonTheme: TextButtonThemeData(
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.blueAccent, // Set text color to blue for TextButtons
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
) ?? false;
|
||||
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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,176 @@
|
||||
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
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
Future<void> requestPermissionIfNeeded(String subtitleFilePath, BuildContext context) async {
|
||||
if (subtitleFilePath.isNotEmpty) {
|
||||
// Only request permission if a subtitle file is chosen
|
||||
PermissionStatus status = await Permission.storage.status;
|
||||
|
||||
if (!status.isGranted) {
|
||||
// If permission is not granted, request it
|
||||
status = await Permission.storage.request();
|
||||
if (status.isDenied) {
|
||||
// If permission is denied, show an alert dialog
|
||||
_showPermissionDeniedDialog(context);
|
||||
return; // Exit as we cannot proceed without permission
|
||||
} else if (status.isPermanentlyDenied) {
|
||||
// If the permission is permanently denied, guide the user to settings
|
||||
_showPermissionDeniedDialog(context, permanentlyDenied: true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _showPermissionDeniedDialog(BuildContext context, {bool permanentlyDenied = false}) {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false, // Prevent tapping outside to dismiss
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('Permission Denied'),
|
||||
content: Text(
|
||||
permanentlyDenied
|
||||
? 'The permission to access external storage has been permanently denied. Please go to the app settings to enable it.'
|
||||
: 'You have denied the permission to access external storage. Please allow it to proceed.',
|
||||
),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
if (permanentlyDenied) {
|
||||
// Optionally, open app settings if permission is permanently denied
|
||||
openAppSettings();
|
||||
}
|
||||
Navigator.of(context).pop(); // Close the dialog
|
||||
},
|
||||
child: Text('OK'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
class WebVttCue {
|
||||
final Duration start;
|
||||
final Duration end;
|
||||
final String text;
|
||||
|
||||
WebVttCue({
|
||||
required this.start,
|
||||
required this.end,
|
||||
required this.text,
|
||||
});
|
||||
}
|
||||
|
||||
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 text = match.group(3)!;
|
||||
cues.add(WebVttCue(start: start, end: end, text: text));
|
||||
}
|
||||
|
||||
return cues;
|
||||
}
|
||||
|
||||
Duration parseTime(String time) {
|
||||
final parts = time.split(':');
|
||||
final secondsParts = parts[2].split('.');
|
||||
return Duration(
|
||||
hours: int.parse(parts[0]),
|
||||
minutes: int.parse(parts[1]),
|
||||
seconds: int.parse(secondsParts[0]),
|
||||
milliseconds: int.parse(secondsParts[1]),
|
||||
);
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -129,6 +129,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
file_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file_picker
|
||||
sha256: be325344c1f3070354a1d84a231a1ba75ea85d413774ec4bdf444c023342e030
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.5.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@ -150,6 +158,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.0"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.24"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@ -304,6 +320,46 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.0"
|
||||
permission_handler:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: permission_handler
|
||||
sha256: bc56bfe9d3f44c3c612d8d393bd9b174eb796d706759f9b495ac254e4294baa5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.4.5"
|
||||
permission_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
sha256: "59c6322171c29df93a22d150ad95f3aa19ed86542eaec409ab2691b8f35f9a47"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.3.6"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_apple
|
||||
sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.1.4"
|
||||
permission_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_platform_interface
|
||||
sha256: "6760eb5ef34589224771010805bea6054ad28453906936f843a8cc4d3a55c4a4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.12.0"
|
||||
permission_handler_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_windows
|
||||
sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.3"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -33,6 +33,8 @@ dependencies:
|
||||
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
|
||||
keep_screen_on: ^3.0.0 # Add this line
|
||||
file_picker: ^5.2.2 # Use the latest version
|
||||
permission_handler: ^10.2.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@ -51,7 +53,7 @@ dev_dependencies:
|
||||
flutter_icons:
|
||||
android: true
|
||||
ios: true
|
||||
image_path: "assets/icon/icon.png"
|
||||
image_path: "assets/icon.png"
|
||||
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
|
Loading…
Reference in New Issue
Block a user