240 lines
8.3 KiB
Dart
240 lines
8.3 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../../../../core/theme/app_colors.dart';
|
|
import '../../../../core/widgets/riotz_scaffold.dart';
|
|
import '../../../music/presentation/widgets/track_card.dart';
|
|
import '../../domain/models/discover_data_model.dart';
|
|
import '../../domain/models/trending_user_model.dart';
|
|
import '../providers/discover_providers.dart';
|
|
|
|
class DiscoverPage extends ConsumerStatefulWidget {
|
|
const DiscoverPage({super.key});
|
|
|
|
@override
|
|
ConsumerState<DiscoverPage> createState() => _DiscoverPageState();
|
|
}
|
|
|
|
class _DiscoverPageState extends ConsumerState<DiscoverPage> {
|
|
final _searchController = TextEditingController();
|
|
String _query = '';
|
|
|
|
@override
|
|
void dispose() {
|
|
_searchController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
final discoverAsync = ref.watch(discoverDataProvider);
|
|
|
|
return RiotzScaffold(
|
|
appBar: AppBar(
|
|
title: const Text('RIOTZ // DISCOVER'),
|
|
),
|
|
body: RefreshIndicator(
|
|
color: AppColors.neonRed,
|
|
backgroundColor: AppColors.black,
|
|
onRefresh: () async => ref.invalidate(discoverDataProvider),
|
|
child: ListView(
|
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 24),
|
|
children: [
|
|
// Brutalist Search Bar
|
|
TextField(
|
|
controller: _searchController,
|
|
decoration: const InputDecoration(
|
|
prefixIcon: Icon(Icons.search, color: AppColors.white),
|
|
hintText: 'SEARCH THE VOID...',
|
|
),
|
|
onChanged: (value) => setState(() => _query = value.trim()),
|
|
),
|
|
const SizedBox(height: 32),
|
|
|
|
discoverAsync.when(
|
|
loading: () => const Center(
|
|
child: Padding(
|
|
padding: EdgeInsets.all(48),
|
|
child: CircularProgressIndicator(color: AppColors.neonRed),
|
|
),
|
|
),
|
|
error: (error, _) => Center(child: Text('DISCOVERY OFFLINE: $error')),
|
|
data: (data) {
|
|
final filteredUsers = _filterUsers(data);
|
|
final filteredTracks = data.trendingTracks.where((track) {
|
|
final q = _query.toLowerCase();
|
|
return track.title.toLowerCase().contains(q) ||
|
|
track.username.toLowerCase().contains(q) ||
|
|
track.genreTag.toLowerCase().contains(q);
|
|
}).toList();
|
|
final filteredPosts = data.trendingPosts.where((post) {
|
|
final q = _query.toLowerCase();
|
|
return post.caption.toLowerCase().contains(q) ||
|
|
post.username.toLowerCase().contains(q);
|
|
}).toList();
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Trending Artists
|
|
if (filteredUsers.isNotEmpty) ...[
|
|
_SectionHeader(title: 'TRENDING AGENTS', count: filteredUsers.length),
|
|
const SizedBox(height: 16),
|
|
SizedBox(
|
|
height: 110,
|
|
child: ListView.builder(
|
|
scrollDirection: Axis.horizontal,
|
|
itemCount: filteredUsers.length,
|
|
itemBuilder: (context, index) {
|
|
final user = filteredUsers[index];
|
|
return _TrendingUserCard(user: user);
|
|
},
|
|
),
|
|
),
|
|
const SizedBox(height: 40),
|
|
],
|
|
|
|
// Trending Tracks
|
|
if (filteredTracks.isNotEmpty) ...[
|
|
_SectionHeader(title: 'SONIC FREQUENCIES', count: filteredTracks.length),
|
|
const SizedBox(height: 16),
|
|
...filteredTracks.take(5).map((track) => TrackCard(track: track)),
|
|
const SizedBox(height: 40),
|
|
],
|
|
|
|
// Popular Posts
|
|
if (filteredPosts.isNotEmpty) ...[
|
|
_SectionHeader(title: 'VISUAL CHAOS', count: filteredPosts.length),
|
|
const SizedBox(height: 16),
|
|
GridView.builder(
|
|
shrinkWrap: true,
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
|
crossAxisCount: 2,
|
|
crossAxisSpacing: 12,
|
|
mainAxisSpacing: 12,
|
|
childAspectRatio: 0.8,
|
|
),
|
|
itemCount: filteredPosts.length,
|
|
itemBuilder: (context, index) {
|
|
final post = filteredPosts[index];
|
|
return _DiscoveryGridTile(post: post);
|
|
},
|
|
),
|
|
],
|
|
|
|
if (filteredUsers.isEmpty && filteredTracks.isEmpty && filteredPosts.isEmpty)
|
|
const Center(
|
|
child: Padding(
|
|
padding: EdgeInsets.all(48),
|
|
child: Text('THE VOID IS EMPTY.'),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
List<TrendingUserModel> _filterUsers(DiscoverDataModel data) {
|
|
final q = _query.toLowerCase();
|
|
if (q.isEmpty) return data.trendingUsers;
|
|
return data.trendingUsers
|
|
.where((user) => user.username.toLowerCase().contains(q))
|
|
.toList();
|
|
}
|
|
}
|
|
|
|
class _SectionHeader extends StatelessWidget {
|
|
const _SectionHeader({required this.title, required this.count});
|
|
final String title;
|
|
final int count;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
return Row(
|
|
children: [
|
|
Text(title, style: theme.textTheme.labelLarge?.copyWith(letterSpacing: 2)),
|
|
const Spacer(),
|
|
Text('[$count]', style: theme.textTheme.labelLarge?.copyWith(color: AppColors.neonRed)),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class _TrendingUserCard extends StatelessWidget {
|
|
const _TrendingUserCard({required this.user});
|
|
final TrendingUserModel user;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
width: 90,
|
|
margin: const EdgeInsets.only(right: 12),
|
|
child: Column(
|
|
children: [
|
|
Container(
|
|
width: 70,
|
|
height: 70,
|
|
decoration: BoxDecoration(
|
|
border: Border.all(color: AppColors.white, width: 1.5),
|
|
color: AppColors.surfaceLight,
|
|
),
|
|
child: user.avatarUrl.isNotEmpty
|
|
? Image.network(user.avatarUrl, fit: BoxFit.cover)
|
|
: const Icon(Icons.person, color: AppColors.grey),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
user.username.toUpperCase(),
|
|
style: const TextStyle(fontSize: 10, fontWeight: FontWeight.bold),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _DiscoveryGridTile extends StatelessWidget {
|
|
const _DiscoveryGridTile({required this.post});
|
|
final dynamic post;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
decoration: BoxDecoration(
|
|
border: Border.all(color: AppColors.border),
|
|
color: AppColors.surface,
|
|
),
|
|
child: Stack(
|
|
fit: StackFit.expand,
|
|
children: [
|
|
if (post.imageUrl.isNotEmpty)
|
|
Image.network(post.imageUrl, fit: BoxFit.cover),
|
|
Positioned(
|
|
bottom: 0,
|
|
left: 0,
|
|
right: 0,
|
|
child: Container(
|
|
padding: const EdgeInsets.all(8),
|
|
color: AppColors.black.withOpacity(0.7),
|
|
child: Text(
|
|
post.username.toUpperCase(),
|
|
style: const TextStyle(fontSize: 10, fontWeight: FontWeight.bold),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|