Compare commits
	
		
			8 Commits
		
	
	
		
			4f1a947d2b
			...
			v1.2.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 795060a05b | |||
| bbd3583939 | |||
| 1c1ac3385b | |||
| eeceb706d6 | |||
| 165efcd1a3 | |||
| 509849db5b | |||
| 82f8748177 | |||
| 8fd0511242 | 
							
								
								
									
										57
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										57
									
								
								README.md
									
									
									
									
									
								
							@@ -1,16 +1,53 @@
 | 
			
		||||
# gamer_updater
 | 
			
		||||
# Gamer Updater
 | 
			
		||||
 | 
			
		||||
A new Flutter project.
 | 
			
		||||
A Flutter application for tracking game version updates via RSS feeds.<br>
 | 
			
		||||
The idea is to track updates to games you might have previously played<br>
 | 
			
		||||
To simply know how much you might be missing and whether you would have new content to look forward to
 | 
			
		||||
 | 
			
		||||
## Getting Started
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
This project is a starting point for a Flutter application.
 | 
			
		||||
## Features
 | 
			
		||||
 | 
			
		||||
A few resources to get you started if this is your first Flutter project:
 | 
			
		||||
- Game version tracking
 | 
			
		||||
- Automatic version checking through RSS feeds
 | 
			
		||||
- Last played date tracking
 | 
			
		||||
- Custom version regex patterns
 | 
			
		||||
- Thumbnails!
 | 
			
		||||
 | 
			
		||||
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
 | 
			
		||||
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
 | 
			
		||||
## Setup
 | 
			
		||||
 | 
			
		||||
For help getting started with Flutter development, view the
 | 
			
		||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
 | 
			
		||||
samples, guidance on mobile development, and a full API reference.
 | 
			
		||||
1. Clone repository
 | 
			
		||||
2. Install Flutter
 | 
			
		||||
3. Run `flutter pub get`
 | 
			
		||||
4. Run `flutter run`
 | 
			
		||||
 | 
			
		||||
## Usage
 | 
			
		||||
 | 
			
		||||
Add games by providing:
 | 
			
		||||
- Name
 | 
			
		||||
- RSS feed URL
 | 
			
		||||
- Version regex pattern
 | 
			
		||||
- Optional game image
 | 
			
		||||
 | 
			
		||||
## Example
 | 
			
		||||
 | 
			
		||||
If we wanted to track the versions of Rimworld we would enter the rss feed as:
 | 
			
		||||
https://store.steampowered.com/feeds/news/app/294100/?cc=HR&l=english
 | 
			
		||||
 | 
			
		||||
And the version regex pattern as:
 | 
			
		||||
`Update (\d+\.\d+\.\d+)`
 | 
			
		||||
 | 
			
		||||
Seeing as their posts usually follow this convention:
 | 
			
		||||
Update 1.5.4104 released
 | 
			
		||||
...
 | 
			
		||||
Update 1.4.3704 released
 | 
			
		||||
 | 
			
		||||
In theory the rss link can be any link at all and the version regex can be anything at all<br>
 | 
			
		||||
The regex is simply ran on the entirety of the contents of the get request to the rss link<br>
 | 
			
		||||
Which means we can also simply search html<br>
 | 
			
		||||
But it is not as reliable as the rss because of all the additional shit
 | 
			
		||||
 | 
			
		||||
Currently the thumbnails must be manually added
 | 
			
		||||
 | 
			
		||||
They can also be any image at all but the cards are designed to fit the thumbnails from the game update pages:
 | 
			
		||||

 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								git_static/screenshot.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								git_static/screenshot.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 299 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								git_static/thumbnails.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								git_static/thumbnails.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 662 KiB  | 
