Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • master
  • 32-improve-app-metadata
  • Release_0.0.10_10
  • Release_0.0.11_11
  • Release_0.0.12_12
  • Release_0.0.13_13
  • Release_0.0.14_14
  • Release_0.0.15_15
  • Release_0.0.16_16
  • Release_0.0.17_17
  • Release_0.0.18_18
  • Release_0.0.19_19
  • Release_0.0.20_20
  • Release_0.0.21_21
  • Release_0.0.22_22
  • Release_0.0.23_23
  • Release_0.0.24_24
  • Release_0.0.25_25
  • Release_0.0.26_26
  • Release_0.0.2_2
  • Release_0.0.3_3
  • Release_0.0.4_4
  • Release_0.0.5_5
  • Release_0.0.6_6
  • Release_0.0.7_7
  • Release_0.0.8_8
  • Release_0.0.9_9
  • Release_0.1.0_27
  • Release_0.1.1_28
  • Release_0.1.2_29
  • Release_0.2.0_30
  • Release_0.2.1_31
  • Release_0.3.0_32
  • Release_0.3.1_33
  • Release_0.4.0_34
  • Release_0.4.1_35
  • Release_0.4.2_36
  • Release_0.5.0_37
  • Release_0.6.0_38
  • Release_0.7.0_39
  • Release_0.8.0_40
  • Release_0.8.1_41
  • Release_0.8.2_42
  • Release_0.9.0_43
  • Release_0.9.1_44
  • Release_0.9.2_45
46 results

Target

Select target project
  • android / org.benoitharrault.sortgame
1 result
Select Git revision
  • master
  • 32-improve-app-metadata
  • Release_0.0.10_10
  • Release_0.0.11_11
  • Release_0.0.12_12
  • Release_0.0.13_13
  • Release_0.0.14_14
  • Release_0.0.15_15
  • Release_0.0.16_16
  • Release_0.0.17_17
  • Release_0.0.18_18
  • Release_0.0.19_19
  • Release_0.0.20_20
  • Release_0.0.21_21
  • Release_0.0.22_22
  • Release_0.0.23_23
  • Release_0.0.24_24
  • Release_0.0.25_25
  • Release_0.0.26_26
  • Release_0.0.2_2
  • Release_0.0.3_3
  • Release_0.0.4_4
  • Release_0.0.5_5
  • Release_0.0.6_6
  • Release_0.0.7_7
  • Release_0.0.8_8
  • Release_0.0.9_9
  • Release_0.1.0_27
  • Release_0.1.1_28
  • Release_0.1.2_29
  • Release_0.2.0_30
  • Release_0.2.1_31
  • Release_0.3.0_32
  • Release_0.3.1_33
  • Release_0.4.0_34
  • Release_0.4.1_35
  • Release_0.4.2_36
  • Release_0.5.0_37
  • Release_0.6.0_38
  • Release_0.7.0_39
  • Release_0.8.0_40
  • Release_0.8.1_41
  • Release_0.8.2_42
  • Release_0.9.0_43
  • Release_0.9.1_44
  • Release_0.9.2_45
46 results
Show changes
86 files
+ 7409
233
Compare changes
  • Side-by-side
  • Inline

Files

+2 −13
Original line number Original line Diff line number Diff line
image: ghcr.io/cirruslabs/flutter:latest
image: ghcr.io/cirruslabs/flutter:latest


stages:
stages:
  - update
  - build-debug
  - build-debug
  - build-release
  - build-release
  - release
  - release
  - deploy
  - deploy


update:
  stage: update
  except:
    - tags
  script:
    - flutter packages get
    - flutter packages upgrade
  interruptible: true

android:build-debug:
android:build-debug:
  stage: build-debug
  stage: build-debug
  except:
  except:
    - tags
    - tags
    - master
  script:
  script:
    # Flutter local configuration
    # Flutter local configuration
    - echo flutter.sdk=$FLUTTER_PATH > android/local.properties
    - echo flutter.sdk=$FLUTTER_PATH > android/local.properties
@@ -49,8 +40,6 @@ android:build-release:
    - master
    - master
  except:
  except:
    - tags
    - tags
  dependencies:
    - android:build-debug
  script:
  script:
    # Flutter local configuration
    # Flutter local configuration
    - echo flutter.sdk=$FLUTTER_PATH > android/local.properties
    - echo flutter.sdk=$FLUTTER_PATH > android/local.properties
@@ -121,4 +110,4 @@ android:deploy:
  dependencies:
  dependencies:
    - application:release
    - application:release
  script:
  script:
    - curl "${REPOSITORY_UPDATE_WEBHOOK}?token=${REPOSITORY_TOKEN}"
    - curl "${REPOSITORY_UPDATE_WEBHOOK}?token=${REPOSITORY_TOKEN}" --fail

analysis_options.yaml

0 → 100644
+1 −0
Original line number Original line Diff line number Diff line
include: package:flutter_lints/flutter.yaml
+8 −10
Original line number Original line Diff line number Diff line
plugins {
    id "com.android.application"
    id "kotlin-android"
    id "dev.flutter.flutter-gradle-plugin"
}

def localProperties = new Properties()
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
if (localPropertiesFile.exists()) {
@@ -14,11 +20,6 @@ if (gradlePropertiesFile.exists()) {
    }
    }
}
}


def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
    throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}

