♻️ Refactor lyrics fetching
This commit is contained in:
@@ -1,201 +0,0 @@
|
|||||||
# GroovyBox Track Repository Refactor Summary
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
Successfully refactored the `lib/data/track_repository.dart` to support more settings including in-place indexing and folder watching functionality.
|
|
||||||
|
|
||||||
## Key Changes Made
|
|
||||||
|
|
||||||
### 1. Database Schema Updates (`lib/data/db.dart`)
|
|
||||||
- Added `WatchFolders` table for managing watch folders
|
|
||||||
- Added `AppSettings` table for storing app preferences
|
|
||||||
- Updated database schema version to 6
|
|
||||||
- Added proper migrations for new tables
|
|
||||||
|
|
||||||
### 2. Settings Provider (`lib/providers/settings_provider.dart`)
|
|
||||||
- Created comprehensive settings management with Riverpod
|
|
||||||
- Added `ImportMode` enum (Copy vs In-place)
|
|
||||||
- Added auto-scan and watch-for-changes settings
|
|
||||||
- Added supported audio formats configuration
|
|
||||||
- Persistent storage using SharedPreferences
|
|
||||||
|
|
||||||
### 3. Watch Folder Provider (`lib/providers/watch_folder_provider.dart`)
|
|
||||||
- Created service for managing watch folders
|
|
||||||
- Added database operations for CRUD operations
|
|
||||||
- Simplified implementation avoiding complex watcher issues
|
|
||||||
- Added folder scanning functionality
|
|
||||||
- Added missing track cleanup
|
|
||||||
|
|
||||||
### 4. Track Repository Refactor (`lib/data/track_repository.dart`)
|
|
||||||
- **Major Changes:**
|
|
||||||
- Split `importFiles` into mode-specific methods
|
|
||||||
- `_importFilesWithCopy`: Original behavior (copies files to internal storage)
|
|
||||||
- `_importFilesInPlace`: New behavior (indexes files in original location)
|
|
||||||
- Added `scanDirectory` method for folder scanning
|
|
||||||
- Added `scanWatchFolders` method for bulk scanning
|
|
||||||
- Added file event handlers (`addFileFromWatch`, `removeFileFromWatch`, `updateFileFromWatch`)
|
|
||||||
- Added `cleanupMissingTracks` for maintaining database integrity
|
|
||||||
- Updated `deleteTrack` to handle in-place vs copied files correctly
|
|
||||||
|
|
||||||
### 5. Settings UI (`lib/ui/screens/settings_screen.dart`)
|
|
||||||
- Created comprehensive settings interface
|
|
||||||
- Import mode selection (Copy vs In-place)
|
|
||||||
- Auto-scan and watch-for-changes toggles
|
|
||||||
- Watch folders management section
|
|
||||||
- Supported formats display
|
|
||||||
- Integration with new providers
|
|
||||||
|
|
||||||
### 6. Dependencies (`pubspec.yaml`)
|
|
||||||
- Added `watcher: ^1.2.0` for file system monitoring
|
|
||||||
- Added `shared_preferences: ^2.3.5` for settings persistence
|
|
||||||
|
|
||||||
## New Functionality
|
|
||||||
|
|
||||||
### Import Modes
|
|
||||||
1. **Copy Mode (Default):**
|
|
||||||
- Original behavior maintained
|
|
||||||
- Files copied to internal music directory
|
|
||||||
- Safe file management
|
|
||||||
- Suitable for mobile devices
|
|
||||||
|
|
||||||
2. **In-place Mode:**
|
|
||||||
- Files indexed in original location
|
|
||||||
- No additional storage usage
|
|
||||||
- Preserves original file organization
|
|
||||||
- Suitable for desktop/storage-rich environments
|
|
||||||
|
|
||||||
### Watch Folder Features
|
|
||||||
- Add/remove watch folders
|
|
||||||
- Toggle active/inactive status
|
|
||||||
- Bulk scanning of all active folders
|
|
||||||
- Automatic cleanup of missing tracks
|
|
||||||
- Support for recursive scanning
|
|
||||||
|
|
||||||
### Settings Management
|
|
||||||
- Persistent storage of user preferences
|
|
||||||
- Auto-scan scheduling
|
|
||||||
- File change monitoring toggle
|
|
||||||
- Configurable audio formats
|
|
||||||
|
|
||||||
## Usage Examples
|
|
||||||
|
|
||||||
### Switch to In-place Indexing
|
|
||||||
```dart
|
|
||||||
// Update settings to use in-place indexing
|
|
||||||
ref.read(settingsProvider.notifier).setImportMode(ImportMode.inplace);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Add Watch Folder
|
|
||||||
```dart
|
|
||||||
// Add a folder to watch list
|
|
||||||
final watchService = ref.read(watchFolderServiceProvider);
|
|
||||||
await watchService.addWatchFolder('/path/to/music', name: 'My Music');
|
|
||||||
```
|
|
||||||
|
|
||||||
### Scan Watch Folders
|
|
||||||
```dart
|
|
||||||
// Scan all active watch folders
|
|
||||||
final trackRepo = ref.read(trackRepositoryProvider);
|
|
||||||
await trackRepo.scanWatchFolders();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Cleanup Missing Tracks
|
|
||||||
```dart
|
|
||||||
// Remove tracks that no longer exist
|
|
||||||
final trackRepo = ref.read(trackRepositoryProvider);
|
|
||||||
await trackRepo.cleanupMissingTracks();
|
|
||||||
```
|
|
||||||
|
|
||||||
## Benefits
|
|
||||||
|
|
||||||
### User Experience
|
|
||||||
- Flexible import options for different use cases
|
|
||||||
- Automatic library maintenance
|
|
||||||
- Real-time folder monitoring capabilities
|
|
||||||
- Preserved file organization when desired
|
|
||||||
|
|
||||||
### Performance
|
|
||||||
- Efficient database operations
|
|
||||||
- Selective file scanning
|
|
||||||
- Proper resource cleanup
|
|
||||||
- Minimal storage impact for in-place mode
|
|
||||||
|
|
||||||
### Maintainability
|
|
||||||
- Clear separation of concerns
|
|
||||||
- Modular provider architecture
|
|
||||||
- Comprehensive error handling
|
|
||||||
- Extensible design for future features
|
|
||||||
|
|
||||||
## Future Enhancements
|
|
||||||
|
|
||||||
### Potential Additions
|
|
||||||
1. Real-time file watching implementation
|
|
||||||
2. Advanced file format detection
|
|
||||||
3. Folder exclusion/inclusion patterns
|
|
||||||
4. Metadata caching for performance
|
|
||||||
5. Batch operations optimization
|
|
||||||
6. Conflict resolution for duplicate files
|
|
||||||
|
|
||||||
### UI Improvements
|
|
||||||
1. Watch folder management interface
|
|
||||||
2. Import progress indicators
|
|
||||||
3. Folder scanning status
|
|
||||||
4. Settings organization and search
|
|
||||||
5. Conflict resolution dialogs
|
|
||||||
|
|
||||||
## Migration Guide
|
|
||||||
|
|
||||||
### For Existing Users
|
|
||||||
- Current behavior preserved (copy mode by default)
|
|
||||||
- Manual switch to in-place mode available
|
|
||||||
- Existing copied files unaffected
|
|
||||||
- Gradual migration possible
|
|
||||||
|
|
||||||
### Recommended Workflow
|
|
||||||
1. Start with copy mode for testing
|
|
||||||
2. Add watch folders in in-place mode
|
|
||||||
3. Enable auto-scan when comfortable
|
|
||||||
4. Use cleanup to maintain library
|
|
||||||
|
|
||||||
## Technical Notes
|
|
||||||
|
|
||||||
### Database Considerations
|
|
||||||
- Unique path constraint ensures no duplicates
|
|
||||||
- Cascade deletion maintains referential integrity
|
|
||||||
- Proper indexing on path for performance
|
|
||||||
- Migration handles existing installations
|
|
||||||
|
|
||||||
### File System Safety
|
|
||||||
- Existence checks before operations
|
|
||||||
- Graceful error handling
|
|
||||||
- Safe disposal of file watchers
|
|
||||||
- Album art always stored internally
|
|
||||||
|
|
||||||
### Memory Management
|
|
||||||
- Lazy loading of watch folders
|
|
||||||
- Efficient streaming for large libraries
|
|
||||||
- Proper cleanup of resources
|
|
||||||
- Minimal memory footprint
|
|
||||||
|
|
||||||
## Testing Recommendations
|
|
||||||
|
|
||||||
### Unit Tests
|
|
||||||
- Test import mode switching
|
|
||||||
- Test watch folder operations
|
|
||||||
- Test file event handling
|
|
||||||
- Test cleanup functionality
|
|
||||||
- Test settings persistence
|
|
||||||
|
|
||||||
### Integration Tests
|
|
||||||
- Test full import workflows
|
|
||||||
- Test settings changes
|
|
||||||
- Test database migrations
|
|
||||||
- Test file system scenarios
|
|
||||||
|
|
||||||
### Edge Cases
|
|
||||||
- Large file collections
|
|
||||||
- Network storage scenarios
|
|
||||||
- Permission denials
|
|
||||||
- File system errors
|
|
||||||
- Corrupted metadata
|
|
||||||
|
|
||||||
This refactor provides a solid foundation for enhanced music library management while maintaining backward compatibility and enabling powerful new features.
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:dio/dio.dart';
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
import 'package:path_provider/path_provider.dart' as path_provider;
|
import 'package:path_provider/path_provider.dart' as path_provider;
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
@@ -16,10 +16,10 @@ class Lyrics {
|
|||||||
|
|
||||||
/// Abstract base class for LRC providers
|
/// Abstract base class for LRC providers
|
||||||
abstract class LrcProvider {
|
abstract class LrcProvider {
|
||||||
late final http.Client session;
|
late final Dio session;
|
||||||
|
|
||||||
LrcProvider() {
|
LrcProvider() {
|
||||||
session = http.Client();
|
session = Dio();
|
||||||
}
|
}
|
||||||
|
|
||||||
String get name;
|
String get name;
|
||||||
@@ -41,7 +41,7 @@ class MusixmatchProvider extends LrcProvider {
|
|||||||
@override
|
@override
|
||||||
String get name => 'Musixmatch';
|
String get name => 'Musixmatch';
|
||||||
|
|
||||||
Future<http.Response> _get(
|
Future<Response> _get(
|
||||||
String action,
|
String action,
|
||||||
List<MapEntry<String, String>> query,
|
List<MapEntry<String, String>> query,
|
||||||
) async {
|
) async {
|
||||||
@@ -55,7 +55,7 @@ class MusixmatchProvider extends LrcProvider {
|
|||||||
final t = DateTime.now().millisecondsSinceEpoch.toString();
|
final t = DateTime.now().millisecondsSinceEpoch.toString();
|
||||||
query.add(MapEntry("t", t));
|
query.add(MapEntry("t", t));
|
||||||
final url = rootUrl + action;
|
final url = rootUrl + action;
|
||||||
return await session.get(Uri.parse(url), headers: Map.fromEntries(query));
|
return await session.get(url, queryParameters: Map.fromEntries(query));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _getToken() async {
|
Future<void> _getToken() async {
|
||||||
@@ -84,7 +84,7 @@ class MusixmatchProvider extends LrcProvider {
|
|||||||
await Future.delayed(Duration(seconds: 10));
|
await Future.delayed(Duration(seconds: 10));
|
||||||
return await _getToken();
|
return await _getToken();
|
||||||
}
|
}
|
||||||
final newToken = jsonDecode(d.body)["message"]["body"]["user_token"];
|
final newToken = jsonDecode(d.data)["message"]["body"]["user_token"];
|
||||||
final currentTime = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
final currentTime = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||||
final expirationTime = currentTime + 600; // 10 minutes
|
final expirationTime = currentTime + 600; // 10 minutes
|
||||||
token = newToken;
|
token = newToken;
|
||||||
@@ -105,7 +105,7 @@ class MusixmatchProvider extends LrcProvider {
|
|||||||
MapEntry("translation_fields_set", "minimal"),
|
MapEntry("translation_fields_set", "minimal"),
|
||||||
MapEntry("selected_language", lang!),
|
MapEntry("selected_language", lang!),
|
||||||
]);
|
]);
|
||||||
final bodyTr = jsonDecode(rTr.body)["message"]["body"];
|
final bodyTr = jsonDecode(rTr.data)["message"]["body"];
|
||||||
if (bodyTr["translations_list"] == null ||
|
if (bodyTr["translations_list"] == null ||
|
||||||
(bodyTr["translations_list"] as List).isEmpty) {
|
(bodyTr["translations_list"] as List).isEmpty) {
|
||||||
throw Exception("Couldn't find translations");
|
throw Exception("Couldn't find translations");
|
||||||
@@ -113,7 +113,7 @@ class MusixmatchProvider extends LrcProvider {
|
|||||||
// Translation handling would need full implementation
|
// Translation handling would need full implementation
|
||||||
}
|
}
|
||||||
if (r.statusCode != 200) return null;
|
if (r.statusCode != 200) return null;
|
||||||
final body = jsonDecode(r.body)["message"]["body"];
|
final body = jsonDecode(r.data)["message"]["body"];
|
||||||
if (body == null) return null;
|
if (body == null) return null;
|
||||||
final lrcStr = body["subtitle"]["subtitle_body"];
|
final lrcStr = body["subtitle"]["subtitle_body"];
|
||||||
final lrc = Lyrics(synced: lrcStr);
|
final lrc = Lyrics(synced: lrcStr);
|
||||||
@@ -124,9 +124,9 @@ class MusixmatchProvider extends LrcProvider {
|
|||||||
var lrc = Lyrics();
|
var lrc = Lyrics();
|
||||||
final r = await _get("track.richsync.get", [MapEntry("track_id", trackId)]);
|
final r = await _get("track.richsync.get", [MapEntry("track_id", trackId)]);
|
||||||
if (r.statusCode == 200 &&
|
if (r.statusCode == 200 &&
|
||||||
jsonDecode(r.body)["message"]["header"]["status_code"] == 200) {
|
jsonDecode(r.data)["message"]["header"]["status_code"] == 200) {
|
||||||
final lrcRaw = jsonDecode(
|
final lrcRaw = jsonDecode(
|
||||||
r.body,
|
r.data,
|
||||||
)["message"]["body"]["richsync"]["richsync_body"];
|
)["message"]["body"]["richsync"]["richsync_body"];
|
||||||
final data = jsonDecode(lrcRaw);
|
final data = jsonDecode(lrcRaw);
|
||||||
String lrcStr = "";
|
String lrcStr = "";
|
||||||
@@ -157,9 +157,9 @@ class MusixmatchProvider extends LrcProvider {
|
|||||||
MapEntry("page_size", "5"),
|
MapEntry("page_size", "5"),
|
||||||
MapEntry("page", "1"),
|
MapEntry("page", "1"),
|
||||||
]);
|
]);
|
||||||
final statusCode = jsonDecode(r.body)["message"]["header"]["status_code"];
|
final statusCode = jsonDecode(r.data)["message"]["header"]["status_code"];
|
||||||
if (statusCode != 200) return null;
|
if (statusCode != 200) return null;
|
||||||
final body = jsonDecode(r.body)["message"]["body"];
|
final body = jsonDecode(r.data)["message"]["body"];
|
||||||
if (body == null || body is! Map) return null;
|
if (body == null || body is! Map) return null;
|
||||||
final tracks = body["track_list"];
|
final tracks = body["track_list"];
|
||||||
if (tracks == null || tracks is! List || tracks.isEmpty) return null;
|
if (tracks == null || tracks is! List || tracks.isEmpty) return null;
|
||||||
@@ -194,12 +194,13 @@ class NetEaseProvider extends LrcProvider {
|
|||||||
Future<Map<String, dynamic>?> searchTrack(String searchTerm) async {
|
Future<Map<String, dynamic>?> searchTrack(String searchTerm) async {
|
||||||
final params = {"limit": "10", "type": "1", "offset": "0", "s": searchTerm};
|
final params = {"limit": "10", "type": "1", "offset": "0", "s": searchTerm};
|
||||||
final response = await session.get(
|
final response = await session.get(
|
||||||
Uri.parse(apiEndpointMetadata).replace(queryParameters: params),
|
apiEndpointMetadata,
|
||||||
headers: {"cookie": cookie},
|
queryParameters: params,
|
||||||
|
options: Options(headers: {"cookie": cookie}),
|
||||||
);
|
);
|
||||||
// Update the session cookies from the new sent cookies for the next request.
|
// Update the session cookies from the new sent cookies for the next request.
|
||||||
// In http package, we can set it, but for simplicity, pass to next call
|
// In http package, we can set it, but for simplicity, pass to next call
|
||||||
final results = jsonDecode(response.body)["result"]["songs"];
|
final results = jsonDecode(response.data)["result"]["songs"];
|
||||||
if (results == null || results.isEmpty) return null;
|
if (results == null || results.isEmpty) return null;
|
||||||
// Simple best match - first track
|
// Simple best match - first track
|
||||||
return results[0];
|
return results[0];
|
||||||
@@ -208,10 +209,11 @@ class NetEaseProvider extends LrcProvider {
|
|||||||
Future<Lyrics?> getLrcById(String trackId) async {
|
Future<Lyrics?> getLrcById(String trackId) async {
|
||||||
final params = {"id": trackId, "lv": "1"};
|
final params = {"id": trackId, "lv": "1"};
|
||||||
final response = await session.get(
|
final response = await session.get(
|
||||||
Uri.parse(apiEndpointLyrics).replace(queryParameters: params),
|
apiEndpointLyrics,
|
||||||
headers: {"cookie": cookie},
|
queryParameters: params,
|
||||||
|
options: Options(headers: {"cookie": cookie}),
|
||||||
);
|
);
|
||||||
final data = jsonDecode(response.body);
|
final data = jsonDecode(response.data);
|
||||||
final lrc = Lyrics(synced: data["lrc"]["lyric"]);
|
final lrc = Lyrics(synced: data["lrc"]["lyric"]);
|
||||||
return lrc;
|
return lrc;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:riverpod/riverpod.dart';
|
import 'package:groovybox/data/db.dart';
|
||||||
|
import 'package:groovybox/data/track_repository.dart';
|
||||||
|
import 'package:groovybox/providers/db_provider.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import '../data/db.dart';
|
|
||||||
import '../data/track_repository.dart';
|
|
||||||
import '../providers/db_provider.dart';
|
|
||||||
|
|
||||||
// Simple watch folder provider using Riverpod
|
// Simple watch folder provider using Riverpod
|
||||||
final watchFoldersProvider = FutureProvider<List<WatchFolder>>((ref) async {
|
final watchFoldersProvider = FutureProvider<List<WatchFolder>>((ref) async {
|
||||||
|
|||||||
@@ -732,7 +732,7 @@ class LibraryScreen extends HookConsumerWidget {
|
|||||||
if (await file.exists()) {
|
if (await file.exists()) {
|
||||||
final stat = await file.stat();
|
final stat = await file.stat();
|
||||||
final sizeInMB = (stat.size / (1024 * 1024)).toStringAsFixed(2);
|
final sizeInMB = (stat.size / (1024 * 1024)).toStringAsFixed(2);
|
||||||
fileSize = '${sizeInMB} MB';
|
fileSize = '$sizeInMB MB';
|
||||||
dateAdded = stat.modified.toString().split(
|
dateAdded = stat.modified.toString().split(
|
||||||
' ',
|
' ',
|
||||||
)[0]; // Just the date part
|
)[0]; // Just the date part
|
||||||
@@ -752,7 +752,9 @@ class LibraryScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
final screenSize = MediaQuery.of(context).size;
|
if (!context.mounted) return;
|
||||||
|
|
||||||
|
final screenSize = MediaQuery.sizeOf(context);
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
|||||||
@@ -371,7 +371,12 @@ class _CoverView extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Center(child: _PlayerCoverArt(metadataAsync: metadataAsync)),
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 32),
|
||||||
|
child: Center(
|
||||||
|
child: _PlayerCoverArt(metadataAsync: metadataAsync),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
_PlayerControls(
|
_PlayerControls(
|
||||||
player: player,
|
player: player,
|
||||||
@@ -456,7 +461,7 @@ class _PlayerCoverArt extends StatelessWidget {
|
|||||||
loading: () => const Center(child: CircularProgressIndicator()),
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
error: (_, _) => Container(
|
error: (_, _) => Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
borderRadius: BorderRadius.circular(24),
|
borderRadius: BorderRadius.circular(24),
|
||||||
),
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
|
|||||||
@@ -160,40 +160,6 @@ class SettingsScreen extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
|
|
||||||
// Supported Formats Section
|
|
||||||
Card(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
'Supported Formats',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Wrap(
|
|
||||||
spacing: 8,
|
|
||||||
runSpacing: 4,
|
|
||||||
children: settings.supportedFormats.map((format) {
|
|
||||||
return Chip(
|
|
||||||
label: Text(format.toUpperCase()),
|
|
||||||
backgroundColor: Theme.of(
|
|
||||||
context,
|
|
||||||
).primaryColor.withOpacity(0.1),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
18
pubspec.lock
18
pubspec.lock
@@ -305,6 +305,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.11"
|
version: "0.7.11"
|
||||||
|
dio:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: dio
|
||||||
|
sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.9.0"
|
||||||
|
dio_web_adapter:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dio_web_adapter
|
||||||
|
sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
drift:
|
drift:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -505,7 +521,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "0.15.6"
|
version: "0.15.6"
|
||||||
http:
|
http:
|
||||||
dependency: "direct main"
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: http
|
name: http
|
||||||
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
|
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ dependencies:
|
|||||||
gap: ^3.0.1
|
gap: ^3.0.1
|
||||||
styled_widget: ^0.4.1
|
styled_widget: ^0.4.1
|
||||||
super_sliver_list: ^0.4.1
|
super_sliver_list: ^0.4.1
|
||||||
http: ^1.0.0
|
dio: ^5.0.0
|
||||||
audio_service: ^0.18.18
|
audio_service: ^0.18.18
|
||||||
palette_generator: ^0.3.3+4
|
palette_generator: ^0.3.3+4
|
||||||
watcher: ^1.2.0
|
watcher: ^1.2.0
|
||||||
|
|||||||
Reference in New Issue
Block a user