mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-29 21:02:09 +02:00
Add category browsing, a two-column Recently Added grid, and a dedicated download queue. Keep compact navigation icons and their animations aligned with the active tab. Keep profile avatars circular and report lyric enrichment outcomes accurately, preserving existing lyrics and supporting instrumental markers.
104 lines
3.0 KiB
Dart
104 lines
3.0 KiB
Dart
import 'dart:io';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:spotiflac_android/l10n/l10n.dart';
|
|
import 'package:spotiflac_android/providers/user_profile_provider.dart';
|
|
import 'package:spotiflac_android/services/shell_navigation_service.dart';
|
|
|
|
class ProfileAvatar extends StatelessWidget {
|
|
const ProfileAvatar({
|
|
super.key,
|
|
this.name = '',
|
|
this.photoPath,
|
|
this.photo,
|
|
this.size = 40,
|
|
});
|
|
|
|
final String name;
|
|
final String? photoPath;
|
|
final Uint8List? photo;
|
|
final double size;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return LayoutBuilder(
|
|
builder: (context, constraints) {
|
|
// ListTile can constrain height more than width. Keep the clipped
|
|
// image square even when the surrounding slot is rectangular.
|
|
final diameter = constraints.biggest.shortestSide.clamp(0.0, size);
|
|
return Align(
|
|
widthFactor: 1,
|
|
heightFactor: 1,
|
|
child: _buildAvatar(context, diameter),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _buildAvatar(BuildContext context, double diameter) {
|
|
final scheme = Theme.of(context).colorScheme;
|
|
final fallback = ColoredBox(
|
|
color: scheme.primaryContainer,
|
|
child: Center(
|
|
child: name.trim().isEmpty
|
|
? Icon(
|
|
Icons.person,
|
|
size: diameter * 0.55,
|
|
color: scheme.onPrimaryContainer,
|
|
)
|
|
: Text(
|
|
name.trim().characters.first.toUpperCase(),
|
|
style: TextStyle(
|
|
fontSize: diameter * 0.42,
|
|
fontWeight: FontWeight.w600,
|
|
color: scheme.onPrimaryContainer,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
return ExcludeSemantics(
|
|
child: ClipOval(
|
|
child: SizedBox.square(
|
|
dimension: diameter,
|
|
child: photo != null
|
|
? Image.memory(
|
|
photo!,
|
|
fit: BoxFit.cover,
|
|
errorBuilder: (_, _, _) => fallback,
|
|
)
|
|
: photoPath != null
|
|
? Image.file(
|
|
File(photoPath!),
|
|
fit: BoxFit.cover,
|
|
cacheWidth:
|
|
(diameter * MediaQuery.devicePixelRatioOf(context))
|
|
.ceil(),
|
|
errorBuilder: (_, _, _) => fallback,
|
|
)
|
|
: fallback,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class HomeProfileButton extends ConsumerWidget {
|
|
const HomeProfileButton({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final profile = ref.watch(userProfileProvider).value;
|
|
return IconButton(
|
|
tooltip: context.l10n.settingsTitle,
|
|
onPressed: () => ShellNavigationService.requestTab(ShellTab.settings),
|
|
icon: ProfileAvatar(
|
|
name: profile?.name ?? '',
|
|
photoPath: profile?.photoPath,
|
|
size: 34,
|
|
),
|
|
);
|
|
}
|
|
}
|