diff --git a/android/app/build.gradle b/android/app/build.gradle
index 21a9eb139869ecb2b0876d6317575b9e4357596b..a32ab7a818c4540d1a3d091d0680d829f7283a32 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -37,7 +37,7 @@ if (keystorePropertiesFile.exists()) {
 }
 
 android {
-    compileSdkVersion 33
+    compileSdkVersion 34
     namespace "org.benoitharrault.plotter"
 
     defaultConfig {
diff --git a/android/gradle.properties b/android/gradle.properties
index 777ac2de0980e935649cf32bd85097eaf789185a..cd2d833ca96b3d1ada4a39df51dc5f5ee67665b7 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=0.0.16
-app.versionCode=16
+app.versionName=0.0.17
+app.versionCode=17
diff --git a/assets/fonts/Nunito-Bold.ttf b/assets/fonts/Nunito-Bold.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..6519feb781449ebe0015cbc74dfd9e13110fbba9
Binary files /dev/null and b/assets/fonts/Nunito-Bold.ttf differ
diff --git a/assets/fonts/Nunito-Light.ttf b/assets/fonts/Nunito-Light.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..8a0736c41cd6c2a1225d356bf274de1d0afc3497
Binary files /dev/null and b/assets/fonts/Nunito-Light.ttf differ
diff --git a/assets/fonts/Nunito-Medium.ttf b/assets/fonts/Nunito-Medium.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..88fccdc0638b6f5d6ac49d9d269dc3d518618ad1
Binary files /dev/null and b/assets/fonts/Nunito-Medium.ttf differ
diff --git a/assets/fonts/Nunito-Regular.ttf b/assets/fonts/Nunito-Regular.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..e7b8375a896ef0cd8e06730a78c84532b377e784
Binary files /dev/null and b/assets/fonts/Nunito-Regular.ttf differ
diff --git a/assets/icons/application.png b/assets/icons/application.png
deleted file mode 100644
index 20f13b46a5bdbc19ac735249aa0790402548970f..0000000000000000000000000000000000000000
Binary files a/assets/icons/application.png and /dev/null differ
diff --git a/assets/translations/en.json b/assets/translations/en.json
new file mode 100644
index 0000000000000000000000000000000000000000..b060f0b5f2d6850dfffd8766bfb7bbcd8c732db8
--- /dev/null
+++ b/assets/translations/en.json
@@ -0,0 +1,16 @@
+{
+  "app_name": "Plotter",
+
+  "settings_title": "Settings",
+  "settings_label_theme": "Theme mode",
+  "settings_label_api_host": "API host",
+
+  "about_title": "Informations",
+  "about_content": "Plotter",
+  "about_version": "Version: {version}",
+
+  "page_home": "Home",
+  "page_remote_control": "Control",
+
+  "": ""
+}
diff --git a/assets/translations/fr.json b/assets/translations/fr.json
new file mode 100644
index 0000000000000000000000000000000000000000..718515612759a1034a1813885cba17c5388cbca0
--- /dev/null
+++ b/assets/translations/fr.json
@@ -0,0 +1,16 @@
+{
+  "app_name": "Plotter",
+
+  "settings_title": "Réglages",
+  "settings_label_theme": "Thème de couleurs",
+  "settings_label_api_host": "URL de l'API",
+
+  "about_title": "Informations",
+  "about_content": "Plotter.",
+  "about_version": "Version : {version}",
+
+  "page_home": "Accueil",
+  "page_remote_control": "Télécommande",
+
+  "": ""
+}
diff --git a/fastlane/metadata/android/en-US/changelogs/17.txt b/fastlane/metadata/android/en-US/changelogs/17.txt
new file mode 100644
index 0000000000000000000000000000000000000000..060b202b87dcd33388eb8a9d1a45cbd276fc8ba3
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/17.txt
@@ -0,0 +1 @@
+Improve/normalize app architecture.
diff --git a/fastlane/metadata/android/fr-FR/changelogs/17.txt b/fastlane/metadata/android/fr-FR/changelogs/17.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3cab228cbe7443782c4b187cbe805e4d0ba8e71a
--- /dev/null
+++ b/fastlane/metadata/android/fr-FR/changelogs/17.txt
@@ -0,0 +1 @@
+Amélioration/normalisation de l'architecture de l'application.
diff --git a/icons/build_repository_icons.sh b/icons/build_repository_icons.sh
deleted file mode 100755
index 569a16a50dd3015a45c291a93305ff0f67f77c72..0000000000000000000000000000000000000000
--- a/icons/build_repository_icons.sh
+++ /dev/null
@@ -1,48 +0,0 @@
-#! /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; }
-
-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
-build_icon 512 ${BASE_DIR}/fastlane/metadata/android/en-US/images/icon
diff --git a/lib/config/activity_page.dart b/lib/config/activity_page.dart
new file mode 100644
index 0000000000000000000000000000000000000000..33e4c09df971eac6f52b0b6cf68840a8d069bbd5
--- /dev/null
+++ b/lib/config/activity_page.dart
@@ -0,0 +1,53 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
+import 'package:unicons/unicons.dart';
+
+import 'package:plotter/ui/pages/remote_control.dart';
+import 'package:plotter/ui/pages/home.dart';
+
+class ActivityPageItem {
+  final Icon icon;
+  final Widget page;
+  final String code;
+
+  const ActivityPageItem({
+    required this.icon,
+    required this.page,
+    required this.code,
+  });
+}
+
+class ActivityPage {
+  static const indexHome = 0;
+  static const pageHome = ActivityPageItem(
+    icon: Icon(UniconsLine.home),
+    page: PageHome(),
+    code: 'page_home',
+  );
+
+  static const indexRemoteControl = 1;
+  static const pageRemoteControl = ActivityPageItem(
+    icon: Icon(UniconsLine.arrow),
+    page: PageRemoteControl(),
+    code: 'page_remote_control',
+  );
+
+  static Map<int, ActivityPageItem> items = {
+    indexHome: pageHome,
+    indexRemoteControl: pageRemoteControl,
+  };
+
+  static bool isIndexAllowed(int pageIndex) {
+    return items.keys.contains(pageIndex);
+  }
+
+  static ActivityPageItem getPageItem(int pageIndex) {
+    return items[pageIndex] ?? pageHome;
+  }
+
+  static Widget getPageWidget(int pageIndex) {
+    return items[pageIndex]?.page ?? pageHome.page;
+  }
+
+  static int itemsCount = ActivityPage.items.length;
+}
diff --git a/lib/config/default_global_settings.dart b/lib/config/default_global_settings.dart
new file mode 100644
index 0000000000000000000000000000000000000000..3906b1fea4e387ef10e5519d6bde3e87935c7f7f
--- /dev/null
+++ b/lib/config/default_global_settings.dart
@@ -0,0 +1,33 @@
+import 'package:plotter/utils/tools.dart';
+
+class DefaultGlobalSettings {
+  // available global parameters codes
+  static const String parameterCodeSkin = 'skin';
+  static const List<String> availableParameters = [
+    parameterCodeSkin,
+  ];
+
+  // skin: available values
+  static const String skinValueDefault = 'default';
+  static const List<String> allowedSkinValues = [
+    skinValueDefault,
+  ];
+  // skin: default value
+  static const String defaultSkinValue = skinValueDefault;
+
+  // available values from parameter code
+  static List<String> getAvailableValues(String parameterCode) {
+    switch (parameterCode) {
+      case parameterCodeSkin:
+        return DefaultGlobalSettings.allowedSkinValues;
+    }
+
+    printlog('Did not find any available value for global parameter "$parameterCode".');
+    return [];
+  }
+
+  // parameters displayed with assets (instead of painter)
+  static List<String> displayedWithAssets = [
+    //
+  ];
+}
diff --git a/lib/config/screen.dart b/lib/config/screen.dart
new file mode 100644
index 0000000000000000000000000000000000000000..3cf39b6d0c0770f20dcb6e2372474232ea2d2378
--- /dev/null
+++ b/lib/config/screen.dart
@@ -0,0 +1,61 @@
+import 'package:flutter/material.dart';
+import 'package:unicons/unicons.dart';
+
+import 'package:plotter/ui/screens/about.dart';
+import 'package:plotter/ui/screens/activity.dart';
+import 'package:plotter/ui/screens/settings.dart';
+
+class ScreenItem {
+  final Icon icon;
+  final Widget screen;
+  final bool displayBottomNavBar;
+
+  const ScreenItem({
+    required this.icon,
+    required this.screen,
+    required this.displayBottomNavBar,
+  });
+}
+
+class Screen {
+  static const indexActivity = 0;
+  static const screenActivity = ScreenItem(
+    icon: Icon(UniconsLine.home),
+    screen: ScreenActivity(),
+    displayBottomNavBar: true,
+  );
+
+  static const indexSettings = 1;
+  static const screenSettings = ScreenItem(
+    icon: Icon(UniconsLine.setting),
+    screen: ScreenSettings(),
+    displayBottomNavBar: false,
+  );
+
+  static const indexAbout = 2;
+  static const screenAbout = ScreenItem(
+    icon: Icon(UniconsLine.info_circle),
+    screen: ScreenAbout(),
+    displayBottomNavBar: false,
+  );
+
+  static Map<int, ScreenItem> items = {
+    indexActivity: screenActivity,
+    indexSettings: screenSettings,
+    indexAbout: screenAbout,
+  };
+
+  static bool isIndexAllowed(int screenIndex) {
+    return items.keys.contains(screenIndex);
+  }
+
+  static Widget getWidget(int screenIndex) {
+    return items[screenIndex]?.screen ?? screenActivity.screen;
+  }
+
+  static bool displayBottomNavBar(int screenIndex) {
+    return items[screenIndex]?.displayBottomNavBar ?? screenActivity.displayBottomNavBar;
+  }
+
+  static int itemsCount = Screen.items.length;
+}
diff --git a/lib/config/theme.dart b/lib/config/theme.dart
new file mode 100644
index 0000000000000000000000000000000000000000..74f532fd5abf693979118609564d29167e902009
--- /dev/null
+++ b/lib/config/theme.dart
@@ -0,0 +1,190 @@
+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,
+  onSurface: textSwatch.shade500,
+  surface: textSwatch.shade50,
+  surfaceContainerHighest: Colors.white,
+  shadow: textSwatch.shade900.withOpacity(.1),
+);
+
+final ColorScheme darkColorScheme = ColorScheme.dark(
+  primary: primarySwatch.shade500,
+  secondary: primarySwatch.shade500,
+  onSecondary: Colors.white,
+  error: errorColor,
+  onSurface: textSwatch.shade300,
+  surface: const Color(0xFF262630),
+  surfaceContainerHighest: 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',
+    ),
+  ),
+);
diff --git a/lib/cubit/activity_cubit.dart b/lib/cubit/activity_cubit.dart
new file mode 100644
index 0000000000000000000000000000000000000000..e0e45d1501b88dd7a32e2d80d52c935366b836dc
--- /dev/null
+++ b/lib/cubit/activity_cubit.dart
@@ -0,0 +1,92 @@
+import 'package:equatable/equatable.dart';
+import 'package:flutter/material.dart';
+import 'package:hydrated_bloc/hydrated_bloc.dart';
+
+import 'package:plotter/models/activity/activity.dart';
+import 'package:plotter/models/settings/settings_global.dart';
+
+part 'activity_state.dart';
+
+class ActivityCubit extends HydratedCubit<ActivityState> {
+  ActivityCubit()
+      : super(ActivityState(
+          currentActivity: Activity.createNull(),
+        ));
+
+  void updateState(Activity activity) {
+    emit(ActivityState(
+      currentActivity: activity,
+    ));
+  }
+
+  void refresh() {
+    final Activity activity = Activity(
+      // Settings
+      globalSettings: state.currentActivity.globalSettings,
+      // State
+      isRunning: state.currentActivity.isRunning,
+      // Base data
+      apiHost: state.currentActivity.apiHost,
+      // Activity data
+      apiStatus: state.currentActivity.apiStatus,
+    );
+    // activity.dump();
+
+    updateState(activity);
+  }
+
+  void startNewActivity({
+    required GlobalSettings globalSettings,
+  }) {
+    final Activity newActivity = Activity.createNew(
+      // Settings
+      globalSettings: globalSettings,
+    );
+
+    newActivity.dump();
+
+    updateState(newActivity);
+    refresh();
+  }
+
+  void quitActivity() {
+    state.currentActivity.isRunning = false;
+    refresh();
+  }
+
+  void resumeSavedActivity() {
+    state.currentActivity.isRunning = true;
+    refresh();
+  }
+
+  void deleteSavedActivity() {
+    state.currentActivity.isRunning = false;
+    refresh();
+  }
+
+  void setApiHost(String apiHost) {
+    state.currentActivity.apiHost = apiHost;
+    refresh();
+  }
+
+  void updateApiStatus(String apiStatus) {
+    state.currentActivity.apiStatus = apiStatus;
+    refresh();
+  }
+
+  @override
+  ActivityState? fromJson(Map<String, dynamic> json) {
+    final Activity currentActivity = json['currentActivity'] as Activity;
+
+    return ActivityState(
+      currentActivity: currentActivity,
+    );
+  }
+
+  @override
+  Map<String, dynamic>? toJson(ActivityState state) {
+    return <String, dynamic>{
+      'currentActivity': state.currentActivity.toJson(),
+    };
+  }
+}
diff --git a/lib/cubit/activity_state.dart b/lib/cubit/activity_state.dart
new file mode 100644
index 0000000000000000000000000000000000000000..887b45e4255fd7de1cc7744569d82a38a66602f2
--- /dev/null
+++ b/lib/cubit/activity_state.dart
@@ -0,0 +1,15 @@
+part of 'activity_cubit.dart';
+
+@immutable
+class ActivityState extends Equatable {
+  const ActivityState({
+    required this.currentActivity,
+  });
+
+  final Activity currentActivity;
+
+  @override
+  List<dynamic> get props => <dynamic>[
+        currentActivity,
+      ];
+}
diff --git a/lib/cubit/nav_cubit_pages.dart b/lib/cubit/nav_cubit_pages.dart
new file mode 100644
index 0000000000000000000000000000000000000000..aee283fd13a259db40373a04a2e4182a20624b99
--- /dev/null
+++ b/lib/cubit/nav_cubit_pages.dart
@@ -0,0 +1,25 @@
+import 'package:hydrated_bloc/hydrated_bloc.dart';
+
+import 'package:plotter/config/activity_page.dart';
+
+class NavCubitPage extends HydratedCubit<int> {
+  NavCubitPage() : super(0);
+
+  void updateIndex(int index) {
+    if (ActivityPage.isIndexAllowed(index)) {
+      emit(index);
+    } else {
+      emit(ActivityPage.indexHome);
+    }
+  }
+
+  @override
+  int fromJson(Map<String, dynamic> json) {
+    return ActivityPage.indexHome;
+  }
+
+  @override
+  Map<String, dynamic>? toJson(int state) {
+    return <String, int>{'pageIndex': state};
+  }
+}
diff --git a/lib/cubit/nav_cubit_screens.dart b/lib/cubit/nav_cubit_screens.dart
new file mode 100644
index 0000000000000000000000000000000000000000..32f8d9b4322401d7d27eabfd9fb4d26e2c7d4c6e
--- /dev/null
+++ b/lib/cubit/nav_cubit_screens.dart
@@ -0,0 +1,37 @@
+import 'package:hydrated_bloc/hydrated_bloc.dart';
+
+import 'package:plotter/config/screen.dart';
+
+class NavCubitScreen extends HydratedCubit<int> {
+  NavCubitScreen() : super(0);
+
+  void updateIndex(int index) {
+    if (Screen.isIndexAllowed(index)) {
+      emit(index);
+    } else {
+      goToActivityPage();
+    }
+  }
+
+  void goToActivityPage() {
+    emit(Screen.indexActivity);
+  }
+
+  void goToSettingsPage() {
+    emit(Screen.indexSettings);
+  }
+
+  void goToAboutPage() {
+    emit(Screen.indexAbout);
+  }
+
+  @override
+  int fromJson(Map<String, dynamic> json) {
+    return Screen.indexActivity;
+  }
+
+  @override
+  Map<String, dynamic>? toJson(int state) {
+    return <String, int>{'screenIndex': state};
+  }
+}
diff --git a/lib/cubit/settings_global_cubit.dart b/lib/cubit/settings_global_cubit.dart
new file mode 100644
index 0000000000000000000000000000000000000000..67344c1b4e9b2d5e573608211ac20ab18f9ce866
--- /dev/null
+++ b/lib/cubit/settings_global_cubit.dart
@@ -0,0 +1,68 @@
+import 'package:equatable/equatable.dart';
+import 'package:flutter/material.dart';
+import 'package:hydrated_bloc/hydrated_bloc.dart';
+
+import 'package:plotter/config/default_global_settings.dart';
+import 'package:plotter/models/settings/settings_global.dart';
+
+part 'settings_global_state.dart';
+
+class GlobalSettingsCubit extends HydratedCubit<GlobalSettingsState> {
+  GlobalSettingsCubit() : super(GlobalSettingsState(settings: GlobalSettings.createDefault()));
+
+  void setValues({
+    String? skin,
+    String? apiHost,
+  }) {
+    emit(
+      GlobalSettingsState(
+        settings: GlobalSettings(
+          skin: skin ?? state.settings.skin,
+          apiHost: apiHost ?? state.settings.apiHost,
+        ),
+      ),
+    );
+  }
+
+  String getParameterValue(String code) {
+    switch (code) {
+      case DefaultGlobalSettings.parameterCodeSkin:
+        return GlobalSettings.getSkinValueFromUnsafe(state.settings.skin);
+      case 'apiHost':
+        return state.settings.apiHost;
+    }
+    return '';
+  }
+
+  void setParameterValue(String code, String value) {
+    final String skin = (code == DefaultGlobalSettings.parameterCodeSkin)
+        ? value
+        : getParameterValue(DefaultGlobalSettings.parameterCodeSkin);
+    final String apiHost = (code == 'apiHost') ? value : getParameterValue('apiHost');
+
+    setValues(
+      skin: skin,
+      apiHost: apiHost,
+    );
+  }
+
+  @override
+  GlobalSettingsState? fromJson(Map<String, dynamic> json) {
+    final String skin = json[DefaultGlobalSettings.parameterCodeSkin] as String;
+    final String apiHost = json['apiHost'] as String;
+
+    return GlobalSettingsState(
+      settings: GlobalSettings(
+        skin: skin,
+        apiHost: apiHost,
+      ),
+    );
+  }
+
+  @override
+  Map<String, dynamic>? toJson(GlobalSettingsState state) {
+    return <String, dynamic>{
+      DefaultGlobalSettings.parameterCodeSkin: state.settings.skin,
+    };
+  }
+}
diff --git a/lib/cubit/settings_global_state.dart b/lib/cubit/settings_global_state.dart
new file mode 100644
index 0000000000000000000000000000000000000000..ebcddd700f252257223ca8e16c85202b04f3ff24
--- /dev/null
+++ b/lib/cubit/settings_global_state.dart
@@ -0,0 +1,15 @@
+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,
+      ];
+}
diff --git a/lib/cubit/theme_cubit.dart b/lib/cubit/theme_cubit.dart
new file mode 100644
index 0000000000000000000000000000000000000000..b793e895dbb0c672d451cd403e0036c3d9ac9b42
--- /dev/null
+++ b/lib/cubit/theme_cubit.dart
@@ -0,0 +1,31 @@
+import 'package:equatable/equatable.dart';
+import 'package:flutter/material.dart';
+import 'package:hydrated_bloc/hydrated_bloc.dart';
+
+part 'theme_state.dart';
+
+class ThemeCubit extends HydratedCubit<ThemeModeState> {
+  ThemeCubit() : super(const ThemeModeState());
+
+  void getTheme(ThemeModeState state) {
+    emit(state);
+  }
+
+  @override
+  ThemeModeState? fromJson(Map<String, dynamic> json) {
+    switch (json['themeMode']) {
+      case 'ThemeMode.dark':
+        return const ThemeModeState(themeMode: ThemeMode.dark);
+      case 'ThemeMode.light':
+        return const ThemeModeState(themeMode: ThemeMode.light);
+      case 'ThemeMode.system':
+      default:
+        return const ThemeModeState(themeMode: ThemeMode.system);
+    }
+  }
+
+  @override
+  Map<String, String>? toJson(ThemeModeState state) {
+    return <String, String>{'themeMode': state.themeMode.toString()};
+  }
+}
diff --git a/lib/cubit/theme_state.dart b/lib/cubit/theme_state.dart
new file mode 100644
index 0000000000000000000000000000000000000000..e479a50f12fe72a35a1fd1722ff72afbb692a136
--- /dev/null
+++ b/lib/cubit/theme_state.dart
@@ -0,0 +1,15 @@
+part of 'theme_cubit.dart';
+
+@immutable
+class ThemeModeState extends Equatable {
+  const ThemeModeState({
+    this.themeMode,
+  });
+
+  final ThemeMode? themeMode;
+
+  @override
+  List<Object?> get props => <Object?>[
+        themeMode,
+      ];
+}
diff --git a/lib/layout/keyboard.dart b/lib/layout/keyboard.dart
deleted file mode 100644
index 33536402876df56dac117f23eb4c2882593566df..0000000000000000000000000000000000000000
--- a/lib/layout/keyboard.dart
+++ /dev/null
@@ -1,74 +0,0 @@
-import 'package:flutter/material.dart';
-
-import 'package:plotter/provider/data.dart';
-import 'package:plotter/utils/api.dart';
-
-class Keyboard {
-  static Container buildWidget(Data myProvider) {
-    Widget buildKeyWidget(String direction) {
-      String keyText = '';
-
-      String north = '🔼';
-      String south = '🔽';
-      String west = '◀️';
-      String east = '▶️';
-
-      if (direction == 'north') {
-        keyText = north;
-      } else {
-        if (direction == 'south') {
-          keyText = south;
-        } else {
-          if (direction == 'west') {
-            keyText = west;
-          } else {
-            if (direction == 'east') {
-              keyText = east;
-            }
-          }
-        }
-      }
-
-      return TextButton(
-        style: TextButton.styleFrom(
-          padding: const EdgeInsets.all(0),
-        ),
-        child: Text(keyText,
-            style: const TextStyle(
-              fontSize: 60.0,
-              fontWeight: FontWeight.w800,
-            ),
-            textAlign: TextAlign.center),
-        onPressed: () {
-          if (keyText != '') {
-            Api.move(myProvider, direction);
-          }
-        },
-      );
-    }
-
-    return Container(
-        margin: const EdgeInsets.symmetric(horizontal: 2),
-        padding: const EdgeInsets.all(2),
-        child: Table(
-          defaultVerticalAlignment: TableCellVerticalAlignment.middle,
-          children: [
-            TableRow(children: [
-              TableCell(child: buildKeyWidget('')),
-              TableCell(child: buildKeyWidget('north')),
-              TableCell(child: buildKeyWidget('')),
-            ]),
-            TableRow(children: [
-              TableCell(child: buildKeyWidget('west')),
-              TableCell(child: buildKeyWidget('')),
-              TableCell(child: buildKeyWidget('east')),
-            ]),
-            TableRow(children: [
-              TableCell(child: buildKeyWidget('')),
-              TableCell(child: buildKeyWidget('south')),
-              TableCell(child: buildKeyWidget('')),
-            ]),
-          ],
-        ));
-  }
-}
diff --git a/lib/main.dart b/lib/main.dart
index 6a1c1d8321c952f9132ced9b83c458b4324ed1ea..04a3dfed0a74b879bbc26f9bbfb93888edd366f1 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,15 +1,42 @@
+import 'dart:io';
+
+import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.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 'screens/home.dart';
-import 'screens/settings.dart';
+import 'package:plotter/config/theme.dart';
+import 'package:plotter/cubit/activity_cubit.dart';
+import 'package:plotter/cubit/nav_cubit_pages.dart';
+import 'package:plotter/cubit/nav_cubit_screens.dart';
+import 'package:plotter/cubit/settings_global_cubit.dart';
+import 'package:plotter/cubit/theme_cubit.dart';
+import 'package:plotter/ui/skeleton.dart';
 
