🎉 Initial commit: AudiOhm - Alternative à Spotify avec streaming YouTube

Features:
- Frontend Flutter avec thème néon cyberpunk
- Backend FastAPI avec streaming YouTube
- Base de données PostgreSQL + Redis
- Authentification JWT complète
- Recherche multi-source (DB + YouTube)
- Playlists CRUD avec drag & drop
- Queue management
- Settings avec audio quality
- Interface adaptative (Desktop + Mobile)

Tech Stack:
- Frontend: Flutter 3.2+, Riverpod
- Backend: Python 3.11+, FastAPI
- Database: PostgreSQL 15+
- Cache: Redis 7+
- Streaming: yt-dlp + FFmpeg

🚀 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
feldenr
2026-01-18 17:08:59 +01:00
commit 9c504d2c3d
128 changed files with 22638 additions and 0 deletions
@@ -0,0 +1,195 @@
/// Settings Tile - Reusable settings item widget
library;
import 'package:flutter/material.dart';
import '../../../core/theme/colors.dart';
import '../../../core/theme/text_styles.dart';
/// Reusable settings tile widget
class SettingsTile extends StatelessWidget {
const SettingsTile({
super.key,
required this.title,
this.subtitle,
this.leading,
this.trailing,
this.onTap,
this.isEnabled = true,
});
final String title;
final String? subtitle;
final Widget? leading;
final Widget? trailing;
final VoidCallback? onTap;
final bool isEnabled;
@override
Widget build(BuildContext context) {
return Opacity(
opacity: isEnabled ? 1.0 : 0.5,
child: InkWell(
onTap: isEnabled ? onTap : null,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
child: Row(
children: [
if (leading != null) ...[
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: AppColors.cyan.withOpacity(0.1),
borderRadius: BorderRadius.circular(10),
),
child: IconTheme(
data: IconThemeData(
color: AppColors.cyan,
size: 20,
),
child: leading!,
),
),
const SizedBox(width: 16),
],
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: AppTextStyles.body.copyWith(
color: AppColors.onBackground,
fontWeight: FontWeight.w500,
),
),
if (subtitle != null) ...[
const SizedBox(height: 4),
Text(
subtitle!,
style: AppTextStyles.bodySmall.copyWith(
color: AppColors.muted,
),
),
],
],
),
),
if (trailing != null) trailing!,
],
),
),
),
);
}
}
/// Settings tile with toggle switch
class SettingsToggleTile extends StatelessWidget {
const SettingsToggleTile({
super.key,
required this.title,
this.subtitle,
this.leading,
required this.value,
required this.onChanged,
this.isEnabled = true,
});
final String title;
final String? subtitle;
final Widget? leading;
final bool value;
final ValueChanged<bool>? onChanged;
final bool isEnabled;
@override
Widget build(BuildContext context) {
return SettingsTile(
title: title,
subtitle: subtitle,
leading: leading,
isEnabled: isEnabled,
trailing: Switch(
value: value,
onChanged: isEnabled ? onChanged : null,
activeColor: AppColors.cyan,
activeTrackColor: AppColors.cyan.withOpacity(0.3),
inactiveTrackColor: AppColors.surfaceVariant,
inactiveThumbColor: AppColors.muted,
),
);
}
}
/// Settings section header
class SettingsSectionHeader extends StatelessWidget {
const SettingsSectionHeader({
super.key,
required this.title,
this.padding = const EdgeInsets.fromLTRB(16, 24, 16, 8),
});
final String title;
final EdgeInsetsGeometry padding;
@override
Widget build(BuildContext context) {
return Padding(
padding: padding,
child: Text(
title.toUpperCase(),
style: AppTextStyles.label.copyWith(
color: AppColors.cyan,
fontWeight: FontWeight.w600,
letterSpacing: 1.2,
),
),
);
}
}
/// Settings card container
class SettingsCard extends StatelessWidget {
const SettingsCard({
super.key,
required this.children,
this.padding = const EdgeInsets.all(8),
});
final List<Widget> children;
final EdgeInsetsGeometry padding;
@override
Widget build(BuildContext context) {
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,
),
boxShadow: [
BoxShadow(
color: AppColors.cyan.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
child: Padding(
padding: padding,
child: Column(
mainAxisSize: MainAxisSize.min,
children: children,
),
),
);
}
}