Compare commits
	
		
			5 Commits
		
	
	
		
			1c1ac3385b
			...
			v1.3.1
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ad04a6317b | |||
| ba8b669399 | |||
| 336cb87a06 | |||
| 795060a05b | |||
| bbd3583939 | 
@@ -55,10 +55,17 @@ class Game {
 | 
				
			|||||||
      throw Exception('No version found for $name');
 | 
					      throw Exception('No version found for $name');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    actualVersion = version;
 | 
					    actualVersion = version;
 | 
				
			||||||
    lastUpdated =
 | 
					    try {
 | 
				
			||||||
        parseRfc822Date(
 | 
					      // Some sites use weird ass dogshit fucking formats
 | 
				
			||||||
          response.headers['last-modified'] ?? '',
 | 
					      // We cannot really reliably parse every single one of them
 | 
				
			||||||
        ).toIso8601String();
 | 
					      // So - fuck it
 | 
				
			||||||
 | 
					      lastUpdated =
 | 
				
			||||||
 | 
					          parseRfc822Date(
 | 
				
			||||||
 | 
					            response.headers['last-modified'] ?? '',
 | 
				
			||||||
 | 
					          ).toIso8601String();
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      lastUpdated = DateTime.now().toIso8601String();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -102,7 +109,7 @@ last_updated = excluded.last_updated
 | 
				
			|||||||
  static Future<Map<String, Game>> getAll() async {
 | 
					  static Future<Map<String, Game>> getAll() async {
 | 
				
			||||||
    final db = DB.db;
 | 
					    final db = DB.db;
 | 
				
			||||||
    final games = await db.rawQuery(
 | 
					    final games = await db.rawQuery(
 | 
				
			||||||
      'SELECT name, actual_version, last_played, rss_feed_url, version_regex, last_updated, image_data FROM games',
 | 
					      'SELECT name, actual_version, last_played, rss_feed_url, version_regex, last_updated, image_data FROM games ORDER BY name',
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    return games
 | 
					    return games
 | 
				
			||||||
        .map((e) => Game.fromMap(e))
 | 
					        .map((e) => Game.fromMap(e))
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										176
									
								
								lib/main.dart
									
									
									
									
									
								
							
							
						
						
									
										176
									
								
								lib/main.dart
									
									
									
									
									
								
							@@ -3,6 +3,7 @@ import 'package:gamer_updater/db.dart';
 | 
				
			|||||||
import 'package:gamer_updater/game.dart';
 | 
					import 'package:gamer_updater/game.dart';
 | 
				
			||||||
import 'package:gamer_updater/widgets/new_game_card.dart';
 | 
					import 'package:gamer_updater/widgets/new_game_card.dart';
 | 
				
			||||||
import 'package:gamer_updater/widgets/game_card.dart';
 | 
					import 'package:gamer_updater/widgets/game_card.dart';
 | 
				
			||||||
 | 
					import 'dart:async';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void main() async {
 | 
					void main() async {
 | 
				
			||||||
  await DB.init();
 | 
					  await DB.init();
 | 
				
			||||||
@@ -54,6 +55,9 @@ class MyHomePage extends StatefulWidget {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
class _MyHomePageState extends State<MyHomePage> {
 | 
					class _MyHomePageState extends State<MyHomePage> {
 | 
				
			||||||
  late Map<String, Game> games = {};
 | 
					  late Map<String, Game> games = {};
 | 
				
			||||||
 | 
					  String searchQuery = "";
 | 
				
			||||||
 | 
					  final TextEditingController _searchController = TextEditingController();
 | 
				
			||||||
 | 
					  Timer? _debounce;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  void initState() {
 | 
					  void initState() {
 | 
				
			||||||
@@ -61,13 +65,20 @@ class _MyHomePageState extends State<MyHomePage> {
 | 
				
			|||||||
    _refreshGames();
 | 
					    _refreshGames();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  void dispose() {
 | 
				
			||||||
 | 
					    _searchController.dispose();
 | 
				
			||||||
 | 
					    _debounce?.cancel();
 | 
				
			||||||
 | 
					    super.dispose();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<void> _refreshGames() async {
 | 
					  Future<void> _refreshGames() async {
 | 
				
			||||||
    final games = await GameRepository.getAll();
 | 
					    final games = await GameRepository.getAll();
 | 
				
			||||||
    games.forEach((key, game) {
 | 
					    games.forEach((key, game) {
 | 
				
			||||||
      game.updateActualVersion().then((_) {
 | 
					      game.updateActualVersion().then((_) {
 | 
				
			||||||
        GameRepository.upsert(game);
 | 
					        GameRepository.upsert(game);
 | 
				
			||||||
        setState(() {
 | 
					        setState(() {
 | 
				
			||||||
          games[game.name] = game;
 | 
					          this.games[game.name] = game;
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
@@ -76,6 +87,24 @@ class _MyHomePageState extends State<MyHomePage> {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  List<Game> 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
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
    return Scaffold(
 | 
					    return Scaffold(
 | 
				
			||||||
@@ -86,80 +115,81 @@ class _MyHomePageState extends State<MyHomePage> {
 | 
				
			|||||||
          IconButton(icon: const Icon(Icons.refresh), onPressed: _refreshGames),
 | 
					          IconButton(icon: const Icon(Icons.refresh), onPressed: _refreshGames),
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
      body: RefreshIndicator(
 | 
					      body: Column(
 | 
				
			||||||
        onRefresh: _refreshGames,
 | 
					        children: [
 | 
				
			||||||
        child: SingleChildScrollView(
 | 
					          Padding(
 | 
				
			||||||
          padding: const EdgeInsets.all(8),
 | 
					            padding: const EdgeInsets.all(8.0),
 | 
				
			||||||
          child: Column(
 | 
					            child: TextField(
 | 
				
			||||||
            children: [
 | 
					              controller: _searchController,
 | 
				
			||||||
              for (var i = 0; i < games.length + 1; i += 2)
 | 
					              decoration: InputDecoration(
 | 
				
			||||||
                Padding(
 | 
					                hintText: 'Search games...',
 | 
				
			||||||
                  padding: const EdgeInsets.only(bottom: 8),
 | 
					                prefixIcon: const Icon(Icons.search),
 | 
				
			||||||
                  child: Row(
 | 
					                suffixIcon: searchQuery.isNotEmpty 
 | 
				
			||||||
                    crossAxisAlignment: CrossAxisAlignment.start,
 | 
					                  ? IconButton(
 | 
				
			||||||
                    children: [
 | 
					                      icon: const Icon(Icons.clear),
 | 
				
			||||||
                      Expanded(
 | 
					                      onPressed: () {
 | 
				
			||||||
                        child:
 | 
					                        _searchController.clear();
 | 
				
			||||||
                            i < games.length
 | 
					                        setState(() {
 | 
				
			||||||
                                ? GameCard(
 | 
					                          searchQuery = "";
 | 
				
			||||||
                                  game: games.values.elementAt(i),
 | 
					                        });
 | 
				
			||||||
                                  onGameUpdated: (game) async {
 | 
					                      },
 | 
				
			||||||
                                    game = await GameRepository.upsert(game);
 | 
					                    )
 | 
				
			||||||
                                    setState(() {
 | 
					                  : null,
 | 
				
			||||||
                                      games[game.name] = game;
 | 
					                border: OutlineInputBorder(
 | 
				
			||||||
                                    });
 | 
					                  borderRadius: BorderRadius.circular(10),
 | 
				
			||||||
                                  },
 | 
					 | 
				
			||||||
                                  onDelete: () async {
 | 
					 | 
				
			||||||
                                    await GameRepository.delete(
 | 
					 | 
				
			||||||
                                      games.values.elementAt(i),
 | 
					 | 
				
			||||||
                                    );
 | 
					 | 
				
			||||||
                                    setState(() {
 | 
					 | 
				
			||||||
                                      games.remove(
 | 
					 | 
				
			||||||
                                        games.values.elementAt(i).name,
 | 
					 | 
				
			||||||
                                      );
 | 
					 | 
				
			||||||
                                    });
 | 
					 | 
				
			||||||
                                  },
 | 
					 | 
				
			||||||
                                )
 | 
					 | 
				
			||||||
                                : NewGameCard(
 | 
					 | 
				
			||||||
                                  onGameCreated: (game) async {
 | 
					 | 
				
			||||||
                                    game = await GameRepository.upsert(game);
 | 
					 | 
				
			||||||
                                    setState(() {
 | 
					 | 
				
			||||||
                                      games[game.name] = game;
 | 
					 | 
				
			||||||
                                    });
 | 
					 | 
				
			||||||
                                  },
 | 
					 | 
				
			||||||
                                ),
 | 
					 | 
				
			||||||
                      ),
 | 
					 | 
				
			||||||
                      const SizedBox(width: 8),
 | 
					 | 
				
			||||||
                      Expanded(
 | 
					 | 
				
			||||||
                        child:
 | 
					 | 
				
			||||||
                            i + 1 < games.length
 | 
					 | 
				
			||||||
                                ? GameCard(
 | 
					 | 
				
			||||||
                                  game: games.values.elementAt(i + 1),
 | 
					 | 
				
			||||||
                                  onGameUpdated: (game) async {
 | 
					 | 
				
			||||||
                                    game = await GameRepository.upsert(game);
 | 
					 | 
				
			||||||
                                    setState(() {
 | 
					 | 
				
			||||||
                                      games[game.name] = game;
 | 
					 | 
				
			||||||
                                    });
 | 
					 | 
				
			||||||
                                  },
 | 
					 | 
				
			||||||
                                  onDelete: () async {
 | 
					 | 
				
			||||||
                                    await GameRepository.delete(
 | 
					 | 
				
			||||||
                                      games.values.elementAt(i + 1),
 | 
					 | 
				
			||||||
                                    );
 | 
					 | 
				
			||||||
                                    setState(() {
 | 
					 | 
				
			||||||
                                      games.remove(
 | 
					 | 
				
			||||||
                                        games.values.elementAt(i + 1).name,
 | 
					 | 
				
			||||||
                                      );
 | 
					 | 
				
			||||||
                                    });
 | 
					 | 
				
			||||||
                                  },
 | 
					 | 
				
			||||||
                                )
 | 
					 | 
				
			||||||
                                : const SizedBox(), // Empty space for odd number of items
 | 
					 | 
				
			||||||
                      ),
 | 
					 | 
				
			||||||
                    ],
 | 
					 | 
				
			||||||
                  ),
 | 
					 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
            ],
 | 
					              ),
 | 
				
			||||||
 | 
					              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;
 | 
				
			||||||
 | 
					                          });
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                  ],
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
      ),
 | 
					      ),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -64,10 +64,10 @@ class _GameCardState extends State<GameCard>
 | 
				
			|||||||
        if (name.isNotEmpty) {
 | 
					        if (name.isNotEmpty) {
 | 
				
			||||||
          widget.onGameUpdated(
 | 
					          widget.onGameUpdated(
 | 
				
			||||||
            Game(
 | 
					            Game(
 | 
				
			||||||
              name: name,
 | 
					              name: name.trim(),
 | 
				
			||||||
              versionRegex: _versionRegexController.text,
 | 
					              versionRegex: _versionRegexController.text,
 | 
				
			||||||
              lastPlayed: _lastPlayedController.text,
 | 
					              lastPlayed: _lastPlayedController.text.trim(),
 | 
				
			||||||
              rssFeedUrl: _rssFeedUrlController.text,
 | 
					              rssFeedUrl: _rssFeedUrlController.text.trim(),
 | 
				
			||||||
              actualVersion: widget.game.actualVersion,
 | 
					              actualVersion: widget.game.actualVersion,
 | 
				
			||||||
              lastUpdated: widget.game.lastUpdated,
 | 
					              lastUpdated: widget.game.lastUpdated,
 | 
				
			||||||
              imageData: widget.game.imageData,
 | 
					              imageData: widget.game.imageData,
 | 
				
			||||||
@@ -156,10 +156,10 @@ class _GameCardState extends State<GameCard>
 | 
				
			|||||||
      final imageBytes = await image.readAsBytes();
 | 
					      final imageBytes = await image.readAsBytes();
 | 
				
			||||||
      final updatedGame = await GameRepository.updateImage(
 | 
					      final updatedGame = await GameRepository.updateImage(
 | 
				
			||||||
        Game(
 | 
					        Game(
 | 
				
			||||||
          name: widget.game.name,
 | 
					          name: widget.game.name.trim(),
 | 
				
			||||||
          versionRegex: _versionRegexController.text,
 | 
					          versionRegex: _versionRegexController.text,
 | 
				
			||||||
          lastPlayed: _lastPlayedController.text,
 | 
					          lastPlayed: _lastPlayedController.text.trim(),
 | 
				
			||||||
          rssFeedUrl: _rssFeedUrlController.text,
 | 
					          rssFeedUrl: _rssFeedUrlController.text.trim(),
 | 
				
			||||||
          actualVersion: widget.game.actualVersion,
 | 
					          actualVersion: widget.game.actualVersion,
 | 
				
			||||||
          lastUpdated: widget.game.lastUpdated,
 | 
					          lastUpdated: widget.game.lastUpdated,
 | 
				
			||||||
          imageData: imageBytes,
 | 
					          imageData: imageBytes,
 | 
				
			||||||
@@ -183,7 +183,10 @@ class _GameCardState extends State<GameCard>
 | 
				
			|||||||
                borderRadius: BorderRadius.circular(12),
 | 
					                borderRadius: BorderRadius.circular(12),
 | 
				
			||||||
                child: Opacity(
 | 
					                child: Opacity(
 | 
				
			||||||
                  opacity: 0.4,
 | 
					                  opacity: 0.4,
 | 
				
			||||||
                  child: Image.memory(widget.game.imageData!, fit: BoxFit.cover),
 | 
					                  child: Image.memory(
 | 
				
			||||||
 | 
					                    widget.game.imageData!,
 | 
				
			||||||
 | 
					                    fit: BoxFit.cover,
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
@@ -205,7 +208,10 @@ class _GameCardState extends State<GameCard>
 | 
				
			|||||||
              ),
 | 
					              ),
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
          Padding(
 | 
					          Padding(
 | 
				
			||||||
            padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0),
 | 
					            padding: const EdgeInsets.symmetric(
 | 
				
			||||||
 | 
					              horizontal: 16.0,
 | 
				
			||||||
 | 
					              vertical: 24.0,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
            child: Column(
 | 
					            child: Column(
 | 
				
			||||||
              crossAxisAlignment: CrossAxisAlignment.start,
 | 
					              crossAxisAlignment: CrossAxisAlignment.start,
 | 
				
			||||||
              children: [
 | 
					              children: [
 | 
				
			||||||
@@ -215,17 +221,29 @@ class _GameCardState extends State<GameCard>
 | 
				
			|||||||
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
 | 
					                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
 | 
				
			||||||
                    children: [
 | 
					                    children: [
 | 
				
			||||||
                      Expanded(
 | 
					                      Expanded(
 | 
				
			||||||
                        child: TextField(
 | 
					                        child:
 | 
				
			||||||
                          controller: _nameController,
 | 
					                            widget.isNameEditable
 | 
				
			||||||
                          focusNode: _nameFocus,
 | 
					                                ? TextField(
 | 
				
			||||||
                          style: Theme.of(context).textTheme.titleLarge
 | 
					                                  controller: _nameController,
 | 
				
			||||||
                              ?.copyWith(color: hasImage ? Colors.white : null),
 | 
					                                  focusNode: _nameFocus,
 | 
				
			||||||
                          enabled: widget.isNameEditable,
 | 
					                                  style: Theme.of(
 | 
				
			||||||
                          decoration: const InputDecoration.collapsed(
 | 
					                                    context,
 | 
				
			||||||
                            hintText: 'New Game',
 | 
					                                  ).textTheme.titleLarge?.copyWith(
 | 
				
			||||||
                          ),
 | 
					                                    color: hasImage ? Colors.white : null,
 | 
				
			||||||
                          onSubmitted: (_) => _nameFocus.unfocus(),
 | 
					                                  ),
 | 
				
			||||||
                        ),
 | 
					                                  decoration: const InputDecoration.collapsed(
 | 
				
			||||||
 | 
					                                    hintText: 'New Game',
 | 
				
			||||||
 | 
					                                  ),
 | 
				
			||||||
 | 
					                                  onSubmitted: (_) => _nameFocus.unfocus(),
 | 
				
			||||||
 | 
					                                )
 | 
				
			||||||
 | 
					                                : SelectableText(
 | 
				
			||||||
 | 
					                                  widget.game.name,
 | 
				
			||||||
 | 
					                                  style: Theme.of(
 | 
				
			||||||
 | 
					                                    context,
 | 
				
			||||||
 | 
					                                  ).textTheme.titleLarge?.copyWith(
 | 
				
			||||||
 | 
					                                    color: hasImage ? Colors.white : null,
 | 
				
			||||||
 | 
					                                  ),
 | 
				
			||||||
 | 
					                                ),
 | 
				
			||||||
                      ),
 | 
					                      ),
 | 
				
			||||||
                      Row(
 | 
					                      Row(
 | 
				
			||||||
                        mainAxisSize: MainAxisSize.min,
 | 
					                        mainAxisSize: MainAxisSize.min,
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user