def appVersionCode = gradleProperties.getProperty('app.versionCode')
def appVersionCode = gradleProperties.getProperty('app.versionCode')
if (appVersionCode == null) {
if (appVersionCode == null) {
    appVersionCode = '1'
    appVersionCode = '1'
@@ -29,9 +30,6 @@ if (appVersionName == null) {
    appVersionName = '1.0'
    appVersionName = '1.0'
}
}


apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

def keystoreProperties = new Properties()
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
if (keystorePropertiesFile.exists()) {
@@ -39,12 +37,12 @@ if (keystorePropertiesFile.exists()) {
}
}


android {
android {
    compileSdkVersion 33
    compileSdkVersion 34
    namespace "org.benoitharrault.sortgame"
    namespace "org.benoitharrault.sortgame"


    defaultConfig {
    defaultConfig {
        applicationId "org.benoitharrault.sortgame"
        applicationId "org.benoitharrault.sortgame"
        minSdkVersion 16
        minSdkVersion flutter.minSdkVersion
        targetSdkVersion 30
        targetSdkVersion 30
        versionCode appVersionCode.toInteger()
        versionCode appVersionCode.toInteger()
        versionName appVersionName
        versionName appVersionName
+1 −12
Original line number Original line Diff line number Diff line
buildscript {
    repositories {
        google()
        jcenter()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:7.2.2'
    }
}

allprojects {
allprojects {
    repositories {
    repositories {
        google()
        google()
        jcenter()
        mavenCentral()
    }
    }
}
}


+2 −2
Original line number Original line Diff line number Diff line
org.gradle.jvmargs=-Xmx1536M
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.useAndroidX=true
android.enableJetifier=true
android.enableJetifier=true
app.versionName=0.0.13
app.versionName=0.0.25
app.versionCode=13
app.versionCode=25
+23 −8
Original line number Original line Diff line number Diff line
include ':app'
pluginManagement {

    def flutterSdkPath = {
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
        def properties = new Properties()
        def properties = new Properties()

        file("local.properties").withInputStream { properties.load(it) }
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }

        def flutterSdkPath = properties.getProperty("flutter.sdk")
        def flutterSdkPath = properties.getProperty("flutter.sdk")
        assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
        assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
        return flutterSdkPath
    }
    settings.ext.flutterSdkPath = flutterSdkPath()

    includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")

    repositories {
        google()
        mavenCentral()
        gradlePluginPortal()
    }
}

plugins {
    id "dev.flutter.flutter-plugin-loader" version "1.0.0"
    id "com.android.application" version "7.2.2" apply false
    id "org.jetbrains.kotlin.android" version "1.9.22" apply false
}

include ":app"

assets/files/data.json

deleted100644 → 0
+0 −3
Original line number Original line Diff line number Diff line
{
  data: []
}
+129 KiB

File added.

No diff preview for this file type.

+129 KiB

File added.

No diff preview for this file type.

+129 KiB

File added.

No diff preview for this file type.

+129 KiB

File added.

No diff preview for this file type.

+3.68 KiB

3.68 KiB

+3.91 KiB

3.91 KiB

+170 B

170 B

+3 −0
Original line number Original line Diff line number Diff line
{
  "app_name": "SortGame"
}
+3 −0
Original line number Original line Diff line number Diff line
{
  "app_name": "SortGame"
}
+1 −0
Original line number Original line Diff line number Diff line
Add automatic flutter linter. Apply code lints. Update dependencies.
+1 −0
Original line number Original line Diff line number Diff line
Update flutter gradle plugin.
+1 −0
Original line number Original line Diff line number Diff line
Avoid print calls in production code.
+1 −0
Original line number Original line Diff line number Diff line
Improve CI/CD, remove "update" step, disable "build-debug" in master branch.
+1 −0
Original line number Original line Diff line number Diff line
Add minimal playable game.
+1 −0
Original line number Original line Diff line number Diff line
Add script to manage data, improve dataset.
+1 −0
Original line number Original line Diff line number Diff line
Improve data and management script.
+1 −0
Original line number Original line Diff line number Diff line
Enlarge / improve database.
+1 −0
Original line number Original line Diff line number Diff line
Improve some code/design.
+1 −0
Original line number Original line Diff line number Diff line
Add choose categories theme.
+1 −0
Original line number Original line Diff line number Diff line
Lowercase all items.
+1 −0
Original line number Original line Diff line number Diff line
Add emojis to categories.
+1 −0
Original line number Original line Diff line number Diff line
Ajout d'un correcteur automatique de code. Application des correction. Mise à jour des dépendances.
+1 −0
Original line number Original line Diff line number Diff line
Mise à jour du plugin gradle pour flutter.
+1 −0
Original line number Original line Diff line number Diff line
Supprime les appels à print dans le code de production.
+1 −0
Original line number Original line Diff line number Diff line
Amélioration de la CI/CD. Suppression de "update" et désactivation de "build-debug" sur master.
+1 −0
Original line number Original line Diff line number Diff line
Ajout du jeu minimal.
+1 −0
Original line number Original line Diff line number Diff line
Ajout d'un script de gestion des données, compléments sur le jeu de données.
+1 −0
Original line number Original line Diff line number Diff line
Améliorations des données et du script de gestion.
+1 −0
Original line number Original line Diff line number Diff line
Compléments et améliorations sur la base de données.
+1 −0
Original line number Original line Diff line number Diff line
Améliorations de code et de design.
+1 −0
Original line number Original line Diff line number Diff line
Ajout du choix du thème des catégories.
+1 −0
Original line number Original line Diff line number Diff line
Mise en minuscules de tous les items.
+1 −0
Original line number Original line Diff line number Diff line
Ajout d'emojis aux catégories.
+3 −23
Original line number Original line Diff line number Diff line
@@ -16,10 +16,9 @@ ICON_SIZE=192


# Game images
# Game images
AVAILABLE_GAME_IMAGES="
AVAILABLE_GAME_IMAGES="
"
  button_back

  button_start
# Settings images
  placeholder
AVAILABLES_GAME_SETTINGS="
"
"


#######################################################
#######################################################
@@ -64,19 +63,6 @@ function build_icon() {
  optipng ${OPTIPNG_OPTIONS} ${TARGET}
  optipng ${OPTIPNG_OPTIONS} ${TARGET}
}
}


function build_settings_icons() {
  INPUT_STRING="$1"

  SETTING_NAME="$(echo "${INPUT_STRING}" | cut -d":" -f1)"
  SETTING_VALUES="$(echo "${INPUT_STRING}" | cut -d":" -f2 | tr "," " ")"

  for SETTING_VALUE in ${SETTING_VALUES}
  do
    SETTING_CODE="${SETTING_NAME}_${SETTING_VALUE}"
    build_icon ${CURRENT_DIR}/${SETTING_CODE}.svg ${ASSETS_DIR}/icons/${SETTING_CODE}.png
  done
}

#######################################################
#######################################################


# Create output folders
# Create output folders
@@ -90,9 +76,3 @@ for GAME_IMAGE in ${AVAILABLE_GAME_IMAGES}
do
do
  build_icon ${CURRENT_DIR}/${GAME_IMAGE}.svg ${ASSETS_DIR}/icons/${GAME_IMAGE}.png
  build_icon ${CURRENT_DIR}/${GAME_IMAGE}.svg ${ASSETS_DIR}/icons/${GAME_IMAGE}.png
done
done

# build settings images
for GAME_SETTING in ${AVAILABLES_GAME_SETTINGS}
do
  build_settings_icons "${GAME_SETTING}"
done

icons/build_icons.sh

deleted100755 → 0
+0 −48
Original line number Original line Diff line number Diff line
#! /bin/bash

# Check dependencies
command -v inkscape >/dev/null 2>&1 || { echo >&2 "I require inkscape but it's not installed. Aborting."; exit 1; }
command -v scour >/dev/null 2>&1 || { echo >&2 "I require scour but it's not installed. Aborting."; exit 1; }
command -v optipng >/dev/null 2>&1 || { echo >&2 "I require optipng but it's not installed. Aborting."; exit 1; }
command -v convert >/dev/null 2>&1 || { echo >&2 "I require convert (imagemagick) but it's not installed. Aborting."; exit 1; }

CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
BASE_DIR="$(dirname "${CURRENT_DIR}")"

SOURCE="${CURRENT_DIR}/icon.svg"
OPTIPNG_OPTIONS="-preserve -quiet -o7"

# optimize svg
cp ${SOURCE} ${SOURCE}.tmp
scour \
    --remove-descriptive-elements \
    --enable-id-stripping \
    --enable-viewboxing \
    --enable-comment-stripping \
    --nindent=4 \
    -i ${SOURCE}.tmp \
    -o ${SOURCE}
rm ${SOURCE}.tmp

# build icons
function build_icon() {
  ICON_SIZE="$1"
  TARGET="$2"

  TARGET_PNG="${TARGET}.png"

  inkscape \
      --export-width=${ICON_SIZE} \
      --export-height=${ICON_SIZE} \
      --export-filename=${TARGET_PNG} \
      ${SOURCE}

  optipng ${OPTIPNG_OPTIONS} ${TARGET_PNG}
}


build_icon  72 ${BASE_DIR}/android/app/src/main/res/mipmap-hdpi/ic_launcher
build_icon  48 ${BASE_DIR}/android/app/src/main/res/mipmap-mdpi/ic_launcher
build_icon  96 ${BASE_DIR}/android/app/src/main/res/mipmap-xhdpi/ic_launcher
build_icon 144 ${BASE_DIR}/android/app/src/main/res/mipmap-xxhdpi/ic_launcher
build_icon 192 ${BASE_DIR}/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher

icons/button_back.svg

0 → 100644
+2 −0
Original line number Original line Diff line number Diff line
<?xml version="1.0" encoding="UTF-8"?>
<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 93.665 93.676" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x=".44662" y=".89101" width="92.772" height="91.894" ry="11.689" fill="#e41578" stroke="#fff" stroke-width=".238"/><path d="m59.387 71.362c1.1248 1.1302 4.0012 1.1302 4.0012 0v-45.921c0-1.1316-2.8832-1.1316-4.0121 0l-37.693 20.918c-1.1289 1.1248-1.1479 2.9551-0.02171 4.084z" fill="#fefeff" stroke="#930e4e" stroke-linecap="round" stroke-linejoin="round" stroke-width="8.257"/><path d="m57.857 68.048c0.96243 0.96706 3.4236 0.96706 3.4236 0v-39.292c0-0.96825-2.467-0.96825-3.4329 0l-32.252 17.898c-0.96594 0.96243-0.9822 2.5285-0.01858 3.4945z" fill="#fefeff" stroke="#feffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="4.314"/></svg>

icons/button_start.svg

0 → 100644
+2 −0
Original line number Original line Diff line number Diff line
<?xml version="1.0" encoding="UTF-8"?>
<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 93.665 93.676" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x=".44662" y=".89101" width="92.772" height="91.894" ry="11.689" fill="#49a1ee" stroke="#fff" stroke-width=".238"/><path d="m34.852 25.44c-1.1248-1.1302-4.0012-1.1302-4.0012 0v45.921c0 1.1316 2.8832 1.1316 4.0121 0l37.693-20.918c1.1289-1.1248 1.1479-2.9551 0.02171-4.084z" fill="#fefeff" stroke="#105ca1" stroke-linecap="round" stroke-linejoin="round" stroke-width="8.257"/><path d="m36.382 28.754c-0.96243-0.96706-3.4236-0.96706-3.4236 0v39.292c0 0.96825 2.467 0.96825 3.4329 0l32.252-17.898c0.96594-0.96243 0.9822-2.5285 0.01858-3.4945z" fill="#fefeff" stroke="#feffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="4.314"/></svg>

icons/placeholder.svg

0 → 100644
+2 −0
Original line number Original line Diff line number Diff line
<?xml version="1.0" encoding="UTF-8"?>
<svg enable-background="new 0 0 100 100" version="1.1" viewBox="0 0 102 102" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"/>
+40 −0
Original line number Original line Diff line number Diff line
import 'package:sortgame/data/fetch_data_helper.dart';
import 'package:sortgame/utils/tools.dart';

class DefaultGameSettings {
  static const List<String> availableParameters = [
    'itemsCount',
    'theme',
  ];

  static const int itemsCountValueLow = 5;
  static const int itemsCountValueMedium = 10;
  static const int itemsCountValueHigh = 15;
  static const int itemsCountValueVeryHigh = 20;

  static const int defaultItemsCountValue = itemsCountValueMedium;
  static const List<int> allowedItemsCountValues = [
    itemsCountValueLow,
    itemsCountValueMedium,
    itemsCountValueHigh,
    itemsCountValueVeryHigh,
  ];

  static const int defaultThemeValue = 0;

  static List<int> getAvailableValues(String parameterCode) {
    switch (parameterCode) {
      case 'itemsCount':
        return DefaultGameSettings.allowedItemsCountValues;
    }

    switch (parameterCode) {
      case 'theme':
        final int count = FetchDataHelper().getThemes().length;
        return List<int>.generate(count, (i) => i);
    }

    printlog('Did not find any available value for game parameter "$parameterCode".');
    return [];
  }
}
+10 −0
Original line number Original line Diff line number Diff line
import 'package:sortgame/utils/tools.dart';

class DefaultGlobalSettings {
  static const List<String> availableParameters = [];

  static List<int> getAvailableValues(String parameterCode) {
    printlog('Did not find any available value for global parameter "$parameterCode".');
    return [];
  }
}

lib/config/theme.dart

0 → 100644
+196 −0
Original line number Original line Diff line number Diff line
import 'package:flutter/material.dart';

/// Colors from Tailwind CSS (v3.0) - June 2022
///
/// https://tailwindcss.com/docs/customizing-colors

const int _primaryColor = 0xFF6366F1;
const MaterialColor primarySwatch = MaterialColor(_primaryColor, <int, Color>{
  50: Color(0xFFEEF2FF), // indigo-50
  100: Color(0xFFE0E7FF), // indigo-100
  200: Color(0xFFC7D2FE), // indigo-200
  300: Color(0xFFA5B4FC), // indigo-300
  400: Color(0xFF818CF8), // indigo-400
  500: Color(_primaryColor), // indigo-500
  600: Color(0xFF4F46E5), // indigo-600
  700: Color(0xFF4338CA), // indigo-700
  800: Color(0xFF3730A3), // indigo-800
  900: Color(0xFF312E81), // indigo-900
});

const int _textColor = 0xFF64748B;
const MaterialColor textSwatch = MaterialColor(_textColor, <int, Color>{
  50: Color(0xFFF8FAFC), // slate-50
  100: Color(0xFFF1F5F9), // slate-100
  200: Color(0xFFE2E8F0), // slate-200
  300: Color(0xFFCBD5E1), // slate-300
  400: Color(0xFF94A3B8), // slate-400
  500: Color(_textColor), // slate-500
  600: Color(0xFF475569), // slate-600
  700: Color(0xFF334155), // slate-700
  800: Color(0xFF1E293B), // slate-800
  900: Color(0xFF0F172A), // slate-900
});

const Color errorColor = Color(0xFFDC2626); // red-600

final ColorScheme lightColorScheme = ColorScheme.light(
  primary: primarySwatch.shade500,
  secondary: primarySwatch.shade500,
  onSecondary: Colors.white,
  error: errorColor,
  background: textSwatch.shade200,
  onBackground: textSwatch.shade500,
  onSurface: textSwatch.shade500,
  surface: textSwatch.shade50,
  surfaceVariant: Colors.white,
  shadow: textSwatch.shade900.withOpacity(.1),
);

final ColorScheme darkColorScheme = ColorScheme.dark(
  primary: primarySwatch.shade500,
  secondary: primarySwatch.shade500,
  onSecondary: Colors.white,
  error: errorColor,
  background: const Color(0xFF171724),
  onBackground: textSwatch.shade400,
  onSurface: textSwatch.shade300,
  surface: const Color(0xFF262630),
  surfaceVariant: const Color(0xFF282832),
  shadow: textSwatch.shade900.withOpacity(.2),
);

final ThemeData lightTheme = ThemeData(
  colorScheme: lightColorScheme,
  fontFamily: 'Nunito',
  textTheme: TextTheme(
    displayLarge: TextStyle(
      color: textSwatch.shade700,
      fontFamily: 'Nunito',
    ),
    displayMedium: TextStyle(
      color: textSwatch.shade600,
      fontFamily: 'Nunito',
    ),
    displaySmall: TextStyle(
      color: textSwatch.shade500,
      fontFamily: 'Nunito',
    ),
    headlineLarge: TextStyle(
      color: textSwatch.shade700,
      fontFamily: 'Nunito',
    ),
    headlineMedium: TextStyle(
      color: textSwatch.shade600,
      fontFamily: 'Nunito',
    ),
    headlineSmall: TextStyle(
      color: textSwatch.shade500,
      fontFamily: 'Nunito',
    ),
    titleLarge: TextStyle(
      color: textSwatch.shade700,
      fontFamily: 'Nunito',
    ),
    titleMedium: TextStyle(
      color: textSwatch.shade600,
      fontFamily: 'Nunito',
    ),
    titleSmall: TextStyle(
      color: textSwatch.shade500,
      fontFamily: 'Nunito',
    ),
    bodyLarge: TextStyle(
      color: textSwatch.shade700,
      fontFamily: 'Nunito',
    ),
    bodyMedium: TextStyle(
      color: textSwatch.shade600,
      fontFamily: 'Nunito',
    ),
    bodySmall: TextStyle(
      color: textSwatch.shade500,
      fontFamily: 'Nunito',
    ),
    labelLarge: TextStyle(
      color: textSwatch.shade700,
      fontFamily: 'Nunito',
    ),
    labelMedium: TextStyle(
      color: textSwatch.shade600,
      fontFamily: 'Nunito',
    ),
    labelSmall: TextStyle(
      color: textSwatch.shade500,
      fontFamily: 'Nunito',
    ),
  ),
);

final ThemeData darkTheme = lightTheme.copyWith(
  colorScheme: darkColorScheme,
  textTheme: TextTheme(
    displayLarge: TextStyle(
      color: textSwatch.shade200,
      fontFamily: 'Nunito',
    ),
    displayMedium: TextStyle(
      color: textSwatch.shade300,
      fontFamily: 'Nunito',
    ),
    displaySmall: TextStyle(
      color: textSwatch.shade400,
      fontFamily: 'Nunito',
    ),
    headlineLarge: TextStyle(
      color: textSwatch.shade200,
      fontFamily: 'Nunito',
    ),
    headlineMedium: TextStyle(
      color: textSwatch.shade300,
      fontFamily: 'Nunito',
    ),
    headlineSmall: TextStyle(
      color: textSwatch.shade400,
      fontFamily: 'Nunito',
    ),
    titleLarge: TextStyle(
      color: textSwatch.shade200,
      fontFamily: 'Nunito',
    ),
    titleMedium: TextStyle(
      color: textSwatch.shade300,
      fontFamily: 'Nunito',
    ),
    titleSmall: TextStyle(
      color: textSwatch.shade400,
      fontFamily: 'Nunito',
    ),
    bodyLarge: TextStyle(
      color: textSwatch.shade200,
      fontFamily: 'Nunito',
    ),
    bodyMedium: TextStyle(
      color: textSwatch.shade300,
      fontFamily: 'Nunito',
    ),
    bodySmall: TextStyle(
      color: textSwatch.shade400,
      fontFamily: 'Nunito',
    ),
    labelLarge: TextStyle(
      color: textSwatch.shade200,
      fontFamily: 'Nunito',
    ),
    labelMedium: TextStyle(
      color: textSwatch.shade300,
      fontFamily: 'Nunito',
    ),
    labelSmall: TextStyle(
      color: textSwatch.shade400,
      fontFamily: 'Nunito',
    ),
  ),
);

final ThemeData appTheme = darkTheme;
+86 −0
Original line number Original line Diff line number Diff line
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';

import 'package:sortgame/models/game.dart';
import 'package:sortgame/models/settings_game.dart';
import 'package:sortgame/models/settings_global.dart';

part 'game_state.dart';

class GameCubit extends HydratedCubit<GameState> {
  GameCubit()
      : super(GameState(
          currentGame: Game.createNull(),
        ));

  void updateState(Game game) {
    emit(GameState(
      currentGame: game,
    ));
  }

  void refresh() {
    final Game game = Game(
      items: state.currentGame.items,
      gameSettings: state.currentGame.gameSettings,
      globalSettings: state.currentGame.globalSettings,
      isRunning: state.currentGame.isRunning,
      isFinished: state.currentGame.isFinished,
      position: state.currentGame.position,
      score: state.currentGame.score,
    );

    updateState(game);
  }

  void quitGame() {
    state.currentGame.updateGameIsRunning(false);
    refresh();
  }

  void increasePosition() {
    if (state.currentGame.position < state.currentGame.items.length) {
      state.currentGame.increasePosition();
    } else {
      state.currentGame.updateGameIsFinished(true);
    }
    refresh();
  }

  void increaseScore(int increment) {
    state.currentGame.increaseScore(increment);
    refresh();
  }

  void startNewGame({
    required GameSettings gameSettings,
    required GlobalSettings globalSettings,
  }) {
    Game newGame = Game.createNew(
      gameSettings: gameSettings,
      globalSettings: globalSettings,
    );

    newGame.dump();

    updateState(newGame);
    refresh();
  }

  @override
  GameState? fromJson(Map<String, dynamic> json) {
    Game currentGame = json['currentGame'] as Game;

    return GameState(
      currentGame: currentGame,
    );
  }

  @override
  Map<String, dynamic>? toJson(GameState state) {
    return <String, dynamic>{
      'currentGame': state.currentGame.toJson(),
    };
  }
}
+19 −0
Original line number Original line Diff line number Diff line
part of 'game_cubit.dart';

@immutable
class GameState extends Equatable {
  const GameState({
    required this.currentGame,
  });

  final Game currentGame;

  @override
  List<dynamic> get props => <dynamic>[
        currentGame,
      ];

  Map<String, dynamic> get values => <String, dynamic>{
        'currentGame': currentGame,
      };
}
+70 −0
Original line number Original line Diff line number Diff line
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';

import 'package:sortgame/models/settings_game.dart';
import 'package:sortgame/utils/tools.dart';

part 'settings_game_state.dart';

class GameSettingsCubit extends HydratedCubit<GameSettingsState> {
  GameSettingsCubit() : super(GameSettingsState(settings: GameSettings.createDefault()));

  void setValues({
    int? itemsCount,
    int? theme,
  }) {
    emit(
      GameSettingsState(
        settings: GameSettings(
          itemsCount: itemsCount ?? state.settings.itemsCount,
          theme: theme ?? state.settings.theme,
        ),
      ),
    );
  }

  int getParameterValue(String code) {
    switch (code) {
      case 'itemsCount':
        return GameSettings.getItemsCountValueFromUnsafe(state.settings.itemsCount);
      case 'theme':
        return GameSettings.getThemeValueFromUnsafe(state.settings.theme);
    }
    return 0;
  }

  void setParameterValue(String code, int value) {
    printlog('GameSettingsCubit.setParameterValue');
    printlog('code: $code  / value: $value');

    int itemsCount = code == 'itemsCount' ? value : getParameterValue('itemsCount');
    int theme = code == 'theme' ? value : getParameterValue('theme');

    setValues(
      itemsCount: itemsCount,
      theme: theme,
    );
  }

  @override
  GameSettingsState? fromJson(Map<String, dynamic> json) {
    int itemsCount = json['itemsCount'] as int;
    int theme = json['theme'] as int;

    return GameSettingsState(
      settings: GameSettings(
        itemsCount: itemsCount,
        theme: theme,
      ),
    );
  }

  @override
  Map<String, dynamic>? toJson(GameSettingsState state) {
    return <String, dynamic>{
      'itemsCount': state.settings.itemsCount,
      'theme': state.settings.theme,
    };
  }
}
+19 −0
Original line number Original line Diff line number Diff line
part of 'settings_game_cubit.dart';

@immutable
class GameSettingsState extends Equatable {
  const GameSettingsState({
    required this.settings,
  });

  final GameSettings settings;

  @override
  List<dynamic> get props => <dynamic>[
        settings,
      ];

  Map<String, dynamic> get values => <String, dynamic>{
        'settings': settings,
      };
}
+44 −0
Original line number Original line Diff line number Diff line
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';

import 'package:sortgame/models/settings_global.dart';
import 'package:sortgame/utils/tools.dart';

part 'settings_global_state.dart';

class GlobalSettingsCubit extends HydratedCubit<GlobalSettingsState> {
  GlobalSettingsCubit() : super(GlobalSettingsState(settings: GlobalSettings.createDefault()));

  void setValues() {
    emit(
      GlobalSettingsState(
        settings: GlobalSettings(),
      ),
    );
  }

  int getParameterValue(String code) {
    switch (code) {}
    return 0;
  }

  void setParameterValue(String code, int value) {
    printlog('GlobalSettingsCubit.setParameterValue');
    printlog('code: $code  / value: $value');

    setValues();
  }

  @override
  GlobalSettingsState? fromJson(Map<String, dynamic> json) {
    return GlobalSettingsState(
      settings: GlobalSettings(),
    );
  }

  @override
  Map<String, dynamic>? toJson(GlobalSettingsState state) {
    return <String, dynamic>{};
  }
}
+19 −0
Original line number Original line Diff line number Diff line
part of 'settings_global_cubit.dart';

@immutable
class GlobalSettingsState extends Equatable {
  const GlobalSettingsState({
    required this.settings,
  });

  final GlobalSettings settings;

  @override
  List<dynamic> get props => <dynamic>[
        settings,
      ];

  Map<String, dynamic> get values => <String, dynamic>{
        'settings': settings,
      };
}
+138 −0
Original line number Original line Diff line number Diff line
import 'package:sortgame/data/game_data.dart';
import 'package:sortgame/models/data/category.dart';
import 'package:sortgame/models/data/game_item.dart';
import 'package:sortgame/models/data/game_theme.dart';
import 'package:sortgame/models/data/item.dart';
import 'package:sortgame/models/settings_game.dart';
import 'package:sortgame/utils/tools.dart';

class FetchDataHelper {
  FetchDataHelper();

  final List<Category> _categories = [];
  List<Category> get categories => _categories;

  final List<Item> _items = [];
  List<Item> get items => _items;

  final List<GameTheme> _themes = [];
  List<GameTheme> get themes => _themes;

  final List<GameItem> _mapping = [];

  Category getCategory(String code) {
    return _categories.firstWhere((category) => (category.key == code));
  }

  void init() {
    try {
      const gameData = GameData.data;

      final Map<String, String> emojis = {};
      final Map<String, dynamic> rawResources = gameData['resources'] as Map<String, dynamic>;
      final Map<String, String> rawEmojis = rawResources['categories'] as Map<String, String>;
      rawEmojis.forEach((categoryCode, emoji) {
        emojis[categoryCode] = emoji;
      });

      final List<dynamic> rawCategories = gameData['categories'] as List<dynamic>;
      for (var rawElement in rawCategories) {
        final categoryCode = rawElement.toString();
        _categories.add(Category(
          key: categoryCode,
          text: categoryCode,
          emoji: emojis[categoryCode] ?? '',
        ));
      }

      final Map<String, dynamic> rawThemes = gameData['themes'] as Map<String, dynamic>;
      rawThemes.forEach((code, rawCategories) {
        final List<Category> categories = [];
        for (var rawElement in rawCategories) {
          final category = getCategory(rawElement.toString());
          categories.add(category);
        }
        _themes.add(GameTheme(code: code, categories: categories));
      });

      final List<dynamic> rawItems = gameData['items'] as List<dynamic>;
      for (var rawElement in rawItems) {
        final element = rawElement.toString();
        _items.add(Item(key: element, text: element));
      }

      final Map<String, dynamic> rawMapping = gameData['mapping'] as Map<String, dynamic>;
      final Map<String, dynamic> rawMappingItems = rawMapping['items'] as Map<String, dynamic>;
      rawMappingItems.forEach(
        (String itemName, itemMappings) {
          final List<String> rawIsCategories = [];
          for (var category in itemMappings['is'] as List<dynamic>) {
            rawIsCategories.add(category.toString());
          }

          final List<String> rawIsNotCategories = [];
          for (var category in itemMappings['isnot'] as List<dynamic>) {
            rawIsNotCategories.add(category.toString());
          }

          _mapping.add(GameItem(
            item: Item(
              key: itemName,
              text: itemName,
            ),
            isCategory: rawIsCategories.map((String code) => getCategory(code)).toList(),
            isNotCategory: rawIsNotCategories.map((String code) => getCategory(code)).toList(),
          ));
        },
      );
    } catch (e) {
      printlog("$e");
    }
  }

  List<GameItem> getItems(GameSettings gameSettings) {
    if (_mapping.isEmpty) {
      init();
    }

    final int count = gameSettings.itemsCount;
    final int theme = gameSettings.theme;

    List<GameItem> items = _mapping;

    // Remove unwanted categories if theme is selected
    if (theme != 0) {
      final GameTheme gameTheme = _themes[theme];
      for (GameItem item in items) {
        item.isCategory.removeWhere((Category category) =>
            (!gameTheme.categories.map((Category c) => c.key).contains(category.key)));
        item.isNotCategory.removeWhere((Category category) =>
            (!gameTheme.categories.map((Category c) => c.key).contains(category.key)));
      }
    }

    // Remove items without enough data
    items.removeWhere((GameItem gameItem) =>
        (gameItem.isCategory.isEmpty || gameItem.isNotCategory.isEmpty));

    items.shuffle();

    return items.take(count).toList();
  }

  List<GameTheme> getThemes() {
    if (_themes.isEmpty) {
      init();
    }

    return _themes.toList();
  }

  GameTheme getTheme(int themeIndex) {
    if (_themes.isEmpty) {
      init();
    }

    return _themes[themeIndex];
  }
}
+1495 −0

File added.

Preview size limit exceeded, changes collapsed.

+54 −19
Original line number Original line Diff line number Diff line
import 'dart:io';

import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hive/hive.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:path_provider/path_provider.dart';


import 'provider/data.dart';
import 'package:sortgame/config/theme.dart';
import 'screens/home.dart';
import 'package:sortgame/cubit/game_cubit.dart';
import 'package:sortgame/cubit/settings_game_cubit.dart';
import 'package:sortgame/cubit/settings_global_cubit.dart';
import 'package:sortgame/ui/skeleton.dart';


void main() => runApp(MyApp());
void main() async {
  // Initialize packages
  WidgetsFlutterBinding.ensureInitialized();
  await EasyLocalization.ensureInitialized();
  final Directory tmpDir = await getTemporaryDirectory();
  Hive.init(tmpDir.toString());
  HydratedBloc.storage = await HydratedStorage.build(
    storageDirectory: tmpDir,
  );

  runApp(
    EasyLocalization(
      path: 'assets/translations',
      supportedLocales: const <Locale>[
        Locale('en'),
        Locale('fr'),
      ],
      fallbackLocale: const Locale('en'),
      useFallbackTranslations: true,
      child: const MyApp(),
    ),
  );
}


class MyApp extends StatelessWidget {
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  @override
  Widget build(BuildContext context) {
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
    return MultiBlocProvider(
      create: (BuildContext context) => Data(),
      providers: [
      child: Consumer<Data>(builder: (context, data, child) {
        BlocProvider<GameCubit>(create: (context) => GameCubit()),
        return MaterialApp(
        BlocProvider<GlobalSettingsCubit>(create: (context) => GlobalSettingsCubit()),
        BlocProvider<GameSettingsCubit>(create: (context) => GameSettingsCubit()),
      ],
      child: MaterialApp(
        title: 'SortGame',
        theme: appTheme,
        home: const SkeletonScreen(),

        // Localization stuff
        localizationsDelegates: context.localizationDelegates,
        supportedLocales: context.supportedLocales,
        locale: context.locale,
        debugShowCheckedModeBanner: false,
        debugShowCheckedModeBanner: false,
          theme: ThemeData(
            primaryColor: Colors.blue,
            visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      ),
          home: Home(),
          routes: {
            Home.id: (context) => Home(),
          },
        );
      }),
    );
    );
  }
  }
}
}
+24 −0
Original line number Original line Diff line number Diff line
class Category {
  final String key;
  final String text;
  final String emoji;

  const Category({
    required this.key,
    required this.text,
    required this.emoji,
  });

  Map<String, dynamic> toJson() {
    return {
      'key': key,
      'text': text,
      'emoji': emoji,
    };
  }

  @override
  String toString() {
    return toJson().toString();
  }
}
+27 −0
Original line number Original line Diff line number Diff line
import 'package:sortgame/models/data/category.dart';
import 'package:sortgame/models/data/item.dart';

class GameItem {
  final Item item;
  final List<Category> isCategory;
  final List<Category> isNotCategory;

  GameItem({
    required this.item,
    required this.isCategory,
    required this.isNotCategory,
  });

  @override
  String toString() {
    return '$GameItem(${toJson()})';
  }

  Map<String, dynamic>? toJson() {
    return <String, dynamic>{
      'item': item.toJson(),
      'isCategory': isCategory.toString(),
      'isNotCategory': isNotCategory.toString(),
    };
  }
}
+23 −0
Original line number Original line Diff line number Diff line
import 'package:sortgame/models/data/category.dart';

class GameTheme {
  final String code;
  final List<Category> categories;

  GameTheme({
    required this.code,
    required this.categories,
  });

  @override
  String toString() {
    return '$GameTheme(${toJson()})';
  }

  Map<String, dynamic>? toJson() {
    return <String, dynamic>{
      'code': code,
      'categories': categories.toString(),
    };
  }
}
+21 −0
Original line number Original line Diff line number Diff line
class Item {
  final String key;
  final String text;

  const Item({
    required this.key,
    required this.text,
  });

  Map<String, dynamic> toJson() {
    return {
      'key': key,
      'text': text,
    };
  }

  @override
  String toString() {
    return toJson().toString();
  }
}

lib/models/game.dart

0 → 100644
+104 −0
Original line number Original line Diff line number Diff line
import 'package:sortgame/data/fetch_data_helper.dart';
import 'package:sortgame/models/data/game_item.dart';
import 'package:sortgame/models/settings_game.dart';
import 'package:sortgame/models/settings_global.dart';
import 'package:sortgame/utils/tools.dart';

class Game {
  final List<GameItem> items;
  final GameSettings gameSettings;
  final GlobalSettings globalSettings;
  bool isRunning = false;
  bool isFinished = false;
  int position = 1;
  int score = 0;

  Game({
    required this.items,
    required this.gameSettings,
    required this.globalSettings,
    this.isRunning = false,
    this.isFinished = false,
    this.position = 1,
    this.score = 0,
  });

  factory Game.createNull() {
    return Game(
      items: [],
      gameSettings: GameSettings.createDefault(),
      globalSettings: GlobalSettings.createDefault(),
    );
  }

  factory Game.createNew({
    GameSettings? gameSettings,
    GlobalSettings? globalSettings,
  }) {
    GameSettings newGameSettings = gameSettings ?? GameSettings.createDefault();
    GlobalSettings newGlobalSettings = globalSettings ?? GlobalSettings.createDefault();

    List<GameItem> items = FetchDataHelper().getItems(newGameSettings);

    return Game(
      items: items,
      gameSettings: newGameSettings,
      globalSettings: newGlobalSettings,
      isRunning: true,
    );
  }

  void increaseScore(int? count) {
    score += (count ?? 0);
  }

  void increasePosition() {
    position += 1;
  }

  void updateGameIsRunning(bool gameIsRunning) {
    isRunning = gameIsRunning;
  }

  void updateGameIsFinished(bool gameIsFinished) {
    isFinished = gameIsFinished;
  }

  GameItem getCurrentGameItem() {
    return items[position - 1];
  }

  void dump() {
    printlog('');
    printlog('## Current game dump:');
    printlog('');
    gameSettings.dump();
    globalSettings.dump();
    printlog('');
    items.toString();
    printlog('');
    printlog('Game: ');
    printlog('  isRunning: $isRunning');
    printlog('  isFinished: $isFinished');
    printlog('  position: $position');
    printlog('  score: $score');
    printlog('');
  }

  @override
  String toString() {
    return '$Game(${toJson()})';
  }

  Map<String, dynamic>? toJson() {
    return <String, dynamic>{
      'items': items.toString(),
      'gameSettings': gameSettings.toJson(),
      'globalSettings': globalSettings.toJson(),
      'isRunning': isRunning,
      'isFinished': isFinished,
      'position': position,
      'score': score,
    };
  }
}
+53 −0
Original line number Original line Diff line number Diff line
import 'package:sortgame/config/default_game_settings.dart';
import 'package:sortgame/utils/tools.dart';

class GameSettings {
  final int itemsCount;
  final int theme;

  GameSettings({
    required this.itemsCount,
    required this.theme,
  });

  static int getItemsCountValueFromUnsafe(int itemsCount) {
    if (DefaultGameSettings.allowedItemsCountValues.contains(itemsCount)) {
      return itemsCount;
    }

    return DefaultGameSettings.defaultItemsCountValue;
  }

  static int getThemeValueFromUnsafe(int theme) {
    if (DefaultGameSettings.getAvailableValues('theme').contains(theme)) {
      return theme;
    }

    return DefaultGameSettings.defaultThemeValue;
  }

  factory GameSettings.createDefault() {
    return GameSettings(
      itemsCount: DefaultGameSettings.defaultItemsCountValue,
      theme: DefaultGameSettings.defaultThemeValue,
    );
  }

  void dump() {
    printlog('Settings: ');
    printlog('  itemsCount: $itemsCount');
    printlog('  theme: $theme');
  }

  @override
  String toString() {
    return '$GameSettings(${toJson()})';
  }

  Map<String, dynamic>? toJson() {
    return <String, dynamic>{
      'itemsCount': itemsCount,
      'theme': theme,
    };
  }
}
+22 −0
Original line number Original line Diff line number Diff line
import 'package:sortgame/utils/tools.dart';

class GlobalSettings {
  GlobalSettings();

  factory GlobalSettings.createDefault() {
    return GlobalSettings();
  }

  void dump() {
    printlog('Settings: ');
  }

  @override
  String toString() {
    return '$GlobalSettings(${toJson()})';
  }

  Map<String, dynamic>? toJson() {
    return <String, dynamic>{};
  }
}

lib/provider/data.dart

deleted100644 → 0
+0 −25
Original line number Original line Diff line number Diff line
import 'package:flutter/foundation.dart';

class Data extends ChangeNotifier {
  bool _searchingImage = false;
  String _image = '';

  bool get searchingImage => _searchingImage;

  set searchingImage(bool value) {
    _searchingImage = value;
    notifyListeners();
  }

  String get image => _image;

  set updateImage(String value) {
    _image = value;
    notifyListeners();
  }

  void resetGame() {
    _image = '';
    notifyListeners();
  }
}

lib/screens/home.dart

deleted100644 → 0
+0 −53
Original line number Original line Diff line number Diff line
import 'package:flutter/material.dart';

class Home extends StatelessWidget {
  static const String id = 'home';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Sorting game'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Container(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Container(
                    margin: EdgeInsets.all(4),
                    padding: EdgeInsets.all(4),
                    decoration: BoxDecoration(
                      color: Colors.blue,
                      borderRadius: BorderRadius.circular(4),
                      border: Border.all(
                        color: Colors.green,
                        width: 4,
                      ),
                    ),
                    child: TextButton(
                      child: Text(
                        '🎲',
                        textAlign: TextAlign.center,
                        style: TextStyle(
                          fontSize: 50,
                          fontWeight: FontWeight.w600,
                          color: Colors.black,
                        ),
                      ),
                      onPressed: () => print('X'),
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}
+196 −0
Original line number Original line Diff line number Diff line
import 'dart:math';

import 'package:flutter/material.dart';

import 'package:sortgame/config/default_game_settings.dart';
import 'package:sortgame/data/fetch_data_helper.dart';
import 'package:sortgame/models/data/game_theme.dart';
import 'package:sortgame/models/settings_game.dart';
import 'package:sortgame/models/settings_global.dart';
import 'package:sortgame/utils/tools.dart';

class ParameterPainter extends CustomPainter {
  const ParameterPainter({
    required this.code,
    required this.value,
    required this.isSelected,
    required this.gameSettings,
    required this.globalSettings,
  });

  final String code;
  final int value;
  final bool isSelected;
  final GameSettings gameSettings;
  final GlobalSettings globalSettings;

  @override
  void paint(Canvas canvas, Size size) {
    // force square
    final double canvasSize = min(size.width, size.height);

    const Color borderColorEnabled = Colors.blue;
    const Color borderColorDisabled = Colors.white;

    // "enabled/disabled" border
    final paint = Paint();
    paint.style = PaintingStyle.stroke;
    paint.color = isSelected ? borderColorEnabled : borderColorDisabled;
    paint.strokeJoin = StrokeJoin.round;
    paint.strokeWidth = 20 / 100 * canvasSize;
    canvas.drawRect(
        Rect.fromPoints(const Offset(0, 0), Offset(canvasSize, canvasSize)), paint);

    // content
    switch (code) {
      case 'itemsCount':
        paintItemsCountParameterItem(value, canvas, canvasSize);
        break;
      case 'theme':
        paintThemeParameterItem(value, canvas, canvasSize);
        break;
      default:
        printlog('Unknown parameter: $code/$value');
        paintUnknownParameterItem(value, canvas, canvasSize);
    }
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return false;
  }

  // "unknown" parameter -> simple bock with text
  void paintUnknownParameterItem(
    final int value,
    final Canvas canvas,
    final double size,
  ) {
    final paint = Paint();
    paint.strokeJoin = StrokeJoin.round;
    paint.strokeWidth = 3 / 100 * size;

    paint.color = Colors.grey;
    paint.style = PaintingStyle.fill;
    canvas.drawRect(Rect.fromPoints(const Offset(0, 0), Offset(size, size)), paint);

    final textSpan = TextSpan(
      text: '?\n$value',
      style: const TextStyle(
        color: Colors.black,
        fontSize: 18,
        fontWeight: FontWeight.bold,
      ),
    );
    final textPainter = TextPainter(
      text: textSpan,
      textDirection: TextDirection.ltr,
    );
    textPainter.layout();
    textPainter.paint(
      canvas,
      Offset(
        (size - textPainter.width) * 0.5,
        (size - textPainter.height) * 0.5,
      ),
    );
  }

  void paintItemsCountParameterItem(
    final int value,
    final Canvas canvas,
    final double size,
  ) {
    Color backgroundColor = Colors.grey;

    switch (value) {
      case DefaultGameSettings.itemsCountValueLow:
        backgroundColor = Colors.green;
        break;
      case DefaultGameSettings.itemsCountValueMedium:
        backgroundColor = Colors.orange;
        break;
      case DefaultGameSettings.itemsCountValueHigh:
        backgroundColor = Colors.red;
        break;
      case DefaultGameSettings.itemsCountValueVeryHigh:
        backgroundColor = Colors.purple;
        break;
      default:
        printlog('Wrong value for itemsCount parameter value: $value');
    }

    final paint = Paint();
    paint.strokeJoin = StrokeJoin.round;
    paint.strokeWidth = 3 / 100 * size;

    // Colored background
    paint.color = backgroundColor;
    paint.style = PaintingStyle.fill;
    canvas.drawRect(Rect.fromPoints(const Offset(0, 0), Offset(size, size)), paint);

    // centered text value
    final textSpan = TextSpan(
      text: value.toString(),
      style: TextStyle(
        color: Colors.black,
        fontSize: size / 4,
        fontWeight: FontWeight.bold,
      ),
    );
    final textPainter = TextPainter(
      text: textSpan,
      textDirection: TextDirection.ltr,
    );
    textPainter.layout();
    textPainter.paint(
      canvas,
      Offset(
        (size - textPainter.width) * 0.5,
        (size - textPainter.height) * 0.5,
      ),
    );
  }

  void paintThemeParameterItem(
    final int value,
    final Canvas canvas,
    final double size,
  ) {
    final GameTheme theme = FetchDataHelper().getTheme(value);

    final Color backgroundColor =
        Color((theme.code.hashCode * 0xFFFFFF).toInt()).withOpacity(1.0);

    final paint = Paint();
    paint.strokeJoin = StrokeJoin.round;
    paint.strokeWidth = 3 / 100 * size;

    // Colored background
    paint.color = backgroundColor;
    paint.style = PaintingStyle.fill;
    canvas.drawRect(Rect.fromPoints(const Offset(0, 0), Offset(size, size)), paint);

    // centered text value
    final textSpan = TextSpan(
      text: theme.code,
      style: TextStyle(
        color: Colors.black,
        fontSize: size / 4,
        fontWeight: FontWeight.bold,
      ),
    );
    final textPainter = TextPainter(
      text: textSpan,
      textDirection: TextDirection.ltr,
    );
    textPainter.layout();
    textPainter.paint(
      canvas,
      Offset(
        (size - textPainter.width) * 0.5,
        (size - textPainter.height) * 0.5,
      ),
    );
  }
}
+39 −0
Original line number Original line Diff line number Diff line
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import 'package:sortgame/cubit/game_cubit.dart';
import 'package:sortgame/models/game.dart';
import 'package:sortgame/ui/widgets/game_bottom_buttons.dart';
import 'package:sortgame/ui/widgets/game_question.dart';
import 'package:sortgame/ui/widgets/game_top_indicator.dart';

class ScreenGame extends StatelessWidget {
  const ScreenGame({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<GameCubit, GameState>(
      builder: (BuildContext context, GameState gameState) {
        final Game currentGame = gameState.currentGame;

        return Column(
          mainAxisAlignment: MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            const SizedBox(height: 8),
            const GameTopIndicatorWidget(),
            const SizedBox(height: 8),
            Expanded(
              child: !currentGame.isFinished
                  ? const GameQuestionWidget()
                  : const SizedBox(height: 8),
            ),
            !currentGame.isFinished
                ? const SizedBox(height: 8)
                : const GameBottomButtonsWidget(),
          ],
        );
      },
    );
  }
}
+159 −0
Original line number Original line Diff line number Diff line
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import 'package:sortgame/config/default_game_settings.dart';
import 'package:sortgame/config/default_global_settings.dart';
import 'package:sortgame/cubit/game_cubit.dart';
import 'package:sortgame/cubit/settings_game_cubit.dart';
import 'package:sortgame/cubit/settings_global_cubit.dart';
import 'package:sortgame/ui/painters/parameter_painter.dart';

class ScreenParameters extends StatelessWidget {
  const ScreenParameters({super.key});

  final double separatorHeight = 8.0;

  @override
  Widget build(BuildContext context) {
    final List<Widget> lines = [];

    // Game settings
    for (String code in DefaultGameSettings.availableParameters) {
      lines.add(Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: buildParametersLine(
          code: code,
          isGlobal: false,
        ),
      ));

      lines.add(SizedBox(height: separatorHeight));
    }

    lines.add(SizedBox(height: separatorHeight));
    lines.add(Expanded(child: buildStartNewGameButton()));
    lines.add(SizedBox(height: separatorHeight));

    // Global settings
    for (String code in DefaultGlobalSettings.availableParameters) {
      lines.add(Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: buildParametersLine(
          code: code,
          isGlobal: true,
        ),
      ));

      lines.add(SizedBox(height: separatorHeight));
    }

    return Column(
      children: lines,
    );
  }

  List<Widget> buildParametersLine({
    required String code,
    required bool isGlobal,
  }) {
    final List<Widget> parameterButtons = [];

    final List<int> availableValues = isGlobal
        ? DefaultGlobalSettings.getAvailableValues(code)
        : DefaultGameSettings.getAvailableValues(code);

    for (int value in availableValues) {
      final Widget parameterButton = BlocBuilder<GameSettingsCubit, GameSettingsState>(
        builder: (BuildContext context, GameSettingsState gameSettingsState) {
          return BlocBuilder<GlobalSettingsCubit, GlobalSettingsState>(
            builder: (BuildContext context, GlobalSettingsState globalSettingsState) {
              final GameSettingsCubit gameSettingsCubit =
                  BlocProvider.of<GameSettingsCubit>(context);
              final GlobalSettingsCubit globalSettingsCubit =
                  BlocProvider.of<GlobalSettingsCubit>(context);

              final int currentValue = isGlobal
                  ? globalSettingsCubit.getParameterValue(code)
                  : gameSettingsCubit.getParameterValue(code);

              final bool isActive = (value == currentValue);

              final double displayWidth = MediaQuery.of(context).size.width;
              final double itemWidth = displayWidth / availableValues.length - 25;

              return TextButton(
                child: Container(
                  margin: const EdgeInsets.all(0),
                  padding: const EdgeInsets.all(0),
                  child: CustomPaint(
                    size: Size(itemWidth, itemWidth),
                    willChange: false,
                    painter: ParameterPainter(
                      code: code,
                      value: value,
                      isSelected: isActive,
                      gameSettings: gameSettingsState.settings,
                      globalSettings: globalSettingsState.settings,
                    ),
                    isComplex: true,
                  ),
                ),
                onPressed: () => isGlobal
                    ? globalSettingsCubit.setParameterValue(code, value)
                    : gameSettingsCubit.setParameterValue(code, value),
              );
            },
          );
        },
      );

      parameterButtons.add(parameterButton);
    }

    return parameterButtons;
  }

  Image buildImageWidget(String imageAssetCode) {
    return Image(
      image: AssetImage('assets/icons/$imageAssetCode.png'),
      fit: BoxFit.fill,
    );
  }

  Container buildImageContainerWidget(String imageAssetCode) {
    return Container(
      child: buildImageWidget(imageAssetCode),
    );
  }

  Column buildDecorationImageWidget() {
    return Column(
      children: [
        TextButton(
          child: buildImageContainerWidget('placeholder'),
          onPressed: () {},
        ),
      ],
    );
  }

  Widget buildStartNewGameButton() {
    return BlocBuilder<GameSettingsCubit, GameSettingsState>(
      builder: (BuildContext context, GameSettingsState gameSettingsState) {
        return BlocBuilder<GlobalSettingsCubit, GlobalSettingsState>(
          builder: (BuildContext context, GlobalSettingsState globalSettingsState) {
            final GameCubit gameCubit = BlocProvider.of<GameCubit>(context);

            return TextButton(
              child: buildImageContainerWidget('button_start'),
              onPressed: () => gameCubit.startNewGame(
                gameSettings: gameSettingsState.settings,
                globalSettings: globalSettingsState.settings,
              ),
            );
          },
        );
      },
    );
  }
}