@@ -102,7 +102,7 @@ last_updated = excluded.last_updated
 | 
			
		||||
  static Future<Map<String, Game>> getAll() async {
 | 
			
		||||
    final db = DB.db;
 | 
			
		||||
    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
 | 
			
		||||
        .map((e) => Game.fromMap(e))
 | 
			
		||||
 
 | 
			
		||||
@@ -33,8 +33,8 @@ class MyApp extends StatelessWidget {
 | 
			
		||||
        ),
 | 
			
		||||
        textTheme: const TextTheme(
 | 
			
		||||
          titleLarge: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
 | 
			
		||||
          bodyLarge: TextStyle(fontSize: 18),
 | 
			
		||||
          bodyMedium: TextStyle(fontSize: 16),
 | 
			
		||||
          bodyLarge: TextStyle(fontSize: 20),
 | 
			
		||||
          bodyMedium: TextStyle(fontSize: 20),
 | 
			
		||||
          bodySmall: TextStyle(fontSize: 14),
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
@@ -63,6 +63,14 @@ class _MyHomePageState extends State<MyHomePage> {
 | 
			
		||||
 | 
			
		||||
  Future<void> _refreshGames() async {
 | 
			
		||||
    final games = await GameRepository.getAll();
 | 
			
		||||
    games.forEach((key, game) {
 | 
			
		||||
      game.updateActualVersion().then((_) {
 | 
			
		||||
        GameRepository.upsert(game);
 | 
			
		||||
        setState(() {
 | 
			
		||||
          games[game.name] = game;
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    setState(() {
 | 
			
		||||
      this.games = games;
 | 
			
		||||
    });
 | 
			
		||||
@@ -82,18 +90,15 @@ class _MyHomePageState extends State<MyHomePage> {
 | 
			
		||||
        onRefresh: _refreshGames,
 | 
			
		||||
        child: SingleChildScrollView(
 | 
			
		||||
          padding: const EdgeInsets.all(8),
 | 
			
		||||
          child: Column(
 | 
			
		||||
          child: Wrap(
 | 
			
		||||
            spacing: 8,
 | 
			
		||||
            runSpacing: 8,
 | 
			
		||||
            children: [
 | 
			
		||||
              for (var i = 0; i < games.length + 1; i += 2)
 | 
			
		||||
                Padding(
 | 
			
		||||
                  padding: const EdgeInsets.only(bottom: 8),
 | 
			
		||||
                  child: Row(
 | 
			
		||||
                    crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
                    children: [
 | 
			
		||||
                      Expanded(
 | 
			
		||||
                        child: i < games.length 
 | 
			
		||||
                          ? GameCard(
 | 
			
		||||
                              game: games.values.elementAt(i),
 | 
			
		||||
              ...games.values.map(
 | 
			
		||||
                (game) => SizedBox(
 | 
			
		||||
                  width: (MediaQuery.of(context).size.width - 24) / 2,
 | 
			
		||||
                  child: GameCard(
 | 
			
		||||
                    game: game,
 | 
			
		||||
                    onGameUpdated: (game) async {
 | 
			
		||||
                      game = await GameRepository.upsert(game);
 | 
			
		||||
                      setState(() {
 | 
			
		||||
@@ -101,13 +106,17 @@ class _MyHomePageState extends State<MyHomePage> {
 | 
			
		||||
                      });
 | 
			
		||||
                    },
 | 
			
		||||
                    onDelete: () async {
 | 
			
		||||
                                await GameRepository.delete(games.values.elementAt(i));
 | 
			
		||||
                      await GameRepository.delete(game);
 | 
			
		||||
                      setState(() {
 | 
			
		||||
                                  games.remove(games.values.elementAt(i).name);
 | 
			
		||||
                        games.remove(game.name);
 | 
			
		||||
                      });
 | 
			
		||||
                    },
 | 
			
		||||
                            )
 | 
			
		||||
                          : NewGameCard(
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
              SizedBox(
 | 
			
		||||
                width: (MediaQuery.of(context).size.width - 24) / 2,
 | 
			
		||||
                child: NewGameCard(
 | 
			
		||||
                  onGameCreated: (game) async {
 | 
			
		||||
                    game = await GameRepository.upsert(game);
 | 
			
		||||
                    setState(() {
 | 
			
		||||
@@ -116,29 +125,6 @@ class _MyHomePageState extends State<MyHomePage> {
 | 
			
		||||
                  },
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
                      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
 | 
			
		||||
                      ),
 | 
			
		||||
                    ],
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
            ],
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
 
 | 
			
		||||
@@ -267,7 +267,7 @@ class _GameCardState extends State<GameCard>
 | 
			
		||||
                Row(
 | 
			
		||||
                  children: [
 | 
			
		||||
                    SizedBox(
 | 
			
		||||
                      width: 120,
 | 
			
		||||
                      width: 200,
 | 
			
		||||
                      child: Text(
 | 
			
		||||
                        'Version:',
 | 
			
		||||
                        style: Theme.of(context).textTheme.bodyLarge?.copyWith(
 | 
			
		||||
@@ -290,7 +290,7 @@ class _GameCardState extends State<GameCard>
 | 
			
		||||
                Row(
 | 
			
		||||
                  children: [
 | 
			
		||||
                    SizedBox(
 | 
			
		||||
                      width: 120,
 | 
			
		||||
                      width: 200,
 | 
			
		||||
                      child: Text(
 | 
			
		||||
                        'Last Updated:',
 | 
			
		||||
                        style: Theme.of(context).textTheme.bodyLarge?.copyWith(
 | 
			
		||||
@@ -312,7 +312,7 @@ class _GameCardState extends State<GameCard>
 | 
			
		||||
                Row(
 | 
			
		||||
                  children: [
 | 
			
		||||
                    SizedBox(
 | 
			
		||||
                      width: 120,
 | 
			
		||||
                      width: 200,
 | 
			
		||||
                      child: Text(
 | 
			
		||||
                        'Last Played:',
 | 
			
		||||
                        style: Theme.of(context).textTheme.bodyLarge?.copyWith(
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										62
									
								
								release.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								release.sh
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
 | 
			
		||||
# Determine the tag
 | 
			
		||||
echo "Figuring out the tag..."
 | 
			
		||||
TAG=$(git describe --tags --exact-match 2>/dev/null || echo "")
 | 
			
		||||
if [ -z "$TAG" ]; then
 | 
			
		||||
  # Get the latest tag
 | 
			
		||||
  LATEST_TAG=$(git describe --tags $(git rev-list --tags --max-count=1))
 | 
			
		||||
  # Increment the patch version
 | 
			
		||||
  IFS='.' read -r -a VERSION_PARTS <<< "$LATEST_TAG"
 | 
			
		||||
  VERSION_PARTS[2]=$((VERSION_PARTS[2]+1))
 | 
			
		||||
  TAG="${VERSION_PARTS[0]}.${VERSION_PARTS[1]}.${VERSION_PARTS[2]}"
 | 
			
		||||
  # Create a new tag
 | 
			
		||||
  git tag $TAG
 | 
			
		||||
  git push origin $TAG
 | 
			
		||||
fi
 | 
			
		||||
echo "Tag: $TAG"
 | 
			
		||||
 | 
			
		||||
# Build the application
 | 
			
		||||
echo "Building the thing..."
 | 
			
		||||
flutter build windows --release
 | 
			
		||||
flutter build apk --release
 | 
			
		||||
 | 
			
		||||
echo "Creating a release..."
 | 
			
		||||
TOKEN="$GITEA_API_KEY"
 | 
			
		||||
GITEA="https://git.site.quack-lab.dev"
 | 
			
		||||
REPO="dave/flutter-gamer-updater"
 | 
			
		||||
ZIP="gamer-updater-${TAG}.zip"
 | 
			
		||||
APK="gamer-updater-${TAG}.apk"
 | 
			
		||||
# Create a release
 | 
			
		||||
RELEASE_RESPONSE=$(curl -s -X POST \
 | 
			
		||||
  -H "Authorization: token $TOKEN" \
 | 
			
		||||
  -H "Accept: application/json" \
 | 
			
		||||
  -H "Content-Type: application/json" \
 | 
			
		||||
  -d '{
 | 
			
		||||
    "tag_name": "'"$TAG"'",
 | 
			
		||||
    "name": "'"$TAG"'",
 | 
			
		||||
    "draft": false,
 | 
			
		||||
    "prerelease": false
 | 
			
		||||
  }' \
 | 
			
		||||
  $GITEA/api/v1/repos/$REPO/releases)
 | 
			
		||||
 | 
			
		||||
# Extract the release ID
 | 
			
		||||
echo $RELEASE_RESPONSE
 | 
			
		||||
RELEASE_ID=$(echo $RELEASE_RESPONSE | awk -F'"id":' '{print $2+0; exit}')
 | 
			
		||||
echo "Release ID: $RELEASE_ID"
 | 
			
		||||
 | 
			
		||||
echo "Uploading the things..."
 | 
			
		||||
WINRELEASE="./build/windows/x64/runner/Release/"
 | 
			
		||||
7z a $WINRELEASE/$ZIP $WINRELEASE/*
 | 
			
		||||
curl -X POST \
 | 
			
		||||
  -H "Authorization: token $TOKEN" \
 | 
			
		||||
  -F "attachment=@$WINRELEASE/$ZIP" \
 | 
			
		||||
  "$GITEA/api/v1/repos/$REPO/releases/${RELEASE_ID}/assets?name=$ZIP"
 | 
			
		||||
rm $WINRELEASE/$ZIP
 | 
			
		||||
 | 
			
		||||
ANDROIDRELEASE="./build/app/outputs/flutter-apk/"
 | 
			
		||||
mv $ANDROIDRELEASE/app-release.apk $ANDROIDRELEASE/$APK
 | 
			
		||||
curl -X POST \
 | 
			
		||||
  -H "Authorization: token $TOKEN" \
 | 
			
		||||
  -F "attachment=@$ANDROIDRELEASE/$APK" \
 | 
			
		||||
  "$GITEA/api/v1/repos/$REPO/releases/${RELEASE_ID}/assets?name=$APK"
 | 
			
		||||
		Reference in New Issue
	
	Block a user