feat: Murattal player enhancements & prayer schedule auto-scroll

- Murattal: Spotify-style 5-button controls [Shuffle, Prev, Play, Next, Playlist]
- Murattal: Animated 7-bar equalizer visualization in player circle
- Murattal: Unsplash API background with frosted glass player overlay
- Murattal: Transparent AppBar with backdrop blur
- Murattal: Surah playlist bottom sheet with full 114 Surah list
- Murattal: Auto-play disabled on screen open, enabled on navigation
- Murattal: Shuffle mode for random Surah playback
- Murattal: Photographer attribution per Unsplash guidelines
- Dashboard: Auto-scroll prayer schedule to next active prayer
- Fix: setState lifecycle errors on Reading & Murattal screens
- Setup: flutter_dotenv, cached_network_image, url_launcher deps
This commit is contained in:
dwindown
2026-03-13 15:42:17 +07:00
commit faadc1865d
189 changed files with 23834 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
import 'package:flutter/material.dart';
import '../../app/theme/app_colors.dart';
/// Reusable linear progress bar with primary fill.
/// Configurable height, borderRadius, and value (0.01.0).
class AppProgressBar extends StatelessWidget {
const AppProgressBar({
super.key,
required this.value,
this.height = 12.0,
this.borderRadius,
this.backgroundColor,
this.fillColor,
});
/// Progress value from 0.0 to 1.0.
final double value;
/// Height of the bar. Default 12dp.
final double height;
/// Border radius. Defaults to stadium (full).
final BorderRadius? borderRadius;
/// Background track color. Defaults to white/10 (dark) or primary/10 (light).
final Color? backgroundColor;
/// Fill color. Defaults to AppColors.primary.
final Color? fillColor;
@override
Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark;
final trackColor = backgroundColor ??
(isDark
? Colors.white.withValues(alpha: 0.1)
: AppColors.primary.withValues(alpha: 0.1));
final fill = fillColor ?? AppColors.primary;
final radius = borderRadius ?? BorderRadius.circular(height / 2);
return ClipRRect(
borderRadius: radius,
child: SizedBox(
height: height,
child: Stack(
children: [
// Track
Container(
decoration: BoxDecoration(
color: trackColor,
borderRadius: radius,
),
),
// Fill
FractionallySizedBox(
widthFactor: value.clamp(0.0, 1.0),
child: Container(
decoration: BoxDecoration(
color: fill,
borderRadius: radius,
),
),
),
],
),
),
);
}
}