From d1cd6018dc0ad5afc9443fd6bf8041abe5e9f4db Mon Sep 17 00:00:00 2001
From: Plague Fox <plugfox@gmail.com>
Date: Sun, 8 Dec 2024 19:53:45 +0400
Subject: [PATCH] Add initial package layout

---
 .github/FUNDING.yml                       |  13 +
 .github/ISSUE_TEMPLATE/bug_report.md      |  40 ++
 .github/ISSUE_TEMPLATE/config.yml         |   5 +
 .github/ISSUE_TEMPLATE/feature_request.md |  19 +
 .github/actions/setup/action.yaml         |  65 +++
 .github/dependabot.yaml                   |  10 +
 .github/workflows/checkout.yml            | 116 ++++++
 .github/workflows/test-report.yml         |  27 ++
 .gitignore                                |  87 ++++
 .metadata                                 |  10 +
 .pubignore                                |   1 +
 .vscode/extensions.json                   |   9 +
 .vscode/launch.json                       |  29 ++
 .vscode/settings.json                     |  98 +++++
 .vscode/tasks.json                        | 470 ++++++++++++++++++++++
 CHANGELOG.md                              |   3 +
 CONTRIBUTING.md                           |   0
 LICENSE                                   |  21 +
 Makefile                                  |  96 +++++
 analysis_options.yaml                     | 209 ++++++++++
 dart_test.yaml                            |  13 +
 lib/mess.dart                             |   1 +
 lib/src/.keep                             |   0
 pubspec.yaml                              |  56 +++
 test/unit_test.dart                       |  39 ++
 test/widget_test.dart                     |  15 +
 26 files changed, 1452 insertions(+)
 create mode 100644 .github/FUNDING.yml
 create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md
 create mode 100644 .github/ISSUE_TEMPLATE/config.yml
 create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md
 create mode 100644 .github/actions/setup/action.yaml
 create mode 100644 .github/dependabot.yaml
 create mode 100644 .github/workflows/checkout.yml
 create mode 100644 .github/workflows/test-report.yml
 create mode 100644 .gitignore
 create mode 100644 .metadata
 create mode 100644 .pubignore
 create mode 100644 .vscode/extensions.json
 create mode 100644 .vscode/launch.json
 create mode 100644 .vscode/settings.json
 create mode 100644 .vscode/tasks.json
 create mode 100644 CHANGELOG.md
 create mode 100644 CONTRIBUTING.md
 create mode 100644 LICENSE
 create mode 100644 Makefile
 create mode 100644 analysis_options.yaml
 create mode 100644 dart_test.yaml
 create mode 100644 lib/mess.dart
 create mode 100644 lib/src/.keep
 create mode 100644 pubspec.yaml
 create mode 100644 test/unit_test.dart
 create mode 100644 test/widget_test.dart

diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..90ef515
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,13 @@
+# These are supported funding model platforms
+
+#github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+patreon: plugfox
+#open_collective: # Replace with a single Open Collective username
+#ko_fi: # Replace with a single Ko-fi username
+#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+#liberapay: # Replace with a single Liberapay username
+#issuehunt: # Replace with a single IssueHunt username
+#otechie: # Replace with a single Otechie username
+#lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
+custom: ['https://www.buymeacoffee.com/plugfox', 'https://boosty.to/plugfox']
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..9b77ea7
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,40 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ""
+labels: ""
+assignees: ""
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Desktop (please complete the following information):**
+
+- OS: [e.g. iOS]
+- Browser [e.g. chrome, safari]
+- Version [e.g. 22]
+
+**Smartphone (please complete the following information):**
+
+- Device: [e.g. iPhone6]
+- OS: [e.g. iOS8.1]
+- Browser [e.g. stock browser, safari]
+- Version [e.g. 22]
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..c2b01f3
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,5 @@
+blank_issues_enabled: false
+contact_links:
+  - name: Questions & Help
+    url: https://t.me/ru_dart
+    about: Ask a question about Spinify
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..2bc5d5f
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,19 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ""
+labels: ""
+assignees: ""
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/.github/actions/setup/action.yaml b/.github/actions/setup/action.yaml
new file mode 100644
index 0000000..7b9150f
--- /dev/null
+++ b/.github/actions/setup/action.yaml
@@ -0,0 +1,65 @@
+name: Setup
+description: Sets up the Flutter environment
+
+inputs:
+  flutter-version:
+    description: 'The version of Flutter to use'
+    required: false
+    default: '3.24.5'
+  pub-cache:
+    description: 'The name of the pub cache variable'
+    required: false
+    default: app
+
+runs:
+  using: composite
+  steps:
+    - name: πŸ“¦ Checkout the repo
+      uses: actions/checkout@v4
+
+    - name: πŸ”’ Set up version from tags
+      id: set-version
+      if: startsWith(github.ref, 'refs/tags')
+      shell: bash
+      run: |
+        BASE_VERSION="${GITHUB_REF#refs/tags/v}"
+        UNIXTIME=$(date +%s)
+        VERSION="${BASE_VERSION}+${UNIXTIME}"
+        echo "VERSION=$VERSION" >> $GITHUB_ENV
+        sed -i "s/^version: .*/version: ${VERSION}/" pubspec.yaml
+        echo "Version set to $VERSION"
+
+    - name: πŸš‚ Setup Flutter
+      uses: subosito/flutter-action@v2
+      with:
+        channel: "stable"
+        flutter-version: '${{ inputs.flutter-version }}'
+
+    - name: πŸ“€ Restore Pub modules
+      id: cache-pub-restore
+      uses: actions/cache/restore@v4
+      with:
+        path: |
+          /home/runner/.pub-cache
+        key: ${{ runner.os }}-pub-${{ inputs.pub-cache }}-${{ hashFiles('pubspec.lock') }}
+
+    - name: πŸ‘· Install Dependencies
+      shell: bash
+      run: |
+        echo /home/runner/.pub-cache/bin >> $GITHUB_PATH
+        flutter config --no-cli-animations --no-analytics
+        flutter pub get
+
+    #- name: ⏲️ Run build runner
+    #  shell: bash
+    #  run: |
+    #    dart run build_runner build --delete-conflicting-outputs --release
+
+    - name: πŸ“₯ Save Pub modules
+      id: cache-pub-save
+      if: steps.cache-pub-restore.outputs.cache-hit != 'true'
+      uses: actions/cache/save@v4
+      with:
+        path: |
+          /home/runner/.pub-cache
+        key: ${{ steps.cache-pub-restore.outputs.cache-primary-key }}
diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml
new file mode 100644
index 0000000..e54675c
--- /dev/null
+++ b/.github/dependabot.yaml
@@ -0,0 +1,10 @@
+version: 2
+enable-beta-ecosystems: true
+updates:
+  - directory: "/"
+    open-pull-requests-limit: 5
+    package-ecosystem: "pub"
+    rebase-strategy: auto
+    schedule:
+      interval: "monthly"
+      timezone: "Etc/GMT"
diff --git a/.github/workflows/checkout.yml b/.github/workflows/checkout.yml
new file mode 100644
index 0000000..e646263
--- /dev/null
+++ b/.github/workflows/checkout.yml
@@ -0,0 +1,116 @@
+name: Checkout
+
+on:
+  workflow_dispatch:
+  push:
+    branches:
+      - "main"
+      - "master"
+      #- "dev"
+      #- "develop"
+      #- "feature/**"
+      #- "bugfix/**"
+      #- "hotfix/**"
+      #- "support/**"
+  #  paths:
+  #    - "pubspec.yaml"
+  #    - "pubspec.lock"
+  #    - ".github/**.yaml"
+  #    - ".github/**.yml"
+  #    - "lib/**.dart"
+  #    - "test/**.dart"
+  #    - "packages/**"
+  #    - "example/**.dart"
+  pull_request:
+    branches:
+      - "main"
+      - "master"
+      - "dev"
+      - "develop"
+      - "feature/**"
+      - "bugfix/**"
+      - "hotfix/**"
+      - "support/**"
+    paths:
+      - "pubspec.yaml"
+      - ".github/**.yaml"
+      - ".github/**.yml"
+      - "lib/**.dart"
+      - "test/**.dart"
+      - "example/**.dart"
+
+permissions:
+  contents: read
+  actions: read
+  checks: write
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  checkout:
+    name: "πŸ§ͺ Check code with analysis, format, and tests"
+    runs-on: ubuntu-latest
+    defaults:
+      run:
+        working-directory: ./
+    timeout-minutes: 10
+    steps:
+      - name: πŸ“¦ Get the .github actions
+        uses: actions/checkout@v4
+        with:
+          sparse-checkout: |
+            .github
+
+      - name: πŸš‚ Setup Flutter and dependencies
+        uses: ./.github/actions/setup
+        with:
+          flutter-version: 3.24.3
+
+      - name: πŸ‘· Install Dependencies
+        timeout-minutes: 1
+        run: |
+          flutter pub get
+
+      - name: 🚦 Check code format
+        id: check-format
+        timeout-minutes: 1
+        run: |
+          find lib test -name "*.dart" ! -name "*.*.dart" -print0 | xargs -0 dart format --set-exit-if-changed --line-length 80 -o none lib/ test/
+
+      - name: πŸ“ˆ Check analyzer
+        id: check-analyzer
+        timeout-minutes: 1
+        run: flutter analyze --fatal-infos --fatal-warnings lib/ test/
+
+      - name: πŸ‘€ Verify versions
+        id: verify-versions
+        timeout-minutes: 1
+        run: |
+          test -f pubspec.yaml && test -f CHANGELOG.md
+          version_pubspec=$(grep '^version:' pubspec.yaml | awk '{print $2}' | sed 's/[^[:print:]]//g')
+          test -n "$version_pubspec"
+          echo "Version from pubspec.yaml: '$version_pubspec'"
+          echo "$version_pubspec" > /tmp/version_pubspec
+          grep -q "# $version_pubspec" CHANGELOG.md || (echo "Version not found in CHANGELOG.md" >&2; exit 1)
+
+      - name: πŸ§ͺ Unit & Widget tests
+        timeout-minutes: 20
+        run: |
+          flutter test --coverage --concurrency=40 test/unit_test.dart test/widget_test.dart
+
+      #- name: πŸ“₯ Upload coverage report
+      #  timeout-minutes: 5
+      #  if: ${{ github.actor != 'dependabot[bot]' }}
+      #  uses: codecov/codecov-action@v2
+      #  with:
+      #    token: ${{ secrets.CODECOV_TOKEN }}
+      #    files: ./coverage/lcov.info
+
+      - name: πŸ“₯ Upload test report
+        uses: actions/upload-artifact@v4
+        if: (success() || failure()) && ${{ github.actor != 'dependabot[bot]' }}
+        with:
+          name: test-results
+          path: reports/tests.json
diff --git a/.github/workflows/test-report.yml b/.github/workflows/test-report.yml
new file mode 100644
index 0000000..6fe454d
--- /dev/null
+++ b/.github/workflows/test-report.yml
@@ -0,0 +1,27 @@
+name: "Test Report"
+
+on:
+  workflow_run:
+    workflows: ["Checkout"] # runs after "Checkout" workflow
+    types:
+      - completed
+
+permissions:
+  contents: read
+  actions: read
+  checks: write
+
+jobs:
+  report:
+    name: "πŸš› Test report"
+    runs-on: ubuntu-latest
+    timeout-minutes: 10
+    steps:
+      - name: Test report
+        uses: dorny/test-reporter@v1
+        with:
+          artifact: test-results
+          name: Test Report
+          path: "**/tests.json"
+          reporter: flutter-json
+          fail-on-error: false
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f3125e5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,87 @@
+# Don’t commit the following directories created by pub.
+.buildlog
+.dart_tool/
+.pub/
+build/
+packages
+*.packages
+.idea/
+doc
+
+# Or the files created by dart2js.
+*.dart.js
+*.js_
+*.js.deps
+*.js.map
+
+# Include when developing application packages.
+pubspec.lock
+coverage*
+
+# Codegen
+*.g.dart
+!pubspec.yaml.g.dart
+
+# Logs
+l/
+
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+/build/
+
+# Web related
+lib/generated_plugin_registrant.dart
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
+
+# Pana
+log.pana.json
+
+# Test
+coverage/
+.coverage/
+/test/**/*.json
+/test/.test_coverage.dart
+reports/
+.reports/
+
+# Binaries for programs and plugins
+*.exe
+*.exe~
+
+pubspec.lock
\ No newline at end of file
diff --git a/.metadata b/.metadata
new file mode 100644
index 0000000..7025a64
--- /dev/null
+++ b/.metadata
@@ -0,0 +1,10 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+  revision: "dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668"
+  channel: "stable"
+
+project_type: package
diff --git a/.pubignore b/.pubignore
new file mode 100644
index 0000000..34c62e3
--- /dev/null
+++ b/.pubignore
@@ -0,0 +1 @@
+pubspec.lock
\ No newline at end of file
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000..767a67f
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,9 @@
+{
+  "recommendations": [
+    "dart-code.dart-code",
+    "Dart-Code.flutter",
+    "github.vscode-github-actions",
+    "raczzalan.webgl-glsl-editor",
+    "circledev.glsl-canvas"
+  ]
+}
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..34a8c7e
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,29 @@
+{
+  "version": "0.2.0",
+  "configurations": [
+    {
+      "name": "Example (Debug)",
+      "type": "dart",
+      "program": "lib/main.dart",
+      "request": "launch",
+      "flutterMode": "debug",
+      "cwd": "${workspaceFolder}/example",
+      "args": [
+        "--dart-define-from-file=config/development.json",
+      ],
+      "env": {}
+    },
+    {
+      "name": "Example (Release)",
+      "type": "dart",
+      "program": "lib/main.dart",
+      "request": "launch",
+      "flutterMode": "release",
+      "cwd": "${workspaceFolder}/example",
+      "args": [
+        "--dart-define-from-file=config/development.json",
+      ],
+      "env": {}
+    }
+  ]
+}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..d7b3b3e
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,98 @@
+{
+  "[dart]": {
+    "editor.insertSpaces": true,
+    "editor.tabSize": 2,
+    "editor.suggest.snippetsPreventQuickSuggestions": false,
+    "editor.suggestSelection": "first",
+    "editor.tabCompletion": "onlySnippets",
+    "editor.wordBasedSuggestions": "off",
+    "editor.selectionHighlight": false,
+    "editor.defaultFormatter": "Dart-Code.dart-code",
+    "editor.formatOnSave": true,
+    "editor.formatOnType": true,
+    "editor.formatOnPaste": true,
+    "editor.codeActionsOnSave": {
+      "source.fixAll": "explicit",
+      "source.organizeImports": "explicit"
+    },
+    "editor.quickSuggestions": {
+      "comments": "on",
+      "strings": "on",
+      "other": "on"
+    },
+    "editor.links": true,
+    "editor.rulers": [
+      80
+    ]
+  },
+  "dart.lineLength": 80,
+  "dart.doNotFormat": [
+    "**.g.dart",
+    "**.gql.dart",
+    "**.freezed.dart",
+    "**.config.dart",
+    "**.mocks.dart",
+    "**.gen.dart",
+    "**.pb.dart",
+    "**.pbenum.dart",
+    "**.pbjson.dart",
+    "**/generated/**"
+  ],
+  // Flutter Version Manager
+  //"dart.flutterSdkPath": ".fvm/flutter_sdk",
+  // Remove .fvm files from search
+  "search.exclude": {
+    //"**/.fvm": true,
+    ".dart_tool": true,
+    "coverage": true,
+    "build": true
+  },
+  // Remove from file watching
+  "files.watcherExclude": {
+    //"**/.fvm": true,
+    ".dart_tool": true,
+    "coverage": true,
+    "build": true
+  },
+  // Causes the debug view to automatically appear when a breakpoint is hit. This
+  // setting is global and not configurable per-language.
+  "debug.openDebug": "openOnDebugBreak",
+  "explorer.fileNesting.enabled": true,
+  "explorer.fileNesting.expand": false,
+  "explorer.fileNesting.patterns": {
+    "pubspec.yaml": ".flutter-plugins, .packages, .dart_tool, .flutter-plugins-dependencies, .metadata, .packages, pubspec.lock, build.yaml, analysis_options.yaml, all_lint_rules.yaml, dart*.yaml, flutter*.yaml, icons_launcher.yaml",
+    ".gitignore": ".gitattributes, .gitmodules, .gitmessage, .mailmap, .git-blame*",
+    "readme.*": "authors, backers.md, changelog*, citation*, code_of_conduct.md, codeowners, contributing.md, contributors, copying, credits, governance.md, history.md, license*, maintainers, readme*, security.md, sponsors.md",
+    "*.dart": "$(capture).g.dart, $(capture).freezed.dart, $(capture).config.dart"
+  },
+  /* "files.associations": {
+    "*.drift": "sql"
+  }, */
+  /* "highlight.regexes": {
+    "(\"@\\s*.+\":\\s{0,1}{},)": {
+      "filterFileRegex": ".*\\.arb",
+      "decorations": [
+        {
+          "overviewRulerColor": "#d19a66",
+          "backgroundColor": "#d19a66",
+          "color": "#282c34",
+          "fontWeight": "bold"
+        }
+      ]
+    }
+  }, */
+  // -- Shaders -- //
+  "glsl-canvas.refreshOnChange": false,
+  "glsl-canvas.refreshOnSave": true,
+  "[glsl]": {
+    "editor.defaultFormatter": "raczzalan.webgl-glsl-editor"
+  },
+  "[frag]": {
+    "editor.defaultFormatter": "raczzalan.webgl-glsl-editor"
+  },
+  "[vert]": {
+    "editor.defaultFormatter": "raczzalan.webgl-glsl-editor"
+  },
+  "editor.defaultFormatter": "raczzalan.webgl-glsl-editor",
+  "notebook.defaultFormatter": "raczzalan.webgl-glsl-editor",
+}
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000..075d269
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,470 @@
+{
+  "version": "2.0.0",
+  "tasks": [
+    {
+      "label": "dart:pub:get",
+      "detail": "Get dependencies for the project",
+      "icon": {
+        "color": "terminal.ansiGreen",
+        "id": "cloud-download"
+      },
+      "dependsOn": [],
+      "type": "shell",
+      "command": "dart pub get",
+      "args": [],
+      "group": {
+        "kind": "none",
+        "isDefault": true
+      },
+      "problemMatcher": [],
+      "options": {
+        "cwd": "${workspaceFolder}"
+      },
+      "isBackground": false,
+      "presentation": {
+        "reveal": "silent",
+        "focus": false,
+        "panel": "shared",
+        "showReuseMessage": false,
+        "clear": false,
+        "group": "dart"
+      }
+    },
+    {
+      "label": "dart:get-protoc-plugin",
+      "detail": "Get protoc plugin",
+      "icon": {
+        "color": "terminal.ansiGreen",
+        "id": "cloud-download"
+      },
+      "type": "shell",
+      "command": "dart pub global activate protoc_plugin",
+      "dependsOn": [],
+      "args": [],
+      "group": {
+        "kind": "none",
+        "isDefault": true
+      },
+      "problemMatcher": [],
+      "options": {
+        "cwd": "${workspaceFolder}"
+      },
+      "isBackground": false,
+      "presentation": {
+        "reveal": "silent",
+        "focus": false,
+        "panel": "shared",
+        "showReuseMessage": false,
+        "clear": false,
+        "group": "dart"
+      }
+    },
+    {
+      "label": "dart:generate-protobuf",
+      "detail": "Generate protobuf files",
+      "icon": {
+        "color": "terminal.ansiGreen",
+        "id": "code"
+      },
+      "type": "shell",
+      "command": [
+        "protoc",
+        "--proto_path=lib/src/transport/protobuf",
+        "--dart_out=lib/src/transport/protobuf lib/src/transport/protobuf/client.proto"
+      ],
+      "dependsOn": [
+        "dart:get-protoc-plugin"
+      ],
+      "args": [],
+      "group": {
+        "kind": "none",
+        "isDefault": true
+      },
+      "problemMatcher": [],
+      "options": {
+        "cwd": "${workspaceFolder}"
+      },
+      "isBackground": false,
+      "presentation": {
+        "reveal": "silent",
+        "focus": false,
+        "panel": "shared",
+        "showReuseMessage": false,
+        "clear": false,
+        "group": "dart"
+      }
+    },
+    {
+      "label": "dart:build_runner:all",
+      "detail": "Generate code for the project",
+      "icon": {
+        "color": "terminal.ansiGreen",
+        "id": "code"
+      },
+      "type": "shell",
+      "command": [
+        "dart run build_runner build --delete-conflicting-outputs",
+        "&& dart format --fix -l 80 lib test tool example"
+      ],
+      "dependsOn": [
+        "dart:dependencies",
+        "dart:generate-protobuf"
+      ],
+      "args": [],
+      "group": {
+        "kind": "none",
+        "isDefault": true
+      },
+      "problemMatcher": [],
+      "options": {
+        "cwd": "${workspaceFolder}"
+      },
+      "isBackground": false,
+      "presentation": {
+        "reveal": "silent",
+        "focus": false,
+        "panel": "shared",
+        "showReuseMessage": false,
+        "clear": false,
+        "group": "dart"
+      }
+    },
+    {
+      "label": "dart:build_runner:dir",
+      "detail": "Generate code for the directory",
+      "type": "shell",
+      "icon": {
+        "color": "terminal.ansiGreen",
+        "id": "code"
+      },
+      "command": [
+        "dart run build_runner build --build-filter '${fileDirname}/*.dart'",
+        "&& dart format --fix -l 80 '${fileDirname}'"
+      ],
+      "group": {
+        "kind": "none",
+        "isDefault": true
+      },
+      "problemMatcher": [],
+      "dependsOn": [
+        "dart:pub:get"
+      ],
+      "isBackground": false,
+      "presentation": {
+        "reveal": "silent",
+        "focus": false,
+        "panel": "shared",
+        "showReuseMessage": false,
+        "clear": false,
+        "group": "dart"
+      }
+    },
+    {
+      "label": "dart:build_runner:watch",
+      "detail": "Watch for changes in the project",
+      "type": "shell",
+      "icon": {
+        "color": "terminal.ansiGreen",
+        "id": "code"
+      },
+      "command": "dart run build_runner watch --build-filter \"${input:directory}/**/*.dart\"",
+      "group": {
+        "kind": "none",
+        "isDefault": true
+      },
+      "problemMatcher": [],
+      "dependsOn": [
+        "dart:pub:get"
+      ],
+      "isBackground": false,
+      "presentation": {
+        "reveal": "silent",
+        "focus": false,
+        "panel": "shared",
+        "showReuseMessage": false,
+        "clear": false,
+        "group": "dart"
+      }
+    },
+    {
+      "label": "dart:format",
+      "detail": "Format all files in the project",
+      "icon": {
+        "color": "terminal.ansiGreen",
+        "id": "lightbulb-autofix"
+      },
+      "type": "shell",
+      "command": [
+        "dart format --fix -l 80 lib test tool example"
+      ],
+      "dependsOn": [],
+      "args": [],
+      "group": {
+        "kind": "none",
+        "isDefault": true
+      },
+      "problemMatcher": [],
+      "options": {
+        "cwd": "${workspaceFolder}"
+      },
+      "isBackground": false,
+      "presentation": {
+        "reveal": "silent",
+        "focus": false,
+        "panel": "shared",
+        "showReuseMessage": false,
+        "clear": false,
+        "group": "dart"
+      }
+    },
+    {
+      "label": "centrifugo:start",
+      "detail": "Start centrifugo server",
+      "icon": {
+        "color": "terminal.ansiBlue",
+        "id": "server"
+      },
+      "type": "shell",
+      "windows": {
+        "command": "docker",
+        "args": [
+          "run",
+          "-d",
+          "--rm",
+          "--ulimit=nofile=65536:65536",
+          "-p=8000:8000/tcp",
+          "--volume=${PWD}/config.json:/centrifugo/config.json:ro",
+          "--name=centrifugo",
+          "centrifugo/centrifugo:latest",
+          "centrifugo",
+          "--client_insecure",
+          "--admin",
+          "--admin_insecure",
+          "--log_level=debug"
+        ]
+      },
+      "linux": {
+        "command": "docker",
+        "args": [
+          "run",
+          "-d",
+          "--rm",
+          "--ulimit=nofile=65536:65536",
+          "-p=8000:8000/tcp",
+          "--volume=${PWD}/config.json:/centrifugo/config.json:ro",
+          "--name=centrifugo",
+          "centrifugo/centrifugo:latest",
+          "centrifugo",
+          //"--client_insecure",
+          "--admin",
+          "--admin_insecure",
+          "--log_level=debug"
+        ]
+      },
+      "osx": {
+        "command": "docker",
+        "args": [
+          "run",
+          "-d",
+          "--rm",
+          "--ulimit=nofile=65536:65536",
+          "-p=8000:8000/tcp",
+          "--volume=${PWD}/config.json:/centrifugo/config.json:ro",
+          "--name=centrifugo",
+          "centrifugo/centrifugo:latest",
+          "centrifugo",
+          "--client_insecure",
+          "--admin",
+          "--admin_insecure",
+          "--log_level=debug"
+        ]
+      },
+      "dependsOn": [],
+      "args": [],
+      "group": {
+        "kind": "none",
+        "isDefault": true
+      },
+      "problemMatcher": [],
+      "options": {
+        "cwd": "${workspaceFolder}"
+      },
+      "isBackground": false,
+      "presentation": {
+        "reveal": "silent",
+        "focus": false,
+        "panel": "shared",
+        "showReuseMessage": false,
+        "clear": false,
+        "group": "centrifugo"
+      }
+    },
+    {
+      "label": "centrifugo:stop",
+      "detail": "Stop centrifugo server",
+      "icon": {
+        "color": "terminal.ansiRed",
+        "id": "server"
+      },
+      "type": "shell",
+      "command": "docker",
+      "args": [
+        "stop",
+        "centrifugo"
+      ],
+      "dependsOn": [],
+      "group": {
+        "kind": "none",
+        "isDefault": true
+      },
+      "problemMatcher": [],
+      "options": {
+        "cwd": "${workspaceFolder}"
+      },
+      "isBackground": false,
+      "presentation": {
+        "reveal": "silent",
+        "focus": false,
+        "panel": "shared",
+        "showReuseMessage": false,
+        "clear": false,
+        "group": "centrifugo"
+      }
+    },
+    {
+      "label": "centrifugo:gentoken",
+      "detail": "Generate new user token for centrifugo server",
+      "icon": {
+        "color": "terminal.ansiCyan",
+        "id": "key"
+      },
+      "type": "shell",
+      "command": "docker",
+      "args": [
+        "run",
+        "-it",
+        "--rm",
+        "--volume=${PWD}/config.json:/centrifugo/config.json:ro",
+        "--name=centrifugo-cli",
+        "centrifugo/centrifugo:latest",
+        "centrifugo",
+        "gentoken",
+        "--user=dart",
+        "--ttl=604800"
+      ],
+      "dependsOn": [],
+      "group": {
+        "kind": "none",
+        "isDefault": true
+      },
+      "problemMatcher": [],
+      "options": {
+        "cwd": "${workspaceFolder}"
+      },
+      "isBackground": false,
+      "presentation": {
+        "reveal": "silent",
+        "focus": false,
+        "panel": "shared",
+        "showReuseMessage": false,
+        "clear": false,
+        "group": "centrifugo"
+      }
+    },
+    {
+      "label": "echo:start",
+      "detail": "Start echo server",
+      "icon": {
+        "color": "terminal.ansiBlue",
+        "id": "server"
+      },
+      "type": "shell",
+      "command": "dart",
+      "isBackground": false,
+      "options": {
+        "cwd": "${workspaceFolder}/tool",
+        "env": {}
+      },
+      "args": [
+        "run",
+        "echo_up.dart"
+      ],
+      "group": {
+        "kind": "none",
+        "isDefault": true
+      },
+      "problemMatcher": [],
+      "presentation": {
+        "reveal": "silent",
+        "focus": false,
+        "panel": "shared",
+        "showReuseMessage": false,
+        "clear": false,
+        "group": "centrifugo"
+      }
+    },
+    {
+      "label": "echo:stop",
+      "detail": "Stop echo server",
+      "icon": {
+        "color": "terminal.ansiRed",
+        "id": "server"
+      },
+      "type": "shell",
+      "command": "dart",
+      "options": {
+        "cwd": "${workspaceFolder}/tool",
+      },
+      "dependsOn": [],
+      "args": [
+        "run",
+        "echo_down.dart"
+      ],
+      "group": {
+        "kind": "none",
+        "isDefault": true
+      },
+      "problemMatcher": [],
+      "isBackground": false,
+      "presentation": {
+        "reveal": "silent",
+        "focus": false,
+        "panel": "shared",
+        "showReuseMessage": false,
+        "clear": false,
+        "group": "echo"
+      }
+    },
+    {
+      "label": "echo:run",
+      "detail": "Run echo server with Go",
+      "icon": {
+        "color": "terminal.ansiGreen",
+        "id": "server"
+      },
+      "options": {
+        "cwd": "${workspaceFolder}/tool/echo",
+        "env": {}
+      },
+      "type": "shell",
+      "command": "go",
+      "args": [
+        "run",
+        "echo.go"
+      ],
+      "isBackground": true,
+      "group": {
+        "kind": "none",
+        "isDefault": true
+      },
+      "problemMatcher": [],
+      "presentation": {
+        "reveal": "always",
+        "panel": "dedicated",
+        "focus": true,
+        "clear": true,
+        "group": "echo"
+      }
+    },
+  ]
+}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..acb2741
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 0.0.1
+
+- Basic functionality
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..e69de29
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..393e439
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 Matiunin Mikhail <plugfox@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..766340a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,96 @@
+SHELL :=/bin/bash -e -o pipefail
+PWD   :=$(shell pwd)
+
+.DEFAULT_GOAL := all
+.PHONY: all
+all: ## build pipeline
+all: format check test
+
+.PHONY: ci
+ci: ## CI build pipeline
+ci: all
+
+.PHONY: precommit
+precommit: ## validate the branch before commit
+precommit: all
+
+.PHONY: help
+help:
+	@echo 'Usage: make <OPTIONS> ... <TARGETS>'
+	@echo ''
+	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
+
+.PHONY: format
+format: ## Format the code
+	@dart format -l 80 --fix lib/ test/
+	@dart fix --apply .
+
+.PHONY: get
+get: ## Get the dependencies
+	@flutter pub get
+
+.PHONY: outdated
+outdated: get ## Check for outdated dependencies
+	@flutter pub outdated --show-all --dev-dependencies --dependency-overrides --transitive --no-prereleases
+
+.PHONY: test
+test: get ## Run the tests
+	@flutter test --concurrency=40 test/unit_test.dart test/widget_test.dart
+
+.PHONY: publish-check
+publish-check: ## Check the package before publishing
+	@flutter pub publish --dry-run
+
+.PHONY: deploy-check
+deploy-check: publish-check
+
+.PHONY: publish
+publish: ## Publish the package
+	@flutter pub publish
+
+.PHONY: deploy
+deploy: publish
+
+.PHONY: coverage
+coverage: get ## Generate the coverage report
+	@flutter test --coverage --concurrency=40 test/unit_test.dart test/widget_test.dart
+#	@lcov --remove coverage/lcov.info 'lib/**/*.g.dart' -o coverage/lcov.info
+	@lcov --list coverage/lcov.info
+	@genhtml -o coverage coverage/lcov.info
+
+.PHONY: analyze
+analyze: get ## Analyze the code
+	@dart format --set-exit-if-changed -l 80 -o none lib/ test/
+	@flutter analyze --fatal-infos --fatal-warnings lib/ test/
+
+.PHONY: check
+check: analyze publish-check ## Check the code
+	@dart pub global activate pana
+	@pana --json --no-warning --line-length 80 > log.pana.json
+
+.PHONY: pana
+pana: check
+
+#.PHONY: generate
+#generate: get ## Generate the code
+#	@dart pub global activate protoc_plugin
+#	@protoc --proto_path=lib/src/protobuf --dart_out=lib/src/protobuf lib/src/protobuf/client.proto
+#	@dart run build_runner build --delete-conflicting-outputs
+#	@dart format -l 80 lib/src/model/pubspec.yaml.g.dart lib/src/protobuf/ test/
+
+#.PHONY: gen
+#gen: generate
+
+#.PHONY: codegen
+#codegen: generate
+
+.PHONY: version
+version: ## Show the Flutter version
+	@flutter --version
+	@which flutter
+
+.PHONY: diff
+diff: ## git diff
+	$(call print-target)
+	@git diff --exit-code
+	@RES=$$(git status --porcelain) ; if [ -n "$$RES" ]; then echo $$RES && exit 1 ; fi
diff --git a/analysis_options.yaml b/analysis_options.yaml
new file mode 100644
index 0000000..a35f5dd
--- /dev/null
+++ b/analysis_options.yaml
@@ -0,0 +1,209 @@
+include: package:flutter_lints/flutter.yaml
+
+analyzer:
+  exclude:
+    # Build
+    - "build/**"
+    # Codegen
+    - "lib/**.g.dart"
+    # Tests
+    - "test/**.mocks.dart"
+    - ".test_coverage.dart"
+    # Assets
+    - "assets/**"
+
+  # Enable the following options to enable strong mode.
+  language:
+    strict-casts: true
+    strict-raw-types: true
+    strict-inference: true
+
+  errors:
+    # Allow having TODOs in the code
+    todo: ignore
+
+    # Info
+    directives_ordering: info
+    always_declare_return_types: info
+
+    # Warning
+    unsafe_html: warning
+    missing_return: warning
+    missing_required_param: warning
+    no_logic_in_create_state: warning
+    empty_catches: warning
+
+    # Error
+    prefer_relative_imports: error
+    avoid_relative_lib_imports: error
+    avoid_slow_async_io: error
+    avoid_types_as_parameter_names: error
+    valid_regexps: error
+    always_require_non_null_named_parameters: error
+
+linter:
+  rules:
+    # Public packages
+    public_member_api_docs: true
+    lines_longer_than_80_chars: true
+
+    # Enabling rules
+    prefer_relative_imports: true
+    avoid_relative_lib_imports: true
+
+    # Disable rules
+    sort_pub_dependencies: false
+    always_use_package_imports: false
+    prefer_final_locals: false
+    avoid_escaping_inner_quotes: false
+    curly_braces_in_flow_control_structures: false
+
+    # Enabled
+    use_named_constants: true
+    unnecessary_constructor_name: true
+    sort_constructors_first: true
+    exhaustive_cases: true
+    sort_unnamed_constructors_first: true
+    type_literal_in_constant_pattern: true
+    always_put_required_named_parameters_first: true
+    avoid_annotating_with_dynamic: true
+    avoid_bool_literals_in_conditional_expressions: true
+    avoid_double_and_int_checks: true
+    avoid_field_initializers_in_const_classes: true
+    avoid_implementing_value_types: true
+    avoid_js_rounded_ints: true
+    avoid_print: true
+    avoid_renaming_method_parameters: true
+    avoid_returning_null_for_void: true
+    avoid_single_cascade_in_expression_statements: true
+    avoid_slow_async_io: true
+    avoid_unnecessary_containers: true
+    avoid_unused_constructor_parameters: true
+    avoid_void_async: true
+    await_only_futures: true
+    cancel_subscriptions: true
+    cascade_invocations: true
+    close_sinks: true
+    control_flow_in_finally: true
+    empty_statements: true
+    collection_methods_unrelated_type: true
+    join_return_with_assignment: true
+    leading_newlines_in_multiline_strings: true
+    literal_only_boolean_expressions: true
+    missing_whitespace_between_adjacent_strings: true
+    no_adjacent_strings_in_list: true
+    no_logic_in_create_state: true
+    no_runtimeType_toString: true
+    only_throw_errors: true
+    overridden_fields: true
+    package_names: true
+    package_prefixed_library_names: true
+    parameter_assignments: true
+    prefer_asserts_in_initializer_lists: true
+    prefer_asserts_with_message: true
+    prefer_const_constructors: true
+    prefer_const_constructors_in_immutables: true
+    prefer_const_declarations: true
+    prefer_const_literals_to_create_immutables: true
+    prefer_constructors_over_static_methods: true
+    prefer_expression_function_bodies: true
+    prefer_final_in_for_each: true
+    prefer_foreach: true
+    prefer_if_elements_to_conditional_expressions: true
+    prefer_inlined_adds: true
+    prefer_int_literals: true
+    prefer_is_not_operator: true
+    prefer_null_aware_operators: true
+    prefer_typing_uninitialized_variables: true
+    prefer_void_to_null: true
+    provide_deprecation_message: true
+    sized_box_for_whitespace: true
+    sort_child_properties_last: true
+    test_types_in_equals: true
+    throw_in_finally: true
+    unnecessary_null_aware_assignments: true
+    unnecessary_overrides: true
+    unnecessary_parenthesis: true
+    unnecessary_raw_strings: true
+    unnecessary_statements: true
+    unnecessary_string_escapes: true
+    unnecessary_string_interpolations: true
+    unsafe_html: true
+    use_full_hex_values_for_flutter_colors: true
+    use_raw_strings: true
+    use_string_buffers: true
+    valid_regexps: true
+    void_checks: true
+
+    # Pedantic 1.9.0
+    always_declare_return_types: true
+    annotate_overrides: true
+    avoid_empty_else: true
+    avoid_init_to_null: true
+    avoid_null_checks_in_equality_operators: true
+    avoid_return_types_on_setters: true
+    avoid_shadowing_type_parameters: true
+    avoid_types_as_parameter_names: true
+    camel_case_extensions: true
+    empty_catches: true
+    empty_constructor_bodies: true
+    library_names: true
+    library_prefixes: true
+    no_duplicate_case_values: true
+    null_closures: true
+    omit_local_variable_types: true
+    prefer_adjacent_string_concatenation: true
+    prefer_collection_literals: true
+    prefer_conditional_assignment: true
+    prefer_contains: true
+    prefer_final_fields: true
+    prefer_for_elements_to_map_fromIterable: true
+    prefer_generic_function_type_aliases: true
+    prefer_if_null_operators: true
+    prefer_is_empty: true
+    prefer_is_not_empty: true
+    prefer_iterable_whereType: true
+    prefer_single_quotes: true
+    prefer_spread_collections: true
+    recursive_getters: true
+    slash_for_doc_comments: true
+    type_init_formals: true
+    unawaited_futures: true
+    unnecessary_const: true
+    unnecessary_new: true
+    unnecessary_null_in_if_null_operators: true
+    unnecessary_this: true
+    unrelated_type_equality_checks: true
+    use_function_type_syntax_for_parameters: true
+    use_rethrow_when_possible: true
+
+    # Effective_dart 1.2.0
+    camel_case_types: true
+    file_names: true
+    non_constant_identifier_names: true
+    constant_identifier_names: true
+    directives_ordering: true
+    package_api_docs: true
+    implementation_imports: true
+    prefer_interpolation_to_compose_strings: true
+    unnecessary_brace_in_string_interps: true
+    avoid_function_literals_in_foreach_calls: true
+    prefer_function_declarations_over_variables: true
+    unnecessary_lambdas: true
+    unnecessary_getters_setters: true
+    prefer_initializing_formals: true
+    avoid_catches_without_on_clauses: true
+    avoid_catching_errors: true
+    use_to_and_as_if_applicable: true
+    one_member_abstracts: true
+    avoid_classes_with_only_static_members: true
+    prefer_mixin: true
+    use_setters_to_change_properties: true
+    avoid_setters_without_getters: true
+    avoid_returning_this: true
+    type_annotate_public_apis: true
+    avoid_types_on_closure_parameters: true
+    avoid_private_typedef_functions: true
+    avoid_positional_boolean_parameters: true
+    hash_and_equals: true
+    avoid_equals_and_hash_code_on_mutable_classes: true
diff --git a/dart_test.yaml b/dart_test.yaml
new file mode 100644
index 0000000..d14b29c
--- /dev/null
+++ b/dart_test.yaml
@@ -0,0 +1,13 @@
+timeout: 1x
+
+platforms:
+  - vm
+
+file_reporters:
+  json: reports/tests.json
+
+tags:
+  unit:
+    timeout: 1x
+  smoke:
+    timeout: 2x
diff --git a/lib/mess.dart b/lib/mess.dart
new file mode 100644
index 0000000..316a905
--- /dev/null
+++ b/lib/mess.dart
@@ -0,0 +1 @@
+library;
diff --git a/lib/src/.keep b/lib/src/.keep
new file mode 100644
index 0000000..e69de29
diff --git a/pubspec.yaml b/pubspec.yaml
new file mode 100644
index 0000000..9f27356
--- /dev/null
+++ b/pubspec.yaml
@@ -0,0 +1,56 @@
+name: mess
+description: >
+  **Mass Entity State System (MESS)** is a robust ECS (Entity-Component-System) library for Flutter
+  designed to manage large-scale entities and their states.
+  MESS enables developers to build complex systems with a clear architecture, ensuring high performance and scalability.
+
+version: 0.0.1
+
+homepage: https://github.com/PlugFox/mess
+
+repository: https://github.com/PlugFox/mess
+
+issue_tracker: https://github.com/PlugFox/mess/issues
+
+funding:
+  - https://www.buymeacoffee.com/plugfox
+  - https://www.patreon.com/plugfox
+  - https://boosty.to/plugfox
+
+topics:
+  - ECS
+  - Entity-Component-System
+  - State Management
+  - Game Development
+  - Mass Entity State System
+
+platforms:
+  android:
+  ios:
+  linux:
+  macos:
+  web:
+  windows:
+
+#screenshots:
+#  - description: 'Example of using the library.'
+#    path: example.png
+
+environment:
+  sdk: ^3.5.0
+  flutter: ">=3.20.0"
+
+dependencies:
+  flutter:
+    sdk: flutter
+  # Annotations
+  meta: ^1.9.1
+
+dev_dependencies:
+  flutter_test:
+    sdk: flutter
+  test: ^1.25.0
+  fake_async: ^1.3.0
+  flutter_lints: '>=4.0.0 <6.0.0'
+
+flutter:
diff --git a/test/unit_test.dart b/test/unit_test.dart
new file mode 100644
index 0000000..876e41b
--- /dev/null
+++ b/test/unit_test.dart
@@ -0,0 +1,39 @@
+import 'package:test/test.dart';
+
+void main() {
+  group('Unit', () {
+    group('Time', () {
+      test('Seconds', () {
+        const duration = Duration(seconds: 5);
+        expect(
+          duration.inSeconds * Duration.millisecondsPerSecond,
+          equals(5 * 1000),
+        );
+        expect(
+          duration.inSeconds *
+              Duration.millisecondsPerSecond *
+              Duration.microsecondsPerMillisecond,
+          equals(5 * 1000 * 1000),
+        );
+        expect(
+          duration.inSeconds * Duration.microsecondsPerSecond,
+          equals(5 * 1000 * 1000),
+        );
+      });
+
+      test('Delta', () {
+        const elapsed = Duration(seconds: 15);
+        const previous = Duration(seconds: 14);
+        final delta = elapsed - previous;
+        final ms = delta.inMicroseconds / Duration.microsecondsPerMillisecond;
+        expect(ms, equals(1000));
+      });
+
+      test('Frame rate', () {
+        const frameRate = 60;
+        const deltaMs = 1000 / frameRate;
+        expect(deltaMs, equals(1000 / 60));
+      });
+    });
+  });
+}
diff --git a/test/widget_test.dart b/test/widget_test.dart
new file mode 100644
index 0000000..18ee546
--- /dev/null
+++ b/test/widget_test.dart
@@ -0,0 +1,15 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+  group('Widget', () {
+    testWidgets(
+      'Pump Widget',
+      (tester) async {
+        await tester.pumpWidget(const MaterialApp(home: Scaffold()));
+        expect(find.byType(MaterialApp), findsOneWidget);
+        expect(find.byType(Scaffold), findsOneWidget);
+      },
+    );
+  });
+}