From 552ad84f100f9cfd5851d5c02e3ad970a1a90fd9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Beno=C3=AEt=20Harrault?= <benoit@harrault.fr>
Date: Sun, 18 Feb 2024 22:46:55 +0100
Subject: [PATCH] Use and apply flutter lints

---
 analysis_options.yaml                   |  1 +
 android/gradle.properties               |  4 +-
 lib/config/menu.dart                    | 28 ++++-----
 lib/cubit/api_cubit.dart                |  2 +-
 lib/cubit/api_state.dart                |  1 +
 lib/cubit/settings_cubit.dart           |  6 +-
 lib/main.dart                           |  2 +-
 lib/models/api_data.dart                |  9 +--
 lib/models/game/game.dart               | 41 ++++++-------
 lib/models/game/game_board.dart         |  7 ++-
 lib/models/game/game_cell.dart          |  5 +-
 lib/models/game/game_settings.dart      |  7 ++-
 lib/models/interface_type.dart          |  4 +-
 lib/network/api.dart                    |  6 +-
 lib/repository/api.dart                 |  2 +-
 lib/ui/painters/cell_painter.dart       |  2 +-
 lib/ui/painters/graph_painter.dart      |  4 +-
 lib/ui/screens/about_page.dart          |  8 +--
 lib/ui/screens/api_page.dart            |  6 +-
 lib/ui/screens/camera_page.dart         |  6 +-
 lib/ui/screens/demo_page.dart           | 28 ++++-----
 lib/ui/screens/game_page.dart           |  8 +--
 lib/ui/screens/graph_page.dart          | 80 ++++++++++++-------------
 lib/ui/screens/settings_page.dart       |  2 +-
 lib/ui/skeleton.dart                    |  2 +-
 lib/ui/widgets/api_data.dart            | 14 ++---
 lib/ui/widgets/app_bar.dart             |  4 +-
 lib/ui/widgets/debug_bloc.dart          |  4 +-
 lib/ui/widgets/error.dart               |  4 +-
 lib/ui/widgets/game/game_board.dart     | 18 +++---
 lib/ui/widgets/game/game_score.dart     | 18 +++---
 lib/ui/widgets/game/game_settings.dart  |  2 +-
 lib/ui/widgets/header_app.dart          | 14 ++---
 lib/ui/widgets/settings_form.dart       | 22 +++----
 lib/ui/widgets/take_picture_widget.dart | 56 ++++++++---------
 lib/utils/picture_storage.dart          |  4 +-
 pubspec.lock                            | 32 +++++++---
 pubspec.yaml                            |  6 +-
 38 files changed, 245 insertions(+), 224 deletions(-)
 create mode 100644 analysis_options.yaml

diff --git a/analysis_options.yaml b/analysis_options.yaml
new file mode 100644
index 0000000..f9b3034
--- /dev/null
+++ b/analysis_options.yaml
@@ -0,0 +1 @@
+include: package:flutter_lints/flutter.yaml
diff --git a/android/gradle.properties b/android/gradle.properties
index 84e1e41..99e5b79 100644
--- a/android/gradle.properties
+++ b/android/gradle.properties
@@ -1,5 +1,5 @@
 org.gradle.jvmargs=-Xmx1536M
 android.useAndroidX=true
 android.enableJetifier=true
