|
@@ -1,13 +1,13 @@
|
|
|
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/app_layout.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/nav_item.dart';
|
|
|
import 'package:veloe_kemono_party_flutter/models/post.dart';
|
|
|
+import 'package:veloe_kemono_party_flutter/pages/home_screen.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
|
|
@@ -48,194 +48,6 @@ final imageCacheManager = CacheManager(
|
|
|
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(
|