lib/ui/skeleton.dart

0 → 100644
+30 −0
Original line number Original line Diff line number Diff line
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import 'package:sortgame/cubit/game_cubit.dart';
import 'package:sortgame/ui/screens/screen_game.dart';
import 'package:sortgame/ui/screens/screen_parameters.dart';
import 'package:sortgame/ui/widgets/global_app_bar.dart';

class SkeletonScreen extends StatelessWidget {
  const SkeletonScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: const GlobalAppBar(),
      extendBodyBehindAppBar: false,
      body: Material(
        color: Theme.of(context).colorScheme.background,
        child: BlocBuilder<GameCubit, GameState>(
          builder: (BuildContext context, GameState gameState) {
            return gameState.currentGame.isRunning
                ? const ScreenGame()
                : const ScreenParameters();
          },
        ),
      ),
      backgroundColor: Theme.of(context).colorScheme.background,
    );
  }
}
+51 −0
Original line number Original line Diff line number Diff line
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import 'package:sortgame/cubit/game_cubit.dart';

class GameBottomButtonsWidget extends StatelessWidget {
  const GameBottomButtonsWidget({super.key});

  @override
  Widget build(BuildContext context) {
    const String decorationImageAssetName = 'assets/icons/placeholder.png';

    const Widget decorationWidget = TextButton(
      onPressed: null,
      child: Image(
        image: AssetImage(decorationImageAssetName),
        fit: BoxFit.fill,
      ),
    );

    return Table(
      defaultColumnWidth: const IntrinsicColumnWidth(),
      children: [
        TableRow(
          children: [
            const Column(
              children: [decorationWidget],
            ),
            Column(
              children: [
                TextButton(
                  child: const Image(
                    image: AssetImage('assets/icons/button_back.png'),
                    fit: BoxFit.fill,
                  ),
                  onPressed: () {
                    final GameCubit gameCubit = BlocProvider.of<GameCubit>(context);
                    gameCubit.quitGame();
                  },
                )
              ],
            ),
            const Column(
              children: [decorationWidget],
            ),
          ],
        ),
      ],
    );
  }
}
+46 −0
Original line number Original line Diff line number Diff line
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import 'package:sortgame/cubit/game_cubit.dart';
import 'package:sortgame/models/data/game_item.dart';
import 'package:sortgame/models/game.dart';
import 'package:sortgame/ui/widgets/games/buttons_yes_no.dart';
import 'package:sortgame/ui/widgets/helpers/outlined_text_widget.dart';

class GameQuestionWidget extends StatelessWidget {
  const GameQuestionWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<GameCubit, GameState>(
      builder: (BuildContext context, GameState gameState) {
        final Game currentGame = gameState.currentGame;

        final GameItem currentGameItem = currentGame.getCurrentGameItem();

        return Column(
          children: [
            OutlinedText(
              text: currentGameItem.item.text,
              fontSize: 50,
              textColor: Theme.of(context).colorScheme.onSurface,
            ),
            Container(
              padding: const EdgeInsets.all(10),
              margin: const EdgeInsets.all(20),
              decoration: BoxDecoration(
                border: Border.all(
                  color: Theme.of(context).colorScheme.surface,
                  width: 8,
                ),
                borderRadius: const BorderRadius.all(Radius.circular(20)),
                color: Theme.of(context).colorScheme.inversePrimary,
              ),
              child: GameButtonsYesNo(gameItem: currentGameItem),
            ),
          ],
        );
      },
    );
  }
}
+27 −0
Original line number Original line Diff line number Diff line
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import 'package:sortgame/cubit/game_cubit.dart';
import 'package:sortgame/models/game.dart';
import 'package:sortgame/ui/widgets/indicators/indicator_position.dart';
import 'package:sortgame/ui/widgets/indicators/indicator_score.dart';

class GameTopIndicatorWidget extends StatelessWidget {
  const GameTopIndicatorWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<GameCubit, GameState>(
      builder: (BuildContext context, GameState gameState) {
        final Game currentGame = gameState.currentGame;

        return Column(
          children: [
            PositionIndicator(game: currentGame),
            ScoreIndicator(game: currentGame),
          ],
        );
      },
    );
  }
}
+76 −0
Original line number Original line Diff line number Diff line
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:unicons/unicons.dart';

import 'package:sortgame/cubit/game_cubit.dart';
import 'package:sortgame/models/data/category.dart';
import 'package:sortgame/models/data/game_item.dart';

class GameButtonsYesNo extends StatelessWidget {
  const GameButtonsYesNo({super.key, required this.gameItem});

  final GameItem gameItem;

  @override
  Widget build(BuildContext context) {
    final GameCubit gameCubit = BlocProvider.of<GameCubit>(context);

    final bool pickInIsCategory = Random().nextBool();

    final List<Category> categories =
        pickInIsCategory ? gameItem.isCategory : gameItem.isNotCategory;

    categories.shuffle();
    final Category category = categories.first;

    return Column(
      children: [
        Text(
          category.text,
          style: TextStyle(
            color: Theme.of(context).colorScheme.onSurface,
            fontSize: 40,
            fontWeight: FontWeight.bold,
          ),
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            IconButton(
              color: Theme.of(context).colorScheme.onSurface,
              iconSize: 80,
              onPressed: () {
                if (pickInIsCategory) {
                  gameCubit.increaseScore(1);
                }
                gameCubit.increasePosition();
              },
              icon: const Icon(UniconsLine.thumbs_up),
            ),
            Text(
              category.emoji,
              style: TextStyle(
                color: Theme.of(context).colorScheme.onSurface,
                fontSize: 40,
                fontWeight: FontWeight.bold,
              ),
            ),
            IconButton(
              color: Theme.of(context).colorScheme.onSurface,
              iconSize: 80,
              onPressed: () {
                if (!pickInIsCategory) {
                  gameCubit.increaseScore(1);
                }
                gameCubit.increasePosition();
              },
              icon: const Icon(UniconsLine.thumbs_down),
            ),
          ],
        )
      ],
    );
  }
}
+43 −0
Original line number Original line Diff line number Diff line
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import 'package:sortgame/cubit/game_cubit.dart';
import 'package:sortgame/models/game.dart';
import 'package:sortgame/ui/widgets/helpers/app_titles.dart';

class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget {
  const GlobalAppBar({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<GameCubit, GameState>(
      builder: (BuildContext context, GameState gameState) {
        final Game currentGame = gameState.currentGame;

        final List<Widget> menuActions = [];

        if (currentGame.isRunning) {
          menuActions.add(TextButton(
            child: const Image(
              image: AssetImage('assets/icons/button_back.png'),
              fit: BoxFit.fill,
            ),
            onPressed: () {},
            onLongPress: () {
              final GameCubit gameCubit = BlocProvider.of<GameCubit>(context);
              gameCubit.quitGame();
            },
          ));
        }

        return AppBar(
          title: const AppTitle(text: 'app_name'),
          actions: menuActions,
        );
      },
    );
  }

  @override
  Size get preferredSize => const Size.fromHeight(50);
}
+17 −0
Original line number Original line Diff line number Diff line
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';

class AppTitle extends StatelessWidget {
  const AppTitle({super.key, required this.text});

  final String text;

  @override
  Widget build(BuildContext context) {
    return Text(
      tr(text),
      textAlign: TextAlign.start,
      style: Theme.of(context).textTheme.headlineLarge!.apply(fontWeightDelta: 2),
    );
  }
}
+51 −0
Original line number Original line Diff line number Diff line
import 'package:flutter/material.dart';

import 'package:sortgame/utils/color_extensions.dart';

class OutlinedText extends StatelessWidget {
  const OutlinedText({
    super.key,
    required this.text,
    required this.fontSize,
    required this.textColor,
    this.outlineColor,
  });

  final String text;
  final double fontSize;
  final Color textColor;
  final Color? outlineColor;

  @override
  Widget build(BuildContext context) {
    final double delta = fontSize / 30;

    return Text(
      text,
      style: TextStyle(
        inherit: true,
        fontSize: fontSize,
        fontWeight: FontWeight.w600,
        color: textColor,
        shadows: [
          Shadow(
            offset: Offset(-delta, -delta),
            color: outlineColor ?? textColor.darken(),
          ),
          Shadow(
            offset: Offset(delta, -delta),
            color: outlineColor ?? textColor.darken(),
          ),
          Shadow(
            offset: Offset(delta, delta),
            color: outlineColor ?? textColor.darken(),
          ),
          Shadow(
            offset: Offset(-delta, delta),
            color: outlineColor ?? textColor.darken(),
          ),
        ],
      ),
    );
  }
}
+42 −0
Original line number Original line Diff line number Diff line
import 'package:flutter/material.dart';

import 'package:sortgame/models/game.dart';
import 'package:sortgame/ui/widgets/helpers/outlined_text_widget.dart';
import 'package:sortgame/utils/color_extensions.dart';

class PositionIndicator extends StatelessWidget {
  const PositionIndicator({super.key, required this.game});

  final Game game;

  @override
  Widget build(BuildContext context) {
    // Normalized [0..1] value
    final double barValue = game.position / game.gameSettings.itemsCount;

    const Color baseColor = Color.fromARGB(255, 215, 1, 133);

    const barHeight = 40.0;
    const Color textColor = Color.fromARGB(255, 238, 238, 238);
    const Color outlineColor = Color.fromARGB(255, 200, 200, 200);

    return Stack(
      alignment: Alignment.center,
      children: [
        LinearProgressIndicator(
          value: barValue,
          color: baseColor,
          backgroundColor: baseColor.darken(),
          minHeight: barHeight,
          borderRadius: const BorderRadius.all(Radius.circular(barHeight / 4)),
        ),
        OutlinedText(
          text: '${game.position}/${game.gameSettings.itemsCount}',
          fontSize: 0.9 * barHeight,
          textColor: textColor,
          outlineColor: outlineColor,
        ),
      ],
    );
  }
}
+21 −0
Original line number Original line Diff line number Diff line
import 'package:flutter/material.dart';

import 'package:sortgame/models/game.dart';
import 'package:sortgame/ui/widgets/helpers/outlined_text_widget.dart';

class ScoreIndicator extends StatelessWidget {
  const ScoreIndicator({super.key, required this.game});

  final Game game;

  @override
  Widget build(BuildContext context) {
    const Color baseColor = Color.fromARGB(255, 121, 93, 246);

    return OutlinedText(
      text: game.score.toString(),
      fontSize: 70,
      textColor: baseColor,
    );
  }
}
+33 −0
Original line number Original line Diff line number Diff line
import 'dart:ui';

extension ColorExtension on Color {
  Color darken([int percent = 40]) {
    assert(1 <= percent && percent <= 100);
    final value = 1 - percent / 100;
    return Color.fromARGB(
      alpha,
      (red * value).round(),
      (green * value).round(),
      (blue * value).round(),
    );
  }

  Color lighten([int percent = 40]) {
    assert(1 <= percent && percent <= 100);
    final value = percent / 100;
    return Color.fromARGB(
      alpha,
      (red + ((255 - red) * value)).round(),
      (green + ((255 - green) * value)).round(),
      (blue + ((255 - blue) * value)).round(),
    );
  }

