mirror of
https://github.com/Dr-Blank/Vaani.git
synced 2025-12-07 03:29:29 +00:00
migrate to just audio
This commit is contained in:
parent
a1dd0e9d3f
commit
01b3dead49
22 changed files with 1062 additions and 340 deletions
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
|
@ -17,5 +17,6 @@
|
||||||
"riverpod",
|
"riverpod",
|
||||||
"shelfsdk",
|
"shelfsdk",
|
||||||
"tapable"
|
"tapable"
|
||||||
]
|
],
|
||||||
|
"cmake.configureOnOpen": false
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,18 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<!-- Required to fetch data from the internet. -->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<application
|
<application
|
||||||
android:label="whispering_pages"
|
android:label="whispering_pages"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
|
android:enableOnBackInvokedCallback="true"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher">
|
||||||
|
<!-- android:name=".MainActivity" -->
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name="com.ryanheise.audioservice.AudioServiceActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
|
|
@ -16,12 +24,12 @@
|
||||||
while the Flutter UI initializes. After that, this theme continues
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
to determine the Window background behind the Flutter UI. -->
|
to determine the Window background behind the Flutter UI. -->
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="io.flutter.embedding.android.NormalTheme"
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
android:resource="@style/NormalTheme"
|
android:resource="@style/NormalTheme"
|
||||||
/>
|
/>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
|
|
@ -29,6 +37,22 @@
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="flutterEmbedding"
|
android:name="flutterEmbedding"
|
||||||
android:value="2" />
|
android:value="2" />
|
||||||
|
<!-- ADD THIS "SERVICE" element -->
|
||||||
|
<service android:name="com.ryanheise.audioservice.AudioService"
|
||||||
|
android:foregroundServiceType="mediaPlayback"
|
||||||
|
android:exported="true" tools:ignore="Instantiatable">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.media.browse.MediaBrowserService" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<!-- ADD THIS "RECEIVER" element -->
|
||||||
|
<receiver android:name="com.ryanheise.audioservice.MediaButtonReceiver"
|
||||||
|
android:exported="true" tools:ignore="Instantiatable">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
</application>
|
</application>
|
||||||
<!-- Required to query activities that can process text, see:
|
<!-- Required to query activities that can process text, see:
|
||||||
https://developer.android.com/training/package-visibility?hl=en and
|
https://developer.android.com/training/package-visibility?hl=en and
|
||||||
|
|
@ -37,8 +61,8 @@
|
||||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||||
<queries>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
<action android:name="android.intent.action.PROCESS_TEXT" />
|
||||||
<data android:mimeType="text/plain"/>
|
<data android:mimeType="text/plain" />
|
||||||
</intent>
|
</intent>
|
||||||
</queries>
|
</queries>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
@ -12,8 +12,8 @@ import 'package:whispering_pages/constants/hero_tag_conventions.dart';
|
||||||
import 'package:whispering_pages/features/player/providers/audiobook_player.dart';
|
import 'package:whispering_pages/features/player/providers/audiobook_player.dart';
|
||||||
import 'package:whispering_pages/router/models/library_item_extras.dart';
|
import 'package:whispering_pages/router/models/library_item_extras.dart';
|
||||||
import 'package:whispering_pages/settings/app_settings_provider.dart';
|
import 'package:whispering_pages/settings/app_settings_provider.dart';
|
||||||
import 'package:whispering_pages/theme/theme_from_cover_provider.dart';
|
|
||||||
import 'package:whispering_pages/shared/widgets/shelves/book_shelf.dart';
|
import 'package:whispering_pages/shared/widgets/shelves/book_shelf.dart';
|
||||||
|
import 'package:whispering_pages/theme/theme_from_cover_provider.dart';
|
||||||
|
|
||||||
import '../../../shared/widgets/expandable_description.dart';
|
import '../../../shared/widgets/expandable_description.dart';
|
||||||
import 'library_item_sliver_app_bar.dart';
|
import 'library_item_sliver_app_bar.dart';
|
||||||
|
|
@ -219,7 +219,7 @@ class LibraryItemMetadata extends StatelessWidget {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final codec = book.audioFiles.first.codec.toUpperCase();
|
final codec = book.audioFiles.first.codec.toUpperCase();
|
||||||
final bitrate = book.audioFiles.first.bitRate;
|
// final bitrate = book.audioFiles.first.bitRate;
|
||||||
return codec;
|
return codec;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -277,85 +277,83 @@ class LibraryItemActions extends HookConsumerWidget {
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final player = ref.read(audiobookPlayerProvider);
|
final player = ref.read(audiobookPlayerProvider);
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 8.0),
|
||||||
child: Container(
|
child: Row(
|
||||||
child: Row(
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
children: [
|
||||||
children: [
|
// play/resume button the same width as image
|
||||||
// play/resume button the same width as image
|
LayoutBuilder(
|
||||||
LayoutBuilder(
|
builder: (context, constraints) {
|
||||||
|
return SizedBox(
|
||||||
|
width: calculateWidth(context, constraints),
|
||||||
|
// a boxy button with icon and text but little rounded corner
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
onPressed: () async {
|
||||||
|
// play the book
|
||||||
|
debugPrint('Pressed play/resume button');
|
||||||
|
// set the book to the player if not already set
|
||||||
|
if (player.book != book) {
|
||||||
|
debugPrint('Setting the book ${book.libraryItemId}');
|
||||||
|
await player.setSourceAudioBook(book);
|
||||||
|
ref
|
||||||
|
.read(audiobookPlayerProvider.notifier)
|
||||||
|
.notifyListeners();
|
||||||
|
}
|
||||||
|
// toggle play/pause
|
||||||
|
player.togglePlayPause();
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.play_arrow_rounded),
|
||||||
|
label: const Text('Play/Resume'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: calculateWidth(context, constraints),
|
width: constraints.maxWidth * 0.6,
|
||||||
// a boxy button with icon and text but little rounded corner
|
child: Row(
|
||||||
child: ElevatedButton.icon(
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
onPressed: () async {
|
children: [
|
||||||
// play the book
|
// read list button
|
||||||
debugPrint('Pressed play/resume button');
|
IconButton(
|
||||||
// set the book to the player if not already set
|
onPressed: () {},
|
||||||
if (player.book != book) {
|
icon: const Icon(
|
||||||
debugPrint('Setting the book ${book.libraryItemId}');
|
Icons.playlist_add_rounded,
|
||||||
await player.setSourceAudioBook(book);
|
),
|
||||||
ref
|
|
||||||
.read(audiobookPlayerProvider.notifier)
|
|
||||||
.notifyListeners();
|
|
||||||
}
|
|
||||||
// toggle play/pause
|
|
||||||
player.togglePlayPause();
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.play_arrow_rounded),
|
|
||||||
label: const Text('Play/Resume'),
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
),
|
),
|
||||||
),
|
// share button
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {},
|
||||||
|
icon: const Icon(Icons.share_rounded),
|
||||||
|
),
|
||||||
|
// download button
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.download_rounded,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// more button
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.more_vert_rounded,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Expanded(
|
),
|
||||||
child: LayoutBuilder(
|
],
|
||||||
builder: (context, constraints) {
|
|
||||||
return SizedBox(
|
|
||||||
width: constraints.maxWidth * 0.6,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
// read list button
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {},
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.playlist_add_rounded,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// share button
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {},
|
|
||||||
icon: const Icon(Icons.share_rounded),
|
|
||||||
),
|
|
||||||
// download button
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {},
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.download_rounded,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// more button
|
|
||||||
IconButton(
|
|
||||||
onPressed: () {},
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.more_vert_rounded,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,15 @@
|
||||||
/// this is needed as audiobook can be a list of audio files instead of a single file
|
/// this is needed as audiobook can be a list of audio files instead of a single file
|
||||||
library;
|
library;
|
||||||
|
|
||||||
import 'package:audioplayers/audioplayers.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:just_audio/just_audio.dart';
|
||||||
|
import 'package:just_audio_background/just_audio_background.dart';
|
||||||
import 'package:shelfsdk/audiobookshelf_api.dart';
|
import 'package:shelfsdk/audiobookshelf_api.dart';
|
||||||
|
|
||||||
/// will manage the audio player instance
|
/// will manage the audio player instance
|
||||||
class AudiobookPlayer extends AudioPlayer {
|
class AudiobookPlayer extends AudioPlayer {
|
||||||
// constructor which takes in the BookExpanded object
|
// constructor which takes in the BookExpanded object
|
||||||
AudiobookPlayer(this.token, this.baseUrl, {super.playerId}) : super() {
|
AudiobookPlayer(this.token, this.baseUrl) : super() {
|
||||||
// set the source of the player to the first track in the book
|
// set the source of the player to the first track in the book
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -28,7 +30,7 @@ class AudiobookPlayer extends AudioPlayer {
|
||||||
final Uri baseUrl;
|
final Uri baseUrl;
|
||||||
|
|
||||||
// the current index of the audio file in the [book]
|
// the current index of the audio file in the [book]
|
||||||
final int _currentIndex = 0;
|
// final int _currentIndex = 0;
|
||||||
|
|
||||||
// available audio tracks
|
// available audio tracks
|
||||||
int? get availableTracks => _book?.tracks.length;
|
int? get availableTracks => _book?.tracks.length;
|
||||||
|
|
@ -46,15 +48,32 @@ class AudiobookPlayer extends AudioPlayer {
|
||||||
// if the book is the same, do nothing
|
// if the book is the same, do nothing
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// first stop the player
|
// first stop the player and clear the source
|
||||||
await stop();
|
await stop();
|
||||||
|
|
||||||
var track = book.tracks[_currentIndex];
|
await setAudioSource(
|
||||||
var url = '$baseUrl${track.contentUrl}?token=$token';
|
ConcatenatingAudioSource(
|
||||||
await setSourceUrl(
|
useLazyPreparation: true,
|
||||||
url,
|
children: book.tracks.map((track) {
|
||||||
mimeType: track.mimeType,
|
return AudioSource.uri(
|
||||||
);
|
Uri.parse('$baseUrl${track.contentUrl}?token=$token'),
|
||||||
|
tag: MediaItem(
|
||||||
|
// Specify a unique ID for each media item:
|
||||||
|
id: book.libraryItemId + track.index.toString(),
|
||||||
|
// Metadata to display in the notification:
|
||||||
|
album: book.metadata.title,
|
||||||
|
title: book.metadata.title ?? track.title,
|
||||||
|
artUri: Uri.parse(
|
||||||
|
'$baseUrl/api/items/${book.libraryItemId}/cover?token=$token&width=800',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
).catchError((error) {
|
||||||
|
debugPrint('Error: $error');
|
||||||
|
});
|
||||||
|
|
||||||
_book = book;
|
_book = book;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,56 +83,37 @@ class AudiobookPlayer extends AudioPlayer {
|
||||||
if (_book == null) {
|
if (_book == null) {
|
||||||
throw StateError('No book is set');
|
throw StateError('No book is set');
|
||||||
}
|
}
|
||||||
return switch (state) {
|
|
||||||
PlayerState.playing => pause(),
|
// ! refactor this
|
||||||
PlayerState.paused ||
|
return switch (playerState) {
|
||||||
PlayerState.stopped ||
|
PlayerState(playing: var isPlaying) => isPlaying ? pause() : play(),
|
||||||
PlayerState.completed =>
|
|
||||||
resume(),
|
|
||||||
// do nothing if the player is disposed
|
|
||||||
PlayerState.disposed => throw StateError('Player is disposed'),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// override resume to set the source if the book is not set
|
|
||||||
@override
|
|
||||||
Future<void> resume() async {
|
|
||||||
if (_book == null) {
|
|
||||||
throw StateError('No book is set');
|
|
||||||
}
|
|
||||||
return super.resume();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// a convenience stream for onPositionEveryXSeconds
|
|
||||||
Stream<Duration> onPositionEvery(Duration duration) => TimerPositionUpdater(
|
|
||||||
getPosition: getCurrentPosition,
|
|
||||||
interval: duration,
|
|
||||||
).positionStream;
|
|
||||||
|
|
||||||
/// need to override getDuration and getCurrentPosition to return according to the book instead of the current track
|
/// need to override getDuration and getCurrentPosition to return according to the book instead of the current track
|
||||||
/// this is because the book can be a list of audio files and the player is only aware of the current track
|
/// this is because the book can be a list of audio files and the player is only aware of the current track
|
||||||
/// so we need to calculate the duration and current position based on the book
|
/// so we need to calculate the duration and current position based on the book
|
||||||
@override
|
// @override
|
||||||
Future<Duration?> getDuration() async {
|
// Future<Duration?> getDuration() async {
|
||||||
if (_book == null) {
|
// if (_book == null) {
|
||||||
return null;
|
// return null;
|
||||||
}
|
// }
|
||||||
return _book!.tracks.fold<Duration>(
|
// return _book!.tracks.fold<Duration>(
|
||||||
Duration.zero,
|
// Duration.zero,
|
||||||
(previousValue, element) => previousValue + element.duration,
|
// (previousValue, element) => previousValue + element.duration,
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
@override
|
// @override
|
||||||
Future<Duration?> getCurrentPosition() async {
|
// Future<Duration?> getCurrentPosition() async {
|
||||||
if (_book == null) {
|
// if (_book == null) {
|
||||||
return null;
|
// return null;
|
||||||
}
|
// }
|
||||||
var currentTrack = _book!.tracks[_currentIndex];
|
// var currentTrack = _book!.tracks[_currentIndex];
|
||||||
var currentTrackDuration = currentTrack.duration;
|
// var currentTrackDuration = currentTrack.duration;
|
||||||
var currentTrackPosition = await super.getCurrentPosition();
|
// var currentTrackPosition = await super.getCurrentPosition();
|
||||||
return currentTrackPosition != null
|
// return currentTrackPosition != null
|
||||||
? currentTrackPosition + currentTrackDuration
|
// ? currentTrackPosition + currentTrackDuration
|
||||||
: null;
|
// : null;
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,12 @@ class AudiobookPlayer extends _$AudiobookPlayer {
|
||||||
abp.AudiobookPlayer build() {
|
abp.AudiobookPlayer build() {
|
||||||
final api = ref.watch(authenticatedApiProvider);
|
final api = ref.watch(authenticatedApiProvider);
|
||||||
final player =
|
final player =
|
||||||
abp.AudiobookPlayer(api.token!, api.baseUrl, playerId: playerId);
|
abp.AudiobookPlayer(api.token!, api.baseUrl);
|
||||||
|
|
||||||
ref.onDispose(player.dispose);
|
ref.onDispose(player.dispose);
|
||||||
|
|
||||||
// bind notify listeners to the player
|
// bind notify listeners to the player
|
||||||
player.onPlayerStateChanged.listen((_) {
|
player.playerStateStream.listen((_) {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
67
lib/features/player/providers/player_form.dart
Normal file
67
lib/features/player/providers/player_form.dart
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
// this provider is used to manage the player form state
|
||||||
|
// it will inform about the percentage of the player expanded
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:miniplayer/miniplayer.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
part 'player_form.g.dart';
|
||||||
|
|
||||||
|
const double playerMinHeight = 70;
|
||||||
|
const miniplayerPercentageDeclaration = 0.2;
|
||||||
|
|
||||||
|
extension on Ref {
|
||||||
|
// We can move the previous logic to a Ref extension.
|
||||||
|
// This enables reusing the logic between providers
|
||||||
|
T disposeAndListenChangeNotifier<T extends ChangeNotifier>(T notifier) {
|
||||||
|
onDispose(notifier.dispose);
|
||||||
|
notifier.addListener(notifyListeners);
|
||||||
|
// We return the notifier to ease the usage a bit
|
||||||
|
return notifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Riverpod(keepAlive: true)
|
||||||
|
Raw<ValueNotifier<double>> playerExpandProgressNotifier(
|
||||||
|
PlayerExpandProgressNotifierRef ref,
|
||||||
|
) {
|
||||||
|
final ValueNotifier<double> playerExpandProgress =
|
||||||
|
ValueNotifier(playerMinHeight);
|
||||||
|
|
||||||
|
return ref.disposeAndListenChangeNotifier(playerExpandProgress);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Riverpod(keepAlive: true)
|
||||||
|
// Raw<ValueNotifier<double>> dragDownPercentageNotifier(
|
||||||
|
// DragDownPercentageNotifierRef ref,
|
||||||
|
// ) {
|
||||||
|
// final ValueNotifier<double> notifier = ValueNotifier(0);
|
||||||
|
|
||||||
|
// return ref.disposeAndListenChangeNotifier(notifier);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// a provider that will listen to the playerExpandProgressNotifier and return the percentage of the player expanded
|
||||||
|
@Riverpod(keepAlive: true)
|
||||||
|
double playerHeight(
|
||||||
|
PlayerHeightRef ref,
|
||||||
|
) {
|
||||||
|
final playerExpandProgress = ref.watch(playerExpandProgressNotifierProvider);
|
||||||
|
|
||||||
|
// on change of the playerExpandProgress invalidate
|
||||||
|
playerExpandProgress.addListener(() {
|
||||||
|
ref.invalidateSelf();
|
||||||
|
});
|
||||||
|
|
||||||
|
// listen to the playerExpandProgressNotifier and return the value
|
||||||
|
return playerExpandProgress.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// a final MiniplayerController controller = MiniplayerController();
|
||||||
|
@Riverpod(keepAlive: true)
|
||||||
|
MiniplayerController miniplayerController(
|
||||||
|
MiniplayerControllerRef ref,
|
||||||
|
) {
|
||||||
|
return MiniplayerController();
|
||||||
|
}
|
||||||
58
lib/features/player/providers/player_form.g.dart
Normal file
58
lib/features/player/providers/player_form.g.dart
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'player_form.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$playerExpandProgressNotifierHash() =>
|
||||||
|
r'e4817361b9a311b61ca23e51082ed11b0a1120ab';
|
||||||
|
|
||||||
|
/// See also [playerExpandProgressNotifier].
|
||||||
|
@ProviderFor(playerExpandProgressNotifier)
|
||||||
|
final playerExpandProgressNotifierProvider =
|
||||||
|
Provider<Raw<ValueNotifier<double>>>.internal(
|
||||||
|
playerExpandProgressNotifier,
|
||||||
|
name: r'playerExpandProgressNotifierProvider',
|
||||||
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$playerExpandProgressNotifierHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef PlayerExpandProgressNotifierRef
|
||||||
|
= ProviderRef<Raw<ValueNotifier<double>>>;
|
||||||
|
String _$playerHeightHash() => r'26dbcb180d494575488d700bd5bdb58c02c224a9';
|
||||||
|
|
||||||
|
/// See also [playerHeight].
|
||||||
|
@ProviderFor(playerHeight)
|
||||||
|
final playerHeightProvider = Provider<double>.internal(
|
||||||
|
playerHeight,
|
||||||
|
name: r'playerHeightProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product') ? null : _$playerHeightHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef PlayerHeightRef = ProviderRef<double>;
|
||||||
|
String _$miniplayerControllerHash() =>
|
||||||
|
r'489579a18f4e08793de08a4828172bd924768301';
|
||||||
|
|
||||||
|
/// See also [miniplayerController].
|
||||||
|
@ProviderFor(miniplayerController)
|
||||||
|
final miniplayerControllerProvider = Provider<MiniplayerController>.internal(
|
||||||
|
miniplayerController,
|
||||||
|
name: r'miniplayerControllerProvider',
|
||||||
|
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$miniplayerControllerHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef MiniplayerControllerRef = ProviderRef<MiniplayerController>;
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
import 'package:audio_video_progress_bar/audio_video_progress_bar.dart';
|
import 'package:audio_video_progress_bar/audio_video_progress_bar.dart';
|
||||||
import 'package:audioplayers/audioplayers.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:just_audio/just_audio.dart';
|
||||||
import 'package:miniplayer/miniplayer.dart';
|
import 'package:miniplayer/miniplayer.dart';
|
||||||
import 'package:whispering_pages/api/image_provider.dart';
|
import 'package:whispering_pages/api/image_provider.dart';
|
||||||
import 'package:whispering_pages/api/library_item_provider.dart';
|
import 'package:whispering_pages/api/library_item_provider.dart';
|
||||||
import 'package:whispering_pages/features/player/providers/audiobook_player.dart';
|
import 'package:whispering_pages/features/player/providers/audiobook_player.dart';
|
||||||
import 'package:whispering_pages/features/player/providers/currently_playing_provider.dart';
|
import 'package:whispering_pages/features/player/providers/currently_playing_provider.dart';
|
||||||
|
import 'package:whispering_pages/features/player/providers/player_form.dart';
|
||||||
import 'package:whispering_pages/shared/widgets/shelves/book_shelf.dart';
|
import 'package:whispering_pages/shared/widgets/shelves/book_shelf.dart';
|
||||||
import 'package:whispering_pages/theme/theme_from_cover_provider.dart';
|
import 'package:whispering_pages/theme/theme_from_cover_provider.dart';
|
||||||
|
|
||||||
|
|
@ -26,13 +27,7 @@ double percentageFromValueInRange({required final double min, max, value}) {
|
||||||
return (value - min) / (max - min);
|
return (value - min) / (max - min);
|
||||||
}
|
}
|
||||||
|
|
||||||
const double playerMinHeight = 70;
|
|
||||||
const double playerMaxHeight = 500;
|
|
||||||
const miniplayerPercentageDeclaration = 0.2;
|
|
||||||
final ValueNotifier<double> playerExpandProgress =
|
|
||||||
ValueNotifier(playerMinHeight);
|
|
||||||
|
|
||||||
final MiniplayerController controller = MiniplayerController();
|
|
||||||
|
|
||||||
class AudiobookPlayer extends HookConsumerWidget {
|
class AudiobookPlayer extends HookConsumerWidget {
|
||||||
const AudiobookPlayer({super.key});
|
const AudiobookPlayer({super.key});
|
||||||
|
|
@ -64,39 +59,19 @@ class AudiobookPlayer extends HookConsumerWidget {
|
||||||
);
|
);
|
||||||
|
|
||||||
// add controller to the player state listener
|
// add controller to the player state listener
|
||||||
player.onPlayerStateChanged.listen((state) {
|
player.playerStateStream.listen((state) {
|
||||||
if (state == PlayerState.playing) {
|
if (state.playing) {
|
||||||
playPauseController.reverse();
|
|
||||||
} else {
|
|
||||||
playPauseController.forward();
|
playPauseController.forward();
|
||||||
|
} else {
|
||||||
|
playPauseController.reverse();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
final playPauseButton = IconButton(
|
final playPauseButton = AudiobookPlayerPlayPauseButton(
|
||||||
onPressed: () async {
|
playPauseController: playPauseController,
|
||||||
await player.togglePlayPause();
|
|
||||||
},
|
|
||||||
icon: AnimatedIcon(
|
|
||||||
icon: AnimatedIcons.pause_play,
|
|
||||||
progress: playPauseController,
|
|
||||||
size: 50,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
// player.onPositionChanged.listen((event) {
|
|
||||||
// currentProgress.value = event.inSeconds.toDouble();
|
|
||||||
// });
|
|
||||||
|
|
||||||
// final progressStream = TimerPositionUpdater(
|
const progressBar = AudiobookTotalProgressBar();
|
||||||
// getPosition: player.getCurrentPosition,
|
|
||||||
// interval: const Duration(milliseconds: 500),
|
|
||||||
// ).positionStream;
|
|
||||||
// // a debug that will print the current position of the player
|
|
||||||
// progressStream.listen((event) {
|
|
||||||
// debugPrint('Current position: ${event.inSeconds}');
|
|
||||||
// });
|
|
||||||
|
|
||||||
// the widget that will be displayed when the player is expanded
|
|
||||||
const progressBar = PlayerProgressBar();
|
|
||||||
|
|
||||||
// theme from image
|
// theme from image
|
||||||
final imageTheme = ref.watch(
|
final imageTheme = ref.watch(
|
||||||
|
|
@ -105,30 +80,38 @@ class AudiobookPlayer extends HookConsumerWidget {
|
||||||
brightness: Theme.of(context).brightness,
|
brightness: Theme.of(context).brightness,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return Theme(
|
|
||||||
// get the theme from imageThemeProvider
|
|
||||||
|
|
||||||
|
// max height of the player is the height of the screen
|
||||||
|
final playerMaxHeight = MediaQuery.of(context).size.height;
|
||||||
|
|
||||||
|
|
||||||
|
return Theme(
|
||||||
data: ThemeData(
|
data: ThemeData(
|
||||||
colorScheme: imageTheme.valueOrNull ?? Theme.of(context).colorScheme,
|
colorScheme: imageTheme.valueOrNull ?? Theme.of(context).colorScheme,
|
||||||
),
|
),
|
||||||
child: Miniplayer(
|
child: Miniplayer(
|
||||||
valueNotifier: playerExpandProgress,
|
valueNotifier: ref.watch(playerExpandProgressNotifierProvider),
|
||||||
minHeight: playerMinHeight,
|
minHeight: playerMinHeight,
|
||||||
maxHeight: playerMaxHeight,
|
maxHeight: playerMaxHeight,
|
||||||
controller: controller,
|
controller: ref.watch(miniplayerControllerProvider),
|
||||||
elevation: 4,
|
elevation: 4,
|
||||||
onDismissed: () {
|
onDismissed: () {
|
||||||
player.setSourceAudioBook(null);
|
player.setSourceAudioBook(null);
|
||||||
},
|
},
|
||||||
curve: Curves.easeOut,
|
curve: Curves.easeOut,
|
||||||
builder: (height, percentage) {
|
builder: (height, percentage) {
|
||||||
|
// return SafeArea(
|
||||||
|
// child: Text(
|
||||||
|
// 'percentage: ${percentage.toStringAsFixed(2)}, height: ${height.toStringAsFixed(2)}',
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
final bool isFormMiniplayer =
|
final bool isFormMiniplayer =
|
||||||
percentage < miniplayerPercentageDeclaration;
|
percentage < miniplayerPercentageDeclaration;
|
||||||
final double availWidth = MediaQuery.of(context).size.width;
|
final double availWidth = MediaQuery.of(context).size.width;
|
||||||
final maxImgSize = availWidth * 0.4;
|
final maxImgSize = availWidth * 0.4;
|
||||||
|
|
||||||
final bookTitle = Text(player.book?.metadata.title ?? '');
|
final bookTitle = Text(player.book?.metadata.title ?? '');
|
||||||
|
|
||||||
//Declare additional widgets (eg. SkipButton) and variables
|
//Declare additional widgets (eg. SkipButton) and variables
|
||||||
if (!isFormMiniplayer) {
|
if (!isFormMiniplayer) {
|
||||||
var percentageExpandedPlayer = percentageFromValueInRange(
|
var percentageExpandedPlayer = percentageFromValueInRange(
|
||||||
|
|
@ -153,7 +136,7 @@ class AudiobookPlayer extends HookConsumerWidget {
|
||||||
percentage: percentageExpandedPlayer,
|
percentage: percentageExpandedPlayer,
|
||||||
) /
|
) /
|
||||||
2;
|
2;
|
||||||
|
|
||||||
const buttonSkipForward = IconButton(
|
const buttonSkipForward = IconButton(
|
||||||
icon: Icon(Icons.forward_30),
|
icon: Icon(Icons.forward_30),
|
||||||
iconSize: 33,
|
iconSize: 33,
|
||||||
|
|
@ -164,12 +147,6 @@ class AudiobookPlayer extends HookConsumerWidget {
|
||||||
iconSize: 33,
|
iconSize: 33,
|
||||||
onPressed: onTap,
|
onPressed: onTap,
|
||||||
);
|
);
|
||||||
const buttonPlayExpanded = IconButton(
|
|
||||||
icon: Icon(Icons.pause_circle_filled),
|
|
||||||
iconSize: 50,
|
|
||||||
onPressed: onTap,
|
|
||||||
);
|
|
||||||
|
|
||||||
return PlayerWhenExpanded(
|
return PlayerWhenExpanded(
|
||||||
imgPaddingLeft: paddingLeft,
|
imgPaddingLeft: paddingLeft,
|
||||||
imgPaddingVertical: paddingVertical,
|
imgPaddingVertical: paddingVertical,
|
||||||
|
|
@ -178,12 +155,12 @@ class AudiobookPlayer extends HookConsumerWidget {
|
||||||
percentageExpandedPlayer: percentageExpandedPlayer,
|
percentageExpandedPlayer: percentageExpandedPlayer,
|
||||||
text: bookTitle,
|
text: bookTitle,
|
||||||
buttonSkipBackwards: buttonSkipBackwards,
|
buttonSkipBackwards: buttonSkipBackwards,
|
||||||
buttonPlayExpanded: playPauseButton,
|
playPauseButton: playPauseButton,
|
||||||
buttonSkipForward: buttonSkipForward,
|
buttonSkipForward: buttonSkipForward,
|
||||||
progressIndicator: progressBar,
|
progressIndicator: progressBar,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Miniplayer
|
//Miniplayer
|
||||||
final percentageMiniplayer = percentageFromValueInRange(
|
final percentageMiniplayer = percentageFromValueInRange(
|
||||||
min: playerMinHeight,
|
min: playerMinHeight,
|
||||||
|
|
@ -191,16 +168,14 @@ class AudiobookPlayer extends HookConsumerWidget {
|
||||||
playerMinHeight,
|
playerMinHeight,
|
||||||
value: height,
|
value: height,
|
||||||
);
|
);
|
||||||
|
|
||||||
final elementOpacity = 1 - 1 * percentageMiniplayer;
|
final elementOpacity = 1 - 1 * percentageMiniplayer;
|
||||||
final progressIndicatorHeight = 4 - 4 * percentageMiniplayer;
|
|
||||||
|
|
||||||
return PlayerWhenMinimized(
|
return PlayerWhenMinimized(
|
||||||
maxImgSize: maxImgSize,
|
maxImgSize: maxImgSize,
|
||||||
imgWidget: imgWidget,
|
imgWidget: imgWidget,
|
||||||
elementOpacity: elementOpacity,
|
elementOpacity: elementOpacity,
|
||||||
playPauseButton: playPauseButton,
|
playPauseButton: playPauseButton,
|
||||||
progressIndicatorHeight: progressIndicatorHeight,
|
|
||||||
progressIndicator: progressBar,
|
progressIndicator: progressBar,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -209,34 +184,102 @@ class AudiobookPlayer extends HookConsumerWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlayerProgressBar extends HookConsumerWidget {
|
class AudiobookPlayerPlayPauseButton extends HookConsumerWidget {
|
||||||
const PlayerProgressBar({
|
const AudiobookPlayerPlayPauseButton({
|
||||||
|
super.key,
|
||||||
|
required this.playPauseController,
|
||||||
|
});
|
||||||
|
|
||||||
|
final AnimationController playPauseController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final player = ref.watch(audiobookPlayerProvider);
|
||||||
|
return switch (player.processingState) {
|
||||||
|
ProcessingState.loading ||
|
||||||
|
ProcessingState.buffering =>
|
||||||
|
const CircularProgressIndicator(),
|
||||||
|
ProcessingState.completed => IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await player.seek(const Duration(seconds: 0));
|
||||||
|
await player.play();
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.replay),
|
||||||
|
),
|
||||||
|
ProcessingState.ready => IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await player.togglePlayPause();
|
||||||
|
},
|
||||||
|
icon: AnimatedIcon(
|
||||||
|
icon: AnimatedIcons.play_pause,
|
||||||
|
progress: playPauseController,
|
||||||
|
size: 50,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ProcessingState.idle => const SizedBox.shrink(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A progress bar that shows the total progress of the audiobook
|
||||||
|
///
|
||||||
|
/// for chapter progress, use [AudiobookChapterProgressBar]
|
||||||
|
class AudiobookTotalProgressBar extends HookConsumerWidget {
|
||||||
|
const AudiobookTotalProgressBar({
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final player = ref.watch(audiobookPlayerProvider);
|
final player = ref.watch(audiobookPlayerProvider);
|
||||||
final playerState = useState(player.state);
|
// final playerState = useState(player.processingState);
|
||||||
// add a listener to the player state
|
// add a listener to the player state
|
||||||
player.onPlayerStateChanged.listen((state) {
|
// player.processingStateStream.listen((state) {
|
||||||
playerState.value = state;
|
// playerState.value = state;
|
||||||
});
|
// });
|
||||||
return StreamBuilder<Duration>(
|
return StreamBuilder(
|
||||||
stream: player.onPositionChanged,
|
stream: player.currentIndexStream,
|
||||||
builder: (context, snapshot) {
|
builder: (context, currentTrackIndex) {
|
||||||
return ProgressBar(
|
return StreamBuilder(
|
||||||
progress: snapshot.data ?? const Duration(seconds: 0),
|
stream: player.positionStream,
|
||||||
total: player.book?.duration ?? const Duration(seconds: 0),
|
builder: (context, progress) {
|
||||||
onSeek: player.seek,
|
// totalProgress is the sum of the duration of all the tracks before the current track + the current track position
|
||||||
thumbRadius: 8,
|
final totalProgress =
|
||||||
// thumbColor: Theme.of(context).colorScheme.secondary,
|
player.book?.tracks.sublist(0, currentTrackIndex.data).fold(
|
||||||
thumbGlowColor: Theme.of(context).colorScheme.secondary,
|
const Duration(seconds: 0),
|
||||||
thumbGlowRadius: playerState.value == PlayerState.playing ? 10 : 0,
|
(previousValue, element) =>
|
||||||
|
previousValue + element.duration,
|
||||||
|
) ??
|
||||||
|
const Duration(seconds: 0) +
|
||||||
|
(progress.data ?? const Duration(seconds: 0));
|
||||||
|
|
||||||
|
return StreamBuilder(
|
||||||
|
stream: player.bufferedPositionStream,
|
||||||
|
builder: (context, buffered) {
|
||||||
|
final totalBuffered =
|
||||||
|
player.book?.tracks.sublist(0, currentTrackIndex.data).fold(
|
||||||
|
const Duration(seconds: 0),
|
||||||
|
(previousValue, element) =>
|
||||||
|
previousValue + element.duration,
|
||||||
|
) ??
|
||||||
|
const Duration(seconds: 0) +
|
||||||
|
(buffered.data ?? const Duration(seconds: 0));
|
||||||
|
return ProgressBar(
|
||||||
|
progress: totalProgress,
|
||||||
|
total: player.book?.duration ?? const Duration(seconds: 0),
|
||||||
|
onSeek: player.seek,
|
||||||
|
thumbRadius: 8,
|
||||||
|
buffered: totalBuffered,
|
||||||
|
bufferedBarColor: Theme.of(context).colorScheme.secondary,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ! TODO remove onTap
|
||||||
void onTap() {}
|
void onTap() {}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ class PlayerWhenExpanded extends StatelessWidget {
|
||||||
required this.percentageExpandedPlayer,
|
required this.percentageExpandedPlayer,
|
||||||
required this.text,
|
required this.text,
|
||||||
required this.buttonSkipBackwards,
|
required this.buttonSkipBackwards,
|
||||||
required this.buttonPlayExpanded,
|
required this.playPauseButton,
|
||||||
required this.buttonSkipForward,
|
required this.buttonSkipForward,
|
||||||
required this.progressIndicator,
|
required this.progressIndicator,
|
||||||
});
|
});
|
||||||
|
|
@ -23,7 +23,7 @@ class PlayerWhenExpanded extends StatelessWidget {
|
||||||
final double percentageExpandedPlayer;
|
final double percentageExpandedPlayer;
|
||||||
final Text text;
|
final Text text;
|
||||||
final IconButton buttonSkipBackwards;
|
final IconButton buttonSkipBackwards;
|
||||||
final IconButton buttonPlayExpanded;
|
final Widget playPauseButton;
|
||||||
final IconButton buttonSkipForward;
|
final IconButton buttonSkipForward;
|
||||||
final Widget progressIndicator;
|
final Widget progressIndicator;
|
||||||
|
|
||||||
|
|
@ -62,7 +62,7 @@ class PlayerWhenExpanded extends StatelessWidget {
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
buttonSkipBackwards,
|
buttonSkipBackwards,
|
||||||
buttonPlayExpanded,
|
playPauseButton,
|
||||||
buttonSkipForward,
|
buttonSkipForward,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:miniplayer/miniplayer.dart';
|
|
||||||
import 'package:whispering_pages/features/player/providers/audiobook_player.dart';
|
import 'package:whispering_pages/features/player/providers/audiobook_player.dart';
|
||||||
import 'package:whispering_pages/features/player/view/audiobook_player.dart';
|
import 'package:whispering_pages/features/player/providers/player_form.dart';
|
||||||
|
|
||||||
class PlayerWhenMinimized extends HookConsumerWidget {
|
class PlayerWhenMinimized extends HookConsumerWidget {
|
||||||
const PlayerWhenMinimized({
|
const PlayerWhenMinimized({
|
||||||
|
|
@ -11,20 +10,19 @@ class PlayerWhenMinimized extends HookConsumerWidget {
|
||||||
required this.imgWidget,
|
required this.imgWidget,
|
||||||
required this.elementOpacity,
|
required this.elementOpacity,
|
||||||
required this.playPauseButton,
|
required this.playPauseButton,
|
||||||
required this.progressIndicatorHeight,
|
|
||||||
required this.progressIndicator,
|
required this.progressIndicator,
|
||||||
});
|
});
|
||||||
|
|
||||||
final double maxImgSize;
|
final double maxImgSize;
|
||||||
final Widget imgWidget;
|
final Widget imgWidget;
|
||||||
final double elementOpacity;
|
final double elementOpacity;
|
||||||
final IconButton playPauseButton;
|
final Widget playPauseButton;
|
||||||
final double progressIndicatorHeight;
|
|
||||||
final Widget progressIndicator;
|
final Widget progressIndicator;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final player = ref.watch(audiobookPlayerProvider);
|
final player = ref.watch(audiobookPlayerProvider);
|
||||||
|
final controller = ref.watch(miniplayerControllerProvider);
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
|
|
@ -67,14 +65,14 @@ class PlayerWhenMinimized extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
// IconButton(
|
||||||
icon: const Icon(Icons.fullscreen),
|
// icon: const Icon(Icons.fullscreen),
|
||||||
onPressed: () {
|
// onPressed: () {
|
||||||
controller.animateToHeight(state: PanelState.MAX);
|
// controller.animateToHeight(state: PanelState.MAX);
|
||||||
},
|
// },
|
||||||
),
|
// ),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(right: 3),
|
padding: const EdgeInsets.all(8),
|
||||||
child: Opacity(
|
child: Opacity(
|
||||||
opacity: elementOpacity,
|
opacity: elementOpacity,
|
||||||
child: playPauseButton,
|
child: playPauseButton,
|
||||||
|
|
@ -83,13 +81,13 @@ class PlayerWhenMinimized extends HookConsumerWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(
|
// SizedBox(
|
||||||
height: progressIndicatorHeight,
|
// height: progressIndicatorHeight,
|
||||||
child: Opacity(
|
// child: Opacity(
|
||||||
opacity: elementOpacity,
|
// opacity: elementOpacity,
|
||||||
child: progressIndicator,
|
// child: progressIndicator,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
|
import 'package:audio_session/audio_session.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:just_audio_background/just_audio_background.dart'
|
||||||
|
show JustAudioBackground;
|
||||||
|
import 'package:just_audio_media_kit/just_audio_media_kit.dart'
|
||||||
|
show JustAudioMediaKit;
|
||||||
import 'package:whispering_pages/api/server_provider.dart';
|
import 'package:whispering_pages/api/server_provider.dart';
|
||||||
import 'package:whispering_pages/db/storage.dart';
|
import 'package:whispering_pages/db/storage.dart';
|
||||||
import 'package:whispering_pages/router/router.dart';
|
import 'package:whispering_pages/router/router.dart';
|
||||||
|
|
@ -10,9 +15,24 @@ import 'package:whispering_pages/theme/theme.dart';
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
// for playing audio on windows, linux
|
||||||
|
JustAudioMediaKit.ensureInitialized();
|
||||||
|
|
||||||
// initialize the storage
|
// initialize the storage
|
||||||
await initStorage();
|
await initStorage();
|
||||||
|
|
||||||
|
// for configuring how this app will interact with other audio apps
|
||||||
|
final session = await AudioSession.instance;
|
||||||
|
await session.configure(const AudioSessionConfiguration.speech());
|
||||||
|
|
||||||
|
// for playing audio in the background
|
||||||
|
await JustAudioBackground.init(
|
||||||
|
androidNotificationChannelId: 'com.whispering_pages.bg_demo.channel.audio',
|
||||||
|
androidNotificationChannelName: 'Audio playback',
|
||||||
|
androidNotificationOngoing: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// run the app
|
||||||
runApp(
|
runApp(
|
||||||
const ProviderScope(
|
const ProviderScope(
|
||||||
child: MyApp(),
|
child: MyApp(),
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:whispering_pages/features/player/providers/player_form.dart';
|
||||||
import 'package:whispering_pages/features/player/view/audiobook_player.dart';
|
import 'package:whispering_pages/features/player/view/audiobook_player.dart';
|
||||||
|
|
||||||
/// Builds the "shell" for the app by building a Scaffold with a
|
/// Builds the "shell" for the app by building a Scaffold with a
|
||||||
|
|
@ -17,6 +20,16 @@ class ScaffoldWithNavBar extends HookConsumerWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
// playerExpandProgress is used to animate bottom navigation bar to opacity 0 and slide down when player is expanded
|
||||||
|
// final playerProgress =
|
||||||
|
// useValueListenable(ref.watch(playerExpandProgressNotifierProvider));
|
||||||
|
final playerProgress = ref.watch(playerHeightProvider);
|
||||||
|
final playerMaxHeight = MediaQuery.of(context).size.height;
|
||||||
|
var percentExpanded = (playerProgress - playerMinHeight) /
|
||||||
|
(playerMaxHeight - playerMinHeight);
|
||||||
|
// Clamp the value between 0 and 1
|
||||||
|
percentExpanded = max(0, min(1, percentExpanded));
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -24,33 +37,44 @@ class ScaffoldWithNavBar extends HookConsumerWidget {
|
||||||
const AudiobookPlayer(),
|
const AudiobookPlayer(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
bottomNavigationBar: BottomNavigationBar(
|
bottomNavigationBar: Opacity(
|
||||||
elevation: 0.0,
|
// Opacity is interpolated from 1 to 0 when player is expanded
|
||||||
landscapeLayout: BottomNavigationBarLandscapeLayout.centered,
|
opacity: 1 - percentExpanded,
|
||||||
selectedFontSize: Theme.of(context).textTheme.labelMedium!.fontSize!,
|
child: SizedBox(
|
||||||
unselectedFontSize: Theme.of(context).textTheme.labelMedium!.fontSize!,
|
// height is interpolated from 0 to 56 when player is expanded
|
||||||
showUnselectedLabels: false,
|
height: 56 * (1 - percentExpanded),
|
||||||
fixedColor: Theme.of(context).colorScheme.onBackground,
|
|
||||||
// type: BottomNavigationBarType.fixed,
|
|
||||||
|
|
||||||
// Here, the items of BottomNavigationBar are hard coded. In a real
|
child: BottomNavigationBar(
|
||||||
// world scenario, the items would most likely be generated from the
|
elevation: 0.0,
|
||||||
// branches of the shell route, which can be fetched using
|
landscapeLayout: BottomNavigationBarLandscapeLayout.centered,
|
||||||
// `navigationShell.route.branches`.
|
selectedFontSize:
|
||||||
items: const <BottomNavigationBarItem>[
|
Theme.of(context).textTheme.labelMedium!.fontSize!,
|
||||||
BottomNavigationBarItem(
|
unselectedFontSize:
|
||||||
label: 'Home',
|
Theme.of(context).textTheme.labelMedium!.fontSize!,
|
||||||
icon: Icon(Icons.home_outlined),
|
showUnselectedLabels: false,
|
||||||
activeIcon: Icon(Icons.home),
|
fixedColor: Theme.of(context).colorScheme.onBackground,
|
||||||
|
// type: BottomNavigationBarType.fixed,
|
||||||
|
|
||||||
|
// Here, the items of BottomNavigationBar are hard coded. In a real
|
||||||
|
// world scenario, the items would most likely be generated from the
|
||||||
|
// branches of the shell route, which can be fetched using
|
||||||
|
// `navigationShell.route.branches`.
|
||||||
|
items: const <BottomNavigationBarItem>[
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
label: 'Home',
|
||||||
|
icon: Icon(Icons.home_outlined),
|
||||||
|
activeIcon: Icon(Icons.home),
|
||||||
|
),
|
||||||
|
BottomNavigationBarItem(
|
||||||
|
label: 'Settings',
|
||||||
|
icon: Icon(Icons.settings_outlined),
|
||||||
|
activeIcon: Icon(Icons.settings),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
currentIndex: navigationShell.currentIndex,
|
||||||
|
onTap: (int index) => _onTap(context, index),
|
||||||
),
|
),
|
||||||
BottomNavigationBarItem(
|
),
|
||||||
label: 'Settings',
|
|
||||||
icon: Icon(Icons.settings_outlined),
|
|
||||||
activeIcon: Icon(Icons.settings),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
currentIndex: navigationShell.currentIndex,
|
|
||||||
onTap: (int index) => _onTap(context, index),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,30 @@ class AppSettings with _$AppSettings {
|
||||||
const factory AppSettings({
|
const factory AppSettings({
|
||||||
@Default(true) bool isDarkMode,
|
@Default(true) bool isDarkMode,
|
||||||
@Default(false) bool useMaterialThemeOnItemPage,
|
@Default(false) bool useMaterialThemeOnItemPage,
|
||||||
|
@Default(PlayerSettings()) PlayerSettings playerSettings,
|
||||||
}) = _AppSettings;
|
}) = _AppSettings;
|
||||||
|
|
||||||
factory AppSettings.fromJson(Map<String, dynamic> json) =>
|
factory AppSettings.fromJson(Map<String, dynamic> json) =>
|
||||||
_$AppSettingsFromJson(json);
|
_$AppSettingsFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class PlayerSettings with _$PlayerSettings {
|
||||||
|
const factory PlayerSettings({
|
||||||
|
@Default(MinimizedPlayerSettings())
|
||||||
|
MinimizedPlayerSettings miniPlayerSettings,
|
||||||
|
}) = _PlayerSettings;
|
||||||
|
|
||||||
|
factory PlayerSettings.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$PlayerSettingsFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class MinimizedPlayerSettings with _$MinimizedPlayerSettings {
|
||||||
|
const factory MinimizedPlayerSettings({
|
||||||
|
@Default(false) bool useChapterInfo,
|
||||||
|
}) = _MiniPlayerSettings;
|
||||||
|
|
||||||
|
factory MinimizedPlayerSettings.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$MinimizedPlayerSettingsFromJson(json);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ AppSettings _$AppSettingsFromJson(Map<String, dynamic> json) {
|
||||||
mixin _$AppSettings {
|
mixin _$AppSettings {
|
||||||
bool get isDarkMode => throw _privateConstructorUsedError;
|
bool get isDarkMode => throw _privateConstructorUsedError;
|
||||||
bool get useMaterialThemeOnItemPage => throw _privateConstructorUsedError;
|
bool get useMaterialThemeOnItemPage => throw _privateConstructorUsedError;
|
||||||
|
PlayerSettings get playerSettings => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
|
|
@ -35,7 +36,12 @@ abstract class $AppSettingsCopyWith<$Res> {
|
||||||
AppSettings value, $Res Function(AppSettings) then) =
|
AppSettings value, $Res Function(AppSettings) then) =
|
||||||
_$AppSettingsCopyWithImpl<$Res, AppSettings>;
|
_$AppSettingsCopyWithImpl<$Res, AppSettings>;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({bool isDarkMode, bool useMaterialThemeOnItemPage});
|
$Res call(
|
||||||
|
{bool isDarkMode,
|
||||||
|
bool useMaterialThemeOnItemPage,
|
||||||
|
PlayerSettings playerSettings});
|
||||||
|
|
||||||
|
$PlayerSettingsCopyWith<$Res> get playerSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
|
|
@ -53,6 +59,7 @@ class _$AppSettingsCopyWithImpl<$Res, $Val extends AppSettings>
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? isDarkMode = null,
|
Object? isDarkMode = null,
|
||||||
Object? useMaterialThemeOnItemPage = null,
|
Object? useMaterialThemeOnItemPage = null,
|
||||||
|
Object? playerSettings = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
isDarkMode: null == isDarkMode
|
isDarkMode: null == isDarkMode
|
||||||
|
|
@ -63,8 +70,20 @@ class _$AppSettingsCopyWithImpl<$Res, $Val extends AppSettings>
|
||||||
? _value.useMaterialThemeOnItemPage
|
? _value.useMaterialThemeOnItemPage
|
||||||
: useMaterialThemeOnItemPage // ignore: cast_nullable_to_non_nullable
|
: useMaterialThemeOnItemPage // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
|
playerSettings: null == playerSettings
|
||||||
|
? _value.playerSettings
|
||||||
|
: playerSettings // ignore: cast_nullable_to_non_nullable
|
||||||
|
as PlayerSettings,
|
||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$PlayerSettingsCopyWith<$Res> get playerSettings {
|
||||||
|
return $PlayerSettingsCopyWith<$Res>(_value.playerSettings, (value) {
|
||||||
|
return _then(_value.copyWith(playerSettings: value) as $Val);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
|
|
@ -75,7 +94,13 @@ abstract class _$$AppSettingsImplCopyWith<$Res>
|
||||||
__$$AppSettingsImplCopyWithImpl<$Res>;
|
__$$AppSettingsImplCopyWithImpl<$Res>;
|
||||||
@override
|
@override
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({bool isDarkMode, bool useMaterialThemeOnItemPage});
|
$Res call(
|
||||||
|
{bool isDarkMode,
|
||||||
|
bool useMaterialThemeOnItemPage,
|
||||||
|
PlayerSettings playerSettings});
|
||||||
|
|
||||||
|
@override
|
||||||
|
$PlayerSettingsCopyWith<$Res> get playerSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
|
|
@ -91,6 +116,7 @@ class __$$AppSettingsImplCopyWithImpl<$Res>
|
||||||
$Res call({
|
$Res call({
|
||||||
Object? isDarkMode = null,
|
Object? isDarkMode = null,
|
||||||
Object? useMaterialThemeOnItemPage = null,
|
Object? useMaterialThemeOnItemPage = null,
|
||||||
|
Object? playerSettings = null,
|
||||||
}) {
|
}) {
|
||||||
return _then(_$AppSettingsImpl(
|
return _then(_$AppSettingsImpl(
|
||||||
isDarkMode: null == isDarkMode
|
isDarkMode: null == isDarkMode
|
||||||
|
|
@ -101,6 +127,10 @@ class __$$AppSettingsImplCopyWithImpl<$Res>
|
||||||
? _value.useMaterialThemeOnItemPage
|
? _value.useMaterialThemeOnItemPage
|
||||||
: useMaterialThemeOnItemPage // ignore: cast_nullable_to_non_nullable
|
: useMaterialThemeOnItemPage // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,
|
as bool,
|
||||||
|
playerSettings: null == playerSettings
|
||||||
|
? _value.playerSettings
|
||||||
|
: playerSettings // ignore: cast_nullable_to_non_nullable
|
||||||
|
as PlayerSettings,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -109,7 +139,9 @@ class __$$AppSettingsImplCopyWithImpl<$Res>
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class _$AppSettingsImpl implements _AppSettings {
|
class _$AppSettingsImpl implements _AppSettings {
|
||||||
const _$AppSettingsImpl(
|
const _$AppSettingsImpl(
|
||||||
{this.isDarkMode = true, this.useMaterialThemeOnItemPage = false});
|
{this.isDarkMode = true,
|
||||||
|
this.useMaterialThemeOnItemPage = false,
|
||||||
|
this.playerSettings = const PlayerSettings()});
|
||||||
|
|
||||||
factory _$AppSettingsImpl.fromJson(Map<String, dynamic> json) =>
|
factory _$AppSettingsImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
_$$AppSettingsImplFromJson(json);
|
_$$AppSettingsImplFromJson(json);
|
||||||
|
|
@ -120,10 +152,13 @@ class _$AppSettingsImpl implements _AppSettings {
|
||||||
@override
|
@override
|
||||||
@JsonKey()
|
@JsonKey()
|
||||||
final bool useMaterialThemeOnItemPage;
|
final bool useMaterialThemeOnItemPage;
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final PlayerSettings playerSettings;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'AppSettings(isDarkMode: $isDarkMode, useMaterialThemeOnItemPage: $useMaterialThemeOnItemPage)';
|
return 'AppSettings(isDarkMode: $isDarkMode, useMaterialThemeOnItemPage: $useMaterialThemeOnItemPage, playerSettings: $playerSettings)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -136,13 +171,15 @@ class _$AppSettingsImpl implements _AppSettings {
|
||||||
(identical(other.useMaterialThemeOnItemPage,
|
(identical(other.useMaterialThemeOnItemPage,
|
||||||
useMaterialThemeOnItemPage) ||
|
useMaterialThemeOnItemPage) ||
|
||||||
other.useMaterialThemeOnItemPage ==
|
other.useMaterialThemeOnItemPage ==
|
||||||
useMaterialThemeOnItemPage));
|
useMaterialThemeOnItemPage) &&
|
||||||
|
(identical(other.playerSettings, playerSettings) ||
|
||||||
|
other.playerSettings == playerSettings));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode => Object.hash(
|
||||||
Object.hash(runtimeType, isDarkMode, useMaterialThemeOnItemPage);
|
runtimeType, isDarkMode, useMaterialThemeOnItemPage, playerSettings);
|
||||||
|
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
@override
|
@override
|
||||||
|
|
@ -161,7 +198,8 @@ class _$AppSettingsImpl implements _AppSettings {
|
||||||
abstract class _AppSettings implements AppSettings {
|
abstract class _AppSettings implements AppSettings {
|
||||||
const factory _AppSettings(
|
const factory _AppSettings(
|
||||||
{final bool isDarkMode,
|
{final bool isDarkMode,
|
||||||
final bool useMaterialThemeOnItemPage}) = _$AppSettingsImpl;
|
final bool useMaterialThemeOnItemPage,
|
||||||
|
final PlayerSettings playerSettings}) = _$AppSettingsImpl;
|
||||||
|
|
||||||
factory _AppSettings.fromJson(Map<String, dynamic> json) =
|
factory _AppSettings.fromJson(Map<String, dynamic> json) =
|
||||||
_$AppSettingsImpl.fromJson;
|
_$AppSettingsImpl.fromJson;
|
||||||
|
|
@ -171,7 +209,309 @@ abstract class _AppSettings implements AppSettings {
|
||||||
@override
|
@override
|
||||||
bool get useMaterialThemeOnItemPage;
|
bool get useMaterialThemeOnItemPage;
|
||||||
@override
|
@override
|
||||||
|
PlayerSettings get playerSettings;
|
||||||
|
@override
|
||||||
@JsonKey(ignore: true)
|
@JsonKey(ignore: true)
|
||||||
_$$AppSettingsImplCopyWith<_$AppSettingsImpl> get copyWith =>
|
_$$AppSettingsImplCopyWith<_$AppSettingsImpl> get copyWith =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PlayerSettings _$PlayerSettingsFromJson(Map<String, dynamic> json) {
|
||||||
|
return _PlayerSettings.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$PlayerSettings {
|
||||||
|
MinimizedPlayerSettings get miniPlayerSettings =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
$PlayerSettingsCopyWith<PlayerSettings> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $PlayerSettingsCopyWith<$Res> {
|
||||||
|
factory $PlayerSettingsCopyWith(
|
||||||
|
PlayerSettings value, $Res Function(PlayerSettings) then) =
|
||||||
|
_$PlayerSettingsCopyWithImpl<$Res, PlayerSettings>;
|
||||||
|
@useResult
|
||||||
|
$Res call({MinimizedPlayerSettings miniPlayerSettings});
|
||||||
|
|
||||||
|
$MinimizedPlayerSettingsCopyWith<$Res> get miniPlayerSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$PlayerSettingsCopyWithImpl<$Res, $Val extends PlayerSettings>
|
||||||
|
implements $PlayerSettingsCopyWith<$Res> {
|
||||||
|
_$PlayerSettingsCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? miniPlayerSettings = null,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
miniPlayerSettings: null == miniPlayerSettings
|
||||||
|
? _value.miniPlayerSettings
|
||||||
|
: miniPlayerSettings // ignore: cast_nullable_to_non_nullable
|
||||||
|
as MinimizedPlayerSettings,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$MinimizedPlayerSettingsCopyWith<$Res> get miniPlayerSettings {
|
||||||
|
return $MinimizedPlayerSettingsCopyWith<$Res>(_value.miniPlayerSettings,
|
||||||
|
(value) {
|
||||||
|
return _then(_value.copyWith(miniPlayerSettings: value) as $Val);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$PlayerSettingsImplCopyWith<$Res>
|
||||||
|
implements $PlayerSettingsCopyWith<$Res> {
|
||||||
|
factory _$$PlayerSettingsImplCopyWith(_$PlayerSettingsImpl value,
|
||||||
|
$Res Function(_$PlayerSettingsImpl) then) =
|
||||||
|
__$$PlayerSettingsImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call({MinimizedPlayerSettings miniPlayerSettings});
|
||||||
|
|
||||||
|
@override
|
||||||
|
$MinimizedPlayerSettingsCopyWith<$Res> get miniPlayerSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$PlayerSettingsImplCopyWithImpl<$Res>
|
||||||
|
extends _$PlayerSettingsCopyWithImpl<$Res, _$PlayerSettingsImpl>
|
||||||
|
implements _$$PlayerSettingsImplCopyWith<$Res> {
|
||||||
|
__$$PlayerSettingsImplCopyWithImpl(
|
||||||
|
_$PlayerSettingsImpl _value, $Res Function(_$PlayerSettingsImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? miniPlayerSettings = null,
|
||||||
|
}) {
|
||||||
|
return _then(_$PlayerSettingsImpl(
|
||||||
|
miniPlayerSettings: null == miniPlayerSettings
|
||||||
|
? _value.miniPlayerSettings
|
||||||
|
: miniPlayerSettings // ignore: cast_nullable_to_non_nullable
|
||||||
|
as MinimizedPlayerSettings,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$PlayerSettingsImpl implements _PlayerSettings {
|
||||||
|
const _$PlayerSettingsImpl(
|
||||||
|
{this.miniPlayerSettings = const MinimizedPlayerSettings()});
|
||||||
|
|
||||||
|
factory _$PlayerSettingsImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$PlayerSettingsImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final MinimizedPlayerSettings miniPlayerSettings;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'PlayerSettings(miniPlayerSettings: $miniPlayerSettings)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$PlayerSettingsImpl &&
|
||||||
|
(identical(other.miniPlayerSettings, miniPlayerSettings) ||
|
||||||
|
other.miniPlayerSettings == miniPlayerSettings));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType, miniPlayerSettings);
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$PlayerSettingsImplCopyWith<_$PlayerSettingsImpl> get copyWith =>
|
||||||
|
__$$PlayerSettingsImplCopyWithImpl<_$PlayerSettingsImpl>(
|
||||||
|
this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$PlayerSettingsImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _PlayerSettings implements PlayerSettings {
|
||||||
|
const factory _PlayerSettings(
|
||||||
|
{final MinimizedPlayerSettings miniPlayerSettings}) =
|
||||||
|
_$PlayerSettingsImpl;
|
||||||
|
|
||||||
|
factory _PlayerSettings.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$PlayerSettingsImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
MinimizedPlayerSettings get miniPlayerSettings;
|
||||||
|
@override
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
_$$PlayerSettingsImplCopyWith<_$PlayerSettingsImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
MinimizedPlayerSettings _$MinimizedPlayerSettingsFromJson(
|
||||||
|
Map<String, dynamic> json) {
|
||||||
|
return _MiniPlayerSettings.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$MinimizedPlayerSettings {
|
||||||
|
bool get useChapterInfo => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
$MinimizedPlayerSettingsCopyWith<MinimizedPlayerSettings> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $MinimizedPlayerSettingsCopyWith<$Res> {
|
||||||
|
factory $MinimizedPlayerSettingsCopyWith(MinimizedPlayerSettings value,
|
||||||
|
$Res Function(MinimizedPlayerSettings) then) =
|
||||||
|
_$MinimizedPlayerSettingsCopyWithImpl<$Res, MinimizedPlayerSettings>;
|
||||||
|
@useResult
|
||||||
|
$Res call({bool useChapterInfo});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$MinimizedPlayerSettingsCopyWithImpl<$Res,
|
||||||
|
$Val extends MinimizedPlayerSettings>
|
||||||
|
implements $MinimizedPlayerSettingsCopyWith<$Res> {
|
||||||
|
_$MinimizedPlayerSettingsCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? useChapterInfo = null,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
useChapterInfo: null == useChapterInfo
|
||||||
|
? _value.useChapterInfo
|
||||||
|
: useChapterInfo // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$MiniPlayerSettingsImplCopyWith<$Res>
|
||||||
|
implements $MinimizedPlayerSettingsCopyWith<$Res> {
|
||||||
|
factory _$$MiniPlayerSettingsImplCopyWith(_$MiniPlayerSettingsImpl value,
|
||||||
|
$Res Function(_$MiniPlayerSettingsImpl) then) =
|
||||||
|
__$$MiniPlayerSettingsImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call({bool useChapterInfo});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$MiniPlayerSettingsImplCopyWithImpl<$Res>
|
||||||
|
extends _$MinimizedPlayerSettingsCopyWithImpl<$Res,
|
||||||
|
_$MiniPlayerSettingsImpl>
|
||||||
|
implements _$$MiniPlayerSettingsImplCopyWith<$Res> {
|
||||||
|
__$$MiniPlayerSettingsImplCopyWithImpl(_$MiniPlayerSettingsImpl _value,
|
||||||
|
$Res Function(_$MiniPlayerSettingsImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? useChapterInfo = null,
|
||||||
|
}) {
|
||||||
|
return _then(_$MiniPlayerSettingsImpl(
|
||||||
|
useChapterInfo: null == useChapterInfo
|
||||||
|
? _value.useChapterInfo
|
||||||
|
: useChapterInfo // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$MiniPlayerSettingsImpl implements _MiniPlayerSettings {
|
||||||
|
const _$MiniPlayerSettingsImpl({this.useChapterInfo = false});
|
||||||
|
|
||||||
|
factory _$MiniPlayerSettingsImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$MiniPlayerSettingsImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final bool useChapterInfo;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'MinimizedPlayerSettings(useChapterInfo: $useChapterInfo)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$MiniPlayerSettingsImpl &&
|
||||||
|
(identical(other.useChapterInfo, useChapterInfo) ||
|
||||||
|
other.useChapterInfo == useChapterInfo));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType, useChapterInfo);
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$MiniPlayerSettingsImplCopyWith<_$MiniPlayerSettingsImpl> get copyWith =>
|
||||||
|
__$$MiniPlayerSettingsImplCopyWithImpl<_$MiniPlayerSettingsImpl>(
|
||||||
|
this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$MiniPlayerSettingsImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _MiniPlayerSettings implements MinimizedPlayerSettings {
|
||||||
|
const factory _MiniPlayerSettings({final bool useChapterInfo}) =
|
||||||
|
_$MiniPlayerSettingsImpl;
|
||||||
|
|
||||||
|
factory _MiniPlayerSettings.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$MiniPlayerSettingsImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get useChapterInfo;
|
||||||
|
@override
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
_$$MiniPlayerSettingsImplCopyWith<_$MiniPlayerSettingsImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,41 @@ _$AppSettingsImpl _$$AppSettingsImplFromJson(Map<String, dynamic> json) =>
|
||||||
isDarkMode: json['isDarkMode'] as bool? ?? true,
|
isDarkMode: json['isDarkMode'] as bool? ?? true,
|
||||||
useMaterialThemeOnItemPage:
|
useMaterialThemeOnItemPage:
|
||||||
json['useMaterialThemeOnItemPage'] as bool? ?? false,
|
json['useMaterialThemeOnItemPage'] as bool? ?? false,
|
||||||
|
playerSettings: json['playerSettings'] == null
|
||||||
|
? const PlayerSettings()
|
||||||
|
: PlayerSettings.fromJson(
|
||||||
|
json['playerSettings'] as Map<String, dynamic>),
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$$AppSettingsImplToJson(_$AppSettingsImpl instance) =>
|
Map<String, dynamic> _$$AppSettingsImplToJson(_$AppSettingsImpl instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
'isDarkMode': instance.isDarkMode,
|
'isDarkMode': instance.isDarkMode,
|
||||||
'useMaterialThemeOnItemPage': instance.useMaterialThemeOnItemPage,
|
'useMaterialThemeOnItemPage': instance.useMaterialThemeOnItemPage,
|
||||||
|
'playerSettings': instance.playerSettings,
|
||||||
|
};
|
||||||
|
|
||||||
|
_$PlayerSettingsImpl _$$PlayerSettingsImplFromJson(Map<String, dynamic> json) =>
|
||||||
|
_$PlayerSettingsImpl(
|
||||||
|
miniPlayerSettings: json['miniPlayerSettings'] == null
|
||||||
|
? const MinimizedPlayerSettings()
|
||||||
|
: MinimizedPlayerSettings.fromJson(
|
||||||
|
json['miniPlayerSettings'] as Map<String, dynamic>),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$PlayerSettingsImplToJson(
|
||||||
|
_$PlayerSettingsImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'miniPlayerSettings': instance.miniPlayerSettings,
|
||||||
|
};
|
||||||
|
|
||||||
|
_$MiniPlayerSettingsImpl _$$MiniPlayerSettingsImplFromJson(
|
||||||
|
Map<String, dynamic> json) =>
|
||||||
|
_$MiniPlayerSettingsImpl(
|
||||||
|
useChapterInfo: json['useChapterInfo'] as bool? ?? false,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$MiniPlayerSettingsImplToJson(
|
||||||
|
_$MiniPlayerSettingsImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'useChapterInfo': instance.useChapterInfo,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,8 @@ set_target_properties(${BINARY_NAME}
|
||||||
# them to the application.
|
# them to the application.
|
||||||
include(flutter/generated_plugins.cmake)
|
include(flutter/generated_plugins.cmake)
|
||||||
|
|
||||||
|
# as suggested by https://pub.dev/packages/just_audio_media_kit
|
||||||
|
target_link_libraries(${BINARY_NAME} PRIVATE ${MIMALLOC_LIB})
|
||||||
|
|
||||||
# === Installation ===
|
# === Installation ===
|
||||||
# By default, "installing" just makes a relocatable bundle in the build
|
# By default, "installing" just makes a relocatable bundle in the build
|
||||||
|
|
|
||||||
|
|
@ -6,17 +6,17 @@
|
||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
#include <audioplayers_linux/audioplayers_linux_plugin.h>
|
|
||||||
#include <isar_flutter_libs/isar_flutter_libs_plugin.h>
|
#include <isar_flutter_libs/isar_flutter_libs_plugin.h>
|
||||||
|
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
|
||||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||||
|
|
||||||
void fl_register_plugins(FlPluginRegistry* registry) {
|
void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
g_autoptr(FlPluginRegistrar) audioplayers_linux_registrar =
|
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "AudioplayersLinuxPlugin");
|
|
||||||
audioplayers_linux_plugin_register_with_registrar(audioplayers_linux_registrar);
|
|
||||||
g_autoptr(FlPluginRegistrar) isar_flutter_libs_registrar =
|
g_autoptr(FlPluginRegistrar) isar_flutter_libs_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "IsarFlutterLibsPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "IsarFlutterLibsPlugin");
|
||||||
isar_flutter_libs_plugin_register_with_registrar(isar_flutter_libs_registrar);
|
isar_flutter_libs_plugin_register_with_registrar(isar_flutter_libs_registrar);
|
||||||
|
g_autoptr(FlPluginRegistrar) media_kit_libs_linux_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitLibsLinuxPlugin");
|
||||||
|
media_kit_libs_linux_plugin_register_with_registrar(media_kit_libs_linux_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
audioplayers_linux
|
|
||||||
isar_flutter_libs
|
isar_flutter_libs
|
||||||
|
media_kit_libs_linux
|
||||||
url_launcher_linux
|
url_launcher_linux
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
211
pubspec.lock
211
pubspec.lock
|
|
@ -65,6 +65,38 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.11.0"
|
version: "2.11.0"
|
||||||
|
audio_service:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: audio_service
|
||||||
|
sha256: "4547c312a94f9cb2c48b60823fb190767cbd63454a83c73049384d5d3cba4650"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.18.13"
|
||||||
|
audio_service_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: audio_service_platform_interface
|
||||||
|
sha256: "8431a455dac9916cc9ee6f7da5620a666436345c906ad2ebb7fa41d18b3c1bf4"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.1"
|
||||||
|
audio_service_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: audio_service_web
|
||||||
|
sha256: "9d7d5ae5f98a5727f2580fad73062f2484f400eef6cef42919413268e62a363e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.2"
|
||||||
|
audio_session:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: audio_session
|
||||||
|
sha256: a49af9981eec5d7cd73b37bacb6ee73f8143a6a9f9bd5b6021e6c346b9b6cf4e
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.19"
|
||||||
audio_video_progress_bar:
|
audio_video_progress_bar:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -73,62 +105,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2"
|
version: "2.0.2"
|
||||||
audioplayers:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: audioplayers
|
|
||||||
sha256: "752039d6aa752597c98ec212e9759519061759e402e7da59a511f39d43aa07d2"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "6.0.0"
|
|
||||||
audioplayers_android:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: audioplayers_android
|
|
||||||
sha256: de576b890befe27175c2f511ba8b742bec83765fa97c3ce4282bba46212f58e4
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "5.0.0"
|
|
||||||
audioplayers_darwin:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: audioplayers_darwin
|
|
||||||
sha256: e507887f3ff18d8e5a10a668d7bedc28206b12e10b98347797257c6ae1019c3b
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "6.0.0"
|
|
||||||
audioplayers_linux:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: audioplayers_linux
|
|
||||||
sha256: "3d3d244c90436115417f170426ce768856d8fe4dfc5ed66a049d2890acfa82f9"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "4.0.0"
|
|
||||||
audioplayers_platform_interface:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: audioplayers_platform_interface
|
|
||||||
sha256: "6834dd48dfb7bc6c2404998ebdd161f79cd3774a7e6779e1348d54a3bfdcfaa5"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "7.0.0"
|
|
||||||
audioplayers_web:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: audioplayers_web
|
|
||||||
sha256: db8fc420dadf80da18e2286c18e746fb4c3b2c5adbf0c963299dde046828886d
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "5.0.0"
|
|
||||||
audioplayers_windows:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: audioplayers_windows
|
|
||||||
sha256: "8605762dddba992138d476f6a0c3afd9df30ac5b96039929063eceed416795c2"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "4.0.0"
|
|
||||||
auto_scroll_text:
|
auto_scroll_text:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -568,6 +544,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.2"
|
version: "4.0.2"
|
||||||
|
image:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image
|
||||||
|
sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.7"
|
||||||
io:
|
io:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -616,6 +600,46 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.8.0"
|
version: "6.8.0"
|
||||||
|
just_audio:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: just_audio
|
||||||
|
sha256: b7cb6bbf3750caa924d03f432ba401ec300fd90936b3f73a9b33d58b1e96286b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.37"
|
||||||
|
just_audio_background:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: just_audio_background
|
||||||
|
sha256: "3454ffc97edfa1282b7f42759bfa8aa13d9114a24465f4101e0d3ae58a9327fb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.0.1-beta.11"
|
||||||
|
just_audio_media_kit:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: just_audio_media_kit
|
||||||
|
sha256: bbecbd43959c230d9f9610df0e0165855e711b4c960ce730c08f31107cc3bd26
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.4"
|
||||||
|
just_audio_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: just_audio_platform_interface
|
||||||
|
sha256: c3dee0014248c97c91fe6299edb73dc4d6c6930a2f4f713579cd692d9e47f4a1
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.2.2"
|
||||||
|
just_audio_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: just_audio_web
|
||||||
|
sha256: "134356b0fe3d898293102b33b5fd618831ffdc72bb7a1b726140abdf22772b70"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.4.9"
|
||||||
leak_tracker:
|
leak_tracker:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -680,6 +704,30 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.0"
|
version: "0.8.0"
|
||||||
|
media_kit:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: media_kit
|
||||||
|
sha256: "3289062540e3b8b9746e5c50d95bd78a9289826b7227e253dff806d002b9e67a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.10+1"
|
||||||
|
media_kit_libs_linux:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: media_kit_libs_linux
|
||||||
|
sha256: e186891c31daa6bedab4d74dcdb4e8adfccc7d786bfed6ad81fe24a3b3010310
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.3"
|
||||||
|
media_kit_libs_windows_audio:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: media_kit_libs_windows_audio
|
||||||
|
sha256: c2fd558cc87b9d89a801141fcdffe02e338a3b21a41a18fbd63d5b221a1b8e53
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.9"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -699,11 +747,10 @@ packages:
|
||||||
miniplayer:
|
miniplayer:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: miniplayer
|
path: "../miniplayer"
|
||||||
sha256: "6e12c27aef7432fc16508460a6dc824f3edfeb01761bd0dbfbccc84d516121bf"
|
relative: true
|
||||||
url: "https://pub.dev"
|
source: path
|
||||||
source: hosted
|
version: "1.0.3"
|
||||||
version: "1.0.1"
|
|
||||||
octo_image:
|
octo_image:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -776,6 +823,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.2.1"
|
||||||
|
petitparser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: petitparser
|
||||||
|
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.2"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -857,13 +912,21 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.10"
|
version: "2.3.10"
|
||||||
rxdart:
|
rxdart:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: rxdart
|
name: rxdart
|
||||||
sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb"
|
sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.27.7"
|
version: "0.27.7"
|
||||||
|
safe_local_storage:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: safe_local_storage
|
||||||
|
sha256: ede4eb6cb7d88a116b3d3bf1df70790b9e2038bc37cb19112e381217c74d9440
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
scroll_loop_auto_scroll:
|
scroll_loop_auto_scroll:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -1052,6 +1115,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.2"
|
version: "1.3.2"
|
||||||
|
universal_platform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: universal_platform
|
||||||
|
sha256: d315be0f6641898b280ffa34e2ddb14f3d12b1a37882557869646e0cc363d0cc
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0+1"
|
||||||
|
uri_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: uri_parser
|
||||||
|
sha256: "6543c9fd86d2862fac55d800a43e67c0dcd1a41677cb69c2f8edfe73bbcf1835"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
url_launcher:
|
url_launcher:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -1180,6 +1259,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "1.0.4"
|
||||||
|
xml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xml
|
||||||
|
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.5.0"
|
||||||
yaml:
|
yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
11
pubspec.yaml
11
pubspec.yaml
|
|
@ -32,8 +32,8 @@ isar_version: &isar_version ^4.0.0-dev.13 # define the version to be used
|
||||||
dependencies:
|
dependencies:
|
||||||
animated_list_plus: ^0.5.2
|
animated_list_plus: ^0.5.2
|
||||||
animated_theme_switcher: ^2.0.10
|
animated_theme_switcher: ^2.0.10
|
||||||
|
audio_session: ^0.1.19
|
||||||
audio_video_progress_bar: ^2.0.2
|
audio_video_progress_bar: ^2.0.2
|
||||||
audioplayers: ^6.0.0
|
|
||||||
auto_scroll_text: ^0.0.7
|
auto_scroll_text: ^0.0.7
|
||||||
cached_network_image: ^3.3.1
|
cached_network_image: ^3.3.1
|
||||||
coast: ^2.0.2
|
coast: ^2.0.2
|
||||||
|
|
@ -54,11 +54,18 @@ dependencies:
|
||||||
isar: ^4.0.0-dev.13
|
isar: ^4.0.0-dev.13
|
||||||
isar_flutter_libs: ^4.0.0-dev.13
|
isar_flutter_libs: ^4.0.0-dev.13
|
||||||
json_annotation: ^4.9.0
|
json_annotation: ^4.9.0
|
||||||
|
just_audio: ^0.9.37
|
||||||
|
just_audio_background: ^0.0.1-beta.11
|
||||||
|
just_audio_media_kit: ^2.0.4
|
||||||
lottie: ^3.1.0
|
lottie: ^3.1.0
|
||||||
miniplayer: ^1.0.1
|
media_kit_libs_linux: any
|
||||||
|
media_kit_libs_windows_audio: any
|
||||||
|
miniplayer:
|
||||||
|
path: ../miniplayer
|
||||||
path: ^1.9.0
|
path: ^1.9.0
|
||||||
path_provider: ^2.1.0
|
path_provider: ^2.1.0
|
||||||
riverpod_annotation: ^2.3.5
|
riverpod_annotation: ^2.3.5
|
||||||
|
rxdart: ^0.27.7
|
||||||
scroll_loop_auto_scroll: ^0.0.5
|
scroll_loop_auto_scroll: ^0.0.5
|
||||||
shelfsdk:
|
shelfsdk:
|
||||||
path: ../../_dart/shelfsdk
|
path: ../../_dart/shelfsdk
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,15 @@
|
||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
#include <audioplayers_windows/audioplayers_windows_plugin.h>
|
|
||||||
#include <isar_flutter_libs/isar_flutter_libs_plugin.h>
|
#include <isar_flutter_libs/isar_flutter_libs_plugin.h>
|
||||||
|
#include <media_kit_libs_windows_audio/media_kit_libs_windows_audio_plugin_c_api.h>
|
||||||
#include <url_launcher_windows/url_launcher_windows.h>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
AudioplayersWindowsPluginRegisterWithRegistrar(
|
|
||||||
registry->GetRegistrarForPlugin("AudioplayersWindowsPlugin"));
|
|
||||||
IsarFlutterLibsPluginRegisterWithRegistrar(
|
IsarFlutterLibsPluginRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("IsarFlutterLibsPlugin"));
|
registry->GetRegistrarForPlugin("IsarFlutterLibsPlugin"));
|
||||||
|
MediaKitLibsWindowsAudioPluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("MediaKitLibsWindowsAudioPluginCApi"));
|
||||||
UrlLauncherWindowsRegisterWithRegistrar(
|
UrlLauncherWindowsRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
audioplayers_windows
|
|
||||||
isar_flutter_libs
|
isar_flutter_libs
|
||||||
|
media_kit_libs_windows_audio
|
||||||
url_launcher_windows
|
url_launcher_windows
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue