Skip to content

Commit

Permalink
✨ NEW: Currently playing media info (#25)
Browse files Browse the repository at this point in the history
* 💄 Improve UI for Mini audio player

* 🐛 Fix: weird jump in mini player slider

* 🚧 WIP: full screen player

* 🐛 Fix: Seekbar usage

* ✨ New: Full Screen player

* 🐛 Fix: rounded corners not visible for mini player

*  🚧 WIP: Added safer `animateToPage` function

* 👷 Updated app version
  • Loading branch information
devaryakjha authored Sep 23, 2023
1 parent b36543e commit 65f7a80
Show file tree
Hide file tree
Showing 27 changed files with 941 additions and 197 deletions.
30 changes: 23 additions & 7 deletions lib/app.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:varanasi_mobile_app/utils/constants/constants.dart';
import 'package:varanasi_mobile_app/utils/router.dart';
import 'package:varanasi_mobile_app/widgets/responsive_sizer.dart';

import 'cubits/config/config_cubit.dart';
import 'cubits/player/player_cubit.dart';
import 'utils/theme.dart';

class Varanasi extends StatelessWidget {
Expand All @@ -12,13 +15,26 @@ class Varanasi extends StatelessWidget {
Widget build(BuildContext context) {
return ResponsiveSizer(
builder: (context, orientation, screenType) {
return MaterialApp.router(
title: AppStrings.appName,
theme: lightTheme,
darkTheme: darkTheme,
themeMode: ThemeMode.dark,
routerConfig: routerConfig,
debugShowCheckedModeBanner: false,
return MultiBlocProvider(
providers: [
BlocProvider(
lazy: false,
create: (context) => ConfigCubit()..initialise(),
),
BlocProvider(
lazy: false,
create: (context) =>
MediaPlayerCubit(() => context.read<ConfigCubit>())..init(),
),
],
child: MaterialApp.router(
title: AppStrings.appName,
theme: lightTheme,
darkTheme: darkTheme,
themeMode: ThemeMode.dark,
routerConfig: routerConfig,
debugShowCheckedModeBanner: false,
),
);
},
);
Expand Down
49 changes: 47 additions & 2 deletions lib/cubits/config/config_cubit.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import 'package:bloc/bloc.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:carousel_slider/carousel_controller.dart';
import 'package:equatable/equatable.dart';
import 'package:hive/hive.dart';
import 'package:palette_generator/palette_generator.dart';
import 'package:sliding_up_panel/sliding_up_panel.dart';
import 'package:varanasi_mobile_app/models/app_config.dart';
import 'package:varanasi_mobile_app/models/sort_type.dart';
import 'package:varanasi_mobile_app/utils/logger.dart';
Expand All @@ -9,19 +13,27 @@ part 'config_state.dart';

class ConfigCubit extends Cubit<ConfigState> {
late final Box<AppConfig> _configBox;
final Map<String, CachedNetworkImageProvider> _imageProviderCache = {};
late final Expando<PaletteGenerator> _paletteGeneratorExpando;
Logger logger = Logger.instance;

ConfigCubit() : super(ConfigInitial());

void initialise() async {
_paletteGeneratorExpando = Expando<PaletteGenerator>();
_configBox = await Hive.openBox<AppConfig>('config');
if (_configBox.isEmpty) {
logger.i('Config box is empty');
_configBox.add(AppConfig());
} else {
logger.i('Config box is not empty');
}
emit(ConfigLoaded(config: _configBox.values.first));
emit(ConfigLoaded(
config: _configBox.values.first,
panelController: PanelController(),
playerPageController: CarouselController(),
miniPlayerPageController: CarouselController(),
));
}

Stream<SortBy> get sortTypeStream => stream.map(
Expand All @@ -35,7 +47,40 @@ class ConfigCubit extends Cubit<ConfigState> {
if (state is ConfigLoaded) {
final config = (state as ConfigLoaded).config.copyWith(sortBy: sortBy);
_configBox.putAt(0, config);
emit(ConfigLoaded(config: config));
emit(ConfigLoaded(config: config, panelController: PanelController()));
}
}

PaletteGenerator? maybeGetPaletteGenerator(String mediaUrl) {
final provider = _imageProviderCache[mediaUrl];
if (provider == null) return null;
return _paletteGeneratorExpando[provider];
}

CachedNetworkImageProvider getProvider(String mediaUrl) {
return _imageProviderCache[mediaUrl] ??=
CachedNetworkImageProvider(mediaUrl);
}

Future<PaletteGenerator?> generatePalleteGenerator(String mediaUrl) async {
try {
final exists = maybeGetPaletteGenerator(mediaUrl);
if (exists != null) return exists;
final provider = getProvider(mediaUrl);
final paletteGenerator =
await PaletteGenerator.fromImageProvider(provider);
_paletteGeneratorExpando[provider] = paletteGenerator;
return paletteGenerator;
} catch (e) {
return null;
}
}

CarouselController? get miniPlayerPageController => (state is ConfigLoaded)
? (state as ConfigLoaded).miniPlayerPageController
: null;

CarouselController? get playerPageController => (state is ConfigLoaded)
? (state as ConfigLoaded).playerPageController
: null;
}
34 changes: 31 additions & 3 deletions lib/cubits/config/config_state.dart
Original file line number Diff line number Diff line change
@@ -1,19 +1,47 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
part of 'config_cubit.dart';

sealed class ConfigState extends Equatable {
const ConfigState();

@override
List<Object> get props => [];
List<Object?> get props => [];
}

final class ConfigInitial extends ConfigState {}

final class ConfigLoaded extends ConfigState {
final AppConfig config;
final CarouselController? miniPlayerPageController, playerPageController;
final PanelController panelController;

const ConfigLoaded({required this.config});
const ConfigLoaded({
required this.config,
this.miniPlayerPageController,
this.playerPageController,
required this.panelController,
});

@override
List<Object> get props => [config];
List<Object?> get props => [
config,
miniPlayerPageController,
panelController,
playerPageController,
];

ConfigLoaded copyWith({
AppConfig? config,
CarouselController? miniPlayerPageController,
CarouselController? playerPageController,
PanelController? panelController,
}) {
return ConfigLoaded(
config: config ?? this.config,
playerPageController: playerPageController ?? this.playerPageController,
panelController: panelController ?? this.panelController,
miniPlayerPageController:
miniPlayerPageController ?? this.miniPlayerPageController,
);
}
}
55 changes: 52 additions & 3 deletions lib/cubits/player/player_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hive/hive.dart';
import 'package:palette_generator/palette_generator.dart';
import 'package:rxdart/rxdart.dart';
import 'package:varanasi_mobile_app/cubits/config/config_cubit.dart';
import 'package:varanasi_mobile_app/models/media_playlist.dart';
import 'package:varanasi_mobile_app/models/playable_item.dart';
import 'package:varanasi_mobile_app/models/song.dart';
Expand All @@ -15,6 +17,7 @@ import 'package:varanasi_mobile_app/utils/mixins/cachable_mixin.dart';
import 'package:varanasi_mobile_app/utils/mixins/repository_protocol.dart';
import 'package:varanasi_mobile_app/utils/player/audio_handler_impl.dart';
import 'package:varanasi_mobile_app/utils/player/typings.dart';
import 'package:varanasi_mobile_app/utils/safe_animate_to_pageview.dart';
import 'package:varanasi_mobile_app/utils/services/http_services.dart';

part 'player_state.dart';
Expand All @@ -24,12 +27,35 @@ extension MediaPlayerCubitExtension on BuildContext {
MediaPlayerCubit get selectMediaPlayerCubit => watch<MediaPlayerCubit>();
}

class MediaColorPalette {
final Color? backgroundColor;
final Color? foregroundColor;

const MediaColorPalette({
this.backgroundColor,
this.foregroundColor,
});

factory MediaColorPalette.fromPaletteGenerator(PaletteGenerator palette) {
final PaletteColor? selectedColor = palette.darkVibrantColor ??
palette.darkMutedColor ??
palette.dominantColor;
final Color? backgroundColor = selectedColor?.color.withOpacity(1);
final Color? foregroundColor = selectedColor?.bodyTextColor.withOpacity(1);
return MediaColorPalette(
backgroundColor: backgroundColor,
foregroundColor: foregroundColor,
);
}
}

class MediaPlayerCubit extends AppCubit<MediaPlayerState>
with DataProviderProtocol, CacheableService {
final ConfigCubit Function() configCubitGetter;
late final AudioHandlerImpl audioHandler;
late final Box _box;

MediaPlayerCubit() : super(const MediaPlayerState());
MediaPlayerCubit(this.configCubitGetter) : super(const MediaPlayerState());

Future<void> playFromMediaPlaylist<T extends PlayableMedia>(
MediaPlaylist<T> playlist, [
Expand Down Expand Up @@ -105,7 +131,11 @@ class MediaPlayerCubit extends AppCubit<MediaPlayerState>
Future<void> skipToMediaItem(PlayableMedia mediaItem) async {
final index = state.queueState.queue.indexOf(mediaItem.toMediaItem());
if (index == -1) return;
return skipToIndex(index);
await skipToIndex(index);
}

Future<void> seek(Duration position) {
return audioHandler.seek(position);
}

@override
Expand All @@ -126,11 +156,25 @@ class MediaPlayerCubit extends AppCubit<MediaPlayerState>
audioHandler.mediaItem.stream,
audioHandler.queueState,
(playing, mediaItem, queueState) => (playing, mediaItem, queueState),
).distinct().listen((value) {
).distinct().listen((value) async {
PaletteGenerator? palette;
if (value.$2 != null) {
palette = await configCubitGetter()
.generatePalleteGenerator(value.$2?.artUri.toString() ?? '');
final configCubit = configCubitGetter();
final controller = configCubit.miniPlayerPageController;
final carouselController = configCubit.playerPageController;
final index = value.$3.queueIndex ?? 0;
animateToPage(index, controller);
animateToPage(index, carouselController);
} else {
palette = null;
}
emit(state.copyWith(
isPlaying: value.$1,
currentMediaItem: value.$2,
queueState: value.$3,
paletteGenerator: palette,
));
});
}
Expand All @@ -140,4 +184,9 @@ class MediaPlayerCubit extends AppCubit<MediaPlayerState>

@override
String get cacheBoxName => AppStrings.commonCacheBoxName;

MediaColorPalette? get mediaColorPalette {
if (state.paletteGenerator == null) return null;
return MediaColorPalette.fromPaletteGenerator(state.paletteGenerator!);
}
}
32 changes: 30 additions & 2 deletions lib/cubits/player/player_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,58 @@ class MediaPlayerState extends Equatable {
final String? currentPlaylist;
final bool isPlaying;
final MediaItem? currentMediaItem;
final PaletteGenerator? paletteGenerator;
final QueueState queueState;

const MediaPlayerState({
this.currentPlaylist,
this.isPlaying = false,
this.currentMediaItem,
this.queueState = QueueState.empty,
this.paletteGenerator,
});

MediaPlayerState copyWith({
String? currentPlaylist,
bool? isPlaying,
MediaItem? currentMediaItem,
PaletteGenerator? paletteGenerator,
QueueState? queueState,
}) {
return MediaPlayerState(
currentPlaylist: currentPlaylist ?? this.currentPlaylist,
isPlaying: isPlaying ?? this.isPlaying,
currentMediaItem: currentMediaItem ?? this.currentMediaItem,
paletteGenerator: paletteGenerator ?? this.paletteGenerator,
queueState: queueState ?? this.queueState,
);
}

@override
List<Object?> get props =>
[currentPlaylist, isPlaying, currentMediaItem, queueState];
List<Object?> get props => [
currentPlaylist,
isPlaying,
currentMediaItem,
queueState,
paletteGenerator
];

Gradient? get gradient {
if (paletteGenerator == null) return null;
final palette = paletteGenerator!;
final PaletteColor? selectedColor = palette.darkVibrantColor ??
palette.darkMutedColor ??
palette.dominantColor;
final Color? color1 = selectedColor?.color.withOpacity(1);
const Color color2 = Colors.black;
return LinearGradient(
colors: [
color1 ?? color2,
color2,
],
begin: Alignment.topCenter,
end: FractionalOffset.bottomCenter,
stops: const [0, 0.8],
);
}
}
10 changes: 6 additions & 4 deletions lib/features/library/cubit/library_cubit.dart
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:palette_generator/palette_generator.dart';
import 'package:varanasi_mobile_app/cubits/config/config_cubit.dart';
import 'package:varanasi_mobile_app/features/library/data/library_repository.dart';
import 'package:varanasi_mobile_app/models/media_playlist.dart';
import 'package:varanasi_mobile_app/models/playable_item.dart';
import 'package:varanasi_mobile_app/models/sort_type.dart';
import 'package:varanasi_mobile_app/utils/configs.dart';
import 'package:varanasi_mobile_app/utils/extensions/extensions.dart';
import 'package:varanasi_mobile_app/utils/generate_pallette.dart';
import 'package:varanasi_mobile_app/utils/helpers/get_app_context.dart';
import 'package:varanasi_mobile_app/utils/logger.dart';

part 'library_state.dart';
Expand All @@ -27,8 +27,10 @@ class LibraryCubit extends Cubit<LibraryState> {
if (link == appConfig.placeholderImageLink) {
link = media.artworkUrl!;
}
final image = CachedNetworkImageProvider(link);
final colorPalette = await generateColorPalette(imageProvider: image);
if (!appContext.mounted) return;
final configCubit = appContext.read<ConfigCubit>();
final colorPalette = await configCubit.generatePalleteGenerator(link);
final image = configCubit.getProvider(link);
emit(LibraryLoaded(
playlist,
colorPalette!,
Expand Down
1 change: 0 additions & 1 deletion lib/features/library/ui/library_search_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ class LibrarySearchPage extends HookWidget {
} else {
context.readMediaPlayerCubit.playFromMediaPlaylist(playlist);
}
// context.go('/player');
},
),
);
Expand Down
12 changes: 0 additions & 12 deletions lib/utils/generate_pallette.dart

This file was deleted.

Loading

0 comments on commit 65f7a80

Please sign in to comment.