import 'package:flutter/material.dart'; import 'package:gamer_updater/db.dart'; import 'package:gamer_updater/game.dart'; import 'package:gamer_updater/widgets/new_game_card.dart'; import 'package:gamer_updater/widgets/game_card.dart'; import 'dart:async'; void main() async { await DB.init(); runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Gamer Updater', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), darkTheme: ThemeData( brightness: Brightness.dark, useMaterial3: true, scaffoldBackgroundColor: Colors.black, colorSchemeSeed: Colors.deepPurple, cardTheme: CardTheme( color: Colors.grey[900], elevation: 4, margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), ), textTheme: const TextTheme( titleLarge: TextStyle(fontSize: 30, fontWeight: FontWeight.bold), bodyLarge: TextStyle(fontSize: 20), bodyMedium: TextStyle(fontSize: 20), bodySmall: TextStyle(fontSize: 14), ), ), themeMode: ThemeMode.system, home: const MyHomePage(title: 'Gamer Updater'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State createState() => _MyHomePageState(); } class _MyHomePageState extends State { late Map games = {}; String searchQuery = ""; final TextEditingController _searchController = TextEditingController(); Timer? _debounce; @override void initState() { super.initState(); _refreshGames(); } @override void dispose() { _searchController.dispose(); _debounce?.cancel(); super.dispose(); } Future _refreshGames() async { final games = await GameRepository.getAll(); games.forEach((key, game) { game.updateActualVersion().then((_) { GameRepository.upsert(game); setState(() { this.games[game.name] = game; }); }); }); setState(() { this.games = games; }); } List get filteredGames { if (searchQuery.isEmpty) { return games.values.toList(); } return games.values.where((game) => game.name.toLowerCase().contains(searchQuery.toLowerCase()) ).toList(); } void _onSearchChanged(String value) { if (_debounce?.isActive ?? false) _debounce!.cancel(); _debounce = Timer(const Duration(milliseconds: 100), () { setState(() { searchQuery = value; }); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: Text(widget.title), actions: [ IconButton(icon: const Icon(Icons.refresh), onPressed: _refreshGames), ], ), body: Column( children: [ Padding( padding: const EdgeInsets.all(8.0), child: TextField( controller: _searchController, decoration: InputDecoration( hintText: 'Search games...', prefixIcon: const Icon(Icons.search), suffixIcon: searchQuery.isNotEmpty ? IconButton( icon: const Icon(Icons.clear), onPressed: () { _searchController.clear(); setState(() { searchQuery = ""; }); }, ) : null, border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), ), ), onChanged: _onSearchChanged, ), ), Expanded( child: RefreshIndicator( onRefresh: _refreshGames, child: SingleChildScrollView( padding: const EdgeInsets.all(8), child: Wrap( spacing: 8, runSpacing: 8, children: [ ...filteredGames.map( (game) => SizedBox( key: ValueKey(game.name), width: (MediaQuery.of(context).size.width - 24) / 2, child: GameCard( key: ValueKey('card_${game.name}'), game: game, onGameUpdated: (game) async { game = await GameRepository.upsert(game); setState(() { games[game.name] = game; }); }, onDelete: () async { await GameRepository.delete(game); setState(() { games.remove(game.name); }); }, ), ), ), SizedBox( width: (MediaQuery.of(context).size.width - 24) / 2, child: NewGameCard( onGameCreated: (game) async { game = await GameRepository.upsert(game); setState(() { games[game.name] = game; }); }, ), ), ], ), ), ), ), ], ), ); } }