Files
AudiOhm/frontend/lib/presentation/widgets/settings/audio_quality_selector.dart
T
root a89c7894cf Initial commit: AudiOhm - Alternative Spotify avec streaming YouTube
Backend:
- FastAPI avec PostgreSQL et Redis
- Authentification JWT complète
- API REST pour musique, playlists, recherche
- Streaming audio via yt-dlp
- SQLAlchemy 2.0 async

Frontend:
- Flutter avec thème néon cyberpunk
- State management Riverpod
- Layout adaptatif desktop/mobile
- Lecteur audio avec mini-player

Infrastructure:
- Docker Compose (PostgreSQL + Redis)
- Scripts d'installation automatisés
- Scripts de build pour exécutables

Fichiers ajoutés:
- BUILD_CLIENT_*.bat/sh: Scripts de compilation
- BUILD_CLIENT_README.md: Documentation compilation
- CHECK_FLUTTER.sh: Vérificateur d'environnement
- requirements.txt mis à jour pour Python 3.13
- Modèles SQLAlchemy corrigés (metadata -> extra_metadata)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-18 20:08:36 +00:00

248 lines
7.8 KiB
Dart

/// Audio Quality Selector Widget
library;
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../core/theme/colors.dart';
import '../../../core/theme/text_styles.dart';
import '../../providers/settings_provider.dart';
/// Audio quality selector widget
class AudioQualitySelector extends ConsumerWidget {
const AudioQualitySelector({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final settingsState = ref.watch(settingsProvider);
final currentQuality = settingsState.audioQuality;
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: AppColors.cyan.withOpacity(0.15),
width: 1,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header
Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
child: Row(
children: [
Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: AppColors.cyan.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: const Icon(
Icons.high_quality_outlined,
color: AppColors.cyan,
size: 20,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Audio Quality',
style: AppTextStyles.body.copyWith(
color: AppColors.onBackground,
fontWeight: FontWeight.w600,
),
),
Text(
'Higher quality uses more data',
style: AppTextStyles.bodySmall.copyWith(
color: AppColors.muted,
),
),
],
),
),
],
),
),
const Divider(height: 1, color: AppColors.surfaceVariant),
// Audio quality options
_buildQualityOption(
context,
ref,
AudioQuality.low,
'Low',
'96 kbps',
'Best for data saving',
currentQuality,
),
_buildQualityOption(
context,
ref,
AudioQuality.medium,
'Medium',
'160 kbps',
'Good balance',
currentQuality,
),
_buildQualityOption(
context,
ref,
AudioQuality.high,
'High',
'320 kbps',
'Best quality',
currentQuality,
),
_buildQualityOption(
context,
ref,
AudioQuality.lossless,
'Lossless',
'FLAC',
'Requires Premium',
currentQuality,
requiresPremium: true,
),
],
),
);
}
Widget _buildQualityOption(
BuildContext context,
WidgetRef ref,
AudioQuality quality,
String title,
String bitrate,
String description,
AudioQuality currentQuality, {
bool requiresPremium = false,
}) {
final isSelected = currentQuality == quality;
final settingsState = ref.watch(settingsProvider);
final isPremium = settingsState.user?.isPremium ?? false;
final isLocked = requiresPremium && !isPremium;
return InkWell(
onTap: isLocked
? null
: () => ref.read(settingsProvider.notifier).setAudioQuality(quality),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: isSelected
? AppColors.cyan.withOpacity(0.1)
: Colors.transparent,
border: Border(
left: BorderSide(
color: isSelected ? AppColors.cyan : Colors.transparent,
width: 3,
),
),
),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
title,
style: AppTextStyles.body.copyWith(
color: isLocked
? AppColors.muted
: AppColors.onBackground,
fontWeight:
isSelected ? FontWeight.w600 : FontWeight.w500,
),
),
if (requiresPremium) ...[
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 2,
),
decoration: BoxDecoration(
color: AppColors.violet.withOpacity(0.2),
borderRadius: BorderRadius.circular(4),
),
child: Text(
'PRO',
style: AppTextStyles.caption.copyWith(
color: AppColors.violet,
fontWeight: FontWeight.w700,
fontSize: 10,
),
),
),
],
],
),
const SizedBox(height: 4),
Row(
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 2,
),
decoration: BoxDecoration(
color: AppColors.surfaceVariant,
borderRadius: BorderRadius.circular(4),
),
child: Text(
bitrate,
style: AppTextStyles.caption.copyWith(
color: AppColors.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
),
),
const SizedBox(width: 8),
Text(
description,
style: AppTextStyles.bodySmall.copyWith(
color: AppColors.muted,
),
),
],
),
],
),
),
if (isLocked) ...[
Icon(
Icons.lock_outline,
color: AppColors.muted,
size: 20,
),
] else if (isSelected) ...[
Icon(
Icons.check_circle,
color: AppColors.cyan,
size: 24,
),
] else ...[
Icon(
Icons.radio_button_unchecked,
color: AppColors.muted,
size: 24,
),
],
],
),
),
);
}
}