  Color avg(Color other) {
    final red = (this.red + other.red) ~/ 2;
    final green = (this.green + other.green) ~/ 2;
    final blue = (this.blue + other.blue) ~/ 2;
    final alpha = (this.alpha + other.alpha) ~/ 2;
    return Color.fromARGB(alpha, red, green, blue);
  }
}

lib/utils/tools.dart

0 → 100644
+7 −0
Original line number Original line Diff line number Diff line
import 'package:flutter/foundation.dart';

void printlog(String message) {
  if (!kReleaseMode) {
    debugPrint(message);
  }
}
+311 −13
Original line number Original line Diff line number Diff line
# Generated by pub
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
packages:
  args:
    dependency: transitive
    description:
      name: args
      sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
      url: "https://pub.dev"
    source: hosted
    version: "2.5.0"
  bloc:
    dependency: transitive
    description:
      name: bloc
      sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e"
      url: "https://pub.dev"
    source: hosted
    version: "8.1.4"
  characters:
  characters:
    dependency: transitive
    dependency: transitive
    description:
    description:
@@ -9,35 +25,149 @@ packages:
      url: "https://pub.dev"
      url: "https://pub.dev"
    source: hosted
    source: hosted
    version: "1.3.0"
    version: "1.3.0"
  clock:
    dependency: transitive
    description:
      name: clock
      sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
      url: "https://pub.dev"
    source: hosted
    version: "1.1.1"
  collection:
  collection:
    dependency: transitive
    dependency: transitive
    description:
    description:
      name: collection
      name: collection
      sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
      sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
      url: "https://pub.dev"
    source: hosted
    version: "1.18.0"
  crypto:
    dependency: transitive
    description:
      name: crypto
      sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
      url: "https://pub.dev"
    source: hosted
    version: "3.0.3"
  easy_localization:
    dependency: "direct main"
    description:
      name: easy_localization
      sha256: c145aeb6584aedc7c862ab8c737c3277788f47488bfdf9bae0fe112bd0a4789c
      url: "https://pub.dev"
      url: "https://pub.dev"
    source: hosted
    source: hosted
    version: "1.17.2"
    version: "3.0.5"
  easy_logger:
    dependency: transitive
    description:
      name: easy_logger
      sha256: c764a6e024846f33405a2342caf91c62e357c24b02c04dbc712ef232bf30ffb7
      url: "https://pub.dev"
    source: hosted
    version: "0.0.2"
  equatable:
    dependency: "direct main"
    description:
      name: equatable
      sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2
      url: "https://pub.dev"
    source: hosted
    version: "2.0.5"
  ffi:
    dependency: transitive
    description:
      name: ffi
      sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
      url: "https://pub.dev"
    source: hosted
    version: "2.1.2"
  file:
    dependency: transitive
    description:
      name: file
      sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
      url: "https://pub.dev"
    source: hosted
    version: "7.0.0"
  flutter:
  flutter:
    dependency: "direct main"
    dependency: "direct main"
    description: flutter
    description: flutter
    source: sdk
    source: sdk
    version: "0.0.0"
    version: "0.0.0"
  flutter_bloc:
    dependency: "direct main"
    description:
      name: flutter_bloc
      sha256: f0ecf6e6eb955193ca60af2d5ca39565a86b8a142452c5b24d96fb477428f4d2
      url: "https://pub.dev"
    source: hosted
    version: "8.1.5"
  flutter_lints:
    dependency: "direct dev"
    description:
      name: flutter_lints
      sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
      url: "https://pub.dev"
    source: hosted
    version: "3.0.2"
  flutter_localizations:
    dependency: transitive
    description: flutter
    source: sdk
    version: "0.0.0"
  flutter_web_plugins:
    dependency: transitive
    description: flutter
    source: sdk
    version: "0.0.0"
  hive:
    dependency: "direct main"
    description:
      name: hive
      sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
      url: "https://pub.dev"
    source: hosted
    version: "2.2.3"
  hydrated_bloc:
    dependency: "direct main"
    description:
      name: hydrated_bloc
      sha256: af35b357739fe41728df10bec03aad422cdc725a1e702e03af9d2a41ea05160c
      url: "https://pub.dev"
    source: hosted
    version: "9.1.5"
  intl:
    dependency: transitive
    description:
      name: intl
      sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
      url: "https://pub.dev"
    source: hosted
    version: "0.18.1"
  lints:
    dependency: transitive
    description:
      name: lints
      sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
      url: "https://pub.dev"
    source: hosted
    version: "3.0.0"
  material_color_utilities:
  material_color_utilities:
    dependency: transitive
    dependency: transitive
    description:
    description:
      name: material_color_utilities
      name: material_color_utilities
      sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
      sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
      url: "https://pub.dev"
      url: "https://pub.dev"
    source: hosted
    source: hosted
    version: "0.5.0"
    version: "0.8.0"
  meta:
  meta:
    dependency: transitive
    dependency: transitive
    description:
    description:
      name: meta
      name: meta
      sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
      sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
      url: "https://pub.dev"
      url: "https://pub.dev"
    source: hosted
    source: hosted
    version: "1.9.1"
    version: "1.11.0"
  nested:
  nested:
    dependency: transitive
    dependency: transitive
    description:
    description:
@@ -46,19 +176,171 @@ packages:
      url: "https://pub.dev"
      url: "https://pub.dev"
    source: hosted
    source: hosted
    version: "1.0.0"
    version: "1.0.0"
  provider:
  path:
    dependency: transitive
    description:
      name: path
      sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
      url: "https://pub.dev"
    source: hosted
    version: "1.9.0"
  path_provider:
    dependency: "direct main"
    dependency: "direct main"
    description:
      name: path_provider
      sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
      url: "https://pub.dev"
    source: hosted
    version: "2.1.3"
  path_provider_android:
    dependency: transitive
    description:
      name: path_provider_android
      sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d
      url: "https://pub.dev"
    source: hosted
    version: "2.2.4"
  path_provider_foundation:
    dependency: transitive
    description:
      name: path_provider_foundation
      sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
      url: "https://pub.dev"
    source: hosted
    version: "2.3.2"
  path_provider_linux:
    dependency: transitive
    description:
      name: path_provider_linux
      sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
      url: "https://pub.dev"
    source: hosted
    version: "2.2.1"
  path_provider_platform_interface:
    dependency: transitive
    description:
      name: path_provider_platform_interface
      sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
      url: "https://pub.dev"
    source: hosted
    version: "2.1.2"
  path_provider_windows:
    dependency: transitive
    description:
      name: path_provider_windows
      sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
      url: "https://pub.dev"
    source: hosted
    version: "2.2.1"
  platform:
    dependency: transitive
    description:
      name: platform
      sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
      url: "https://pub.dev"
    source: hosted
    version: "3.1.4"
  plugin_platform_interface:
    dependency: transitive
    description:
      name: plugin_platform_interface
      sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
      url: "https://pub.dev"
    source: hosted
    version: "2.1.8"
  provider:
    dependency: transitive
    description:
    description:
      name: provider
      name: provider
      sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f
      sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
      url: "https://pub.dev"
    source: hosted
    version: "6.1.2"
  shared_preferences:
    dependency: transitive
    description:
      name: shared_preferences
      sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180
      url: "https://pub.dev"
    source: hosted
    version: "2.2.3"
  shared_preferences_android:
    dependency: transitive
    description:
      name: shared_preferences_android
      sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2"
      url: "https://pub.dev"
    source: hosted
    version: "2.2.2"
  shared_preferences_foundation:
    dependency: transitive
    description:
      name: shared_preferences_foundation
      sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c"
      url: "https://pub.dev"
    source: hosted
    version: "2.3.5"
  shared_preferences_linux:
    dependency: transitive
    description:
      name: shared_preferences_linux
      sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa"
      url: "https://pub.dev"
    source: hosted
    version: "2.3.2"
  shared_preferences_platform_interface:
    dependency: transitive
    description:
      name: shared_preferences_platform_interface
      sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
      url: "https://pub.dev"
      url: "https://pub.dev"
    source: hosted
    source: hosted
    version: "6.0.5"
    version: "2.3.2"
  shared_preferences_web:
    dependency: transitive
    description:
      name: shared_preferences_web
      sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a"
      url: "https://pub.dev"
    source: hosted
    version: "2.3.0"
  shared_preferences_windows:
    dependency: transitive
    description:
      name: shared_preferences_windows
      sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59"
      url: "https://pub.dev"
    source: hosted
    version: "2.3.2"
  sky_engine:
  sky_engine:
    dependency: transitive
    dependency: transitive
    description: flutter
    description: flutter
    source: sdk
    source: sdk
    version: "0.0.99"
    version: "0.0.99"
  synchronized:
    dependency: transitive
    description:
      name: synchronized
      sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558"
      url: "https://pub.dev"
    source: hosted
    version: "3.1.0+1"
  typed_data:
    dependency: transitive
    description:
      name: typed_data
      sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
      url: "https://pub.dev"
    source: hosted
    version: "1.3.2"
  unicons:
    dependency: "direct main"
    description:
      name: unicons
      sha256: dbfcf93ff4d4ea19b324113857e358e4882115ab85db04417a4ba1c72b17a670
      url: "https://pub.dev"
    source: hosted
    version: "2.1.1"
  vector_math:
  vector_math:
    dependency: transitive
    dependency: transitive
    description:
    description:
@@ -71,10 +353,26 @@ packages:
    dependency: transitive
    dependency: transitive
    description:
    description:
      name: web
      name: web
      sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
      sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
      url: "https://pub.dev"
    source: hosted
    version: "0.5.1"
  win32:
    dependency: transitive
    description:
      name: win32
      sha256: "0a989dc7ca2bb51eac91e8fd00851297cfffd641aa7538b165c62637ca0eaa4a"
      url: "https://pub.dev"
    source: hosted
    version: "5.4.0"
  xdg_directories:
    dependency: transitive
    description:
      name: xdg_directories
      sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
      url: "https://pub.dev"
      url: "https://pub.dev"
    source: hosted
    source: hosted
    version: "0.1.4-beta"
    version: "1.0.4"
sdks:
sdks:
  dart: ">=3.1.0-185.0.dev <4.0.0"
  dart: ">=3.3.0 <4.0.0"
  flutter: ">=1.16.0"
  flutter: ">=3.19.0"
+28 −4
Original line number Original line Diff line number Diff line
name: sortgame
name: sortgame
description: A sorting game application.
description: A sorting game application.

publish_to: 'none'
publish_to: 'none'
version: 1.0.0+1

version: 0.0.25+25


environment:
environment:
  sdk: '^3.0.0'
  sdk: '^3.0.0'
@@ -9,9 +11,31 @@ environment:
dependencies:
dependencies:
  flutter:
  flutter:
    sdk: flutter
    sdk: flutter
  provider: ^6.0.5
  easy_localization: ^3.0.1
  equatable: ^2.0.5
  flutter_bloc: ^8.1.1
  hive: ^2.2.3
  hydrated_bloc: ^9.0.0
  path_provider: ^2.0.11
  unicons: ^2.1.1

dev_dependencies:
  flutter_lints: ^3.0.1


flutter:
flutter:
  uses-material-design: true
  uses-material-design: false
  assets:
  assets:
    - assets/files/
    - assets/icons/
    - assets/translations/

