123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 |
- import 'package:flutter/material.dart';
- import 'package:flutter_riverpod/flutter_riverpod.dart';
- import 'package:flutter_cache_manager/flutter_cache_manager.dart';
- import 'package:veloe_kemono_party_flutter/kemono_client.dart';
- import 'package:veloe_kemono_party_flutter/models/creator.dart';
- import 'package:veloe_kemono_party_flutter/models/post.dart';
- import 'package:veloe_kemono_party_flutter/pages/posts_screen.dart';
- import 'package:veloe_kemono_party_flutter/pages/widgets/creator_card.dart';
- import 'package:veloe_kemono_party_flutter/pages/widgets/post_container.dart';
- import 'package:veloe_kemono_party_flutter/posts_notifier.dart';
- // Providers
- final searchQueryProvider = StateProvider<String>((ref) => '');
- final selectedCreatorProvider = StateProvider<Creator?>((ref) => null);
- final postsPageProvider = StateProvider<int>((ref) => 0);
- final kemonoClientProvider = Provider<KemonoClient>((ref) => KemonoClient());
- final creatorsProvider = FutureProvider<List<Creator>>((ref) async {
- final query = ref.watch(searchQueryProvider);
- final client = ref.read(kemonoClientProvider);
- final creators = await client.getCreators();
-
- return creators.where((creator) =>
- creator.name.toLowerCase().contains(query.toLowerCase()) ||
- creator.service.toLowerCase().contains(query.toLowerCase())
- ).toList();
- });
- final postsProvider = StateNotifierProvider.autoDispose
- .family<PostsNotifier, AsyncValue<List<Post>>, (String, String)>((ref, args) {
- final (creatorId, service) = args;
- return PostsNotifier(
- creatorId: creatorId,
- service: service,
- client: ref.read(kemonoClientProvider),
- );
- });
- // Image Caching
- final imageCacheManager = CacheManager(
- Config(
- 'kemono_images',
- stalePeriod: const Duration(days: 30),
- maxNrOfCacheObjects: 1024,
- fileService: HttpFileService(),
- ),
- );
- // Navigation Items Model
- class NavItem {
- final String label;
- final IconData icon;
- final Widget page;
- const NavItem({
- required this.label,
- required this.icon,
- required this.page,
- });
- }
- // App Layout with Navigation
- class AppLayout extends ConsumerStatefulWidget {
- final List<NavItem> navItems;
-
- const AppLayout({super.key, required this.navItems});
- @override
- ConsumerState<AppLayout> createState() => _AppLayoutState();
- }
- class _AppLayoutState extends ConsumerState<AppLayout> {
- final _navIndex = StateProvider<int>((ref) => 0);
- Widget _buildDesktopNavRail(int selectedIndex) {
- return NavigationRail(
- selectedIndex: selectedIndex,
- onDestinationSelected: (index) =>
- ref.read(_navIndex.notifier).state = index,
- labelType: NavigationRailLabelType.all,
- destinations: widget.navItems.map((item) =>
- NavigationRailDestination(
- icon: Icon(item.icon),
- label: Text(item.label),
- )).toList(),
- );
- }
- Widget _buildMobileDrawer(int selectedIndex) {
- return Drawer(
- child: ListView(
- children: [
- const DrawerHeader(
- decoration: BoxDecoration(color: Colors.blue),
- child: Text('Navigation', style: TextStyle(color: Colors.white)),
- ),
- ...widget.navItems.asMap().entries.map((entry) => ListTile(
- leading: Icon(entry.value.icon),
- title: Text(entry.value.label),
- selected: entry.key == selectedIndex,
- onTap: () {
- ref.read(_navIndex.notifier).state = entry.key;
- Navigator.pop(context);
- },
- )),
- ],
- ),
- );
- }
- @override
- Widget build(BuildContext context) {
- final selectedIndex = ref.watch(_navIndex);
- final isDesktop = MediaQuery.of(context).size.width >= 600;
- return Scaffold(
- appBar: AppBar(
- title: Text(widget.navItems[selectedIndex].label),
- leading: isDesktop ? null : Builder(
- builder: (context) => IconButton(
- icon: const Icon(Icons.menu),
- onPressed: () => Scaffold.of(context).openDrawer(),
- ),
- ),
- ),
- drawer: isDesktop ? null : _buildMobileDrawer(selectedIndex),
- body: Row(
- children: [
- if (isDesktop) _buildDesktopNavRail(selectedIndex),
- Expanded(child: widget.navItems[selectedIndex].page),
- ],
- ),
- );
- }
- }
- class HomeScreen extends ConsumerStatefulWidget {
- const HomeScreen({super.key});
- @override
- ConsumerState<HomeScreen> createState() => _HomeScreenState();
- }
- class _HomeScreenState extends ConsumerState<HomeScreen> {
- final TextEditingController _searchController = TextEditingController();
- final _searchQuery = StateProvider<String>((ref) => ''); // Added search state
- // Modified provider with search filtering
- final filteredCreatorsProvider = FutureProvider.autoDispose.family<List<Creator>, String>(
- (ref, query) async {
- final originalList = await ref.watch(creatorsProvider.future);
- return originalList.where(
- (creator) => creator.name.toLowerCase().contains(query.toLowerCase())
- ).toList();
- },
- );
- @override
- Widget build(BuildContext context) {
- return DefaultTabController(
- length: 2,
- child: Scaffold(
- appBar: AppBar(
- title: _buildSearchField(), // Replaced title with search field
- actions: [
- IconButton(
- icon: const Icon(Icons.refresh),
- onPressed: () => ref.invalidate(filteredCreatorsProvider),
- ),
- ],
- ),
- body: _buildCreatorsList(ref.watch(
- filteredCreatorsProvider(ref.watch(_searchQuery)) // Added query param
- )),
- ),
- );
- }
- // New search field widget
- Widget _buildSearchField() {
- return TextField(
- controller: _searchController,
- decoration: InputDecoration(
- hintText: 'Search creators...',
- border: InputBorder.none,
- suffixIcon: IconButton(
- icon: const Icon(Icons.clear),
- onPressed: () {
- _searchController.clear();
- ref.read(_searchQuery.notifier).state = '';
- },
- ),
- ),
- onChanged: (value) => ref.read(_searchQuery.notifier).state = value,
- );
- }
- // Updated creators list with search
- Widget _buildCreatorsList(AsyncValue<List<Creator>> creatorsAsync) {
- return RefreshIndicator(
- onRefresh: () => ref.refresh(filteredCreatorsProvider(ref.read(_searchQuery)).future),
- child: creatorsAsync.when(
- loading: () => const Center(child: CircularProgressIndicator()),
- error: (error, _) => Center(child: Text(error.toString())),
- data: (creators) => creators.isEmpty
- ? const Center(child: Text('No matching creators found'))
- : ListView.separated(
- padding: const EdgeInsets.all(16),
- itemCount: creators.length,
- separatorBuilder: (_, __) => const SizedBox(height: 8),
- itemBuilder: (context, index) => CreatorCard(creator: creators[index]),
- ),
- ),
- );
- }
- Widget _buildGlobalPostsGrid() {
- final postsAsync = ref.watch(postsProvider(('82522', 'patreon')));
- return postsAsync.when(
- loading: () => const Center(child: CircularProgressIndicator()),
- error: (error, _) => Center(child: Text(error.toString())),
- data: (posts) => CustomScrollView(
- slivers: [
- SliverAppBar(),
- SliverList(
- delegate: SliverChildBuilderDelegate(
- (context, index) => PostContainer(post: posts[index]),
- childCount: posts.length,
- ),
- ),
- ],
- ),
- );
- }
- }
- // Main Application
- void main() => runApp(
- const ProviderScope(
- child: MaterialApp(
- home: AppLayout(
- navItems: [
- NavItem(
- label: 'Home',
- icon: Icons.home,
- page: HomeScreen(),
- ),
- NavItem(
- label: 'Global Feed (Rukis)',
- icon: Icons.public,
- page: PostsScreen( creatorId:'82522', service: 'patreon', withAppBar: false,),
- ),
- NavItem(
- label: 'Settings',
- icon: Icons.settings,
- page: Placeholder(), // Replace with actual SettingsScreen
- ),
- ],
- ),
- ),
- ),
- );
|