-app.versionName=1.0.45
-app.versionCode=46
+app.versionName=1.0.46
+app.versionCode=47
diff --git a/lib/config/menu.dart b/lib/config/menu.dart
index 45c0692..b06e9eb 100644
--- a/lib/config/menu.dart
+++ b/lib/config/menu.dart
@@ -24,39 +24,39 @@ class MenuItem {
 
 class Menu {
   static List<MenuItem> items = [
-    MenuItem(
+    const MenuItem(
       code: 'bottom_nav_sample',
-      icon: const Icon(UniconsLine.image),
+      icon: Icon(UniconsLine.image),
       page: DemoPage(),
     ),
-    MenuItem(
+    const MenuItem(
       code: 'bottom_nav_api',
-      icon: const Icon(UniconsLine.globe),
+      icon: Icon(UniconsLine.globe),
       page: ApiPage(),
     ),
-    MenuItem(
+    const MenuItem(
       code: 'bottom_nav_camera',
-      icon: const Icon(UniconsLine.camera),
+      icon: Icon(UniconsLine.camera),
       page: CameraPage(),
     ),
-    MenuItem(
+    const MenuItem(
       code: 'bottom_nav_chart',
-      icon: const Icon(UniconsLine.pen),
+      icon: Icon(UniconsLine.pen),
       page: GraphPage(),
     ),
-    MenuItem(
+    const MenuItem(
       code: 'bottom_nav_game',
-      icon: const Icon(UniconsLine.star),
+      icon: Icon(UniconsLine.star),
       page: GamePage(),
     ),
-    MenuItem(
+    const MenuItem(
       code: 'bottom_nav_settings',
-      icon: const Icon(UniconsLine.setting),
+      icon: Icon(UniconsLine.setting),
       page: SettingsPage(),
     ),
-    MenuItem(
+    const MenuItem(
       code: 'bottom_nav_about',
-      icon: const Icon(UniconsLine.info_circle),
+      icon: Icon(UniconsLine.info_circle),
       page: AboutPage(),
     ),
   ];
diff --git a/lib/cubit/api_cubit.dart b/lib/cubit/api_cubit.dart
index 27b9f29..5fe5dd7 100644
--- a/lib/cubit/api_cubit.dart
+++ b/lib/cubit/api_cubit.dart
@@ -27,7 +27,7 @@ class ApiDataCubit extends HydratedCubit<ApiDataState> {
         }
       }
     }
-    print('emit new state: ' + stateAsString);
+    print('emit new state: $stateAsString');
 
     emit(state);
   }
diff --git a/lib/cubit/api_state.dart b/lib/cubit/api_state.dart
index a930be0..86cbe00 100644
--- a/lib/cubit/api_state.dart
+++ b/lib/cubit/api_state.dart
@@ -16,6 +16,7 @@ class ApiDataFetchInitial extends ApiDataState {}
 class ApiDataFetchLoading extends ApiDataState {}
 
 class ApiDataFetchLoaded extends ApiDataState {
+  @override
   final ApiData data;
 
   const ApiDataFetchLoaded({
diff --git a/lib/cubit/settings_cubit.dart b/lib/cubit/settings_cubit.dart
index 01d3b94..35d6c2d 100644
--- a/lib/cubit/settings_cubit.dart
+++ b/lib/cubit/settings_cubit.dart
@@ -35,9 +35,9 @@ class SettingsCubit extends HydratedCubit<SettingsState> {
     InterfaceType? interfaceType,
   }) {
     emit(SettingsState(
-      apiUrl: apiUrl != null ? apiUrl : state.apiUrl,
-      securityToken: securityToken != null ? securityToken : state.securityToken,
-      interfaceType: interfaceType != null ? interfaceType : state.interfaceType,
+      apiUrl: apiUrl ?? state.apiUrl,
+      securityToken: securityToken ?? state.securityToken,
+      interfaceType: interfaceType ?? state.interfaceType,
     ));
   }
 
diff --git a/lib/main.dart b/lib/main.dart
index 6b6e38b..ca105a7 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -63,7 +63,7 @@ class MyApp extends StatelessWidget {
       child: MaterialApp(
         title: 'Random application',
         theme: appTheme,
-        home: SkeletonScreen(),
+        home: const SkeletonScreen(),
         localizationsDelegates: context.localizationDelegates,
         supportedLocales: context.supportedLocales,
         locale: context.locale,
diff --git a/lib/models/api_data.dart b/lib/models/api_data.dart
index 8124108..0f8b8fc 100644
--- a/lib/models/api_data.dart
+++ b/lib/models/api_data.dart
@@ -21,13 +21,14 @@ class ApiData {
 
   Map<String, dynamic>? toJson() {
     return <String, dynamic>{
-      'number': this.number ?? 0,
-      'md5': this.md5 ?? '',
-      'updatedAt': this.updatedAt?.toString(),
+      'number': number ?? 0,
+      'md5': md5 ?? '',
+      'updatedAt': updatedAt?.toString(),
     };
   }
 
+  @override
   String toString() {
-    return jsonEncode(this.toJson());
+    return jsonEncode(toJson());
   }
 }
diff --git a/lib/models/game/game.dart b/lib/models/game/game.dart
index bf777d7..e099feb 100644
--- a/lib/models/game/game.dart
+++ b/lib/models/game/game.dart
@@ -37,57 +37,58 @@ class Game {
   }
 
   void stop() {
-    this.isRunning = false;
-    this.isFinished = true;
+    isRunning = false;
+    isFinished = true;
   }
 
   GameCell getCell(int x, int y) {
-    return this.board.cells[y][x];
+    return board.cells[y][x];
   }
 
   int? getCellValue(int x, int y) {
-    return this.getCell(x, y).value;
+    return getCell(x, y).value;
   }
 
   void updateCellValue(int x, int y, int? value) {
-    this.board.cells[y][x].value = value;
+    board.cells[y][x].value = value;
   }
 
   void setRandomCellValue(int x, int y, GameSettings settings) {
     final int maxValue = settings.colorsCount;
-    final rand = new Random();
+    final rand = Random();
     int value = 1 + rand.nextInt(maxValue);
 
-    this.board.cells[y][x].value = value;
+    board.cells[y][x].value = value;
   }
 
   void increaseMovesCount() {
-    this.movesCount += 1;
+    movesCount += 1;
   }
 
   void increaseScore(int? count) {
-    this.score += (count ?? 0);
+    score += (count ?? 0);
   }
 
+  @override
   String toString() {
-    return 'Game(' + this.toJson().toString() + ')';
+    return 'Game(${toJson()})';
   }
 
   Map<String, dynamic>? toJson() {
     return <String, dynamic>{
-      'board': this.board.toJson(),
-      'settings': this.settings.toJson(),
-      'isRunning': this.isRunning,
-      'isFinished': this.isFinished,
-      'availableBlocksCount': this.availableBlocksCount,
-      'movesCount': this.movesCount,
-      'score': this.score,
+      'board': board.toJson(),
+      'settings': settings.toJson(),
+      'isRunning': isRunning,
+      'isFinished': isFinished,
+      'availableBlocksCount': availableBlocksCount,
+      'movesCount': movesCount,
+      'score': score,
     };
   }
 
   void dump() {
-    GameBoard.printGrid(this.board.cells);
-    print(this.settings.toJson());
-    print(this.toJson());
+    GameBoard.printGrid(board.cells);
+    print(settings.toJson());
+    print(toJson());
   }
 }
diff --git a/lib/models/game/game_board.dart b/lib/models/game/game_board.dart
index 6565494..41257e0 100644
--- a/lib/models/game/game_board.dart
+++ b/lib/models/game/game_board.dart
@@ -19,7 +19,7 @@ class GameBoard {
     final int boardSizeVertical = gameSettings.boardSize;
     final int maxValue = gameSettings.colorsCount;
 
-    final rand = new Random();
+    final rand = Random();
 
     List<List<GameCell>> grid = [];
     for (var rowIndex = 0; rowIndex < boardSizeVertical; rowIndex++) {
@@ -49,13 +49,14 @@ class GameBoard {
     print('');
   }
 
+  @override
   String toString() {
-    return 'Board(' + this.toJson().toString() + ')';
+    return 'Board(${toJson()})';
   }
 
   Map<String, dynamic>? toJson() {
     return <String, dynamic>{
-      'cells': this.cells.toString(),
+      'cells': cells.toString(),
     };
   }
 }
diff --git a/lib/models/game/game_cell.dart b/lib/models/game/game_cell.dart
index 25667cc..4cba17b 100644
--- a/lib/models/game/game_cell.dart
+++ b/lib/models/game/game_cell.dart
@@ -5,13 +5,14 @@ class GameCell {
     this.value,
   );
 
+  @override
   String toString() {
-    return 'Cell(' + this.toJson().toString() + ')';
+    return 'Cell(${toJson()})';
   }
 
   Map<String, dynamic>? toJson() {
     return <String, dynamic>{
-      'value': this.value,
+      'value': value,
     };
   }
 }
diff --git a/lib/models/game/game_settings.dart b/lib/models/game/game_settings.dart
index 4610c78..2b108da 100644
--- a/lib/models/game/game_settings.dart
+++ b/lib/models/game/game_settings.dart
@@ -32,14 +32,15 @@ class GameSettings {
     );
   }
 
+  @override
   String toString() {
-    return 'GameSettings(' + this.toJson().toString() + ')';
+    return 'GameSettings(${toJson()})';
   }
 
   Map<String, dynamic>? toJson() {
     return <String, dynamic>{
-      'boardSize': this.boardSize,
-      'colorsCount': this.colorsCount,
+      'boardSize': boardSize,
+      'colorsCount': colorsCount,
     };
   }
 }
diff --git a/lib/models/interface_type.dart b/lib/models/interface_type.dart
index 05dc342..2976321 100644
--- a/lib/models/interface_type.dart
+++ b/lib/models/interface_type.dart
@@ -8,7 +8,7 @@ enum InterfaceType {
 
 class InterfaceTypes {
   static List<Widget> selectWidgets = <Widget>[
-    Text('interface_type_basic').tr(),
-    Text('interface_type_expert').tr(),
+    const Text('interface_type_basic').tr(),
+    const Text('interface_type_expert').tr(),
   ];
 }
diff --git a/lib/network/api.dart b/lib/network/api.dart
index 23a6207..aa75936 100644
--- a/lib/network/api.dart
+++ b/lib/network/api.dart
@@ -10,10 +10,10 @@ class ApiService {
   final String baseUrl = 'https://tools.harrault.fr/tools/api';
 
   Future<Response?> getData() async {
-    String url = baseUrl + '/get.php';
+    String url = '$baseUrl/get.php';
     try {
-      print('fetching api data... ' + url);
-      final Response? response = await _dio.get(url);
+      print('fetching api data... $url');
+      final Response response = await _dio.get(url);
       print('ok got api response.');
       print(response);
       return response;
diff --git a/lib/repository/api.dart b/lib/repository/api.dart
index ce3a0ef..d6548b0 100644
--- a/lib/repository/api.dart
+++ b/lib/repository/api.dart
@@ -8,7 +8,7 @@ class ApiRepository {
 
   Future<ApiData> getApiData() async {
     print('(getApiData) delayed API call...');
-    final response = await Future.delayed(Duration(milliseconds: 1000))
+    final response = await Future.delayed(const Duration(milliseconds: 1000))
         .then((value) => apiService.getData());
     if (response != null) {
       print('(getApiData) got api response');
diff --git a/lib/ui/painters/cell_painter.dart b/lib/ui/painters/cell_painter.dart
index a2cf510..851f70e 100644
--- a/lib/ui/painters/cell_painter.dart
+++ b/lib/ui/painters/cell_painter.dart
@@ -30,7 +30,7 @@ class CellPainter extends CustomPainter {
 
     const borderWidth = 0.05;
 
-    final Rect baseSquare = Rect.fromPoints(Offset(0, 0), Offset(size.width, size.height));
+    final Rect baseSquare = Rect.fromPoints(const Offset(0, 0), Offset(size.width, size.height));
 
     final paintBaseSquare = Paint()
       ..style = PaintingStyle.fill
diff --git a/lib/ui/painters/graph_painter.dart b/lib/ui/painters/graph_painter.dart
index ac9358c..38a30c6 100644
--- a/lib/ui/painters/graph_painter.dart
+++ b/lib/ui/painters/graph_painter.dart
@@ -35,14 +35,14 @@ class GraphPainter extends CustomPainter {
     paintBackground.color = Colors.black;
     paintBackground.style = PaintingStyle.fill;
 
-    final Rect rectBackground = Rect.fromPoints(Offset(0, 0), Offset(size.width, size.height));
+    final Rect rectBackground = Rect.fromPoints(const Offset(0, 0), Offset(size.width, size.height));
     canvas.drawRect(rectBackground, paintBackground);
 
     // Draw some lines
     Paint paintLine = Paint();
     paintLine.style = PaintingStyle.fill;
 
-    for (int i = 0; i < this.linesCount; i++) {
+    for (int i = 0; i < linesCount; i++) {
       paintLine.color = getRandomColor();
       paintLine.strokeWidth = Random().nextDouble() * 4 + 2;
 
diff --git a/lib/ui/screens/about_page.dart b/lib/ui/screens/about_page.dart
index a93505a..f183a4a 100644
--- a/lib/ui/screens/about_page.dart
+++ b/lib/ui/screens/about_page.dart
@@ -14,15 +14,15 @@ class AboutPage extends StatelessWidget {
       crossAxisAlignment: CrossAxisAlignment.start,
       mainAxisSize: MainAxisSize.max,
       children: <Widget>[
-        SizedBox(height: 8),
-        AppHeader(text: 'about_title'),
-        Text('about_content').tr(),
+        const SizedBox(height: 8),
+        const AppHeader(text: 'about_title'),
+        const Text('about_content').tr(),
         FutureBuilder<PackageInfo>(
           future: PackageInfo.fromPlatform(),
           builder: (context, snapshot) {
             switch (snapshot.connectionState) {
               case ConnectionState.done:
-                return Text('about_version').tr(
+                return const Text('about_version').tr(
                   namedArgs: {
                     'version': snapshot.data!.version,
                   },
diff --git a/lib/ui/screens/api_page.dart b/lib/ui/screens/api_page.dart
index 9b02532..22dc963 100644
--- a/lib/ui/screens/api_page.dart
+++ b/lib/ui/screens/api_page.dart
@@ -17,9 +17,9 @@ class ApiPage extends StatelessWidget {
         padding: const EdgeInsets.symmetric(horizontal: 4),
         physics: const BouncingScrollPhysics(),
         children: <Widget>[
-          SizedBox(height: 8),
-          AppHeader(text: 'api_page_title'),
-          SizedBox(height: 20),
+          const SizedBox(height: 8),
+          const AppHeader(text: 'api_page_title'),
+          const SizedBox(height: 20),
           BlocBuilder<ApiDataCubit, ApiDataState>(
             builder: (BuildContext context, ApiDataState apiDataState) {
               return ApiDataWidget(
diff --git a/lib/ui/screens/camera_page.dart b/lib/ui/screens/camera_page.dart
index 98c2282..828c6e2 100644
--- a/lib/ui/screens/camera_page.dart
+++ b/lib/ui/screens/camera_page.dart
@@ -13,12 +13,12 @@ class CameraPage extends StatelessWidget {
   Widget build(BuildContext context) {
     return Material(
       color: Theme.of(context).colorScheme.background,
-      child: Column(
+      child: const Column(
         mainAxisAlignment: MainAxisAlignment.start,
         crossAxisAlignment: CrossAxisAlignment.center,
         children: <Widget>[
-          const SizedBox(height: 8),
-          const TakePictureWidget(),
+          SizedBox(height: 8),
+          TakePictureWidget(),
         ],
       ),
     );
diff --git a/lib/ui/screens/demo_page.dart b/lib/ui/screens/demo_page.dart
index 8d10142..2c7269e 100644
--- a/lib/ui/screens/demo_page.dart
+++ b/lib/ui/screens/demo_page.dart
@@ -18,17 +18,17 @@ class DemoPage extends StatelessWidget {
         padding: const EdgeInsets.symmetric(horizontal: 4),
         physics: const BouncingScrollPhysics(),
         children: <Widget>[
-          SizedBox(height: 8),
-          AppHeader(text: 'TOP'),
-          SizedBox(height: 20),
+          const SizedBox(height: 8),
+          const AppHeader(text: 'TOP'),
+          const SizedBox(height: 20),
           persistedCounterBlock(BlocProvider.of<DataCubit>(context)),
-          SizedBox(height: 20),
+          const SizedBox(height: 20),
           testBlocConsumer(),
           testBlocBuilder(),
-          SizedBox(height: 20),
+          const SizedBox(height: 20),
           fakeApiCall(),
-          SizedBox(height: 20),
-          AppHeader(text: 'BOTTOM'),
+          const SizedBox(height: 20),
+          const AppHeader(text: 'BOTTOM'),
         ],
       ),
     );
@@ -40,13 +40,13 @@ class DemoPage extends StatelessWidget {
       crossAxisAlignment: CrossAxisAlignment.center,
       children: [
         IconButton(
-          icon: Icon(UniconsSolid.arrow_circle_down),
+          icon: const Icon(UniconsSolid.arrow_circle_down),
           color: appTheme.primaryColor,
           onPressed: () => dataCubit.updateCounter(-1),
         ),
         testBlocConsumer(),
         IconButton(
-          icon: Icon(UniconsSolid.arrow_circle_up),
+          icon: const Icon(UniconsSolid.arrow_circle_up),
           color: appTheme.primaryColor,
           onPressed: () => dataCubit.updateCounter(1),
         ),
@@ -61,9 +61,9 @@ class DemoPage extends StatelessWidget {
           mainAxisAlignment: MainAxisAlignment.center,
           crossAxisAlignment: CrossAxisAlignment.center,
           children: [
-            Text('apiUrl: ' + settingsSate.apiUrl.toString()),
-            Text('securityToken: ' + (settingsSate.securityToken.toString())),
-            Text('interfaceType: ' + settingsSate.interfaceType.toString()),
+            Text('apiUrl: ${settingsSate.apiUrl}'),
+            Text('securityToken: ${settingsSate.securityToken}'),
+            Text('interfaceType: ${settingsSate.interfaceType}'),
           ],
         );
       },
@@ -77,7 +77,7 @@ class DemoPage extends StatelessWidget {
       },
       builder: (context, dataState) {
         // return widget here based on state
-        return Text('BlocConsumer / ' + dataState.toString());
+        return Text('BlocConsumer / $dataState');
       },
     );
   }
@@ -94,7 +94,7 @@ class DemoPage extends StatelessWidget {
     return BlocBuilder<DataCubit, DataState>(
       builder: (context, dataState) {
         // return widget here based on state
-        return Text('BlocBuilder / ' + dataState.toString());
+        return Text('BlocBuilder / $dataState');
       },
     );
   }
diff --git a/lib/ui/screens/game_page.dart b/lib/ui/screens/game_page.dart
index 7822504..1f845c8 100644
--- a/lib/ui/screens/game_page.dart
+++ b/lib/ui/screens/game_page.dart
@@ -24,7 +24,7 @@ class _GamePageState extends State<GamePage> {
           onPressed: () {
             gameCubit.updateGameState(Game.createNew());
           },
-          icon: Icon(UniconsSolid.star),
+          icon: const Icon(UniconsSolid.star),
           color: Colors.white,
         )
       ];
@@ -38,7 +38,7 @@ class _GamePageState extends State<GamePage> {
             gameCubit.updateGameState(currentGame);
             setState(() {});
           },
-          icon: Icon(UniconsLine.exit),
+          icon: const Icon(UniconsLine.exit),
           color: Colors.white,
         ));
       }
@@ -65,9 +65,9 @@ class _GamePageState extends State<GamePage> {
             gameState.game?.isRunning == true
                 ? GameBoardWidget(
                     game: gameState.game!,
-                    widgetSize: Size(boardWidgetWidth, boardWidgetHeight),
+                    widgetSize: const Size(boardWidgetWidth, boardWidgetHeight),
                   )
-                : GameSettingsWidget(),
+                : const GameSettingsWidget(),
             buildGameActionsBloc(context),
           ],
         );
diff --git a/lib/ui/screens/graph_page.dart b/lib/ui/screens/graph_page.dart
index 892151e..6a4c247 100644
--- a/lib/ui/screens/graph_page.dart
+++ b/lib/ui/screens/graph_page.dart
@@ -17,50 +17,48 @@ class _GraphPageState extends State<GraphPage> {
     double boardWidth = MediaQuery.of(context).size.width;
 
     return SizedBox.expand(
-      child: Container(
-        child: FittedBox(
-          fit: BoxFit.contain,
-          alignment: Alignment.center,
-          child: SizedBox(
-            height: (MediaQuery.of(context).size.height),
-            width: (MediaQuery.of(context).size.width),
-            child: SafeArea(
-              child: Column(
-                mainAxisAlignment: MainAxisAlignment.start,
-                crossAxisAlignment: CrossAxisAlignment.center,
-                children: [
-                  Center(
-                    child: GestureDetector(
-                      onTapUp: (details) {
-                        double xTap = details.localPosition.dx;
-                        double yTap = details.localPosition.dy;
-                        print('[' + xTap.toString() + ',' + yTap.toString() + ']');
-                      },
-                      child: Container(
-                        margin: EdgeInsets.all(4),
-                        padding: EdgeInsets.all(4),
-                        child: CustomPaint(
-                          size: Size(boardWidth, boardWidth),
-                          willChange: false,
-                          painter: GraphPainter(linesCount: _currentSliderValue.toInt()),
-                        ),
+      child: FittedBox(
+        fit: BoxFit.contain,
+        alignment: Alignment.center,
+        child: SizedBox(
+          height: (MediaQuery.of(context).size.height),
+          width: (MediaQuery.of(context).size.width),
+          child: SafeArea(
+            child: Column(
+              mainAxisAlignment: MainAxisAlignment.start,
+              crossAxisAlignment: CrossAxisAlignment.center,
+              children: [
+                Center(
+                  child: GestureDetector(
+                    onTapUp: (details) {
+                      double xTap = details.localPosition.dx;
+                      double yTap = details.localPosition.dy;
+                      print('[$xTap,$yTap]');
+                    },
+                    child: Container(
+                      margin: const EdgeInsets.all(4),
+                      padding: const EdgeInsets.all(4),
+                      child: CustomPaint(
+                        size: Size(boardWidth, boardWidth),
+                        willChange: false,
+                        painter: GraphPainter(linesCount: _currentSliderValue.toInt()),
                       ),
                     ),
                   ),
-                  Slider(
-                    value: _currentSliderValue,
-                    min: 10,
-                    max: 50,
-                    divisions: 5,
-                    label: _currentSliderValue.round().toString(),
-                    onChanged: (double value) {
-                      setState(() {
-                        _currentSliderValue = value;
-                      });
-                    },
-                  ),
-                ],
-              ),
+                ),
+                Slider(
+                  value: _currentSliderValue,
+                  min: 10,
+                  max: 50,
+                  divisions: 5,
+                  label: _currentSliderValue.round().toString(),
+                  onChanged: (double value) {
+                    setState(() {
+                      _currentSliderValue = value;
+                    });
+                  },
+                ),
+              ],
             ),
           ),
         ),
diff --git a/lib/ui/screens/settings_page.dart b/lib/ui/screens/settings_page.dart
index 3e0195d..195d012 100644
--- a/lib/ui/screens/settings_page.dart
+++ b/lib/ui/screens/settings_page.dart
@@ -8,7 +8,7 @@ class SettingsPage extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    return Column(
+    return const Column(
       mainAxisAlignment: MainAxisAlignment.start,
       crossAxisAlignment: CrossAxisAlignment.start,
       mainAxisSize: MainAxisSize.max,
diff --git a/lib/ui/skeleton.dart b/lib/ui/skeleton.dart
index e863619..513b39d 100644
--- a/lib/ui/skeleton.dart
+++ b/lib/ui/skeleton.dart
@@ -19,7 +19,7 @@ class _SkeletonScreenState extends State<SkeletonScreen> {
   Widget build(BuildContext context) {
     return Scaffold(
       extendBodyBehindAppBar: false,
-      appBar: StandardAppBar(),
+      appBar: const StandardAppBar(),
       body: Swiper(
         itemCount: Menu.itemsCount,
         itemBuilder: (BuildContext context, int index) {
diff --git a/lib/ui/widgets/api_data.dart b/lib/ui/widgets/api_data.dart
index 3690792..669de42 100644
--- a/lib/ui/widgets/api_data.dart
+++ b/lib/ui/widgets/api_data.dart
@@ -19,19 +19,19 @@ class ApiDataWidget extends StatelessWidget {
       mainAxisAlignment: MainAxisAlignment.center,
       crossAxisAlignment: CrossAxisAlignment.start,
       children: [
-        Text('status: ' + status.toString()),
-        Text('number: ' + (this.data?.number.toString() ?? '')),
-        Text('md5: ' + (this.data?.md5.toString() ?? '')),
-        Text('updatedAt: ' + (this.data?.updatedAt.toString() ?? '')),
+        Text('status: $status'),
+        Text('number: ${data?.number.toString() ?? ''}'),
+        Text('md5: ${data?.md5.toString() ?? ''}'),
+        Text('updatedAt: ${data?.updatedAt.toString() ?? ''}'),
         status == ApiStatus.loading
-            ? Container(
+            ? const SizedBox(
                 width: 12,
                 height: 12,
-                child: const CircularProgressIndicator(),
+                child: CircularProgressIndicator(),
               )
             : const SizedBox.shrink(),
         status == ApiStatus.failed
-            ? ShowErrorWidget(message: 'state.failure.message')
+            ? const ShowErrorWidget(message: 'state.failure.message')
             : const SizedBox.shrink(),
       ],
     );
diff --git a/lib/ui/widgets/app_bar.dart b/lib/ui/widgets/app_bar.dart
index 648a641..0d60da6 100644
--- a/lib/ui/widgets/app_bar.dart
+++ b/lib/ui/widgets/app_bar.dart
@@ -8,8 +8,8 @@ class StandardAppBar extends StatelessWidget implements PreferredSizeWidget {
   @override
   Widget build(BuildContext context) {
     return AppBar(
-      title: AppHeader(text: 'app_name'),
-      actions: [
+      title: const AppHeader(text: 'app_name'),
+      actions: const [
         //
       ],
     );
diff --git a/lib/ui/widgets/debug_bloc.dart b/lib/ui/widgets/debug_bloc.dart
index 502bacc..cbc6dfe 100644
--- a/lib/ui/widgets/debug_bloc.dart
+++ b/lib/ui/widgets/debug_bloc.dart
@@ -16,11 +16,11 @@ class DebugBloc extends StatelessWidget {
         ),
       ),
       child: Padding(
-        padding: EdgeInsets.all(5),
+        padding: const EdgeInsets.all(5),
         child: Text(
           content,
           textAlign: TextAlign.start,
-          style: TextStyle(fontSize: 13),
+          style: const TextStyle(fontSize: 13),
         ),
       ),
     );
diff --git a/lib/ui/widgets/error.dart b/lib/ui/widgets/error.dart
index e65f242..9aacdcb 100644
--- a/lib/ui/widgets/error.dart
+++ b/lib/ui/widgets/error.dart
@@ -9,9 +9,9 @@ class ShowErrorWidget extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return Text(
-      '⚠️ ' + tr(message),
+      '⚠️ ${tr(message)}',
       textAlign: TextAlign.start,
-      style: TextStyle(color: Colors.red),
+      style: const TextStyle(color: Colors.red),
     );
   }
 }
diff --git a/lib/ui/widgets/game/game_board.dart b/lib/ui/widgets/game/game_board.dart
index 6f11e5b..f354e21 100644
--- a/lib/ui/widgets/game/game_board.dart
+++ b/lib/ui/widgets/game/game_board.dart
@@ -49,7 +49,7 @@ class _GameBoardWidget extends State<GameBoardWidget> with TickerProviderStateMi
     // "move down" cells
     final controller = AnimationController(
       vsync: this,
-      duration: Duration(milliseconds: 750),
+      duration: const Duration(milliseconds: 750),
     )..addListener(() {
         if (mounted) {
           setState(() {});
@@ -99,7 +99,7 @@ class _GameBoardWidget extends State<GameBoardWidget> with TickerProviderStateMi
     final cellWidth = widgetWidth / columnsCount;
     final cellHeight = widgetHeight / rowsCount;
 
-    if (animations.length == 0) {
+    if (animations.isEmpty) {
       resetAnimations(widget.game.settings);
     }
 
@@ -110,7 +110,7 @@ class _GameBoardWidget extends State<GameBoardWidget> with TickerProviderStateMi
         final int? value = widget.game.getCellValue(x, y);
 
         if (value != null) {
-          final Animation<double>? translation = this.animations[y][x];
+          final Animation<double>? translation = animations[y][x];
 
           final Widget cellContent = CustomPaint(
             size: Size(cellWidth, cellHeight),
@@ -121,7 +121,7 @@ class _GameBoardWidget extends State<GameBoardWidget> with TickerProviderStateMi
           final Widget cellWidget = Positioned(
             left: (x * cellWidth).toDouble(),
             top: ((y + (translation?.value ?? 0)) * cellHeight).toDouble(),
-            child: Container(
+            child: SizedBox(
               width: cellWidth,
               height: cellHeight,
               child: cellContent,
@@ -154,13 +154,13 @@ class _GameBoardWidget extends State<GameBoardWidget> with TickerProviderStateMi
       child: buildBoard(),
       onTapUp: (details) {
         bool canRemoveCell = true;
-        animations.forEach((row) {
-          row.forEach((cell) {
+        for (var row in animations) {
+          for (var cell in row) {
             if (cell != null) {
               canRemoveCell = false;
             }
-          });
-        });
+          }
+        }
 
         if (canRemoveCell) {
           double xTap = details.localPosition.dx;
@@ -168,7 +168,7 @@ class _GameBoardWidget extends State<GameBoardWidget> with TickerProviderStateMi
 
           int x = (xTap / widgetWidth * columnsCount).toInt();
           int y = (yTap / widgetHeight * rowsCount).toInt();
-          print('[' + x.toString() + ',' + y.toString() + ']');
+          print('[$x,$y]');
 
           removeCell(context, x, y);
         } else {
diff --git a/lib/ui/widgets/game/game_score.dart b/lib/ui/widgets/game/game_score.dart
index e52193c..cea8f9a 100644
--- a/lib/ui/widgets/game/game_score.dart
+++ b/lib/ui/widgets/game/game_score.dart
@@ -14,19 +14,19 @@ class GameScoreWidget extends StatelessWidget {
   Widget build(BuildContext context) {
     return Container(
       width: MediaQuery.of(context).size.width,
-      padding: EdgeInsets.all(5),
+      padding: const EdgeInsets.all(5),
       child: Column(
         mainAxisAlignment: MainAxisAlignment.start,
         crossAxisAlignment: CrossAxisAlignment.start,
         children: [
-          Text('Settings:'),
-          Text('  board size: ' + this.game.settings.boardSize.toString()),
-          Text('  colors count: ' + this.game.settings.colorsCount.toString()),
-          Text('Game:'),
-          Text('  isRunning: ' + this.game.isRunning.toString()),
-          Text('  isFinished: ' + this.game.isFinished.toString()),
-          Text('  movesCount: ' + this.game.movesCount.toString()),
-          Text('  score: ' + this.game.score.toString()),
+          const Text('Settings:'),
+          Text('  board size: ${game.settings.boardSize}'),
+          Text('  colors count: ${game.settings.colorsCount}'),
+          const Text('Game:'),
+          Text('  isRunning: ${game.isRunning}'),
+          Text('  isFinished: ${game.isFinished}'),
+          Text('  movesCount: ${game.movesCount}'),
+          Text('  score: ${game.score}'),
         ],
       ),
     );
diff --git a/lib/ui/widgets/game/game_settings.dart b/lib/ui/widgets/game/game_settings.dart
index 3170c69..783e770 100644
--- a/lib/ui/widgets/game/game_settings.dart
+++ b/lib/ui/widgets/game/game_settings.dart
@@ -7,6 +7,6 @@ class GameSettingsWidget extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    return Text('(fake settings block)');
+    return const Text('(fake settings block)');
   }
 }
diff --git a/lib/ui/widgets/header_app.dart b/lib/ui/widgets/header_app.dart
index b19ad44..39bfa86 100644
--- a/lib/ui/widgets/header_app.dart
+++ b/lib/ui/widgets/header_app.dart
@@ -23,11 +23,11 @@ class AppHeader extends StatelessWidget {
           textAlign: TextAlign.start,
           style: Theme.of(context).textTheme.headlineMedium!.apply(fontWeightDelta: 2),
         ),
-        SizedBox(width: 2),
+        const SizedBox(width: 2),
         expertInterfaceIndicator(),
-        SizedBox(width: 2),
+        const SizedBox(width: 2),
         dataCounterIndicator(),
-        SizedBox(width: 2),
+        const SizedBox(width: 2),
         apiLoadingIndicator(),
       ],
     );
@@ -46,7 +46,7 @@ class AppHeader extends StatelessWidget {
   Widget dataCounterIndicator() {
     return BlocBuilder<DataCubit, DataState>(
       builder: (context, dataState) {
-        return Text('(' + dataState.counter.toString() + ')');
+        return Text('(${dataState.counter})');
       },
     );
   }
@@ -55,10 +55,10 @@ class AppHeader extends StatelessWidget {
     return BlocBuilder<ApiDataCubit, ApiDataState>(
       builder: (context, apiDataState) {
         return (apiDataState is ApiDataFetchLoading)
-            ? Text('⏳')
+            ? const Text('⏳')
             : (apiDataState is ApiDataFetchLoaded)
-                ? Text('✅')
-                : Text('⚠️');
+                ? const Text('✅')
+                : const Text('⚠️');
       },
     );
   }
diff --git a/lib/ui/widgets/settings_form.dart b/lib/ui/widgets/settings_form.dart
index ac40063..5cd6348 100644
--- a/lib/ui/widgets/settings_form.dart
+++ b/lib/ui/widgets/settings_form.dart
@@ -54,23 +54,23 @@ class _SettingsFormState extends State<SettingsForm> {
       crossAxisAlignment: CrossAxisAlignment.start,
       mainAxisSize: MainAxisSize.max,
       children: <Widget>[
-        Text('settings_label_api_url').tr(),
+        const Text('settings_label_api_url').tr(),
         TextFormField(
           controller: apiUrlController,
-          decoration: InputDecoration(
+          decoration: const InputDecoration(
             border: UnderlineInputBorder(),
           ),
         ),
-        SizedBox(height: 16),
-        Text('settings_label_security_token').tr(),
+        const SizedBox(height: 16),
+        const Text('settings_label_security_token').tr(),
         TextFormField(
           controller: securityTokenController,
-          decoration: InputDecoration(
+          decoration: const InputDecoration(
             border: UnderlineInputBorder(),
           ),
         ),
-        SizedBox(height: 16),
-        Text('settings_label_interface_type').tr(),
+        const SizedBox(height: 16),
+        const Text('settings_label_interface_type').tr(),
         ToggleButtons(
           direction: Axis.horizontal,
           onPressed: (int index) {
@@ -90,15 +90,15 @@ class _SettingsFormState extends State<SettingsForm> {
           isSelected: _selectedInterfaceType,
           children: interfaceTypesWidgets,
         ),
-        SizedBox(height: 20),
+        const SizedBox(height: 20),
         ElevatedButton(
           child: Row(
             mainAxisAlignment: MainAxisAlignment.center,
             crossAxisAlignment: CrossAxisAlignment.center,
             children: [
-              Icon(UniconsLine.save),
-              SizedBox(width: 8),
-              Text('settings_button_save').tr(),
+              const Icon(UniconsLine.save),
+              const SizedBox(width: 8),
+              const Text('settings_button_save').tr(),
             ],
           ),
           onPressed: () => saveSettings(),
diff --git a/lib/ui/widgets/take_picture_widget.dart b/lib/ui/widgets/take_picture_widget.dart
index 7a73090..efece10 100644
--- a/lib/ui/widgets/take_picture_widget.dart
+++ b/lib/ui/widgets/take_picture_widget.dart
@@ -28,24 +28,20 @@ class TakePictureWidgetState extends State<TakePictureWidget> {
   }
 
   loadCamera() async {
-    final List<CameraDescription>? cameras = await availableCameras();
-    if (cameras != null) {
-      controller = CameraController(
-        cameras.first,
-        ResolutionPreset.max,
-        enableAudio: false,
-      );
+    final List<CameraDescription> cameras = await availableCameras();
+    controller = CameraController(
+      cameras.first,
+      ResolutionPreset.max,
+      enableAudio: false,
+    );
 
-      controller!.initialize().then((_) {
-        if (!mounted) {
-          return;
-        }
-        setState(() {});
-      });
-    } else {
-      print("No camera found.");
+    controller!.initialize().then((_) {
+      if (!mounted) {
+        return;
+      }
+      setState(() {});
+    });
     }
-  }
 
   @override
   void dispose() {
@@ -60,50 +56,50 @@ class TakePictureWidgetState extends State<TakePictureWidget> {
         mainAxisAlignment: MainAxisAlignment.start,
         crossAxisAlignment: CrossAxisAlignment.center,
         children: [
-          Container(
+          SizedBox(
             height: 400,
             child: controller == null
-                ? Center(child: Text("Loading camera..."))
+                ? const Center(child: Text("Loading camera..."))
                 : !controller!.value.isInitialized
-                    ? Center(child: CircularProgressIndicator())
+                    ? const Center(child: CircularProgressIndicator())
                     : CameraPreview(controller!),
           ),
           ElevatedButton.icon(
-            label: Text("Take picture"),
-            icon: Icon(UniconsLine.camera),
+            label: const Text("Take picture"),
+            icon: const Icon(UniconsLine.camera),
             onPressed: () async {
               try {
                 if ((controller != null) && (controller!.value.isInitialized)) {
                   final XFile image = await controller!.takePicture();
-                  print('image.path: ' + image.path);
-                  debug.add('image.path: ' + image.path);
+                  print('image.path: ${image.path}');
+                  debug.add('image.path: ${image.path}');
 
                   File savedFile = await storage!.writeCounter(File(image.path));
-                  debug.add('image.path: ' + image.path);
+                  debug.add('image.path: ${image.path}');
 
                   String imagePath = savedFile.path;
-                  print('imagePath: ' + imagePath);
-                  debug.add('imagePath: ' + imagePath);
+                  print('imagePath: $imagePath');
+                  debug.add('imagePath: $imagePath');
 
                   previousImages.add(imagePath);
                   setState(() {});
                 }
               } catch (e) {
-                debug.add('error: ' + e.toString());
+                debug.add('error: $e');
                 setState(() {});
 
                 print(e);
               }
             },
           ),
-          Text('debug: '),
+          const Text('debug: '),
           Column(
             children: debug.map((String line) {
               return Text(line);
             }).toList(),
           ),
-          previousImages.length == 0
-              ? Text('no previous images')
+          previousImages.isEmpty
+              ? const Text('no previous images')
               : Column(
                   children: previousImages.map((String imagePath) {
                     return Row(
diff --git a/lib/utils/picture_storage.dart b/lib/utils/picture_storage.dart
index 72e2278..ebf663b 100644
--- a/lib/utils/picture_storage.dart
+++ b/lib/utils/picture_storage.dart
@@ -13,14 +13,14 @@ class PictureStorage {
   Future<String> _localFilePath(String name) async {
     final path = await _localPath;
 
-    return path + '/' + name;
+    return '$path/$name';
   }
 
   Future<File> moveFile(File sourceFile, String newPath) async {
     try {
       return await sourceFile.rename(newPath);
     } on FileSystemException catch (e) {
-      print('Found exception while moving file: ' + e.toString());
+      print('Found exception while moving file: $e');
       final newFile = await sourceFile.copy(newPath);
       await sourceFile.delete();
       return newFile;
diff --git a/pubspec.lock b/pubspec.lock
index b3dd33c..dea0f19 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -166,6 +166,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "8.1.3"
+  flutter_lints:
+    dependency: "direct dev"
+    description:
+      name: flutter_lints
+      sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.0.1"
   flutter_localizations:
     dependency: transitive
     description: flutter
@@ -193,7 +201,7 @@ packages:
     source: sdk
     version: "0.0.0"
   hive:
-    dependency: transitive
+    dependency: "direct main"
     description:
       name: hive
       sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
@@ -232,22 +240,30 @@ packages:
       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:
     dependency: transitive
     description:
       name: material_color_utilities
-      sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
+      sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
       url: "https://pub.dev"
     source: hosted
-    version: "0.5.0"
+    version: "0.8.0"
   meta:
     dependency: transitive
     description:
       name: meta
-      sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
+      sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
       url: "https://pub.dev"
     source: hosted
-    version: "1.10.0"
+    version: "1.11.0"
   nested:
     dependency: transitive
     description:
@@ -273,13 +289,13 @@ packages:
     source: hosted
     version: "2.0.1"
   path:
-    dependency: transitive
+    dependency: "direct main"
     description:
       name: path
-      sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
+      sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
       url: "https://pub.dev"
     source: hosted
-    version: "1.8.3"
+    version: "1.9.0"
   path_provider:
     dependency: "direct main"
     description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 96d1982..401d954 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -3,7 +3,7 @@ description: A random application, for testing purpose only.
 
 publish_to: 'none'
 
-version: 1.0.45+46
+version: 1.0.46+47
 
 environment:
   sdk: '^3.0.0'
@@ -22,6 +22,8 @@ dependencies:
   package_info_plus: ^5.0.1
   flutter_swipe: ^1.0.1
   dio: ^5.3.3
+  hive: ^2.2.3
+  path: ^1.9.0
 
 flutter:
   uses-material-design: false
@@ -40,3 +42,5 @@ flutter:
           weight: 400
         - asset: assets/fonts/Nunito-Light.ttf
           weight: 300
+dev_dependencies:
+  flutter_lints: ^3.0.1
-- 
GitLab