  fonts:
    - family: Nunito
      fonts:
        - asset: assets/fonts/Nunito-Bold.ttf
          weight: 700
        - asset: assets/fonts/Nunito-Medium.ttf
          weight: 500
        - asset: assets/fonts/Nunito-Regular.ttf
          weight: 400
        - asset: assets/fonts/Nunito-Light.ttf
          weight: 300

scripts/data.json

0 → 100644
+3268 −0

File added.

Preview size limit exceeded, changes collapsed.

+394 −0
Original line number Original line Diff line number Diff line
<?php

function dump(string $string)
{
    echo $string . PHP_EOL;
}

function write_data(array $data, string $filename)
{
    \file_put_contents($filename, \json_encode($data));
}

function array_clean(array $array)
{
    $output = \array_unique($array);
    \sort($output);
    return $output;
}

function ask(string $prompt)
{
    dump($prompt);
    $input = \rtrim(\fgets(STDIN));
    return $input;
}

function find_missing_associations(array $mappingItems, array $categories, array $items)
{
    $missing = [];
    foreach ($items as $item) {
        $set = \array_merge(
            $mappingItems[$item]['is'],
            $mappingItems[$item]['isnot'],
            $mappingItems[$item]['na'],
        );
        foreach ($categories as $category) {
            if (!\in_array($category, $set)) {
                $missing[] = [
                    'item' => $item,
                    'category' => $category,
                ];
            }
        }
    }
    return $missing;
}

function find_exclusions(array $exclusions, string $searchedCategory)
{
    $output = [];
    foreach ($exclusions as $exclusionSet) {
        if (\is_array($exclusionSet) && \in_array($searchedCategory, $exclusionSet)) {
            foreach ($exclusionSet as $candidate) {
                if ($candidate !== $searchedCategory) {
                    $output[] = $candidate;
                }
            }
        }
    }

    return $output;
}

function clean_item_mappings(array $itemMappings)
{
    $itemMappings['is'] = array_clean($itemMappings['is']);
    $itemMappings['isnot'] = array_clean($itemMappings['isnot']);
    $itemMappings['na'] = array_clean($itemMappings['na']);

    // remove duplicates
    $itemMappings['is'] = array_clean($itemMappings['is']);

    $tmpArray = [];
    foreach (array_clean($itemMappings['isnot']) as $itemIsNot) {
        if (!\in_array($itemIsNot, $itemMappings['is'])) {
            $tmpArray[] = $itemIsNot;
        }
    }
    $itemMappings['isnot'] = $tmpArray;

    $tmpArray = [];
    foreach (array_clean($itemMappings['na']) as $itemNa) {
        if (!\in_array($itemNa, $itemMappings['is']) && !\in_array($itemNa, $itemMappings['isnot'])) {
            $tmpArray[] = $itemNa;
        }
    }
    $itemMappings['na'] = $tmpArray;

    return $itemMappings;
}

if ($argc != 2) {
    dump('Need data json file as parameter.');
    die;
}

$jsonDataFile = $argv[1];

$data = [];
if (is_file($jsonDataFile)) {
    $data = \json_decode(\file_get_contents($jsonDataFile), true);
}

if (!is_writable($jsonDataFile)) {
    dump('Output data json file is not writable.');
    die;
}

$categories = (\array_key_exists('categories', $data) && \is_array($data['categories'])) ? $data['categories'] : [];
$items = (\array_key_exists('items', $data) && \is_array($data['items'])) ? $data['items'] : [];

// Manage categories exclusions
$exclusions = (\array_key_exists('exclusions', $data) && \is_array($data['exclusions'])) ? $data['exclusions'] : [];

// Merge categories with categories found in exclusions
foreach ($exclusions as $exclusionSet) {
    foreach ($exclusionSet as $category) {
        $categories[] = $category;
    }
}
$categories = array_clean($categories);
$items = array_clean($items);

$themes = (\array_key_exists('themes', $data) && \is_array($data['themes'])) ? $data['themes'] : [];
$resources = (\array_key_exists('resources', $data) && \is_array($data['resources'])) ? $data['resources'] : [];

$data['categories'] = $categories;
$data['items'] = $items;
$data['exclusions'] = $exclusions;
$data['themes'] = $themes;
$data['resources'] = $resources;

dump('');
dump('Found ' . \count($categories) . ' unique categories.');
dump('Found ' . \count($items) . ' unique items.');
dump('Found ' . \count($exclusions) . ' exclusions sets.');
dump('Found ' . \count($themes) . ' themes.');

// Get/init mapping data
$mapping = (\array_key_exists('mapping', $data) && \is_array($data['mapping'])) ? $data['mapping'] : [];
$mappingItems = (\array_key_exists('items', $mapping) && \is_array($mapping['items'])) ? $mapping['items'] : [];

foreach ($items as $item) {
    if (!\array_key_exists($item, $mappingItems)) {
        $mappingItems[$item] = [];
    }
    if (!\array_key_exists('is', $mappingItems[$item]) || !\is_array($mappingItems[$item])) {
        $mappingItems[$item]['is'] = [];
    }
    if (!\array_key_exists('isnot', $mappingItems[$item]) || !\is_array($mappingItems[$item])) {
        $mappingItems[$item]['isnot'] = [];
    }
    if (!\array_key_exists('na', $mappingItems[$item]) || !\is_array($mappingItems[$item])) {
        $mappingItems[$item]['na'] = [];
    }

    $mappingItems[$item] = clean_item_mappings($mappingItems[$item]);
}

// TODO: Should check/add unkown items from current mapping
\ksort($mappingItems);
$data['mapping'] = [
    'items' => $mappingItems,
];

function showCategories($categories)
{
    dump(\join("\n", $categories));
}

function showItems($items)
{
    dump(\join("\n", $items));
}

function showExclusions($exclusions)
{
    foreach ($exclusions as $exclusionSet) {
        dump(\join(', ', $exclusionSet));
    }
}

function showThemes($theme)
{
    foreach ($theme as $name => $categories) {
        dump($name . ': ' . \join(', ', $categories));
    }
}

function showMappings($mappingItems)
{
    $columnsWidths = [
        'items' => 0,
    ];

    $items = \array_keys($mappingItems);
    $categories = [];
    foreach ($mappingItems as $item => $mapping) {
        if ($columnsWidths['items'] < \mb_strlen($item)) {
            $columnsWidths['items'] = \mb_strlen($item);
        }

        foreach (\array_merge($mapping['is'], $mapping['isnot'], $mapping['na']) as $category) {
            if (!\in_array($category, $categories)) {
                $categories[] = $category;
                if (!\array_key_exists($category, $columnsWidths)) {
                    $columnsWidths[$category] = 0;
                }
                if ($columnsWidths[$category] < \mb_strlen($category)) {
                    $columnsWidths[$category] = \mb_strlen($category);
                }
            }
        }
    }

    $strIs = '✅';
    $strIsNot = '❌';
    $strNa = '⛔';

    // separator
    $line = [
        \mb_str_pad('', $columnsWidths['items'], '-', STR_PAD_BOTH),
    ];
    foreach ($categories as $category) {
        $line[] = \mb_str_pad('', $columnsWidths[$category], '-', STR_PAD_BOTH);
    }
    dump('--' . \join('---', $line) . '--');

    // header
    $line = [
        \mb_str_pad('', $columnsWidths['items'], ' ', STR_PAD_BOTH),
    ];
    foreach ($categories as $category) {
        $line[] = \mb_str_pad($category, $columnsWidths[$category], ' ', STR_PAD_BOTH);
    }
    dump('| ' . \join(' | ', $line) . ' |');

    // separator
    $line = [
        \mb_str_pad('', $columnsWidths['items'], '-', STR_PAD_BOTH),
    ];
    foreach ($categories as $category) {
        $line[] = \mb_str_pad('', $columnsWidths[$category], '-', STR_PAD_BOTH);
    }
    dump('|-' . \join('-|-', $line) . '-|');

    foreach ($items as $item) {
        $line = [
            \mb_str_pad($item, $columnsWidths['items'], ' ', STR_PAD_RIGHT),
        ];
        foreach ($categories as $category) {
            $value = '';
            if (\in_array($category, $mappingItems[$item]['is'])) {
                $value = $strIs;
            } elseif (\in_array($category, $mappingItems[$item]['isnot'])) {
                $value = $strIsNot;
            } elseif (\in_array($category, $mappingItems[$item]['na'])) {
                $value = $strNa;
            }
            $line[] = \mb_str_pad($value, $columnsWidths[$category], ' ', STR_PAD_BOTH);
        }
        dump('| ' . \join(' | ', $line) . ' |');
    }

    // separator
    $line = [
        \mb_str_pad('', $columnsWidths['items'], '-', STR_PAD_BOTH),
    ];
    foreach ($categories as $category) {
        $line[] = \mb_str_pad('', $columnsWidths[$category], '-', STR_PAD_BOTH);
    }
    dump('--' . \join('---', $line) . '--');
}

function editMappings($mappingItems, $categories, $items, $exclusions)
{
    // Set missing associations
    $exitEditMappings = false;
    $missing = find_missing_associations($mappingItems, $categories, $items);
    while ((\count($missing) !== 0) && ($exitEditMappings === false)) {
        dump('');
        dump('Missing associations: ' . \count($missing));
        dump('');
        $picked = $missing[mt_rand(0, \count($missing) - 1)];

        $item = $picked['item'];
        $category = $picked['category'];

        $question = 'Is "' . $item . '" can be categorised as "' . $category . '"?';

        $ex = find_exclusions($exclusions, $category);
        if (\count($ex) !== 0) {
            $question .= ' (and apply accordingly to "' . join('" and "', $ex) . '")';
        }

        dump($question);
        $answer = ask('1: yes ; 2: no ; 3: n/a ; 0: exit');

        switch ($answer) {
            case '0':
                $exitEditMappings = true;
                break;
            case '1':
                dump(' -> "' . $item . '" is "' . $category . '"');
                $mappingItems[$item]['is'][] = $category;
                // apply "is not" to each other
                foreach ($ex as $exclusion) {
                    dump(' -> "' . $item . '" is not "' . $exclusion . '"');
                    $mappingItems[$item]['isnot'][] = $exclusion;
                }
                break;
            case '2':
                dump(' -> "' . $item . '" is not "' . $category . '"');
                $mappingItems[$item]['isnot'][] = $category;
                // apply "is" only if one exclusion
                if (\count($ex) === 1) {
                    foreach ($ex as $exclusion) {
                        dump(' -> "' . $item . '" is "' . $exclusion . '"');
                        $mappingItems[$item]['is'][] = $exclusion;
                    }
                }
                break;
            case '3':
                dump(' -> "' . $item . '" does not apply as "is or is not" "' . $category . '"');
                $mappingItems[$item]['na'][] = $category;
                foreach ($ex as $excludedCategory) {
                    dump(' -> "' . $item . '" does not apply as "is or is not" "' . $excludedCategory . '"');
                    $mappingItems[$item]['na'][] = $excludedCategory;
                }
                break;

            default:
                dump('wut? skipping...');
                break;
        }

        $mappingItems[$item] = clean_item_mappings($mappingItems[$item]);

        $missing = find_missing_associations($mappingItems, $categories, $items);
    }

    return $mappingItems;
}

// Main loop
$exitMainLoop = false;
while ($exitMainLoop === false) {
    dump('');

    $missing = find_missing_associations($mappingItems, $categories, $items);

    $menu = [
        '0: save and exit',
        '',
        '1: show categories (' . \count($categories) . ' found)',
        '2: show items (' . \count($items) . ' found)',
        '3: show exclusions (' . \count($exclusions) . ' found)',
        '4: show themes (' . \count($themes) . ' found)',
        '5: show mappings (' . \count($mappingItems) . ' found)',
        '',
        '6: complete mappings (' . \count($missing) . ' missing)',
    ];
    $answer = ask(\join("\n", $menu));

    switch ($answer) {
        case '0':
            $exitMainLoop = true;
            break;
        case '1':
            showCategories($categories);
            break;
        case '2':
            showItems($items);
            break;
        case '3':
            showExclusions($exclusions);
            break;
        case '4':
            showThemes($themes);
            break;
        case '5':
            showMappings($mappingItems);
            break;
        case '6':
            $data['mapping']['items'] = editMappings($mappingItems, $categories, $items, $exclusions);
            break;
        default:
            break;
    }
}

write_data($data, $jsonDataFile);

echo "ok, done." . PHP_EOL;

scripts/manage_data.sh

0 → 100755
+30 −0
Original line number Original line Diff line number Diff line
#!/usr/bin/env bash

command -v jq >/dev/null 2>&1 || { echo >&2 "I require jq (json parser) but it's not installed. Aborting."; exit 1; }

CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
BASE_DIR="$(dirname "${CURRENT_DIR}")"

DATA_FILE="${CURRENT_DIR}/data.json"
touch "${DATA_FILE}"

# backup current file
# NOW="$(date '+%Y%m%d_%H%M%S')"
# cp "${DATA_FILE}" "${DATA_FILE}.bak_${NOW}.json"

cd "${CURRENT_DIR}"
php manage_data.php "${DATA_FILE}"

# format json file
cat "${DATA_FILE}" | jq >"${DATA_FILE}.tmp"
mv "${DATA_FILE}.tmp" "${DATA_FILE}"

# inject json file in app code
GAME_DATA_DART_FILE="${BASE_DIR}/lib/data/game_data.dart"
echo "class GameData {" >"${GAME_DATA_DART_FILE}"
echo "  static const Map<String, dynamic> data = $(cat "${DATA_FILE}");" >>"${GAME_DATA_DART_FILE}"
echo "}" >>"${GAME_DATA_DART_FILE}"

dart format "${GAME_DATA_DART_FILE}"

# cat "${DATA_FILE}"
 No newline at end of file