-void main() {
+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,
+  );
+
   SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])
-      .then((value) => runApp(const MyApp()));
+      .then((value) => 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 {
@@ -17,22 +44,33 @@ class MyApp extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    return ChangeNotifierProvider(
-      create: (BuildContext context) => Data(),
-      child: Consumer<Data>(builder: (context, data, child) {
-        return MaterialApp(
-          debugShowCheckedModeBanner: false,
-          theme: ThemeData(
-            primaryColor: Colors.blue,
-            visualDensity: VisualDensity.adaptivePlatformDensity,
-          ),
-          home: const Home(),
-          routes: {
-            Home.id: (context) => const Home(),
-            SettingsPage.id: (context) => const SettingsPage(),
-          },
-        );
-      }),
+    return MultiBlocProvider(
+      providers: [
+        BlocProvider<NavCubitPage>(create: (context) => NavCubitPage()),
+        BlocProvider<NavCubitScreen>(create: (context) => NavCubitScreen()),
+        BlocProvider<ThemeCubit>(create: (context) => ThemeCubit()),
+        BlocProvider<ActivityCubit>(create: (context) => ActivityCubit()),
+        BlocProvider<GlobalSettingsCubit>(create: (context) => GlobalSettingsCubit()),
+      ],
+      child: BlocBuilder<ThemeCubit, ThemeModeState>(
+        builder: (BuildContext context, ThemeModeState state) {
+          return MaterialApp(
+            title: 'Stepper plotter assistant',
+            home: const SkeletonScreen(),
+
+            // Theme stuff
+            theme: lightTheme,
+            darkTheme: darkTheme,
+            themeMode: state.themeMode,
+
+            // Localization stuff
+            localizationsDelegates: context.localizationDelegates,
+            supportedLocales: context.supportedLocales,
+            locale: context.locale,
+            debugShowCheckedModeBanner: false,
+          );
+        },
+      ),
     );
   }
 }
diff --git a/lib/models/activity/activity.dart b/lib/models/activity/activity.dart
new file mode 100644
index 0000000000000000000000000000000000000000..39ee19cb4e0388a80f9f4f5b165ad67056d23b2c
--- /dev/null
+++ b/lib/models/activity/activity.dart
@@ -0,0 +1,88 @@
+import 'package:plotter/models/settings/settings_global.dart';
+import 'package:plotter/utils/tools.dart';
+
+class Activity {
+  Activity({
+    // Settings
+    required this.globalSettings,
+
+    // State
+    this.isRunning = false,
+
+    // Base data
+    this.apiHost = '',
+
+    // Activity data
+    this.apiStatus = '',
+  });
+
+  // Settings
+  final GlobalSettings globalSettings;
+
+  // State
+  bool isRunning;
+
+  // Base data
+  String apiHost;
+
+  // Activity data
+  String apiStatus;
+
+  factory Activity.createNull() {
+    return Activity(
+      // Settings
+      globalSettings: GlobalSettings.createDefault(),
+      // Base data
+      apiHost: '',
+    );
+  }
+
+  factory Activity.createNew({
+    GlobalSettings? globalSettings,
+  }) {
+    final GlobalSettings newGlobalSettings = globalSettings ?? GlobalSettings.createDefault();
+
+    return Activity(
+      // Settings
+      globalSettings: newGlobalSettings,
+      // State
+      isRunning: true,
+      // Base data
+      apiHost: '',
+    );
+  }
+
+  void dump() {
+    printlog('');
+    printlog('## Current activity dump:');
+    printlog('');
+    printlog('$Activity:');
+    printlog('  Settings');
+    globalSettings.dump();
+    printlog('  State');
+    printlog('    isRunning: $isRunning');
+    printlog('  Base data');
+    printlog('    apiHost: $apiHost');
+    printlog('  Activity data');
+    printlog('    apiStatus: $apiStatus');
+    printlog('');
+  }
+
+  @override
+  String toString() {
+    return '$Activity(${toJson()})';
+  }
+
+  Map<String, dynamic>? toJson() {
+    return <String, dynamic>{
+      // Settings
+      'globalSettings': globalSettings.toJson(),
+      // State
+      'isRunning': isRunning,
+      // Base data
+      'apiHost': apiHost,
+      // Activity data
+      'apiStatus': apiStatus,
+    };
+  }
+}
diff --git a/lib/models/settings/settings_global.dart b/lib/models/settings/settings_global.dart
new file mode 100644
index 0000000000000000000000000000000000000000..e22f927595ed5f22eefff58d47def8dcb445e005
--- /dev/null
+++ b/lib/models/settings/settings_global.dart
@@ -0,0 +1,46 @@
+import 'package:plotter/config/default_global_settings.dart';
+import 'package:plotter/utils/tools.dart';
+
+class GlobalSettings {
+  String skin;
+  String apiHost;
+
+  GlobalSettings({
+    required this.skin,
+    required this.apiHost,
+  });
+
+  static String getSkinValueFromUnsafe(String skin) {
+    if (DefaultGlobalSettings.allowedSkinValues.contains(skin)) {
+      return skin;
+    }
+
+    return DefaultGlobalSettings.defaultSkinValue;
+  }
+
+  factory GlobalSettings.createDefault() {
+    return GlobalSettings(
+      skin: DefaultGlobalSettings.defaultSkinValue,
+      apiHost: '',
+    );
+  }
+
+  void dump() {
+    printlog('$GlobalSettings:');
+    printlog('  ${DefaultGlobalSettings.parameterCodeSkin}: $skin');
+    printlog('  apiHost: $apiHost');
+    printlog('');
+  }
+
+  @override
+  String toString() {
+    return '$GlobalSettings(${toJson()})';
+  }
+
+  Map<String, dynamic>? toJson() {
+    return <String, dynamic>{
+      DefaultGlobalSettings.parameterCodeSkin: skin,
+      'apiHost': apiHost,
+    };
+  }
+}
diff --git a/lib/provider/data.dart b/lib/provider/data.dart
deleted file mode 100644
index 1293e1a5314ba11e7a5fd5220fd580e38f3ddd43..0000000000000000000000000000000000000000
--- a/lib/provider/data.dart
+++ /dev/null
@@ -1,54 +0,0 @@
-import 'package:flutter/foundation.dart';
-import 'package:shared_preferences/shared_preferences.dart';
-
-import 'package:plotter/utils/tools.dart';
-
-class Data extends ChangeNotifier {
-  // Application settings
-  String _apiHost = '';
-  String _apiStatus = '';
-
-  String defaultApiHost = '127.0.0.1';
-
-  String getParameterValue(String parameterCode) {
-    switch (parameterCode) {
-      case 'apiHost':
-        {
-          return _apiHost;
-        }
-    }
-
-    return '';
-  }
-
-  setParameterValue(String parameterCode, String parameterValue) async {
-    printlog('set parameter "$parameterCode" to value "$parameterValue"');
-    switch (parameterCode) {
-      case 'apiHost':
-        {
-          updateApiHost(parameterValue);
-        }
-        break;
-    }
-    final prefs = await SharedPreferences.getInstance();
-    prefs.setString(parameterCode, parameterValue);
-  }
-
-  void initParametersValues() async {
-    final prefs = await SharedPreferences.getInstance();
-    setParameterValue('apiHost', prefs.getString('apiHost') ?? defaultApiHost);
-  }
-
-  String get apiHost => _apiHost;
-  void updateApiHost(String apiHost) {
-    _apiHost = apiHost;
-    notifyListeners();
-  }
-
-  String get apiStatus => _apiStatus;
-  void updateApiStatus(String apiStatus) {
-    printlog('new API status: $apiStatus');
-    _apiStatus = apiStatus;
-    notifyListeners();
-  }
-}
diff --git a/lib/screens/home.dart b/lib/screens/home.dart
deleted file mode 100644
index 737a27291a793f72c4389b8616d2339496dfa32d..0000000000000000000000000000000000000000
--- a/lib/screens/home.dart
+++ /dev/null
@@ -1,66 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:provider/provider.dart';
-
-import 'package:plotter/layout/keyboard.dart';
-import 'package:plotter/provider/data.dart';
-import 'package:plotter/screens/settings.dart';
-import 'package:plotter/utils/api.dart';
-
-class Home extends StatefulWidget {
-  static const String id = 'home';
-
-  const Home({super.key});
-
-  @override
-  HomeState createState() => HomeState();
-}
-
-class HomeState extends State<Home> {
-  @override
-  void initState() {
-    super.initState();
-
-    Data myProvider = Provider.of<Data>(context, listen: false);
-    myProvider.initParametersValues();
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    Data myProvider = Provider.of<Data>(context);
-
-    return Scaffold(
-        appBar: AppBar(
-          title: const Text('Stepper plotter assistant'),
-          actions: [
-            IconButton(
-              icon: const Icon(Icons.settings),
-              onPressed: () {
-                Navigator.push(
-                  context,
-                  MaterialPageRoute(builder: (context) {
-                    return const SettingsPage();
-                  }),
-                );
-              },
-            )
-          ],
-          leading: IconButton(
-            icon: Image.asset('assets/icons/application.png'),
-            onPressed: () {},
-          ),
-        ),
-        body: SafeArea(
-          child: Center(
-              child: Column(
-            children: [
-              Keyboard.buildWidget(myProvider),
-              TextButton(
-                child: const Text('get API status'),
-                onPressed: () => Api.updateApiStatus(myProvider),
-              ),
-              Text(myProvider.apiStatus)
-            ],
-          )),
-        ));
-  }
-}
diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart
deleted file mode 100644
index 886c375a2061e402315b1acf82c8d0eb6c9d528d..0000000000000000000000000000000000000000
--- a/lib/screens/settings.dart
+++ /dev/null
@@ -1,59 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:provider/provider.dart';
-
-import 'package:plotter/provider/data.dart';
-
-class SettingsPage extends StatefulWidget {
-  const SettingsPage({super.key});
-
-  static const String id = 'settings';
-
-  @override
-  SettingsPageState createState() => SettingsPageState();
-}
-
-class SettingsPageState extends State<SettingsPage> {
-  TextEditingController apiHostFieldController = TextEditingController();
-
-  @override
-  void dispose() {
-    apiHostFieldController.dispose();
-    super.dispose();
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    Data myProvider = Provider.of<Data>(context);
-
-    apiHostFieldController.text = myProvider.getParameterValue('apiHost');
-
-    return Scaffold(
-      appBar: AppBar(
-        title: const Text('Stepper - settings'),
-        actions: [
-          IconButton(
-            icon: const Icon(Icons.save),
-            onPressed: () {
-              myProvider.setParameterValue('apiHost', apiHostFieldController.text);
-              Navigator.pop(context);
-            },
-          )
-        ],
-        leading: IconButton(
-          icon: Image.asset('assets/icons/application.png'),
-          onPressed: () {},
-        ),
-      ),
-      body: Padding(
-        padding: const EdgeInsets.all(16.0),
-        child: TextFormField(
-          controller: apiHostFieldController,
-          decoration: const InputDecoration(
-            border: UnderlineInputBorder(),
-            labelText: 'API Host',
-          ),
-        ),
-      ),
-    );
-  }
-}
diff --git a/lib/ui/helpers/app_titles.dart b/lib/ui/helpers/app_titles.dart
new file mode 100644
index 0000000000000000000000000000000000000000..b98107b12fabc3114ebfbec994166b588abcf1ad
--- /dev/null
+++ b/lib/ui/helpers/app_titles.dart
@@ -0,0 +1,32 @@
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+
+class AppHeader extends StatelessWidget {
+  const AppHeader({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.headlineMedium!.apply(fontWeightDelta: 2),
+    );
+  }
+}
+
+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.titleLarge!.apply(fontWeightDelta: 2),
+    );
+  }
+}
diff --git a/lib/ui/helpers/outlined_text_widget.dart b/lib/ui/helpers/outlined_text_widget.dart
new file mode 100644
index 0000000000000000000000000000000000000000..8488ded2c4cae42b1cb3f487c0d3b06f58b3d8fc
--- /dev/null
+++ b/lib/ui/helpers/outlined_text_widget.dart
@@ -0,0 +1,51 @@
+import 'package:flutter/material.dart';
+
+import 'package:plotter/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(),
+          ),
+        ],
+      ),
+    );
+  }
+}
diff --git a/lib/ui/nav/bottom_nav_bar.dart b/lib/ui/nav/bottom_nav_bar.dart
new file mode 100644
index 0000000000000000000000000000000000000000..d66026388a61e3a1445f98f7d90a94f848116c35
--- /dev/null
+++ b/lib/ui/nav/bottom_nav_bar.dart
@@ -0,0 +1,49 @@
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:plotter/config/activity_page.dart';
+import 'package:plotter/cubit/nav_cubit_pages.dart';
+
+class BottomNavBar extends StatelessWidget {
+  const BottomNavBar({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return Card(
+      margin: const EdgeInsets.only(top: 1, right: 4, left: 4),
+      elevation: 4,
+      shadowColor: Theme.of(context).colorScheme.shadow,
+      color: Theme.of(context).colorScheme.surfaceContainerHighest,
+      shape: const RoundedRectangleBorder(
+        borderRadius: BorderRadius.only(
+          topLeft: Radius.circular(16),
+          topRight: Radius.circular(16),
+        ),
+      ),
+      child: BlocBuilder<NavCubitPage, int>(builder: (BuildContext context, int state) {
+        final List<ActivityPageItem> pageItems = [
+          ActivityPage.pageHome,
+          ActivityPage.pageRemoteControl,
+        ];
+        final List<BottomNavigationBarItem> items = pageItems.map((ActivityPageItem item) {
+          return BottomNavigationBarItem(
+            icon: item.icon,
+            label: tr(item.code),
+          );
+        }).toList();
+
+        return BottomNavigationBar(
+          currentIndex: state,
+          onTap: (int index) => context.read<NavCubitPage>().updateIndex(index),
+          type: BottomNavigationBarType.fixed,
+          elevation: 0,
+          backgroundColor: Colors.transparent,
+          selectedItemColor: Theme.of(context).colorScheme.primary,
+          unselectedItemColor: Theme.of(context).textTheme.bodySmall!.color,
+          items: items,
+        );
+      }),
+    );
+  }
+}
diff --git a/lib/ui/nav/global_app_bar.dart b/lib/ui/nav/global_app_bar.dart
new file mode 100644
index 0000000000000000000000000000000000000000..ba02c8429493cae500084438d499901b4c44dc96
--- /dev/null
+++ b/lib/ui/nav/global_app_bar.dart
@@ -0,0 +1,67 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:plotter/config/screen.dart';
+import 'package:plotter/cubit/activity_cubit.dart';
+import 'package:plotter/cubit/nav_cubit_screens.dart';
+import 'package:plotter/ui/helpers/app_titles.dart';
+
+class GlobalAppBar extends StatelessWidget implements PreferredSizeWidget {
+  const GlobalAppBar({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<ActivityCubit, ActivityState>(
+      builder: (BuildContext context, ActivityState activityState) {
+        return BlocBuilder<NavCubitScreen, int>(
+          builder: (BuildContext context, int screenIndex) {
+            final List<Widget> menuActions = [];
+
+            if (screenIndex == Screen.indexActivity) {
+              // go to Settings page
+              menuActions.add(ElevatedButton(
+                onPressed: () {
+                  context.read<NavCubitScreen>().goToSettingsPage();
+                },
+                style: ElevatedButton.styleFrom(
+                  shape: const CircleBorder(),
+                ),
+                child: Screen.screenSettings.icon,
+              ));
+
+              // go to About page
+              menuActions.add(ElevatedButton(
+                onPressed: () {
+                  context.read<NavCubitScreen>().goToAboutPage();
+                },
+                style: ElevatedButton.styleFrom(
+                  shape: const CircleBorder(),
+                ),
+                child: Screen.screenAbout.icon,
+              ));
+            } else {
+              // back to Home page
+              menuActions.add(ElevatedButton(
+                onPressed: () {
+                  context.read<NavCubitScreen>().goToActivityPage();
+                },
+                style: ElevatedButton.styleFrom(
+                  shape: const CircleBorder(),
+                ),
+                child: Screen.screenActivity.icon,
+              ));
+            }
+
+            return AppBar(
+              title: const AppHeader(text: 'app_name'),
+              actions: menuActions,
+            );
+          },
+        );
+      },
+    );
+  }
+
+  @override
+  Size get preferredSize => const Size.fromHeight(50);
+}
diff --git a/lib/ui/pages/home.dart b/lib/ui/pages/home.dart
new file mode 100644
index 0000000000000000000000000000000000000000..2c0c20edf8de7b014022e985ca70787a82e2b864
--- /dev/null
+++ b/lib/ui/pages/home.dart
@@ -0,0 +1,35 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:plotter/cubit/activity_cubit.dart';
+import 'package:plotter/models/activity/activity.dart';
+import 'package:plotter/ui/helpers/outlined_text_widget.dart';
+import 'package:plotter/utils/color_extensions.dart';
+
+class PageHome extends StatelessWidget {
+  const PageHome({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<ActivityCubit, ActivityState>(
+      builder: (BuildContext context, ActivityState activityState) {
+        final Activity currentActivity = activityState.currentActivity;
+
+        return Column(
+          mainAxisAlignment: MainAxisAlignment.start,
+          crossAxisAlignment: CrossAxisAlignment.center,
+          children: <Widget>[
+            const SizedBox(height: 8),
+            OutlinedText(
+              text: '[home]',
+              fontSize: 50,
+              textColor: Colors.blueAccent,
+              outlineColor: Colors.blueAccent.lighten(20),
+            ),
+            Text(currentActivity.toString()),
+          ],
+        );
+      },
+    );
+  }
+}
diff --git a/lib/ui/pages/player.dart b/lib/ui/pages/player.dart
new file mode 100644
index 0000000000000000000000000000000000000000..7bc966803618c0c9c91dad8a626144ac3c864775
--- /dev/null
+++ b/lib/ui/pages/player.dart
@@ -0,0 +1,35 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:plotter/cubit/activity_cubit.dart';
+import 'package:plotter/models/activity/activity.dart';
+import 'package:plotter/ui/helpers/outlined_text_widget.dart';
+import 'package:plotter/utils/color_extensions.dart';
+
+class PagePlayer extends StatelessWidget {
+  const PagePlayer({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<ActivityCubit, ActivityState>(
+      builder: (BuildContext context, ActivityState activityState) {
+        final Activity currentActivity = activityState.currentActivity;
+
+        return Column(
+          mainAxisAlignment: MainAxisAlignment.start,
+          crossAxisAlignment: CrossAxisAlignment.center,
+          children: <Widget>[
+            const SizedBox(height: 8),
+            OutlinedText(
+              text: '[player]',
+              fontSize: 50,
+              textColor: Colors.blueAccent,
+              outlineColor: Colors.blueAccent.lighten(20),
+            ),
+            Text(currentActivity.toString()),
+          ],
+        );
+      },
+    );
+  }
+}
diff --git a/lib/ui/pages/remote_control.dart b/lib/ui/pages/remote_control.dart
new file mode 100644
index 0000000000000000000000000000000000000000..00f1a3dc2c8c1b972d44ce7993413b3c71f203e8
--- /dev/null
+++ b/lib/ui/pages/remote_control.dart
@@ -0,0 +1,39 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:plotter/cubit/activity_cubit.dart';
+import 'package:plotter/models/activity/activity.dart';
+import 'package:plotter/ui/widgets/keyboard.dart';
+import 'package:plotter/utils/api.dart';
+
+class PageRemoteControl extends StatelessWidget {
+  const PageRemoteControl({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return SafeArea(
+      child: Center(
+        child: Column(
+          children: [
+            const Keyboard(),
+            TextButton(
+              child: const Text('get API status'),
+              onPressed: () {
+                final ActivityCubit activityCubit = BlocProvider.of<ActivityCubit>(context);
+
+                Api.updateApiStatus(activityCubit);
+              },
+            ),
+            BlocBuilder<ActivityCubit, ActivityState>(
+              builder: (BuildContext context, ActivityState activityState) {
+                final Activity currentActivity = activityState.currentActivity;
+
+                return Text(currentActivity.apiStatus);
+              },
+            )
+          ],
+        ),
+      ),
+    );
+  }
+}
diff --git a/lib/ui/screens/about.dart b/lib/ui/screens/about.dart
new file mode 100644
index 0000000000000000000000000000000000000000..cf8463f8c594967778d1451d8c1883483a9becfa
--- /dev/null
+++ b/lib/ui/screens/about.dart
@@ -0,0 +1,41 @@
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+import 'package:package_info_plus/package_info_plus.dart';
+
+import 'package:plotter/ui/helpers/app_titles.dart';
+
+class ScreenAbout extends StatelessWidget {
+  const ScreenAbout({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return Padding(
+      padding: const EdgeInsets.symmetric(horizontal: 8),
+      child: Column(
+        mainAxisAlignment: MainAxisAlignment.start,
+        crossAxisAlignment: CrossAxisAlignment.start,
+        mainAxisSize: MainAxisSize.max,
+        children: <Widget>[
+          const SizedBox(height: 8),
+          const AppTitle(text: 'about_title'),
+          const Text('about_content').tr(),
+          FutureBuilder<PackageInfo>(
+            future: PackageInfo.fromPlatform(),
+            builder: (context, snapshot) {
+              switch (snapshot.connectionState) {
+                case ConnectionState.done:
+                  return const Text('about_version').tr(
+                    namedArgs: {
+                      'version': snapshot.data!.version,
+                    },
+                  );
+                default:
+                  return const SizedBox();
+              }
+            },
+          ),
+        ],
+      ),
+    );
+  }
+}
diff --git a/lib/ui/screens/activity.dart b/lib/ui/screens/activity.dart
new file mode 100644
index 0000000000000000000000000000000000000000..edebf87e0f2899c18c60f8eb5af8953fdb1db4eb
--- /dev/null
+++ b/lib/ui/screens/activity.dart
@@ -0,0 +1,17 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:plotter/config/activity_page.dart';
+import 'package:plotter/cubit/nav_cubit_pages.dart';
+
+class ScreenActivity extends StatelessWidget {
+  const ScreenActivity({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<NavCubitPage, int>(
+      builder: (BuildContext context, int pageIndex) {
+        return ActivityPage.getPageWidget(pageIndex);
+      },
+    );
+  }
+}
diff --git a/lib/ui/screens/settings.dart b/lib/ui/screens/settings.dart
new file mode 100644
index 0000000000000000000000000000000000000000..93c7b97937b1c37591d5981acf9b3f2e58e2b4a7
--- /dev/null
+++ b/lib/ui/screens/settings.dart
@@ -0,0 +1,26 @@
+import 'package:flutter/material.dart';
+
+import 'package:plotter/ui/helpers/app_titles.dart';
+import 'package:plotter/ui/settings/settings_form.dart';
+
+class ScreenSettings extends StatelessWidget {
+  const ScreenSettings({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return const Padding(
+      padding: EdgeInsets.symmetric(horizontal: 8),
+      child: Column(
+        mainAxisAlignment: MainAxisAlignment.start,
+        crossAxisAlignment: CrossAxisAlignment.start,
+        mainAxisSize: MainAxisSize.max,
+        children: <Widget>[
+          SizedBox(height: 8),
+          AppTitle(text: 'settings_title'),
+          SizedBox(height: 8),
+          SettingsForm(),
+        ],
+      ),
+    );
+  }
+}
diff --git a/lib/ui/settings/settings_form.dart b/lib/ui/settings/settings_form.dart
new file mode 100644
index 0000000000000000000000000000000000000000..d9d3ad81e761f1548c79c493c167f867191fea84
--- /dev/null
+++ b/lib/ui/settings/settings_form.dart
@@ -0,0 +1,100 @@
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:plotter/cubit/settings_global_cubit.dart';
+import 'package:unicons/unicons.dart';
+
+import 'package:plotter/ui/settings/theme_card.dart';
+
+class SettingsForm extends StatefulWidget {
+  const SettingsForm({super.key});
+
+  @override
+  State<SettingsForm> createState() => _SettingsFormState();
+}
+
+class _SettingsFormState extends State<SettingsForm> {
+  final apiHostFieldController = TextEditingController();
+
+  @override
+  void didChangeDependencies() {
+    GlobalSettingsCubit settings = BlocProvider.of<GlobalSettingsCubit>(context);
+
+    apiHostFieldController.text = settings.getParameterValue('apiHost');
+
+    super.didChangeDependencies();
+  }
+
+  @override
+  void dispose() {
+    apiHostFieldController.dispose();
+
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    void saveSettings() {
+      BlocProvider.of<GlobalSettingsCubit>(context).setValues(
+        apiHost: apiHostFieldController.text,
+      );
+    }
+
+    return Column(
+      mainAxisAlignment: MainAxisAlignment.start,
+      crossAxisAlignment: CrossAxisAlignment.start,
+      mainAxisSize: MainAxisSize.max,
+      children: <Widget>[
+        // Light/dark theme
+        Row(
+          mainAxisAlignment: MainAxisAlignment.spaceBetween,
+          crossAxisAlignment: CrossAxisAlignment.center,
+          children: <Widget>[
+            const Text('settings_label_theme').tr(),
+            const Row(
+              mainAxisAlignment: MainAxisAlignment.end,
+              crossAxisAlignment: CrossAxisAlignment.center,
+              children: [
+                ThemeCard(
+                  mode: ThemeMode.system,
+                  icon: UniconsLine.cog,
+                ),
+                ThemeCard(
+                  mode: ThemeMode.light,
+                  icon: UniconsLine.sun,
+                ),
+                ThemeCard(
+                  mode: ThemeMode.dark,
+                  icon: UniconsLine.moon,
+                )
+              ],
+            ),
+          ],
+        ),
+        const SizedBox(height: 16),
+
+        // Api settings
+        const Text('settings_label_api_host').tr(),
+        TextFormField(
+          controller: apiHostFieldController,
+          decoration: InputDecoration(
+            border: const UnderlineInputBorder(),
+            suffixIcon: ElevatedButton(
+              style: ElevatedButton.styleFrom(
+                shape: RoundedRectangleBorder(
+                  borderRadius: BorderRadius.circular(6.0),
+                ),
+              ),
+              child: const Icon(UniconsLine.save),
+              onPressed: () {
+                saveSettings();
+              },
+            ),
+          ),
+        ),
+
+        const SizedBox(height: 16),
+      ],
+    );
+  }
+}
diff --git a/lib/ui/settings/theme_card.dart b/lib/ui/settings/theme_card.dart
new file mode 100644
index 0000000000000000000000000000000000000000..85d394eda698cdd6d9c742385521a88c9fe7978c
--- /dev/null
+++ b/lib/ui/settings/theme_card.dart
@@ -0,0 +1,47 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:plotter/cubit/theme_cubit.dart';
+
+class ThemeCard extends StatelessWidget {
+  const ThemeCard({
+    super.key,
+    required this.mode,
+    required this.icon,
+  });
+
+  final IconData icon;
+  final ThemeMode mode;
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<ThemeCubit, ThemeModeState>(
+      builder: (BuildContext context, ThemeModeState state) {
+        return Card(
+          elevation: 2,
+          shadowColor: Theme.of(context).colorScheme.shadow,
+          color: state.themeMode == mode
+              ? Theme.of(context).colorScheme.primary
+              : Theme.of(context).colorScheme.surface,
+          shape: const RoundedRectangleBorder(
+            borderRadius: BorderRadius.all(Radius.circular(12)),
+          ),
+          margin: const EdgeInsets.all(5),
+          child: InkWell(
+            onTap: () => BlocProvider.of<ThemeCubit>(context).getTheme(
+              ThemeModeState(themeMode: mode),
+            ),
+            borderRadius: const BorderRadius.all(Radius.circular(12)),
+            child: Icon(
+              icon,
+              size: 32,
+              color: state.themeMode != mode
+                  ? Theme.of(context).colorScheme.primary
+                  : Colors.white,
+            ),
+          ),
+        );
+      },
+    );
+  }
+}
diff --git a/lib/ui/skeleton.dart b/lib/ui/skeleton.dart
new file mode 100644
index 0000000000000000000000000000000000000000..27cd6f11c1776736c9f9ad52a016dc84def522b6
--- /dev/null
+++ b/lib/ui/skeleton.dart
@@ -0,0 +1,37 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:plotter/config/screen.dart';
+import 'package:plotter/cubit/nav_cubit_screens.dart';
+import 'package:plotter/ui/nav/bottom_nav_bar.dart';
+import 'package:plotter/ui/nav/global_app_bar.dart';
+
+class SkeletonScreen extends StatelessWidget {
+  const SkeletonScreen({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<NavCubitScreen, int>(
+      builder: (BuildContext context, int screenIndex) {
+        return Scaffold(
+          appBar: const GlobalAppBar(),
+          extendBodyBehindAppBar: false,
+          body: Material(
+            color: Theme.of(context).colorScheme.surface,
+            child: Padding(
+              padding: const EdgeInsets.only(
+                top: 8,
+                left: 2,
+                right: 2,
+              ),
+              child: Screen.getWidget(screenIndex),
+            ),
+          ),
+          backgroundColor: Theme.of(context).colorScheme.surface,
+          bottomNavigationBar:
+              Screen.displayBottomNavBar(screenIndex) ? const BottomNavBar() : null,
+        );
+      },
+    );
+  }
+}
diff --git a/lib/ui/widgets/activity/activity_bottom.dart b/lib/ui/widgets/activity/activity_bottom.dart
new file mode 100644
index 0000000000000000000000000000000000000000..3ea404ef9858222912094c0371610f2abe179f09
--- /dev/null
+++ b/lib/ui/widgets/activity/activity_bottom.dart
@@ -0,0 +1,20 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:plotter/cubit/activity_cubit.dart';
+import 'package:plotter/models/activity/activity.dart';
+
+class ActivityBottomWidget extends StatelessWidget {
+  const ActivityBottomWidget({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return BlocBuilder<ActivityCubit, ActivityState>(
+      builder: (BuildContext context, ActivityState activityState) {
+        final Activity currentActivity = activityState.currentActivity;
+
+        return Text(currentActivity.apiHost.toString());
+      },
+    );
+  }
+}
diff --git a/lib/ui/widgets/activity/activity_content.dart b/lib/ui/widgets/activity/activity_content.dart
new file mode 100644
index 0000000000000000000000000000000000000000..cf150db18504143a7ddc14baed8f286838b726b0
--- /dev/null
+++ b/lib/ui/widgets/activity/activity_content.dart
@@ -0,0 +1,22 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:plotter/cubit/activity_cubit.dart';
+import 'package:plotter/models/activity/activity.dart';
+
+class ActivityContentWidget extends StatelessWidget {
+  const ActivityContentWidget({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return Center(
+      child: BlocBuilder<ActivityCubit, ActivityState>(
+        builder: (BuildContext context, ActivityState activityState) {
+          final Activity currentActivity = activityState.currentActivity;
+
+          return Text(currentActivity.toString());
+        },
+      ),
+    );
+  }
+}
diff --git a/lib/ui/widgets/keyboard.dart b/lib/ui/widgets/keyboard.dart
new file mode 100644
index 0000000000000000000000000000000000000000..774cd4c9848c5cd19b53833cced521efb8fed740
--- /dev/null
+++ b/lib/ui/widgets/keyboard.dart
@@ -0,0 +1,87 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+
+import 'package:plotter/cubit/activity_cubit.dart';
+import 'package:plotter/utils/api.dart';
+
+class Keyboard extends StatelessWidget {
+  const Keyboard({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      margin: const EdgeInsets.symmetric(horizontal: 2),
+      padding: const EdgeInsets.all(2),
+      child: Table(
+        defaultVerticalAlignment: TableCellVerticalAlignment.middle,
+        children: [
+          TableRow(
+            children: [
+              TableCell(child: buildKeyWidget(context, '')),
+              TableCell(child: buildKeyWidget(context, 'north')),
+              TableCell(child: buildKeyWidget(context, '')),
+            ],
+          ),
+          TableRow(
+            children: [
+              TableCell(child: buildKeyWidget(context, 'west')),
+              TableCell(child: buildKeyWidget(context, '')),
+              TableCell(child: buildKeyWidget(context, 'east')),
+            ],
+          ),
+          TableRow(
+            children: [
+              TableCell(child: buildKeyWidget(context, '')),
+              TableCell(child: buildKeyWidget(context, 'south')),
+              TableCell(child: buildKeyWidget(context, '')),
+            ],
+          ),
+        ],
+      ),
+    );
+  }
+
+  Widget buildKeyWidget(BuildContext context, String direction) {
+    String keyText = '';
+
+    String north = '🔼';
+    String south = '🔽';
+    String west = '◀️';
+    String east = '▶️';
+
+    if (direction == 'north') {
+      keyText = north;
+    } else {
+      if (direction == 'south') {
+        keyText = south;
+      } else {
+        if (direction == 'west') {
+          keyText = west;
+        } else {
+          if (direction == 'east') {
+            keyText = east;
+          }
+        }
+      }
+    }
+
+    return TextButton(
+      style: TextButton.styleFrom(
+        padding: const EdgeInsets.all(0),
+      ),
+      child: Text(keyText,
+          style: const TextStyle(
+            fontSize: 60.0,
+            fontWeight: FontWeight.w800,
+          ),
+          textAlign: TextAlign.center),
+      onPressed: () {
+        if (keyText != '') {
+          final ActivityCubit activityCubit = BlocProvider.of<ActivityCubit>(context);
+
+          Api.move(activityCubit, direction);
+        }
+      },
+    );
+  }
+}
diff --git a/lib/utils/api.dart b/lib/utils/api.dart
index eb394946784d0ad93520800f0f2f476ac8192b7e..3767284f504adbc28346bfc76cca818585a5d6dd 100644
--- a/lib/utils/api.dart
+++ b/lib/utils/api.dart
@@ -1,24 +1,27 @@
 import 'dart:convert';
 import 'package:http/http.dart' as http;
-
-import 'package:plotter/provider/data.dart';
+import 'package:plotter/cubit/activity_cubit.dart';
+import 'package:plotter/models/activity/activity.dart';
 
 class Api {
-  static Future<void> updateApiStatus(Data myProvider) async {
-    final response = await http.get(Uri.http(myProvider.apiHost, 'status'));
+  static Future<void> updateApiStatus(ActivityCubit activityCubit) async {
+    final Activity currentActivity = activityCubit.state.currentActivity;
+    final response = await http.get(Uri.http(currentActivity.apiHost, 'status'));
 
     String responseData = utf8.decode(response.bodyBytes);
     Map<String, dynamic> status = json.decode(responseData);
 
-    myProvider.updateApiStatus(status['status']);
+    activityCubit.updateApiStatus(status['status']);
   }
 
-  static Future<void> move(Data myProvider, String direction) async {
-    final response = await http.get(Uri.http(myProvider.apiHost, direction));
+  static Future<void> move(ActivityCubit activityCubit, String direction) async {
+    final Activity currentActivity = activityCubit.state.currentActivity;
+
+    final response = await http.get(Uri.http(currentActivity.apiHost, direction));
 
     String responseData = utf8.decode(response.bodyBytes);
     Map<String, dynamic> result = json.decode(responseData);
 
-    myProvider.updateApiStatus(result['result']);
+    activityCubit.updateApiStatus(result['result']);
   }
 }
diff --git a/lib/utils/color_extensions.dart b/lib/utils/color_extensions.dart
new file mode 100644
index 0000000000000000000000000000000000000000..4e55e338f0d3ed98b233d1ef887b7b3e17e29d97
--- /dev/null
+++ b/lib/utils/color_extensions.dart
@@ -0,0 +1,33 @@
+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);
+  }
+}
diff --git a/pubspec.lock b/pubspec.lock
index 036b3cd865fce9d2c16bd69eea5f54585156f8d0..c1e8da24d00536dce1d9409b0ef6d537b7ff69db 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -1,6 +1,14 @@
 # Generated by pub
 # See https://dart.dev/tools/pub/glossary#lockfile
 packages:
+  args:
+    dependency: transitive
+    description:
+      name: args
+      sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.5.0"
   async:
     dependency: transitive
     description:
@@ -9,6 +17,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.11.0"
+  bloc:
+    dependency: transitive
+    description:
+      name: bloc
+      sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e"
+      url: "https://pub.dev"
+    source: hosted
+    version: "8.1.4"
   characters:
     dependency: transitive
     description:
@@ -17,6 +33,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.3.0"
+  clock:
+    dependency: transitive
+    description:
+      name: clock
+      sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.1.1"
   collection:
     dependency: transitive
     description:
@@ -25,6 +49,38 @@ packages:
       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: fa59bcdbbb911a764aa6acf96bbb6fa7a5cf8234354fc45ec1a43a0349ef0201
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.0.7"
+  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:
@@ -46,19 +102,40 @@ packages:
     description: flutter
     source: sdk
     version: "0.0.0"
+  flutter_bloc:
+    dependency: "direct main"
+    description:
+      name: flutter_bloc
+      sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a
+      url: "https://pub.dev"
+    source: hosted
+    version: "8.1.6"
   flutter_lints:
     dependency: "direct dev"
     description:
       name: flutter_lints
-      sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7
+      sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
       url: "https://pub.dev"
     source: hosted
-    version: "3.0.1"
+    version: "4.0.0"
+  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"
   http:
     dependency: "direct main"
     description:
@@ -75,14 +152,30 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "4.0.2"
+  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: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.19.0"
   lints:
     dependency: transitive
     description:
       name: lints
-      sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
+      sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
       url: "https://pub.dev"
     source: hosted
-    version: "3.0.0"
+    version: "4.0.0"
   material_color_utilities:
     dependency: transitive
     description:
@@ -95,10 +188,10 @@ packages:
     dependency: transitive
     description:
       name: meta
-      sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
+      sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
       url: "https://pub.dev"
     source: hosted
-    version: "1.11.0"
+    version: "1.12.0"
   nested:
     dependency: transitive
     description:
@@ -107,6 +200,22 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.0.0"
+  package_info_plus:
+    dependency: "direct main"
+    description:
+      name: package_info_plus
+      sha256: b93d8b4d624b4ea19b0a5a208b2d6eff06004bc3ce74c06040b120eeadd00ce0
+      url: "https://pub.dev"
+    source: hosted
+    version: "8.0.0"
+  package_info_plus_platform_interface:
+    dependency: transitive
+    description:
+      name: package_info_plus_platform_interface
+      sha256: f49918f3433a3146047372f9d4f1f847511f2acd5cd030e1f44fe5a50036b70e
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.0.0"
   path:
     dependency: transitive
     description:
@@ -115,6 +224,30 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.9.0"
+  path_provider:
+    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: bca87b0165ffd7cdb9cad8edd22d18d2201e886d9a9f19b4fb3452ea7df3a72a
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.2.6"
+  path_provider_foundation:
+    dependency: transitive
+    description:
+      name: path_provider_foundation
+      sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.4.0"
   path_provider_linux:
     dependency: transitive
     description:
@@ -143,10 +276,10 @@ packages:
     dependency: transitive
     description:
       name: platform
-      sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec"
+      sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65"
       url: "https://pub.dev"
     source: hosted
-    version: "3.1.4"
+    version: "3.1.5"
   plugin_platform_interface:
     dependency: transitive
     description:
@@ -156,7 +289,7 @@ packages:
     source: hosted
     version: "2.1.8"
   provider:
-    dependency: "direct main"
+    dependency: transitive
     description:
       name: provider
       sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
@@ -164,29 +297,29 @@ packages:
     source: hosted
     version: "6.1.2"
   shared_preferences:
-    dependency: "direct main"
+    dependency: transitive
     description:
       name: shared_preferences
-      sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02"
+      sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180
       url: "https://pub.dev"
     source: hosted
-    version: "2.2.2"
+    version: "2.2.3"
   shared_preferences_android:
     dependency: transitive
     description:
       name: shared_preferences_android
-      sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06"
+      sha256: "93d0ec9dd902d85f326068e6a899487d1f65ffcd5798721a95330b26c8131577"
       url: "https://pub.dev"
     source: hosted
-    version: "2.2.1"
+    version: "2.2.3"
   shared_preferences_foundation:
     dependency: transitive
     description:
       name: shared_preferences_foundation
-      sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c"
+      sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7"
       url: "https://pub.dev"
     source: hosted
-    version: "2.3.5"
+    version: "2.4.0"
   shared_preferences_linux:
     dependency: transitive
     description:
@@ -240,6 +373,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.2.0"
+  synchronized:
+    dependency: transitive
+    description:
+      name: synchronized
+      sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.1.0+1"
   term_glyph:
     dependency: transitive
     description:
@@ -256,6 +397,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.3.2"
+  unicons:
+    dependency: "direct main"
+    description:
+      name: unicons
+      sha256: "1cca7462df18ff191b7e41b52f747d08854916531d1d7ab7cec0552095995206"
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.1.2"
   vector_math:
     dependency: transitive
     description:
@@ -268,18 +417,18 @@ packages:
     dependency: transitive
     description:
       name: web
-      sha256: "1d9158c616048c38f712a6646e317a3426da10e884447626167240d45209cbad"
+      sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
       url: "https://pub.dev"
     source: hosted
-    version: "0.5.0"
+    version: "0.5.1"
   win32:
     dependency: transitive
     description:
       name: win32
-      sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8"
+      sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4
       url: "https://pub.dev"
     source: hosted
-    version: "5.2.0"
+    version: "5.5.1"
   xdg_directories:
     dependency: transitive
     description:
@@ -289,5 +438,5 @@ packages:
     source: hosted
     version: "1.0.4"
 sdks:
-  dart: ">=3.3.0 <4.0.0"
-  flutter: ">=3.19.0"
+  dart: ">=3.4.0 <4.0.0"
+  flutter: ">=3.22.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index c9b48d1f1fdca6e033f14817780b071315c7598c..dbc4f9f2974a51a9fe978041692da149da5ae877 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,22 +1,47 @@
 name: plotter
 description: A plotter helper application.
-publish_to: 'none'
-version: 0.0.16+16
+
+publish_to: "none"
+
+version: 0.0.17+17
 
 environment:
-  sdk: '^3.0.0'
+  sdk: "^3.0.0"
 
 dependencies:
   flutter:
     sdk: flutter
-  provider: ^6.0.5
-  shared_preferences: ^2.2.1
+
+  # base
+  easy_localization: ^3.0.1
+  equatable: ^2.0.5
+  flutter_bloc: ^8.1.1
+  hive: ^2.2.3
+  hydrated_bloc: ^9.0.0
+  package_info_plus: ^8.0.0
+  path_provider: ^2.0.11
+  unicons: ^2.1.1
+
+  # specific
   http: ^1.1.0
 
 dev_dependencies:
-  flutter_lints: ^3.0.1
+  flutter_lints: ^4.0.0
 
 flutter:
   uses-material-design: true
   assets:
-    - 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
+
diff --git a/icons/build_application_icons.sh b/resources/app/build_application_resources.sh
similarity index 98%
rename from icons/build_application_icons.sh
rename to resources/app/build_application_resources.sh
index 27dbe2647fe4e6d562fbd99451716d1b7d448570..6d67b8f4f9eca701d1aed7331ef41dfb0bd44f20 100755
--- a/icons/build_application_icons.sh
+++ b/resources/app/build_application_resources.sh
@@ -6,7 +6,7 @@ command -v scour >/dev/null 2>&1 || { echo >&2 "I require scour but it's not ins
 command -v optipng >/dev/null 2>&1 || { echo >&2 "I require optipng 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}")"
+BASE_DIR="$(dirname "$(dirname "${CURRENT_DIR}")")"
 
 SOURCE_ICON="${CURRENT_DIR}/icon.svg"
 SOURCE_FASTLANE="${CURRENT_DIR}/featureGraphic.svg"
diff --git a/icons/featureGraphic.svg b/resources/app/featureGraphic.svg
similarity index 100%
rename from icons/featureGraphic.svg
rename to resources/app/featureGraphic.svg
diff --git a/icons/icon.svg b/resources/app/icon.svg
similarity index 100%
rename from icons/icon.svg
rename to resources/app/icon.svg
diff --git a/resources/build_resources.sh b/resources/build_resources.sh
new file mode 100755
index 0000000000000000000000000000000000000000..8d5848a985ed1068f6e6226379a9cedb69c3fa15
--- /dev/null
+++ b/resources/build_resources.sh
@@ -0,0 +1,6 @@
+#! /bin/bash
+
+CURRENT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
+
+${CURRENT_DIR}/app/build_application_resources.sh
+