Add audio player functionality to file explorer and integrate just_audio package
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import 'package:b0esche_cloud/widgets/audio_player_bar.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'dart:ui';
|
||||
import 'dart:js_interop';
|
||||
@@ -35,7 +36,8 @@ class FileExplorer extends StatefulWidget {
|
||||
State<FileExplorer> createState() => _FileExplorerState();
|
||||
}
|
||||
|
||||
class _FileExplorerState extends State<FileExplorer> {
|
||||
class _FileExplorerState extends State<FileExplorer>
|
||||
with SingleTickerProviderStateMixin {
|
||||
String _getFileTypeLabel(FileItem file) {
|
||||
if (file.type == FileType.folder) return 'Folder';
|
||||
final name = file.name.toLowerCase();
|
||||
@@ -111,6 +113,11 @@ class _FileExplorerState extends State<FileExplorer> {
|
||||
}
|
||||
|
||||
String? _selectedFilePath;
|
||||
String? _audioFileName;
|
||||
String? _audioFileUrl;
|
||||
bool _showAudioBar = false;
|
||||
late AnimationController _audioBarController;
|
||||
late Animation<Offset> _audioBarOffset;
|
||||
bool _isSearching = false;
|
||||
bool _showField = false;
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
@@ -138,12 +145,29 @@ class _FileExplorerState extends State<FileExplorer> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_audioBarController.dispose();
|
||||
_searchController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_audioBarController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 350),
|
||||
);
|
||||
_audioBarOffset =
|
||||
Tween<Offset>(begin: const Offset(0, -1), end: Offset.zero).animate(
|
||||
CurvedAnimation(
|
||||
parent: _audioBarController,
|
||||
curve: Curves.easeOutCubic,
|
||||
),
|
||||
);
|
||||
context.read<FileBrowserBloc>().add(
|
||||
LoadDirectory(orgId: widget.orgId, path: '/'),
|
||||
);
|
||||
context.read<PermissionBloc>().add(LoadPermissions(widget.orgId));
|
||||
super.initState();
|
||||
context.read<FileBrowserBloc>().add(
|
||||
LoadDirectory(orgId: widget.orgId, path: '/'),
|
||||
@@ -863,178 +887,214 @@ class _FileExplorerState extends State<FileExplorer> {
|
||||
|
||||
Widget _buildTitle() {
|
||||
const double titleWidth = 72.0;
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
height: 50,
|
||||
child: Stack(
|
||||
alignment: Alignment.centerLeft,
|
||||
children: [
|
||||
const Positioned(
|
||||
left: 0,
|
||||
child: Text(
|
||||
'/Drive',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
color: AppTheme.primaryText,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
AnimatedPositioned(
|
||||
left: _isSearching ? titleWidth + 250.0 : titleWidth,
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeInOut,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 2),
|
||||
child: IconButton(
|
||||
icon: Icon(
|
||||
_isSearching ? Icons.close : Icons.search,
|
||||
color: AppTheme.accentColor,
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 50,
|
||||
child: Stack(
|
||||
alignment: Alignment.centerLeft,
|
||||
children: [
|
||||
const Positioned(
|
||||
left: 0,
|
||||
child: Text(
|
||||
'/Drive',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
color: AppTheme.primaryText,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
splashColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
onPressed: () {
|
||||
if (_isSearching) {
|
||||
setState(() {
|
||||
_showField = false;
|
||||
_isSearching = false;
|
||||
_searchController.clear();
|
||||
_searchQuery = '';
|
||||
context.read<FileBrowserBloc>().add(ApplyFilter(''));
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_isSearching = true;
|
||||
});
|
||||
Future.delayed(const Duration(milliseconds: 150), () {
|
||||
setState(() {
|
||||
_showField = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 0,
|
||||
top: 0,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 2),
|
||||
child: BlocBuilder<FileBrowserBloc, FileBrowserState>(
|
||||
builder: (context, state) {
|
||||
String currentSort = 'name';
|
||||
bool isAscending = true;
|
||||
if (state is DirectoryLoaded) {
|
||||
currentSort = state.sortBy;
|
||||
isAscending = state.isAscending;
|
||||
}
|
||||
return Row(
|
||||
children: [
|
||||
PopupMenuButton<String>(
|
||||
color: AppTheme.accentColor.withAlpha(220),
|
||||
position: PopupMenuPosition.under,
|
||||
offset: const Offset(48, 8),
|
||||
itemBuilder: (BuildContext context) => const [
|
||||
PopupMenuItem(value: 'name', child: Text('Name')),
|
||||
PopupMenuItem(value: 'date', child: Text('Date')),
|
||||
PopupMenuItem(value: 'size', child: Text('Size')),
|
||||
PopupMenuItem(value: 'type', child: Text('Type')),
|
||||
AnimatedPositioned(
|
||||
left: _isSearching ? titleWidth + 250.0 : titleWidth,
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.easeInOut,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 2),
|
||||
child: IconButton(
|
||||
icon: Icon(
|
||||
_isSearching ? Icons.close : Icons.search,
|
||||
color: AppTheme.accentColor,
|
||||
),
|
||||
splashColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
onPressed: () {
|
||||
if (_isSearching) {
|
||||
setState(() {
|
||||
_showField = false;
|
||||
_isSearching = false;
|
||||
_searchController.clear();
|
||||
_searchQuery = '';
|
||||
context.read<FileBrowserBloc>().add(ApplyFilter(''));
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_isSearching = true;
|
||||
});
|
||||
Future.delayed(const Duration(milliseconds: 150), () {
|
||||
setState(() {
|
||||
_showField = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 0,
|
||||
top: 0,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 2),
|
||||
child: BlocBuilder<FileBrowserBloc, FileBrowserState>(
|
||||
builder: (context, state) {
|
||||
String currentSort = 'name';
|
||||
bool isAscending = true;
|
||||
if (state is DirectoryLoaded) {
|
||||
currentSort = state.sortBy;
|
||||
isAscending = state.isAscending;
|
||||
}
|
||||
return Row(
|
||||
children: [
|
||||
PopupMenuButton<String>(
|
||||
color: AppTheme.accentColor.withAlpha(220),
|
||||
position: PopupMenuPosition.under,
|
||||
offset: const Offset(48, 8),
|
||||
itemBuilder: (BuildContext context) => const [
|
||||
PopupMenuItem(value: 'name', child: Text('Name')),
|
||||
PopupMenuItem(value: 'date', child: Text('Date')),
|
||||
PopupMenuItem(value: 'size', child: Text('Size')),
|
||||
PopupMenuItem(value: 'type', child: Text('Type')),
|
||||
],
|
||||
onSelected: (value) {
|
||||
context.read<FileBrowserBloc>().add(
|
||||
ApplySort(value, isAscending: isAscending),
|
||||
);
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.arrow_drop_down,
|
||||
color: AppTheme.primaryText,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
currentSort == 'name'
|
||||
? 'Name'
|
||||
: currentSort == 'date'
|
||||
? 'Date'
|
||||
: currentSort == 'size'
|
||||
? 'Size'
|
||||
: 'Type',
|
||||
style: const TextStyle(
|
||||
color: AppTheme.primaryText,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
isAscending
|
||||
? Icons.arrow_upward
|
||||
: Icons.arrow_downward,
|
||||
color: AppTheme.accentColor,
|
||||
),
|
||||
splashColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
onPressed: () {
|
||||
context.read<FileBrowserBloc>().add(
|
||||
ApplySort(
|
||||
currentSort,
|
||||
isAscending: !isAscending,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
onSelected: (value) {
|
||||
context.read<FileBrowserBloc>().add(
|
||||
ApplySort(value, isAscending: isAscending),
|
||||
);
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.arrow_drop_down,
|
||||
color: AppTheme.primaryText,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
currentSort == 'name'
|
||||
? 'Name'
|
||||
: currentSort == 'date'
|
||||
? 'Date'
|
||||
: currentSort == 'size'
|
||||
? 'Size'
|
||||
: 'Type',
|
||||
style: const TextStyle(
|
||||
color: AppTheme.primaryText,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
isAscending
|
||||
? Icons.arrow_upward
|
||||
: Icons.arrow_downward,
|
||||
color: AppTheme.accentColor,
|
||||
),
|
||||
splashColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
onPressed: () {
|
||||
context.read<FileBrowserBloc>().add(
|
||||
ApplySort(currentSort, isAscending: !isAscending),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_showField)
|
||||
AnimatedPositioned(
|
||||
left: titleWidth,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
child: SizedBox(
|
||||
width: 250,
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
autofocus: true,
|
||||
style: const TextStyle(color: AppTheme.primaryText),
|
||||
cursorColor: AppTheme.accentColor,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Search Files...',
|
||||
hintStyle: const TextStyle(color: AppTheme.secondaryText),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
vertical: 2,
|
||||
horizontal: 12,
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
borderSide: BorderSide(
|
||||
color: AppTheme.secondaryText.withValues(alpha: 0.5),
|
||||
if (_showField)
|
||||
AnimatedPositioned(
|
||||
left: titleWidth,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
child: SizedBox(
|
||||
width: 250,
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
autofocus: true,
|
||||
style: const TextStyle(color: AppTheme.primaryText),
|
||||
cursorColor: AppTheme.accentColor,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Search Files...',
|
||||
hintStyle: const TextStyle(
|
||||
color: AppTheme.secondaryText,
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
vertical: 2,
|
||||
horizontal: 12,
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
borderSide: BorderSide(
|
||||
color: AppTheme.secondaryText.withValues(
|
||||
alpha: 0.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
focusedBorder: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(24)),
|
||||
borderSide: BorderSide(color: AppTheme.accentColor),
|
||||
),
|
||||
),
|
||||
),
|
||||
focusedBorder: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(24)),
|
||||
borderSide: BorderSide(color: AppTheme.accentColor),
|
||||
onChanged: (value) {
|
||||
_searchQuery = value;
|
||||
context.read<FileBrowserBloc>().add(
|
||||
ApplyFilter(_searchQuery),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
_searchQuery = value;
|
||||
context.read<FileBrowserBloc>().add(
|
||||
ApplyFilter(_searchQuery),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Animated audio bar
|
||||
AnimatedBuilder(
|
||||
animation: _audioBarController,
|
||||
builder: (context, child) {
|
||||
return (_showAudioBar &&
|
||||
_audioFileName != null &&
|
||||
_audioFileUrl != null)
|
||||
? SlideTransition(
|
||||
position: _audioBarOffset,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: AudioPlayerBar(
|
||||
fileName: _audioFileName!,
|
||||
fileUrl: _audioFileUrl!,
|
||||
onClose: () {
|
||||
_audioBarController.reverse();
|
||||
setState(() => _showAudioBar = false);
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1064,6 +1124,20 @@ class _FileExplorerState extends State<FileExplorer> {
|
||||
(ext) => file.name.toLowerCase().endsWith(ext),
|
||||
);
|
||||
|
||||
final ext = p.extension(file.name).toLowerCase();
|
||||
const audioExts = [
|
||||
'.mp3',
|
||||
'.wav',
|
||||
'.flac',
|
||||
'.ogg',
|
||||
'.aac',
|
||||
'.m4a',
|
||||
'.opus',
|
||||
'.wma',
|
||||
'.alac',
|
||||
'.aiff',
|
||||
'.amr',
|
||||
];
|
||||
return MouseRegion(
|
||||
onEnter: (_) => setState(() => _hovered[file.path] = true),
|
||||
onExit: (_) => setState(() => _hovered[file.path] = false),
|
||||
@@ -1075,7 +1149,6 @@ class _FileExplorerState extends State<FileExplorer> {
|
||||
if (file.type == FileType.folder) {
|
||||
context.read<FileBrowserBloc>().add(NavigateToFolder(file.path));
|
||||
} else if (isVideo) {
|
||||
// Open video files in video viewer - use viewer session for authenticated URL
|
||||
if (file.id == null || file.id!.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Error: File ID is missing')),
|
||||
@@ -1095,6 +1168,25 @@ class _FileExplorerState extends State<FileExplorer> {
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (audioExts.contains(ext)) {
|
||||
if (file.id == null || file.id!.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Error: File ID is missing')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
final fileService = getIt<FileService>();
|
||||
final url = await fileService.getFileUrl(
|
||||
orgId: widget.orgId,
|
||||
fileId: file.id!,
|
||||
fileName: file.name,
|
||||
);
|
||||
setState(() {
|
||||
_audioFileName = file.name;
|
||||
_audioFileUrl = url;
|
||||
_showAudioBar = true;
|
||||
});
|
||||
_audioBarController.forward();
|
||||
} else {
|
||||
if (file.id == null || file.id!.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
|
||||
139
b0esche_cloud/lib/widgets/audio_player_bar.dart
Normal file
139
b0esche_cloud/lib/widgets/audio_player_bar.dart
Normal file
@@ -0,0 +1,139 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
|
||||
class AudioPlayerBar extends StatefulWidget {
|
||||
final String fileName;
|
||||
final String fileUrl;
|
||||
final VoidCallback? onClose;
|
||||
|
||||
const AudioPlayerBar({
|
||||
super.key,
|
||||
required this.fileName,
|
||||
required this.fileUrl,
|
||||
this.onClose,
|
||||
});
|
||||
|
||||
@override
|
||||
State<AudioPlayerBar> createState() => _AudioPlayerBarState();
|
||||
}
|
||||
|
||||
class _AudioPlayerBarState extends State<AudioPlayerBar> {
|
||||
late AudioPlayer _audioPlayer;
|
||||
Duration _duration = Duration.zero;
|
||||
Duration _position = Duration.zero;
|
||||
bool _isPlaying = false;
|
||||
bool _isLoading = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_audioPlayer = AudioPlayer();
|
||||
_initAudio();
|
||||
}
|
||||
|
||||
Future<void> _initAudio() async {
|
||||
try {
|
||||
await _audioPlayer.setUrl(widget.fileUrl);
|
||||
setState(() {
|
||||
_duration = _audioPlayer.duration ?? Duration.zero;
|
||||
_isLoading = false;
|
||||
});
|
||||
_audioPlayer.positionStream.listen((pos) {
|
||||
setState(() {
|
||||
_position = pos;
|
||||
});
|
||||
});
|
||||
_audioPlayer.playerStateStream.listen((state) {
|
||||
setState(() {
|
||||
_isPlaying = state.playing;
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
// Optionally show error
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_audioPlayer.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
String _formatDuration(Duration d) {
|
||||
String twoDigits(int n) => n.toString().padLeft(2, '0');
|
||||
final minutes = twoDigits(d.inMinutes.remainder(60));
|
||||
final seconds = twoDigits(d.inSeconds.remainder(60));
|
||||
return '$minutes:$seconds';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
elevation: 8,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
height: 64,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(_isPlaying ? Icons.pause : Icons.play_arrow),
|
||||
onPressed: _isLoading
|
||||
? null
|
||||
: () {
|
||||
if (_isPlaying) {
|
||||
_audioPlayer.pause();
|
||||
} else {
|
||||
_audioPlayer.play();
|
||||
}
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.fileName,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Slider(
|
||||
min: 0,
|
||||
max: _duration.inMilliseconds.toDouble(),
|
||||
value: _position.inMilliseconds
|
||||
.clamp(0, _duration.inMilliseconds)
|
||||
.toDouble(),
|
||||
onChanged: _isLoading
|
||||
? null
|
||||
: (value) {
|
||||
_audioPlayer.seek(
|
||||
Duration(milliseconds: value.toInt()),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Text(
|
||||
'${_formatDuration(_position)} / ${_formatDuration(_duration)}',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
if (widget.onClose != null)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: widget.onClose,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,14 @@
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import audio_session
|
||||
import connectivity_plus
|
||||
import desktop_drop
|
||||
import device_info_plus
|
||||
import file_picker
|
||||
import flutter_secure_storage_darwin
|
||||
import irondash_engine_context
|
||||
import just_audio
|
||||
import shared_preferences_foundation
|
||||
import sqflite_darwin
|
||||
import super_native_extensions
|
||||
@@ -19,12 +21,14 @@ import url_launcher_macos
|
||||
import video_player_avfoundation
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
|
||||
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
|
||||
DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin"))
|
||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
|
||||
FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin"))
|
||||
IrondashEngineContextPlugin.register(with: registry.registrar(forPlugin: "IrondashEngineContextPlugin"))
|
||||
JustAudioPlugin.register(with: registry.registrar(forPlugin: "JustAudioPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||
SuperNativeExtensionsPlugin.register(with: registry.registrar(forPlugin: "SuperNativeExtensionsPlugin"))
|
||||
|
||||
@@ -41,6 +41,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.0"
|
||||
audio_session:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: audio_session
|
||||
sha256: "8f96a7fecbb718cb093070f868b4cdcb8a9b1053dce342ff8ab2fde10eb9afb7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.2"
|
||||
bloc:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -696,6 +704,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.8.0"
|
||||
just_audio:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: just_audio
|
||||
sha256: "9694e4734f515f2a052493d1d7e0d6de219ee0427c7c29492e246ff32a219908"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.10.5"
|
||||
just_audio_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: just_audio_platform_interface
|
||||
sha256: "2532c8d6702528824445921c5ff10548b518b13f808c2e34c2fd54793b999a6a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.6.0"
|
||||
just_audio_web:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: just_audio_web
|
||||
sha256: "6ba8a2a7e87d57d32f0f7b42856ade3d6a9fbe0f1a11fabae0a4f00bb73f0663"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.16"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -63,6 +63,8 @@ dependencies:
|
||||
# Video Playback
|
||||
video_player: ^2.8.2
|
||||
syncfusion_flutter_core: ^31.2.18
|
||||
just_audio_web: ^0.4.16
|
||||
just_audio: ^0.10.5
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user