diff --git a/.actrc b/.actrc new file mode 100644 index 00000000..5c08277a --- /dev/null +++ b/.actrc @@ -0,0 +1 @@ +-P ubuntu-latest=catthehacker/ubuntu:act-latest diff --git a/.claude/settings.json b/.claude/settings.json index e64b570d..0400d042 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,4 +1,5 @@ { + "$schema": "https://json.schemastore.org/claude-code-settings.json", "hooks": { "PostToolUse": [ { @@ -6,7 +7,9 @@ "hooks": [ { "type": "command", - "command": "scripts/format_edited_file.sh" + "command": "scripts/format_edited_file.sh", + "timeout": 30, + "statusMessage": "Formatting Dart..." } ] } @@ -16,10 +19,27 @@ "hooks": [ { "type": "command", - "command": "scripts/verify_stop_gate.sh" + "command": "scripts/verify_stop_gate.sh", + "timeout": 60, + "statusMessage": "Checking quality gate..." } ] } ] + }, + "permissions": { + "allow": [ + "Bash(./scripts/verify.sh)", + "Bash(./scripts/verify.sh:*)", + "Bash(./scripts/check_coverage.sh:*)", + "Bash(./scripts/check_l10n.sh)", + "Bash(./scripts/workspace_hash.sh)", + "Bash(fvm flutter analyze)", + "Bash(fvm flutter analyze:*)", + "Bash(fvm flutter test:*)", + "Bash(fvm flutter gen-l10n)", + "Bash(fvm flutter pub get)", + "Bash(fvm dart format:*)" + ] } } diff --git a/.githooks/pre-push b/.githooks/pre-push new file mode 100755 index 00000000..feeb2c9b --- /dev/null +++ b/.githooks/pre-push @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# +# Runs the quality gate before a push, so a failure surfaces here rather than +# as a red CI run minutes later. +# +# Branch protection on main is what actually blocks a broken change. This hook +# is a local shortcut to the same answer, and `git push --no-verify` skips it. + +set -euo pipefail + +readonly root="$(git rev-parse --show-toplevel)" +cd "$root" + +readonly deleted_ref="0000000000000000000000000000000000000000" + +# stdin carries one line per ref being pushed: +# . A branch deletion has no commits to check. +pushing_commits=false +while read -r _ local_sha _ _; do + [[ "$local_sha" == "$deleted_ref" ]] && continue + pushing_commits=true +done + +"$pushing_commits" || exit 0 + +exec "$root/scripts/verify.sh" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..8b2e908e --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,45 @@ +## What + + + +Closes # + +## Why + + + +## Checklist + +Tick what applies, delete what does not, and say why for anything left out. + +**Code** + +- [ ] Follows the existing structure: feature-first, `data`/`domain`/`presentation`, Riverpod for state (see `CLAUDE.md`) +- [ ] No inline colors, spacing, radii or text styles: everything comes from the shared design tokens +- [ ] No hardcoded user-facing text +- [ ] `Result` matched with `when`/`switch`, never `is SuccessResult`/`is FailureResult` +- [ ] Logging goes through `AppLogger`; no SQL text, database names or other user data logged + +**Tests** + +- [ ] New logic has tests (repository, use case, view model, widget behaviour) +- [ ] A bug fix has a test that fails without the fix + +**Generated and localized** + +- [ ] `gen-l10n` re-run, output committed +- [ ] New strings added to all three ARB files (`en`, `es`, `pt`), each with a `description` + +**Documentation** + +- [ ] Docs in `docs/` updated in all three languages, if the change touched behaviour, structure or tooling +- [ ] README scripts table still accurate + +**Gate** + +- [ ] `./scripts/verify.sh` passes locally +- [ ] `act pull_request` run, if this touches `.github/workflows/` + +## Notes for the reviewer + + diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index bc7c0ab9..3fe2c762 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -69,9 +69,62 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage/lcov.info + flags: app name: sql_studio fail_ci_if_error: false + app_ui: + name: packages/app_ui + runs-on: ubuntu-latest + timeout-minutes: 15 + defaults: + run: + working-directory: packages/app_ui + steps: + - uses: actions/checkout@v7 + + - name: Read Flutter version from .fvmrc + id: flutter-version + run: echo "version=$(jq -r '.flutter' ../../.fvmrc)" >> "$GITHUB_OUTPUT" + + - uses: subosito/flutter-action@v2 + with: + flutter-version: ${{ steps.flutter-version.outputs.version }} + channel: 'stable' + cache: true + + - name: Install dependencies + run: flutter pub get + + - name: Verify formatting + run: dart format --output=none --set-exit-if-changed lib/ test/ + + - name: Analyze + run: flutter analyze + + - name: Test with coverage + run: flutter test --coverage + + # A low interim floor: most components are still untested token + # classes (AppColors' ThemeExtension lerp/copyWith) waiting on later + # migration steps. Raise this as coverage catches up, not on faith. + - name: Check coverage threshold + run: ../../scripts/check_coverage.sh packages/app_ui/coverage/lcov.info 79 + + # lcov paths are relative to the package root; Codecov matches them + # against the repository, so they need the package prefix. + - name: Rewrite coverage paths to be repository-relative + run: sed -i 's|^SF:|SF:packages/app_ui/|' coverage/lcov.info + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: packages/app_ui/coverage/lcov.info + flags: app_ui + name: app_ui + fail_ci_if_error: false + build_apk: name: Build release APK needs: quality @@ -177,6 +230,12 @@ jobs: timeout 480 flutter test integration_test/delete_database_test.dart -d emulator-5554 adb -s emulator-5554 shell am force-stop br.com.dariomatias.sql_studio timeout 480 flutter test integration_test/locale_switch_test.dart -d emulator-5554 + adb -s emulator-5554 shell am force-stop br.com.dariomatias.sql_studio + timeout 480 flutter test integration_test/theme_persists_test.dart -d emulator-5554 + adb -s emulator-5554 shell am force-stop br.com.dariomatias.sql_studio + timeout 480 flutter test integration_test/workspace_layout_persists_test.dart -d emulator-5554 + adb -s emulator-5554 shell am force-stop br.com.dariomatias.sql_studio + timeout 480 flutter test integration_test/favorite_database_persists_test.dart -d emulator-5554 osv-scanner: name: Scan dependencies for known vulnerabilities @@ -188,11 +247,12 @@ jobs: steps: - uses: actions/checkout@v7 - # Reads pubspec.lock against the OSV database, which carries pub - # advisories. The lockfile is what matters: the caret ranges in + # Reads the lockfiles against the OSV database, which carries pub + # advisories. The lockfile is what matters: the caret ranges in a # pubspec.yaml say what could resolve, not what actually ships. - - name: Scan the lockfile + - name: Scan the lockfiles uses: google/osv-scanner-action/osv-scanner-action@v2.5.1 with: scan-args: |- --lockfile=pubspec.lock + --lockfile=packages/app_ui/pubspec.lock diff --git a/.gitignore b/.gitignore index 5c8a058e..b6bb66cb 100644 --- a/.gitignore +++ b/.gitignore @@ -31,7 +31,9 @@ migrate_working_dir/ .pub-cache/ .pub/ /build/ +packages/*/build/ /coverage/ +packages/*/coverage/ # Symbolication related app.*.symbols @@ -49,3 +51,8 @@ app.*.map.json # Local verify.sh pass stamp, read by the Stop hook. /.claude/verify-stamp + +# Per-developer Claude Code state: a personal permissions override, and +# worktrees created by isolated agent runs. +/.claude/settings.local.json +/.claude/worktrees/ diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 0ee8c012..2537c1f1 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.3.0" + ".": "0.4.0" } diff --git a/.vscode/settings.json b/.vscode/settings.json index f4c6fab9..30ecbd75 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "dart.flutterSdkPath": ".fvm/versions/3.41.6" + "dart.flutterSdkPath": ".fvm/flutter_sdk" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 81e01e81..c6239a30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,420 @@ All notable changes to this project are documented in this file. Format based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [0.4.0](https://github.com/dariomatias-dev/sql_studio_app/compare/sql_studio-v0.3.0...sql_studio-v0.4.0) (2026-09-20) + + +### Features + +* **a11y:** add semantic labels and clamp text scaling ([c2a9080](https://github.com/dariomatias-dev/sql_studio_app/commit/c2a9080c5b1b3130dda850496b0a9632d1ba0c63)) +* add About screen with a custom license viewer ([0c5d560](https://github.com/dariomatias-dev/sql_studio_app/commit/0c5d56000d2187db263256bbe1ca75be511f9657)) +* add URL opening in settings screen ([46c6729](https://github.com/dariomatias-dev/sql_studio_app/commit/46c67293d2edeb537c3e0f294fde9355fd213929)) +* added `SqlCommandsNotifier` to the widget tree ([e76bd82](https://github.com/dariomatias-dev/sql_studio_app/commit/e76bd8230974e460594cd222a6f3c7102b44a6e1)) +* added app version display ([f3386e2](https://github.com/dariomatias-dev/sql_studio_app/commit/f3386e2200fe4d47e96e8c0947cc4d46eb20d339)) +* added result handling to `DatabaseService` ([31ae0da](https://github.com/dariomatias-dev/sql_studio_app/commit/31ae0da1171e2062d9ba5efaa273085050da6705)) +* added smoothed version of app icon ([541f9ad](https://github.com/dariomatias-dev/sql_studio_app/commit/541f9ad70375f8c4c845be340801683d979cc898)) +* added SQL export ([b3eebbf](https://github.com/dariomatias-dev/sql_studio_app/commit/b3eebbf5f01eee4e8610ab2c38a638a2806feed6)) +* **android/app/build.gradle.kts:** configure signing ([94e4191](https://github.com/dariomatias-dev/sql_studio_app/commit/94e41918e7670c0cd43c161eb7a346d173cd5c91)) +* **assets/sql/schemas:** add table drop commands ([950f897](https://github.com/dariomatias-dev/sql_studio_app/commit/950f89709b6bd3f870c25c5959caf837e6ac9c9a)) +* **assets/sql/seeds:** expand records ([440a871](https://github.com/dariomatias-dev/sql_studio_app/commit/440a87198fd13c3238cf17cbdc5d67c8f666ed7d)) +* **assets/sql:** create new schemas and seeds ([91df07d](https://github.com/dariomatias-dev/sql_studio_app/commit/91df07d4b5e4a5653bf077b9ffcf68fdb25769c7)) +* configure internationalization ([1f99445](https://github.com/dariomatias-dev/sql_studio_app/commit/1f994456de40f57b8694b1f71daa9e170ca3d833)) +* configured routes ([b143072](https://github.com/dariomatias-dev/sql_studio_app/commit/b143072eb49a6c0707c1caa2d91768d45321b156)) +* **console:** add empty states for query execution ([1ab7588](https://github.com/dariomatias-dev/sql_studio_app/commit/1ab75885195582649750e4d63abff9537b7eee46)) +* **core:** add an injectable logger behind a contract ([9d9a266](https://github.com/dariomatias-dev/sql_studio_app/commit/9d9a2661848c91b8cefb3bc0deb4b7d943cb03d5)) +* **core:** add Riverpod infrastructure and feature architecture ([1a297d0](https://github.com/dariomatias-dev/sql_studio_app/commit/1a297d08ab2efa469ba4ec9e16003b4b6db61460)) +* **core:** install global error handlers and a startup failure screen ([0d482fd](https://github.com/dariomatias-dev/sql_studio_app/commit/0d482fd37f993e8ad37762b3712b23e4cdf7a256)) +* created class for `SharedPreferences` abstraction ([a3dd333](https://github.com/dariomatias-dev/sql_studio_app/commit/a3dd33308e1e330cc19d3a4290a7b8c6d90a77e2)) +* created database repository ([079e6bb](https://github.com/dariomatias-dev/sql_studio_app/commit/079e6bbab6abb935612d63525ceddda62354466c)) +* created schemas for default databases ([2214ecb](https://github.com/dariomatias-dev/sql_studio_app/commit/2214ecbdd0101f1b4c9b194a48b2a97aa84ac46d)) +* created seeds for default databases ([181c255](https://github.com/dariomatias-dev/sql_studio_app/commit/181c255d36605b5ef6fa3dddc4fee57208ad1214)) +* **database-visualizer:** add navigate-to-table action from schema diagram ([9f6ebf2](https://github.com/dariomatias-dev/sql_studio_app/commit/9f6ebf2cefdb36bceed6d578351dbf3cecd386aa)) +* **database-visualizer:** fit diagram to the viewport on load ([9acc62b](https://github.com/dariomatias-dev/sql_studio_app/commit/9acc62b084445463579f2123e428cbde462b2411)) +* **database:** add search filter to the databases list ([cbb53c2](https://github.com/dariomatias-dev/sql_studio_app/commit/cbb53c2af8270ac6dfa70fbe2a246e3c4180f0ae)) +* **feedback:** add toast confirmation for database actions ([2fa3193](https://github.com/dariomatias-dev/sql_studio_app/commit/2fa3193d2db6c76f17a20d4d4db2efa68ea8e76c)) +* **l10n:** add l10n.yaml and describe every ARB key ([eb8ae0b](https://github.com/dariomatias-dev/sql_studio_app/commit/eb8ae0b05e53d61d3cd7107fdb26eb61d35c4d8f)) +* **lib/l10n:** added translations for `SqlExecutionService` ([8d357a3](https://github.com/dariomatias-dev/sql_studio_app/commit/8d357a39ef62e604b23f1f7fa93b0aa7dff42d6a)) +* **lib/src/core/constants/default_databases.dart:** add new default databases ([03b88ab](https://github.com/dariomatias-dev/sql_studio_app/commit/03b88ab5f9a6282514a882c5381ead6f1c590d26)) +* **lib/src/core/navigation/widgets/root_drawer:** refactor design ([430d31e](https://github.com/dariomatias-dev/sql_studio_app/commit/430d31e96158579b9aa83c0bb4ce26b55a8bb804)) +* **lib/src/core/navigation:** refactor navigation bar ([92e765b](https://github.com/dariomatias-dev/sql_studio_app/commit/92e765be9c94494fc194d58b1cf629ba8fb1125b)) +* **lib/src/core/result.dart:** added `DatabaseSuccess` class ([b0f6353](https://github.com/dariomatias-dev/sql_studio_app/commit/b0f6353428baa6653d7fc908e420ab17e75d97bb)) +* **lib/src/core/routes:** refactored routing ([bf3a9d9](https://github.com/dariomatias-dev/sql_studio_app/commit/bf3a9d97261798abf0c07b6d26925b3400185527)) +* **lib/src/core/routes:** updated routes ([4244fc8](https://github.com/dariomatias-dev/sql_studio_app/commit/4244fc845119d5cda426fb550fc1f7d0288119ac)) +* **lib/src/core/types/sql_advanced_suggestion_model.dart:** added new features ([ee71cdc](https://github.com/dariomatias-dev/sql_studio_app/commit/ee71cdcc21a9a7c212b4f2bb6393c8c5a6877662)) +* **lib/src/core:** created classes to handle results ([2411cb3](https://github.com/dariomatias-dev/sql_studio_app/commit/2411cb34c334182ca04246b47985978c2df870b3)) +* **lib/src/core:** created list extension ([6e5583a](https://github.com/dariomatias-dev/sql_studio_app/commit/6e5583a0594547e9c28be776f39cbc9f26b1fee0)) +* **lib/src/core:** created new global navigation structure ([4403ff9](https://github.com/dariomatias-dev/sql_studio_app/commit/4403ff94429eed273df86ec107cca64a28caf91b)) +* **lib/src/notifiers/database_notifier.dart:** exposed `getByName` method ([f064a7c](https://github.com/dariomatias-dev/sql_studio_app/commit/f064a7cd2abd27ebf8e060c33364ca3219e1a66d)) +* **lib/src/notifiers/sql_commands_notifier.dart:** add result handling to `resetDatabase` method ([240dc80](https://github.com/dariomatias-dev/sql_studio_app/commit/240dc802b2e4a7d0763677eb0ce9a45bb058d21f)) +* **lib/src/notifiers/sql_commands_notifier.dart:** added `activeDatabase` property ([cec6515](https://github.com/dariomatias-dev/sql_studio_app/commit/cec651596213347cebfd29db07a8b92b3dabce48)) +* **lib/src/notifiers/sql_commands_notifier.dart:** added `getTableColumns` method ([214e6b5](https://github.com/dariomatias-dev/sql_studio_app/commit/214e6b5ce92de3650a5a26bbd829565c466460dd)) +* **lib/src/notifiers/sql_commands_notifier.dart:** added method to reset default database ([144309c](https://github.com/dariomatias-dev/sql_studio_app/commit/144309c0f83cfa044716f80ce27420e615a43188)) +* **lib/src/notifiers/sql_commands_notifier.dart:** simplify reset method ([94149e3](https://github.com/dariomatias-dev/sql_studio_app/commit/94149e33a544d2ccc4e037683b0ae7406e236fb1)) +* **lib/src/notifiers/sql_editor_notifier.dart:** add auto-selection to `insertCommand` method ([d4c3adf](https://github.com/dariomatias-dev/sql_studio_app/commit/d4c3adf59f1e429e3a451666c43d156e8c106ed6)) +* **lib/src/notifiers/sql_editor_notifier.dart:** disable auto-suggestion in code field ([6e39027](https://github.com/dariomatias-dev/sql_studio_app/commit/6e390274ac2cc9d574bee001cb2b75b106080e08)) +* **lib/src/notifiers/sql_suggestions_notifier.dart:** add management for all types of suggestions ([92f8dd5](https://github.com/dariomatias-dev/sql_studio_app/commit/92f8dd57c6c6012363103bab188988d9e016146d)) +* **lib/src/notifiers/sql_suggestions_notifier.dart:** added result handling ([388c596](https://github.com/dariomatias-dev/sql_studio_app/commit/388c59685148460ab22220252f858ad91503783f)) +* **lib/src/notifiers/sql_suggestions_notifier.dart:** removed SQL advanced suggestions list ([fbc93d2](https://github.com/dariomatias-dev/sql_studio_app/commit/fbc93d29e56985c371ab7ea0238b02b2154f7ddd)) +* **lib/src/notifiers/sql_suggestions_notifier.dart:** removed SQL advanced suggestions list ([156dfbe](https://github.com/dariomatias-dev/sql_studio_app/commit/156dfbe38f7565364854d19dd886c0ba51c2228a)) +* **lib/src/notifiers/sql_suggestions_notifiers/sql_advanced_suggestions_notifier.dart:** created `reorderSuggestions` method ([8bde175](https://github.com/dariomatias-dev/sql_studio_app/commit/8bde17547378f2d0b28c92316d961156f204d0b5)) +* **lib/src/notifiers/sql_suggestions_notifiers/sql_basic_suggestions_notifier.dart:** added result handling support ([f6d9eb8](https://github.com/dariomatias-dev/sql_studio_app/commit/f6d9eb8cf15686ad934c44c0ae858c7986451bd2)) +* **lib/src/notifiers/sql_suggestions_notifiers:** created notifier dedicated to basic suggestions ([b3ce640](https://github.com/dariomatias-dev/sql_studio_app/commit/b3ce640a5c3eba8447ccf4f998ecf54c29bf76e8)) +* **lib/src/notifiers/workspace_layout_notifier.dart:** added result handling ([2e3a552](https://github.com/dariomatias-dev/sql_studio_app/commit/2e3a552fde8c8bbbb43fb7c6f407fd2f33336ca3)) +* **lib/src/notifiers:** created app version notifier ([bcf9905](https://github.com/dariomatias-dev/sql_studio_app/commit/bcf99056ba14f2a3b633a5ffbe8a51bb5fdb02aa)) +* **lib/src/notifiers:** created notifier for databases ([735cde2](https://github.com/dariomatias-dev/sql_studio_app/commit/735cde2323eec2482305b77e6c20b8194efeffdd)) +* **lib/src/notifiers:** created notifier for the code editor element ([a07ce3a](https://github.com/dariomatias-dev/sql_studio_app/commit/a07ce3ad5cd15e9631d40e56b4c11a2efe35b8a8)) +* **lib/src/notifiers:** created SQL advanced suggestions notifier ([22ab7a3](https://github.com/dariomatias-dev/sql_studio_app/commit/22ab7a32533239a975fd33c9457bcf4f3c1be37a)) +* **lib/src/repositories/database_repository.dart:** added customizable `getWhere` method ([ba089da](https://github.com/dariomatias-dev/sql_studio_app/commit/ba089dabf2dd90333644555b54ac660c41c35eb1)) +* **lib/src/repositories/database_repository.dart:** created `insertAll` method ([70efb28](https://github.com/dariomatias-dev/sql_studio_app/commit/70efb28d2238d0b45df9904d9f82277aecd4f9ee)) +* **lib/src/repositories/migrations.dart:** created SQL advanced suggestions table ([a09061a](https://github.com/dariomatias-dev/sql_studio_app/commit/a09061a4597dff40025e3292f9c0d250fb0f85e3)) +* **lib/src/repositories:** added creation of app tables ([bad219c](https://github.com/dariomatias-dev/sql_studio_app/commit/bad219c75f91ae82c43c11b9244f72ac8bdbc0a3)) +* **lib/src/screen/home:** created suggestions component ([80fb8fe](https://github.com/dariomatias-dev/sql_studio_app/commit/80fb8fe7e582305e074064eabad0909997a57642)) +* **lib/src/screen/main/main_screen.dart:** removed database creation button ([7d584d8](https://github.com/dariomatias-dev/sql_studio_app/commit/7d584d8bbf97f3bd9a0e92a2c9994dd186f6ce77)) +* **lib/src/screen/main/widgets/drawer/drawer_database_list_tile_widget.dart:** improved design ([9fe8069](https://github.com/dariomatias-dev/sql_studio_app/commit/9fe80696fbe1d76087ae767a58eae35c5cab5f2c)) +* **lib/src/screen/main/widgets/drawer/drawer_widget.dart:** refactored component ([420d664](https://github.com/dariomatias-dev/sql_studio_app/commit/420d6640cabd97af02333f079f54819e0b98d505)) +* **lib/src/screen/main/widgets:** simplified components using text fields ([c4c9562](https://github.com/dariomatias-dev/sql_studio_app/commit/c4c9562dab07bf34437b2e342f79d9b494dafe13)) +* **lib/src/screen/main:** created database creation dialog ([fa5131b](https://github.com/dariomatias-dev/sql_studio_app/commit/fa5131b70fd6d8b10cd23f0cc6514ec8fc170eee)) +* **lib/src/screen/settings:** restructured elements ([af60a91](https://github.com/dariomatias-dev/sql_studio_app/commit/af60a91b9c6e3d1e51577133a82f81ee40d3b3bc)) +* **lib/src/screen:** added tooltips and standardized component organization ([441d968](https://github.com/dariomatias-dev/sql_studio_app/commit/441d9681a5007a36b38904ad8fa6ea43da70d8eb)) +* **lib/src/screen:** created SplashScreen ([eb30a5a](https://github.com/dariomatias-dev/sql_studio_app/commit/eb30a5ab494b82117038fb871008f91954b0d907)) +* **lib/src/screen:** implemented `ConfirmationDialogWidget` ([36e9a3a](https://github.com/dariomatias-dev/sql_studio_app/commit/36e9a3a256b96a1a9d017a524ceccc54dba20988)) +* **lib/src/screen:** implemented `InputDialogWidget` ([3facbc2](https://github.com/dariomatias-dev/sql_studio_app/commit/3facbc265439752bcb6247e1c1e52deb4b7ee4b1)) +* **lib/src/screens/database_visualizer/database_visualizer_screen.dart:** develop display of database structure ([32b8576](https://github.com/dariomatias-dev/sql_studio_app/commit/32b857633e7289a0711f90fcbe7a4d885bb5bf86)) +* **lib/src/screens/database_visualizer:** componentize table element ([deb6367](https://github.com/dariomatias-dev/sql_studio_app/commit/deb636779c854b663abb63ec4a5f597c5c81fd17)) +* **lib/src/screens/database_visualizer:** refactor design ([0a83548](https://github.com/dariomatias-dev/sql_studio_app/commit/0a83548693867f4b5166e6a6c1fcde898f1019a5)) +* **lib/src/screens/databases/databases_screen.dart:** added `PopupMenuButtonWidget` ([e85c8c3](https://github.com/dariomatias-dev/sql_studio_app/commit/e85c8c3b1a35828a5344f7dce2c961811cbfa324)) +* **lib/src/screens/databases/databases_screen.dart:** created screen structure ([259e17a](https://github.com/dariomatias-dev/sql_studio_app/commit/259e17a0e1c52d475602b512bdcf4ec0ca04f243)) +* **lib/src/screens/databases/databases_screen.dart:** implemented `CardWidget` ([0ba4bf5](https://github.com/dariomatias-dev/sql_studio_app/commit/0ba4bf566e741180fb208e18e8291714eb6baaf5)) +* **lib/src/screens/databases/databases_screen.dart:** implemented card option methods ([eff780e](https://github.com/dariomatias-dev/sql_studio_app/commit/eff780e0409146e65321cde56946ecdbc8cf83fd)) +* **lib/src/screens/databases/databases_screen.dart:** simplified methods ([9f81c08](https://github.com/dariomatias-dev/sql_studio_app/commit/9f81c08b8e89ebcf4f37cc359007fdf379890453)) +* **lib/src/screens/databases/widgets/database_card_widget.dart:** refactor design ([076331f](https://github.com/dariomatias-dev/sql_studio_app/commit/076331f247fe9b9ca7963d2e515929d5536f57d6)) +* **lib/src/screens/databases:** componentized database card element ([3f61617](https://github.com/dariomatias-dev/sql_studio_app/commit/3f61617c93637df0d78aeedab061448fc734d59c)) +* **lib/src/screens/home/home_screen.dart:** simplified screen ([fa87cac](https://github.com/dariomatias-dev/sql_studio_app/commit/fa87cac6fbb51726681b9f09b481719c03a9bb64)) +* **lib/src/screens/home/widgets/console_widget.dart:** added usage of `getTableColumns` ([7542133](https://github.com/dariomatias-dev/sql_studio_app/commit/754213380932398c3030c5d6fa4f62fc818599f1)) +* **lib/src/screens/home/widgets/console_widget.dart:** improved design and column name display ([f9e4a7e](https://github.com/dariomatias-dev/sql_studio_app/commit/f9e4a7eea7885f44c282a9185ccec6f3e08ebd0a)) +* **lib/src/screens/home/widgets/panel_widget.dart:** defined default body style ([b38938d](https://github.com/dariomatias-dev/sql_studio_app/commit/b38938d4c38fedb443b9c191dc4c5cdc10daa22d)) +* **lib/src/screens/home/widgets:** added display of selected database name ([037a141](https://github.com/dariomatias-dev/sql_studio_app/commit/037a1416f5109b6ae054c39cde79f1d430d99c12)) +* **lib/src/screens/home/widgets:** added execution and display of SQL commands ([c8df98a](https://github.com/dariomatias-dev/sql_studio_app/commit/c8df98a8c3381670b892ba977c3158abc43b8a7c)) +* **lib/src/screens/home/widgets:** componentized `StyledDataTableWidget` ([e4a9d21](https://github.com/dariomatias-dev/sql_studio_app/commit/e4a9d2158cf0e2d01e494999ff770bef8a26309c)) +* **lib/src/screens/main/screens/databases/widgets/database_card_widget.dart:** add option to view database structure ([610056a](https://github.com/dariomatias-dev/sql_studio_app/commit/610056a8a30b469075d615370291560cc5af03b7)) +* **lib/src/screens/main/screens/databases/widgets/database_card_widget.dart:** redesigned component ([43b859a](https://github.com/dariomatias-dev/sql_studio_app/commit/43b859a6ae4661d332a7b9dc7b542d6330f01ff9)) +* **lib/src/screens/main/screens/settings/widgets:** develop language selection element ([deaa4e0](https://github.com/dariomatias-dev/sql_studio_app/commit/deaa4e00ca3cdf0d7b7331c13b9f8a7157abe516)) +* **lib/src/screens/main/widgets/create_database_dialog_widget.dart:** added database existence check before creation ([4ee8d43](https://github.com/dariomatias-dev/sql_studio_app/commit/4ee8d431a063cc496e4490d28437a4b2941037b9)) +* **lib/src/screens/main/widgets/create_database_dialog_widget.dart:** refactored component ([14199ec](https://github.com/dariomatias-dev/sql_studio_app/commit/14199ecf87c91ad6af77f2820e8ffb88cf1d8b1e)) +* **lib/src/screens/main/widgets/drawer/create_database_dialog_widget.dart:** add automatic focus when creating a database ([e54d43a](https://github.com/dariomatias-dev/sql_studio_app/commit/e54d43a91fbfc721b553c87ca28d16d9bffc4b61)) +* **lib/src/screens/main/widgets/drawer/drawer_database_group/drawer_database_list_tile_widget.dart:** add reset of selected database when it is removed ([f070b7a](https://github.com/dariomatias-dev/sql_studio_app/commit/f070b7a5b7760350363187f0758cbaa5fafeac2e)) +* **lib/src/screens/main/widgets/drawer/drawer_database_group/drawer_database_list_tile_widget.dart:** added click visual feedback ([753ebdc](https://github.com/dariomatias-dev/sql_studio_app/commit/753ebdce25004391870ccc4884e9c25684d116a1)) +* **lib/src/screens/main/widgets/drawer/drawer_database_group/drawer_database_list_tile_widget.dart:** added database selection ([f7fa824](https://github.com/dariomatias-dev/sql_studio_app/commit/f7fa824ed2532fdace6e507bd3eee8fd5aa1f3c4)) +* **lib/src/screens/main/widgets/drawer/drawer_database_group/drawer_database_list_tile_widget.dart:** enhanced database selection ([d6b0f1a](https://github.com/dariomatias-dev/sql_studio_app/commit/d6b0f1a3933d234331d3d1f55de1bf37ccea60d4)) +* **lib/src/screens/main/widgets/drawer/drawer_database_group/drawer_database_list_tile_widget.dart:** implemented `PopupMenuButtonWidget` ([268872a](https://github.com/dariomatias-dev/sql_studio_app/commit/268872a053879b5839c99adda10dc0c055b09922)) +* **lib/src/screens/main/widgets/drawer/drawer_database_group/drawer_database_list_tile_widget.dart:** removed usage of `SnackBarUtils` ([0a55094](https://github.com/dariomatias-dev/sql_studio_app/commit/0a55094b7343a67e37ba2caceeb76851364e6d81)) +* **lib/src/screens/main/widgets/drawer/drawer_database_group:** componentize database removal dialog ([fa87286](https://github.com/dariomatias-dev/sql_studio_app/commit/fa8728652fbf50a05a487737bfaf23bd8b083c77)) +* **lib/src/screens/main/widgets/drawer/drawer_database_group:** rename `DrawerDatabaseListTileWidget` component ([35cf9f5](https://github.com/dariomatias-dev/sql_studio_app/commit/35cf9f5d22f68388b62243824c92855595fcbcbc)) +* **lib/src/screens/main/widgets/drawer/drawer_widget.dart:** add clear search field button ([8deaece](https://github.com/dariomatias-dev/sql_studio_app/commit/8deaece3bcba4b27b198093f7df39e11037f83f8)) +* **lib/src/screens/main/widgets/drawer/drawer_widget.dart:** implemented `DatabaseNotifier` ([426da5a](https://github.com/dariomatias-dev/sql_studio_app/commit/426da5ac89960336d41af47f6fc2d1d0efcac616)) +* **lib/src/screens/main/widgets/drawer:** improved design ([fb5497e](https://github.com/dariomatias-dev/sql_studio_app/commit/fb5497e8554c5f816b109c4353437a57d5186d92)) +* **lib/src/screens/main/widgets/drawer:** minor design improvements ([5c9a985](https://github.com/dariomatias-dev/sql_studio_app/commit/5c9a985f94d49ab28dac18420d7e25074e6e9698)) +* **lib/src/screens/main/widgets/drawer:** moved favorite and remove methods ([1301cb6](https://github.com/dariomatias-dev/sql_studio_app/commit/1301cb628cadbced838816b9c652f8027fb43a46)) +* **lib/src/screens/main/widgets/drawer:** organized components ([9fdfb73](https://github.com/dariomatias-dev/sql_studio_app/commit/9fdfb73defb3b87bf653a017c0b879681c16376a)) +* **lib/src/screens/main/widgets:** added database management operations ([77476b1](https://github.com/dariomatias-dev/sql_studio_app/commit/77476b1dbeee0c9fe2f2e070cd2da6b19f136406)) +* **lib/src/screens/main/widgets:** moved `CreateDatabaseDialogWidget` component ([7bbb571](https://github.com/dariomatias-dev/sql_studio_app/commit/7bbb57100cbb5d9854afa62ef718e01b83ac110d)) +* **lib/src/screens/main:** comment out features to be developed in the future ([ad76eea](https://github.com/dariomatias-dev/sql_studio_app/commit/ad76eea4813c92cc1bd29c8873d606f4f8e3b9dd)) +* **lib/src/screens/main:** componentize main screen elements ([b60fe3a](https://github.com/dariomatias-dev/sql_studio_app/commit/b60fe3a30134e947fab5ccf3842a5e8f872bcd83)) +* **lib/src/screens/settings:** implemented `CardWidget` ([3a87e81](https://github.com/dariomatias-dev/sql_studio_app/commit/3a87e8183db5f656634ac045a62d38d9ae71a531)) +* **lib/src/screens/settings:** refactor design ([6704162](https://github.com/dariomatias-dev/sql_studio_app/commit/6704162e69729d5780253e3f4107263a4cbfe3bd)) +* **lib/src/screens/splash/splash_screen.dart:** add result handling to `DefaultDatabaseService.init` ([e99f7ad](https://github.com/dariomatias-dev/sql_studio_app/commit/e99f7ad804d4b2c8cef60095b3edd1ea7f8ab4e7)) +* **lib/src/screens/splash/splash_screen.dart:** added error handling ([7c2860b](https://github.com/dariomatias-dev/sql_studio_app/commit/7c2860bc912827baffbec2780b754b8a9dffd803)) +* **lib/src/screens/splash/splash_screen.dart:** added error handling for `AppVersionNotifier` ([01b5ba1](https://github.com/dariomatias-dev/sql_studio_app/commit/01b5ba1faef87126ff4d9304b7dfd1471982d1d4)) +* **lib/src/screens/splash/splash_screen.dart:** added result handling for `SqlSuggestionsNotifier` ([fa7f1bb](https://github.com/dariomatias-dev/sql_studio_app/commit/fa7f1bb1d5cb1f6f8cdbf23e2db8b0a94c77da10)) +* **lib/src/screens/splash/splash_screen.dart:** added result handling for advanced suggestions loading ([a6c38c8](https://github.com/dariomatias-dev/sql_studio_app/commit/a6c38c80238e83460130e851ed511c4f9c1e7512)) +* **lib/src/screens/splash/splash_screen.dart:** added result handling to basic suggestions loading ([81edd7f](https://github.com/dariomatias-dev/sql_studio_app/commit/81edd7fde546894966ad2371351a6f9606226c20)) +* **lib/src/screens/splash/splash_screen.dart:** redesign screen ([7550bc4](https://github.com/dariomatias-dev/sql_studio_app/commit/7550bc4d27d2f0d51bb5889ec64867c4474c3b9e)) +* **lib/src/screens/sql_advanced_suggestion_settings/sql_advanced_suggestion_settings_screen.dart:** add creation and deletion dialogs and tooltips to action buttons ([cfe7c61](https://github.com/dariomatias-dev/sql_studio_app/commit/cfe7c61d2c686baad004e80378131feaacfa8773)) +* **lib/src/screens/sql_advanced_suggestion_settings/sql_advanced_suggestion_settings_screen.dart:** added update dialog call ([ee247df](https://github.com/dariomatias-dev/sql_studio_app/commit/ee247dfb52521b66a9e0dbfd0a9ea21cf052d487)) +* **lib/src/screens/sql_advanced_suggestion_settings/sql_advanced_suggestion_settings_screen.dart:** implemented `SqlAdvancedSuggestionsNotifier` usage ([18ddfb6](https://github.com/dariomatias-dev/sql_studio_app/commit/18ddfb69a4706a8dbf165cf196338f401feee7b5)) +* **lib/src/screens/sql_advanced_suggestion_settings/sql_advanced_suggestion_settings_screen.dart:** implemented `SuggestionsSettingsLayoutWidget` ([0551394](https://github.com/dariomatias-dev/sql_studio_app/commit/05513948dcf4614a68b9d243fa04415a93ce77b1)) +* **lib/src/screens/sql_advanced_suggestion_settings/sql_advanced_suggestion_settings_screen.dart:** implemented suggestions ordering ([b471a1b](https://github.com/dariomatias-dev/sql_studio_app/commit/b471a1b3565f3dd75c94361c1f3ff0cbc7c79823)) +* **lib/src/screens/sql_advanced_suggestion_settings/sql_advanced_suggestion_settings_screen.dart:** refactored creation of suggestions order notifier ([912dd45](https://github.com/dariomatias-dev/sql_studio_app/commit/912dd45131bede3c6144f7d52857aa05a50e1c0c)) +* **lib/src/screens/sql_advanced_suggestion_settings/sql_advanced_suggestion_settings_screen.dart:** repositioned save button ([8370f87](https://github.com/dariomatias-dev/sql_studio_app/commit/8370f8788472b0238580430edb57b2cfd86cb6b4)) +* **lib/src/screens/sql_advanced_suggestion_settings/widgets/create_sql_advanced_suggestion_dialog_widget.dart:** implemented `SqlAdvancedSuggestionFormDialogWidget` ([5a9f406](https://github.com/dariomatias-dev/sql_studio_app/commit/5a9f40657dff46f5412fbfe710dd2016ec73f40e)) +* **lib/src/screens/sql_advanced_suggestion_settings/widgets/dialogs/reset_sql_advanced_suggestions_dialog_widget.dart:** add result handling ([dc00554](https://github.com/dariomatias-dev/sql_studio_app/commit/dc00554c382a9f3ca1482549a0e9590c9353c4fb)) +* **lib/src/screens/sql_advanced_suggestion_settings/widgets:** create dialog for removing advanced SQL command suggestion ([535b1f4](https://github.com/dariomatias-dev/sql_studio_app/commit/535b1f4179f31d1da87b1fe363ec6f8d67d7e595)) +* **lib/src/screens/sql_advanced_suggestion_settings/widgets:** created form dialog ([1b8a348](https://github.com/dariomatias-dev/sql_studio_app/commit/1b8a348d776c9b8348df6e1a2f7bed77ab379ad5)) +* **lib/src/screens/sql_advanced_suggestion_settings/widgets:** created update dialog ([7bd3611](https://github.com/dariomatias-dev/sql_studio_app/commit/7bd361155adf861f1724fb7c70ed9e0a3f156d12)) +* **lib/src/screens/sql_advanced_suggestion_settings:** added display of ordered suggestions ([de67164](https://github.com/dariomatias-dev/sql_studio_app/commit/de6716439220db449befddac5e840a41a8a05392)) +* **lib/src/screens/sql_advanced_suggestion_settings:** componentized reset suggestions dialog ([d7f0fc0](https://github.com/dariomatias-dev/sql_studio_app/commit/d7f0fc066b9459883a76ddf885d16ac960803dfe)) +* **lib/src/screens/sql_advanced_suggestion_settings:** componentized suggestion item ([c05de33](https://github.com/dariomatias-dev/sql_studio_app/commit/c05de33ed562d6596a81b1c3e9666347abbcaeac)) +* **lib/src/screens/sql_advanced_suggestion_settings:** create dialog for adding advanced SQL command suggestion ([fbcded0](https://github.com/dariomatias-dev/sql_studio_app/commit/fbcded05035bf45b78f52043e9f0bc80cea9b5a7)) +* **lib/src/screens/sql_advanced_suggestion_settings:** created controller ([cf08631](https://github.com/dariomatias-dev/sql_studio_app/commit/cf086313c3abd753c69ef0492ab0e251c830f6ef)) +* **lib/src/screens/sql_advanced_suggestion_settings:** created dialogs folder ([f3cdacd](https://github.com/dariomatias-dev/sql_studio_app/commit/f3cdacdbdd095883c4e86d2999e6fa32a8305a2a)) +* **lib/src/screens/sql_advanced_suggestion_settings:** moved create, update, and remove suggestion actions ([11fd07e](https://github.com/dariomatias-dev/sql_studio_app/commit/11fd07efbbdea39eade5ae779a00ba16fd876d1b)) +* **lib/src/screens/sql_basic_suggestion_settings/sql_basic_suggestion_settings_screen.dart:** implemented `SuggestionsSettingsLayoutWidget` ([e76268c](https://github.com/dariomatias-dev/sql_studio_app/commit/e76268cde150d0cf39db71d8ae88cdbbc9c6a0a6)) +* **lib/src/screens/sql_basic_suggestion_settings/widgets/dialogs/create_sql_basic_suggestion_dialog_widget.dart:** added result handling ([900a701](https://github.com/dariomatias-dev/sql_studio_app/commit/900a701141d770b26844f43f1f40a75e38a1b179)) +* **lib/src/screens/sql_basic_suggestion_settings/widgets/dialogs/remove_sql_basic_suggestion_dialog_widget.dart:** added result handling ([1bfa98f](https://github.com/dariomatias-dev/sql_studio_app/commit/1bfa98f4faeb1983d59748634a697afda2631be4)) +* **lib/src/screens/sql_basic_suggestion_settings/widgets/dialogs/reset_sql_basic_suggestions_dialog_widget.dart:** added result handling ([08e2fba](https://github.com/dariomatias-dev/sql_studio_app/commit/08e2fbac18e9c2fad6f976c84943c2d56ce60e7f)) +* **lib/src/screens/sql_basic_suggestion_settings:** applied standardizations and componentizations ([8153b4d](https://github.com/dariomatias-dev/sql_studio_app/commit/8153b4d3fe3a9f8a1f57050c8a818169e2654786)) +* **lib/src/screens/sql_basic_suggestion_settings:** componentized reset commands dialog ([2b94f00](https://github.com/dariomatias-dev/sql_studio_app/commit/2b94f00b64788da6203e52abb64998c080bf30c8)) +* **lib/src/screens/sql_basic_suggestion_settings:** componentized suggestion card ([dfc22cb](https://github.com/dariomatias-dev/sql_studio_app/commit/dfc22cbef8136e21239ed029a8d2d10908dee7a9)) +* **lib/src/screens/sql_basic_suggestion_settings:** created controller ([9137532](https://github.com/dariomatias-dev/sql_studio_app/commit/9137532edb946115a512b6845528ccbbf3c0c40b)) +* **lib/src/screens/sql_basic_suggestion_settings:** created dialogs folder ([9698eff](https://github.com/dariomatias-dev/sql_studio_app/commit/9698eff6107a3b04db953aa68ff3de3d716518a5)) +* **lib/src/screens/sql_basic_suggestion_settings:** implemented saving of suggestion order ([abe8b63](https://github.com/dariomatias-dev/sql_studio_app/commit/abe8b631b321e9d12a0cec805a7a12af2f12406e)) +* **lib/src/screens/sql_basic_suggestion_settings:** renamed dialog components ([6fbc939](https://github.com/dariomatias-dev/sql_studio_app/commit/6fbc939cfc32735a7779559bcc0ef652d856d9a7)) +* **lib/src/screens/sql_basic_suggestion_settings:** standardized texts ([65b17dc](https://github.com/dariomatias-dev/sql_studio_app/commit/65b17dce2e4476074e14d5ea5c49c0ef49badda7)) +* **lib/src/screens/sql_command_settings/sql_command_settings_screen.dart:** added commands reset ([f9b1161](https://github.com/dariomatias-dev/sql_studio_app/commit/f9b1161f5215b85ec743ff7e2ea553b797c6db2f)) +* **lib/src/screens/sql_command_settings:** componentized dialogs ([95098d4](https://github.com/dariomatias-dev/sql_studio_app/commit/95098d495095b583af411f8bce1b4910a01a311c)) +* **lib/src/screens/sql_suggestion_settings/sql_suggestion_settings_screen.dart:** add result handling when initializing advanced suggestions ([e0d679e](https://github.com/dariomatias-dev/sql_studio_app/commit/e0d679e8f46a1066f64bb6cda02921d47c054ae3)) +* **lib/src/screens/sql_suggestion_settings/sql_suggestion_settings_screen.dart:** added save button disable state ([8fffbc9](https://github.com/dariomatias-dev/sql_studio_app/commit/8fffbc9e2a89cac9f1af8f9f54e5e2be20854604)) +* **lib/src/screens/sql_suggestion_settings/sql_suggestion_settings_screen.dart:** implement `SqlSuggestionsNotifier` ([b8c982f](https://github.com/dariomatias-dev/sql_studio_app/commit/b8c982f2f375c1f901a697fcc84e458ad2be3486)) +* **lib/src/screens/sql_suggestion_settings/sql_suggestion_settings_screen.dart:** restructure settings state management ([87e9b87](https://github.com/dariomatias-dev/sql_studio_app/commit/87e9b8748db9ad4953647531b456a2f3264a92f1)) +* **lib/src/screens/sql_suggestion_settings:** moved suggestion settings button to `SqlSuggestionSettingsCardWidget` ([8ddff3e](https://github.com/dariomatias-dev/sql_studio_app/commit/8ddff3e03d997e4df95efd059429faacdd78a59a)) +* **lib/src/screens/workspace_layout/workspace_layout_screen.dart:** add `SqlWorkspaceWidget` ([d21626e](https://github.com/dariomatias-dev/sql_studio_app/commit/d21626e2886809eb901b0ef4ee532f501c34734a)) +* **lib/src/screens/workspace_layout/workspace_layout_screen.dart:** added layout save handling ([ca757c0](https://github.com/dariomatias-dev/sql_studio_app/commit/ca757c030f81c461120fa6d2f7e013991fd54df9)) +* **lib/src/screens:** added `SnackBar` display on successful suggestion save ([7e083f7](https://github.com/dariomatias-dev/sql_studio_app/commit/7e083f7cc620fed77d7055bbc29db86a0cc98988)) +* **lib/src/screens:** added `SqlWorkspaceWidget` to `DefaultDatabaseScreen`, fullscreen icon, and active database selection ([ad9aae6](https://github.com/dariomatias-dev/sql_studio_app/commit/ad9aae6c95e83ad0800d4af306a99bd24c3e3172)) +* **lib/src/screens:** added display and update of saved commands list ([4d0b3a9](https://github.com/dariomatias-dev/sql_studio_app/commit/4d0b3a9f4f872d008c1d9535cd3b255e23f0ef05)) +* **lib/src/screens:** create screen for defining `SqlWorkspaceWidget` layout ([fe33534](https://github.com/dariomatias-dev/sql_studio_app/commit/fe33534617e8ebceedbef50cbbabdde45c96729e)) +* **lib/src/screens:** implement `ScaffoldWidget` ([d77e9e7](https://github.com/dariomatias-dev/sql_studio_app/commit/d77e9e72e456bb45b394474f4f243760f7c22fa9)) +* **lib/src/screen:** simplified usage of `ButtonWidget` ([a089d0b](https://github.com/dariomatias-dev/sql_studio_app/commit/a089d0bc7942d4a144b41f9263e3a0f5f22a901d)) +* **lib/src/screens:** improve empty state, icon color, and database removal handling ([74aaeac](https://github.com/dariomatias-dev/sql_studio_app/commit/74aaeac819176e96be7474b5c0cbb7247e365967)) +* **lib/src/screens:** moved main screens into dedicated folder ([e792e16](https://github.com/dariomatias-dev/sql_studio_app/commit/e792e1626e10d20f87751bdecd375bd6840f1f2c)) +* **lib/src/screens:** preserved screen state ([0c1c4fb](https://github.com/dariomatias-dev/sql_studio_app/commit/0c1c4fbb619b0ae72515d940418ec2a039270fee)) +* **lib/src/screens:** standardized screen styles ([cd4114d](https://github.com/dariomatias-dev/sql_studio_app/commit/cd4114de8e2aea82f9448effc83b81535b03e4a9)) +* **lib/src/services/database_service.dart:** added method to retrieve database by name ([1011663](https://github.com/dariomatias-dev/sql_studio_app/commit/10116639ead8c36747f808ff463eb0c01c195c9e)) +* **lib/src/services/database/default_database_service.dart:** add result handling ([27b46c8](https://github.com/dariomatias-dev/sql_studio_app/commit/27b46c8b52778790fcbc9b2935e56c3a87258b5d)) +* **lib/src/services/shared_preferences_service.dart:** add integer methods ([b65d52e](https://github.com/dariomatias-dev/sql_studio_app/commit/b65d52e85caf8cc520fc2358376c48dac5adfd10)) +* **lib/src/services/sql_execution_service.dart:** add support for executing multiple statements ([aad6949](https://github.com/dariomatias-dev/sql_studio_app/commit/aad69499a5e15ccb3aa4e961e6672e0bdc4b609f)) +* **lib/src/services/sql_execution_service.dart:** added affected rows return ([9f68bc2](https://github.com/dariomatias-dev/sql_studio_app/commit/9f68bc29ef8c2254596ae1f1d0fb6626bc1cbabf)) +* **lib/src/services/sql_execution_service.dart:** added method to get database table names ([c8e4617](https://github.com/dariomatias-dev/sql_studio_app/commit/c8e4617ad7a910303afcdd8662068084867acea1)) +* **lib/src/services:** created service for databases table ([064a4aa](https://github.com/dariomatias-dev/sql_studio_app/commit/064a4aa313e81aeaa3f8ec6706394061e93e8320)) +* **lib/src/services:** created service for executing SQL from editor ([d31d402](https://github.com/dariomatias-dev/sql_studio_app/commit/d31d402fa24f4d82013e0af61b475fde72ba9490)) +* **lib/src/services:** created SQL advanced suggestions service ([ae7ae6b](https://github.com/dariomatias-dev/sql_studio_app/commit/ae7ae6bfd7e5472038af35cb4ce40b87fa23913f)) +* **lib/src/shared/models/database_model.dart:** added new properties and methods to `DatabaseModel` ([b1ee6a7](https://github.com/dariomatias-dev/sql_studio_app/commit/b1ee6a7e42d6983c057b34e2e48c40c7445a8859)) +* **lib/src/shared/utils/default_database_initializer.dart:** add versioning for executing schemas and seeds ([2b0b236](https://github.com/dariomatias-dev/sql_studio_app/commit/2b0b236bad9a876115f5ac7f3aa211061ba87124)) +* **lib/src/shared/utils:** created function to handle errors ([e20cc79](https://github.com/dariomatias-dev/sql_studio_app/commit/e20cc79cc0563f2d24f3fdab9ebe41ff2be4cf42)) +* **lib/src/shared/widgets/button_widget.dart:** defined predefined styles ([bca8c5c](https://github.com/dariomatias-dev/sql_studio_app/commit/bca8c5ca38ed454a7d9acb1dce22917cc492762e)) +* **lib/src/shared/widgets/buttons/button_widget.dart:** add optional `padding` property ([81d4e74](https://github.com/dariomatias-dev/sql_studio_app/commit/81d4e7447b2e5265ae77edec22bc103046e04625)) +* **lib/src/shared/widgets/buttons/loading_button_widget.dart:** added visual feedback for disabled state ([c335f99](https://github.com/dariomatias-dev/sql_studio_app/commit/c335f99c54bc5dd217dee2ca20de01470ad5caf2)) +* **lib/src/shared/widgets/card_widget.dart:** add new property ([7960b2d](https://github.com/dariomatias-dev/sql_studio_app/commit/7960b2d80294ff55cf2f6615fbd8c044c1ce8a68)) +* **lib/src/shared/widgets/dialogs/dialog_widget.dart:** refactor design ([d55a1cc](https://github.com/dariomatias-dev/sql_studio_app/commit/d55a1cc1671a55519be6ee13f5ad12c2378bbc8e)) +* **lib/src/shared/widgets/dialogs/input_dialog_widget.dart:** created text input dialog ([8dc7a11](https://github.com/dariomatias-dev/sql_studio_app/commit/8dc7a117576d1a0da8c8ac211cbe05bae4337d80)) +* **lib/src/shared/widgets/dialogs:** created error dialog ([223442d](https://github.com/dariomatias-dev/sql_studio_app/commit/223442d7dbad9df841a76b29abcad86c78180122)) +* **lib/src/shared/widgets/input_widget.dart:** added `suffixIcon` property ([eea4e17](https://github.com/dariomatias-dev/sql_studio_app/commit/eea4e1762b96533797d917725f4d6f838e883678)) +* **lib/src/shared/widgets/input_widget.dart:** refactor design ([931052b](https://github.com/dariomatias-dev/sql_studio_app/commit/931052bdbaf627575365925a2dbe0d1fdcbd7d38)) +* **lib/src/shared/widgets/input_widget.dart:** standardize visual feedback ([5b98c5f](https://github.com/dariomatias-dev/sql_studio_app/commit/5b98c5fe421a5d910771716aa6c3989cce91992f)) +* **lib/src/shared/widgets/sql_workspace/console/console_widget.dart:** improved information display ([eff261d](https://github.com/dariomatias-dev/sql_studio_app/commit/eff261da70deca646f1c5d96f511c07fb24fbfbe)) +* **lib/src/shared/widgets/sql_workspace/console/console_widget.dart:** refactored execution result handling ([a864833](https://github.com/dariomatias-dev/sql_studio_app/commit/a86483302d1c32e546e550c9803f6b70c736b547)) +* **lib/src/shared/widgets/sql_workspace/console:** created controller ([eb9ed01](https://github.com/dariomatias-dev/sql_studio_app/commit/eb9ed01ccdfdcf813fec86a0f180c3574a42e5b8)) +* **lib/src/shared/widgets/sql_workspace/sql_editor/sql_editor_widget.dart:** add icon to access database visualization ([0139ad4](https://github.com/dariomatias-dev/sql_studio_app/commit/0139ad4a42c59b55484adfffe273e2951266739f)) +* **lib/src/shared/widgets/sql_workspace/sql_editor/sql_editor_widget.dart:** added automatic focus when selecting a suggestion ([fb146e2](https://github.com/dariomatias-dev/sql_studio_app/commit/fb146e2b804416c9eaae5f736e4f167aef78261c)) +* **lib/src/shared/widgets/sql_workspace/sql_editor/sql_editor_widget.dart:** added database reset option ([73c3dcd](https://github.com/dariomatias-dev/sql_studio_app/commit/73c3dcd3d26addcb1ff5c644d7f98b03719cde8e)) +* **lib/src/shared/widgets/sql_workspace/sql_editor/sql_editor_widget.dart:** added new actions ([7f28e7a](https://github.com/dariomatias-dev/sql_studio_app/commit/7f28e7a466cf42ca0066122fe8cb8ad4ddd59c46)) +* **lib/src/shared/widgets/sql_workspace/sql_editor/sql_editor_widget.dart:** added reset action ([f08485d](https://github.com/dariomatias-dev/sql_studio_app/commit/f08485d356eeb14aacd333be85d529ec07086670)) +* **lib/src/shared/widgets/sql_workspace/sql_editor/sql_editor_widget.dart:** created popup menu element to simplify editor actions ([86dbfe6](https://github.com/dariomatias-dev/sql_studio_app/commit/86dbfe6d79359c3c1f0773cb8b5c31380fe171b3)) +* **lib/src/shared/widgets/sql_workspace/sql_editor/sql_editor_widget.dart:** implement `SqlSuggestionsNotifier` ([cf6ef15](https://github.com/dariomatias-dev/sql_studio_app/commit/cf6ef1544a5a283a994d448a3b0a988e0f059224)) +* **lib/src/shared/widgets/sql_workspace/sql_editor/sql_editor_widget.dart:** implemented SQL copy action ([002c1ce](https://github.com/dariomatias-dev/sql_studio_app/commit/002c1ce52207eed4bbb34b1dc0d126de9163e036)) +* **lib/src/shared/widgets/sql_workspace/sql_editor/sql_editor_widget.dart:** update method names ([b9717d5](https://github.com/dariomatias-dev/sql_studio_app/commit/b9717d58c12e660107aa092e92d495ff5e3de711)) +* **lib/src/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_advanced_suggestions_bar_widget.dart:** added usage of `SqlAdvancedSuggestionsNotifier` ([8d10a50](https://github.com/dariomatias-dev/sql_studio_app/commit/8d10a50313170e60f727524bd49a2e50a1f1069f)) +* **lib/src/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_advanced_suggestions_bar_widget.dart:** simplify component ([db8c6b9](https://github.com/dariomatias-dev/sql_studio_app/commit/db8c6b98f28882490a14437ad13f3da4d9e3bf77)) +* **lib/src/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars:** add property to define item padding in `SqlSuggestionsBarBaseWidget` ([5155c93](https://github.com/dariomatias-dev/sql_studio_app/commit/5155c936dcb904f3d61f708cfda48814a1fc63e2)) +* **lib/src/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars:** create base component for suggestion bars ([4fc6ae1](https://github.com/dariomatias-dev/sql_studio_app/commit/4fc6ae1cab937b70979ca7bc05f8fb9b45c8f4fe)) +* **lib/src/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars:** implement `SqlSuggestionsBarBaseWidget` ([08c8e1c](https://github.com/dariomatias-dev/sql_studio_app/commit/08c8e1cd5882ecd8b3b1398fec8bd19b2ff19057)) +* **lib/src/shared/widgets/sql_workspace/sql_editor:** added suggestions filtering ([31e1627](https://github.com/dariomatias-dev/sql_studio_app/commit/31e16276dfd8bd573c7a941c560aaf24dacadea5)) +* **lib/src/shared/widgets/sql_workspace/sql_editor:** create character suggestion bar component ([5f9f7cd](https://github.com/dariomatias-dev/sql_studio_app/commit/5f9f7cda54a5327152223c07c885a83e66c278ba)) +* **lib/src/shared/widgets/sql_workspace/sql_editor:** create quick SQL suggestions component ([fd96ae2](https://github.com/dariomatias-dev/sql_studio_app/commit/fd96ae25e1192f15adb3eb9f51e2097ee1841357)) +* **lib/src/shared/widgets/sql_workspace/sql_editor:** created controller ([c5fb1e2](https://github.com/dariomatias-dev/sql_studio_app/commit/c5fb1e299ed8e2a941c661ca81932da4b8ba72ca)) +* **lib/src/shared/widgets/sql_workspace/sql_editor:** standardize suggestion bar component names ([e2141d5](https://github.com/dariomatias-dev/sql_studio_app/commit/e2141d589c00522c65418091158b5f6218dfc2a3)) +* **lib/src/shared/widgets/sql_workspace/sql_editor:** standardize suggestion elements style ([de46b25](https://github.com/dariomatias-dev/sql_studio_app/commit/de46b2580fe95aa793f57df1c330186a917b7cba)) +* **lib/src/shared/widgets/sql_workspace/sql_workspace_widget.dart:** add layout customization ([76276d4](https://github.com/dariomatias-dev/sql_studio_app/commit/76276d4ea9abc67272bc9a4774ae3f5c4c5aee58)) +* **lib/src/shared/widgets/sql_workspace:** add automatic tab navigation ([8bdffec](https://github.com/dariomatias-dev/sql_studio_app/commit/8bdffec9c556b6c11fd9a189e8a7d49cbe98dc7e)) +* **lib/src/shared/widgets/sql_workspace:** add conditional display of maximize icon ([14248b1](https://github.com/dariomatias-dev/sql_studio_app/commit/14248b13e95aeb7e67267b06df00f44daa98e22b)) +* **lib/src/shared/widgets/sql_workspace:** add optional panel title display ([529358e](https://github.com/dariomatias-dev/sql_studio_app/commit/529358e30c11b9e892b539d2bf67bb2a19ae5bca)) +* **lib/src/shared/widgets/suggestions_settings_layout:** added save button enable/disable logic ([2f080a7](https://github.com/dariomatias-dev/sql_studio_app/commit/2f080a7b06b3415e13f3f295af0b08283b417b0d)) +* **lib/src/shared/widgets:** create `Scaffold` component ([357116e](https://github.com/dariomatias-dev/sql_studio_app/commit/357116eeb5e7c852b52bc3d1c8c944b2fe4bd82d)) +* **lib/src/shared/widgets:** created `CardWidget` component ([2e4e328](https://github.com/dariomatias-dev/sql_studio_app/commit/2e4e328ec4fd3a606970413db172ec4f7d1191c0)) +* **lib/src/shared/widgets:** created `PopupMenuButtonWidget` component ([155324b](https://github.com/dariomatias-dev/sql_studio_app/commit/155324b04c2eed6540a84e49ae0d21571943267d)) +* **lib/src/shared/widgets:** created confirmation dialog component ([76f7add](https://github.com/dariomatias-dev/sql_studio_app/commit/76f7add755424749fcc14e7ce461e6f5901337e9)) +* **lib/src/shared/widgets:** created loading button ([db05bf7](https://github.com/dariomatias-dev/sql_studio_app/commit/db05bf7a7927db8b0e2e313ae2367bfc8a19bb59)) +* **lib/src/shared/widgets:** created reusable text field component ([9115094](https://github.com/dariomatias-dev/sql_studio_app/commit/9115094c8088792754b3c26bb676c509e06dadfb)) +* **lib/src/shared/widgets:** created SQL workspace component ([ab3d3f3](https://github.com/dariomatias-dev/sql_studio_app/commit/ab3d3f3fbb295899bed68f8a59015a0ce6321140)) +* **lib/src/shared/widgets:** created suggestions management screen layout component ([e047615](https://github.com/dariomatias-dev/sql_studio_app/commit/e04761517632f3bffc335eb153a0e46514bab748)) +* **lib/src/shared:** refactor button component design ([09d6f20](https://github.com/dariomatias-dev/sql_studio_app/commit/09d6f20f7fd180cde5ffdcfb61f6c364e6df7cde)) +* **lib/src:** add database name passing to `DatabaseVisualizerScreen` ([821442b](https://github.com/dariomatias-dev/sql_studio_app/commit/821442bdc6ad92c1037484b2be8c1fa64762e4c1)) +* **lib/src:** add dynamic command suggestion ([193b0a6](https://github.com/dariomatias-dev/sql_studio_app/commit/193b0a63bea0ead9a8fd00383a6e926b51e74260)) +* **lib/src:** add full database drop on removal ([b98c921](https://github.com/dariomatias-dev/sql_studio_app/commit/b98c9215cb46bbde44ecf42b98fc16187d8911c3)) +* **lib/src:** added `DatabaseModel` to `DrawerDatabaseListTileWidget` and new `isFavorite` property ([4ce5add](https://github.com/dariomatias-dev/sql_studio_app/commit/4ce5add01697f7e5388726f9e797f9ae95b6ee4f)) +* **lib/src:** added `LoadingButtonWidget` to `InputDialogWidget` ([eb4d16f](https://github.com/dariomatias-dev/sql_studio_app/commit/eb4d16fd82550fb27d4c214c7d0abe2faf0cf613)) +* **lib/src:** added `orderIndex` property ([55b160e](https://github.com/dariomatias-dev/sql_studio_app/commit/55b160e93f4a6947480787ad40259d96edbbcffa)) +* **lib/src:** added `show` method ([4493c90](https://github.com/dariomatias-dev/sql_studio_app/commit/4493c908fe94ca3ca96f60d4dfd3a5f47101c241)) +* **lib/src:** added boolean return to `onSubmit` in `InputDialogWidget` ([c243c01](https://github.com/dariomatias-dev/sql_studio_app/commit/c243c018d26d6acbd963ed4b1754f18184799b70)) +* **lib/src:** added database model and validation to `CreateDatabaseDialogWidget` ([997f39f](https://github.com/dariomatias-dev/sql_studio_app/commit/997f39f885b96d8f93b3011d7ee842fa5894b45e)) +* **lib/src:** added error handling to `DatabaseNotifier` ([573750d](https://github.com/dariomatias-dev/sql_studio_app/commit/573750d4b67685d8256ae42510c93a16457e959b)) +* **lib/src:** added feedback when saving suggestions ([caeda0c](https://github.com/dariomatias-dev/sql_studio_app/commit/caeda0c0722534884ae9501ea82a580f53ec761c)) +* **lib/src:** added persistence for selected database ([a6dfd14](https://github.com/dariomatias-dev/sql_studio_app/commit/a6dfd149f83933474b4d11349ff69306cfb8609e)) +* **lib/src:** added persistence for selected database in `SqlCommandsNotifier` ([4d97ffc](https://github.com/dariomatias-dev/sql_studio_app/commit/4d97ffc7e7142c304abca2eba162740d427c0a2a)) +* **lib/src:** added result handling support to `SqlAdvancedSuggestionsNotifier` ([f78e000](https://github.com/dariomatias-dev/sql_studio_app/commit/f78e00036ddbd3b5561cfdb117f368ebf88edf5a)) +* **lib/src:** added translations to `SqlExecutionService` ([fdf810f](https://github.com/dariomatias-dev/sql_studio_app/commit/fdf810f6a2d04c3fd84ad1c8a7c3db03a96c4ecd)) +* **lib/src:** componentized and expanded default databases variable ([cd8a9a4](https://github.com/dariomatias-dev/sql_studio_app/commit/cd8a9a481435cf57d3dfaf128a9e00cf583948af)) +* **lib/src:** configure and add navigation for `WorkspaceLayoutScreen` ([7119bc2](https://github.com/dariomatias-dev/sql_studio_app/commit/7119bc2a86a7a8e415cf34161c1300ddd74cdd55)) +* **lib/src:** create `WorkspaceLayoutNotifier` ([6045760](https://github.com/dariomatias-dev/sql_studio_app/commit/6045760e083ab14e6aa8b976775dabb16e0d6cd5)) +* **lib/src:** create advanced SQL command suggestions management screen ([b830b15](https://github.com/dariomatias-dev/sql_studio_app/commit/b830b15383c2470c6e3202b857646e9e5d846f5f)) +* **lib/src:** create advanced SQL suggestions model and default constant ([2b4886e](https://github.com/dariomatias-dev/sql_studio_app/commit/2b4886ebb6ae78162f2188ce4b1f08db151adc9c)) +* **lib/src:** create and implement class for initializing default databases ([fadf0cb](https://github.com/dariomatias-dev/sql_studio_app/commit/fadf0cbc99e11139a5bf0376a36d80f1dbb20cbc)) +* **lib/src:** create and implement color class ([21c1a6d](https://github.com/dariomatias-dev/sql_studio_app/commit/21c1a6de30ba25103874bc3b639fcdd76d93d593)) +* **lib/src:** create base screen for visualizing database structure ([85dd23d](https://github.com/dariomatias-dev/sql_studio_app/commit/85dd23d3a9efb8b70a986dd588740cc172969d3e)) +* **lib/src:** create method to retrieve database structure ([7dca7cc](https://github.com/dariomatias-dev/sql_studio_app/commit/7dca7cc1a56d3cce58ba6d41dd771af134903461)) +* **lib/src:** create suggestion elements configuration screen ([a634e03](https://github.com/dariomatias-dev/sql_studio_app/commit/a634e03328371a554e5f1adf3db45d2a12e6cdc5)) +* **lib/src:** created `default_sql_suggestions` folder ([09f20d3](https://github.com/dariomatias-dev/sql_studio_app/commit/09f20d36870594d69724393ae1cf50be535a34f4)) +* **lib/src:** created `sqlCommands` constant ([9b662c9](https://github.com/dariomatias-dev/sql_studio_app/commit/9b662c9f47cdf88bd033b01937e130d7a2795987)) +* **lib/src:** created cancel button component ([7a83587](https://github.com/dariomatias-dev/sql_studio_app/commit/7a835875f68506c65d45ab6d1a25402760d915a3)) +* **lib/src:** created controller for `SuggestionsSettingsLayoutWidget` ([70093f0](https://github.com/dariomatias-dev/sql_studio_app/commit/70093f04037ef30d332e5633f17321eace3c32ad)) +* **lib/src:** created default database screen ([2eea730](https://github.com/dariomatias-dev/sql_studio_app/commit/2eea730bb804fb8e8ba463c32eca084f3704bf19)) +* **lib/src:** created global URLs class ([3bb169c](https://github.com/dariomatias-dev/sql_studio_app/commit/3bb169c1d0b075300f62e6f9cf1b082033a6593d)) +* **lib/src:** created not found screen ([046bbfb](https://github.com/dariomatias-dev/sql_studio_app/commit/046bbfbcfc99bd4941f0dde961c597acef29316c)) +* **lib/src:** created SQL command configuration screen ([08408a3](https://github.com/dariomatias-dev/sql_studio_app/commit/08408a32368470eb256c86979f27b557ed3dc3ef)) +* **lib/src:** created SQL commands notifier ([0c50c79](https://github.com/dariomatias-dev/sql_studio_app/commit/0c50c7976655c9971483379083764848ee906546)) +* **lib/src:** created utility for `SnackBar` ([b6de027](https://github.com/dariomatias-dev/sql_studio_app/commit/b6de0279c02d79c8189a3aa504d7d72ee1416cf2)) +* **lib/src:** detail "database not selected" failure and fix table column retrieval ([a274247](https://github.com/dariomatias-dev/sql_studio_app/commit/a2742477d546785a8e1b96c369d405f7b891ea16)) +* **lib/src:** implemented new global navigation structure ([cf5d100](https://github.com/dariomatias-dev/sql_studio_app/commit/cf5d100435650e877cb9e43f8f45073da72b532a)) +* **lib/src:** implemented new navigation system ([a61d488](https://github.com/dariomatias-dev/sql_studio_app/commit/a61d4883f976a0adf337c97e9b8f85458e6d7e9f)) +* **lib/src:** implemented new navigation system ([6cd1c75](https://github.com/dariomatias-dev/sql_studio_app/commit/6cd1c758e0d21c302ad40e0ea65fc60d0b706826)) +* **lib/src:** improved `WorkspaceLayout` elements ([2e72f75](https://github.com/dariomatias-dev/sql_studio_app/commit/2e72f75500e64318a8c672440a132b0ecb395d03)) +* **lib/src:** improved suggestion order saving flow ([4aed383](https://github.com/dariomatias-dev/sql_studio_app/commit/4aed383506b1834406200f06025b15c229d05f62)) +* **lib/src:** minor fixes ([5a0ffb2](https://github.com/dariomatias-dev/sql_studio_app/commit/5a0ffb20d15b3eaa711dce44a6cbbf6b3c1ac18c)) +* **lib/src:** move suggestion state management keys to `SharedPreferencesKeys` ([6a2aabe](https://github.com/dariomatias-dev/sql_studio_app/commit/6a2aabe31171e0408513045062383c6797abb679)) +* **lib/src:** rename `DefaultDatabaseInitializer` class ([738b1a2](https://github.com/dariomatias-dev/sql_studio_app/commit/738b1a22bcdcfe4d51158d90c04fd3feba881b1d)) +* **lib/src:** rename basic command suggestion settings screen ([6087022](https://github.com/dariomatias-dev/sql_studio_app/commit/60870227f9a5f10f75cc8ef93dfcfd4f8e5708d7)) +* **lib/src:** renamed `DefaultDatabaseScreen` to `DatabaseScreen` ([e0f840f](https://github.com/dariomatias-dev/sql_studio_app/commit/e0f840fb4159c1f01ff2a9e8cc5bd89ceef64332)) +* **lib/src:** renamed constant `sqlAdvancedSuggestionsDefault` ([6d11630](https://github.com/dariomatias-dev/sql_studio_app/commit/6d116301e2adc033baaa95685bd4647430b499c8)) +* **lib/src:** renamed constant `sqlCommands` ([ef94908](https://github.com/dariomatias-dev/sql_studio_app/commit/ef9490870f893cca6bc5f8e860d6eaf6c49296d2)) +* **lib/src:** reposition `DefaultDatabaseInitializer` ([6fed7e8](https://github.com/dariomatias-dev/sql_studio_app/commit/6fed7e82309ce2061fd0fa6b210bdbb52a18b3e4)) +* **lib/src:** repositioned `DialogWidget` ([de5e474](https://github.com/dariomatias-dev/sql_studio_app/commit/de5e474771c94b2f8fc43f8e05b0835a097b86ec)) +* **lib/src:** repositioned `SqlAdvancedSuggestionModel` ([58a32d6](https://github.com/dariomatias-dev/sql_studio_app/commit/58a32d67fc2492835f7140a468acfdc231ec2573)) +* **lib/src:** repositioned and renamed `TableInfo` and `ColumnInfo` models ([3571a53](https://github.com/dariomatias-dev/sql_studio_app/commit/3571a539a4fdf05173403944b00e711cc3570e90)) +* **lib/src:** simplified button components and moved to dedicated folder ([0c5163e](https://github.com/dariomatias-dev/sql_studio_app/commit/0c5163e4e6a356f7c87739da34c84f46dd8fc1cc)) +* **lib/src:** simplified interactions ([3402523](https://github.com/dariomatias-dev/sql_studio_app/commit/34025230f25a915a9db2d7887f9193e6fb263e27)) +* **lib/src:** simplified method names in `SqlBasicSuggestionsNotifier` ([239f7a8](https://github.com/dariomatias-dev/sql_studio_app/commit/239f7a8c2eca95b4aa7d6b24b1aa0ec30006da6a)) +* **lib/src:** standardized `IconButton` property hierarchy ([aa4cb84](https://github.com/dariomatias-dev/sql_studio_app/commit/aa4cb84aa8e1a358496ee010dc1e7bdb3274ae77)) +* **lib/src:** updated query results and fixes ([3a2d74c](https://github.com/dariomatias-dev/sql_studio_app/commit/3a2d74c0ba70ef4a9fddf74ec447da2e4b9586f2)) +* **lib:** add internationalization support to `DatabaseCardWidget` component ([f82fffd](https://github.com/dariomatias-dev/sql_studio_app/commit/f82fffd7a03b86689d1e5f24ccc8bbca52d4aa31)) +* **lib:** add internationalization support to `DatabaseDeleteDialogWidget` and translations for `Cancel` ([7a3c826](https://github.com/dariomatias-dev/sql_studio_app/commit/7a3c826e0174d3666e349a14097026b5b50ab473)) +* **lib:** add internationalization support to `DrawerDatabaseCardWidget` component ([e47663c](https://github.com/dariomatias-dev/sql_studio_app/commit/e47663c4ff5a0ff6bc4dd6d8ceed683a99e9764d)) +* **lib:** add internationalization support to `DrawerWidget` component ([0959443](https://github.com/dariomatias-dev/sql_studio_app/commit/095944350815d864ff335c86f6ae45412cbf209d)) +* **lib:** add internationalization support to `RemoveSqlBasicSuggestionDialogWidget` ([cd27dbc](https://github.com/dariomatias-dev/sql_studio_app/commit/cd27dbcb38cf8f5e480a81c7200f473f62e68a1e)) +* **lib:** add internationalization support to `SqlWorkspaceWidget` component ([5d2acc2](https://github.com/dariomatias-dev/sql_studio_app/commit/5d2acc2b6be29ebb1139abcc434c72dff3fa055c)) +* **lib:** add internationalization support to bottom navigation bar ([1d75b1a](https://github.com/dariomatias-dev/sql_studio_app/commit/1d75b1a26a14bc8894a2d895cf753111efa5d16e)) +* **lib:** add internationalization support to settings screen ([586620d](https://github.com/dariomatias-dev/sql_studio_app/commit/586620d15352e4cbbd034dd6eaf4a55f33a87305)) +* **lib:** add internationalization to `CreateSqlBasicSuggestionDialogWidget` ([dfde286](https://github.com/dariomatias-dev/sql_studio_app/commit/dfde286f43589f3a2626181d8a8236f9abc92a83)) +* **lib:** add internationalization to `SqlBasicSuggestionsSettingsScreen` and related components ([b1ff9a3](https://github.com/dariomatias-dev/sql_studio_app/commit/b1ff9a343d00461e830dc519656dbcfd7289c4ed)) +* **lib:** add internationalization to `WorkspaceLayoutScreen` ([1b59f27](https://github.com/dariomatias-dev/sql_studio_app/commit/1b59f27318241c008c97579d190bb76ba29907b8)) +* **lib:** add internationalization to default databases mapping ([fd1b343](https://github.com/dariomatias-dev/sql_studio_app/commit/fd1b343d3cca2667f9b781ccf001221890f2f25d)) +* **lib:** add internationalization to SQL suggestions settings screen ([f163cd3](https://github.com/dariomatias-dev/sql_studio_app/commit/f163cd3fcb66c6a8fd6899dfeb86c751a9ea2678)) +* **lib:** added contact option to settings screen ([f7d2ec1](https://github.com/dariomatias-dev/sql_studio_app/commit/f7d2ec183d730089c07d0fad3f0466034bc0864f)) +* **lib:** added internationalization support to `CreateSqlAdvancedSuggestionDialogWidget` ([6b7c7fa](https://github.com/dariomatias-dev/sql_studio_app/commit/6b7c7fa787a0e7aa2b58735e06ab469167383b54)) +* **lib:** added internationalization support to `DatabaseVisualizerScreen` ([3c520ef](https://github.com/dariomatias-dev/sql_studio_app/commit/3c520ef84ae675724ff52df822b74bce66a06931)) +* **lib:** added internationalization support to `DeleteSqlAdvancedSuggestionDialogWidget` ([9a8a42b](https://github.com/dariomatias-dev/sql_studio_app/commit/9a8a42b60e6b69f99c99658bb3e2399feecd43fa)) +* **lib:** added internationalization support to `InputDialogWidget` and expanded translation usage ([e332193](https://github.com/dariomatias-dev/sql_studio_app/commit/e332193ab6034532256b74f2ef34a7b258463907)) +* **lib:** added internationalization support to `NotFoundScreen` ([beef431](https://github.com/dariomatias-dev/sql_studio_app/commit/beef43184d6f515aa09cadd5c99a87b7f4119ee8)) +* **lib:** added internationalization support to `ResetSqlAdvancedSuggestionsDialogWidget` ([7aa7086](https://github.com/dariomatias-dev/sql_studio_app/commit/7aa7086fa503cb0c4caf2bc477ec7619bb287679)) +* **lib:** added internationalization support to `ResetSqlBasicSuggestionsDialogWidget` ([4066b1f](https://github.com/dariomatias-dev/sql_studio_app/commit/4066b1f2e0544e572c3a0a981874d9fc9eb62f23)) +* **lib:** added internationalization support to `SqlAdvancedSuggestionFormDialogWidget` ([b6aa6eb](https://github.com/dariomatias-dev/sql_studio_app/commit/b6aa6eb8861e74b2b51cd710ab24fa1afa58e5cc)) +* **lib:** added internationalization support to `SuggestionsSettingsLayoutController` ([6029306](https://github.com/dariomatias-dev/sql_studio_app/commit/60293066991d580484f0cc39fde9c80b62c26923)) +* **lib:** added internationalization support to `ThemeSwitcherButtonWidget` ([771813d](https://github.com/dariomatias-dev/sql_studio_app/commit/771813d6a0399b6d20d04cc9cde172f3242c01c7)) +* **lib:** added internationalization support to `UpdateSqlAdvancedSuggestionDialogWidget` ([6c7f5bb](https://github.com/dariomatias-dev/sql_studio_app/commit/6c7f5bb64a1988a484120309aa68705618b56934)) +* **lib:** added internationalization support to notifiers and services ([324becc](https://github.com/dariomatias-dev/sql_studio_app/commit/324becc7b2e488717d99e3958c4e6ea507f87508)) +* **lib:** added internationalization to `CreateDatabaseDialogWidget` and language selection feedback to `LanguageSelectorSheetOptionWidget` ([fd829de](https://github.com/dariomatias-dev/sql_studio_app/commit/fd829debdb8e663b9e9aaecdfcfe43dbd1d921ef)) +* **lib:** added internationalization to SQL copy texts ([647f91b](https://github.com/dariomatias-dev/sql_studio_app/commit/647f91b2c91af7af5115b32095d0bf865fee4556)) +* **lib:** added internationalization to SQL sharing messages ([707f8ec](https://github.com/dariomatias-dev/sql_studio_app/commit/707f8ec3761b1b0f924b676fa3d28f64a1943532)) +* **lib:** added internationalization to the `setLayout` error message in `WorkspaceLayoutNotifier` ([2718e35](https://github.com/dariomatias-dev/sql_studio_app/commit/2718e35172444f3b9bcb75b71d9a60b86b671730)) +* **lib:** added internationalization to the new editor options ([26632c0](https://github.com/dariomatias-dev/sql_studio_app/commit/26632c02a2d10bce9a3efb3eafd5703d41cc9b5b)) +* **lib:** added SQL copy action ([14a9264](https://github.com/dariomatias-dev/sql_studio_app/commit/14a92648cc12247904da62744bbe6c1d2a735532)) +* **lib:** added translation for `loading` ([87ec79e](https://github.com/dariomatias-dev/sql_studio_app/commit/87ec79e91f7a78c58e77a65ed2a55a8a818a749d)) +* **lib:** created notifier to handle reactivity of user-written SQL ([d074a59](https://github.com/dariomatias-dev/sql_studio_app/commit/d074a594361698c81972da2cab0f1064a459af72)) +* **lib:** created notifier to switch main screen ([3403c53](https://github.com/dariomatias-dev/sql_studio_app/commit/3403c531d7967f61e4cb3e7c37b17cae123ce6ab)) +* **lib:** expand internationalization ([03133a7](https://github.com/dariomatias-dev/sql_studio_app/commit/03133a741ab78ad53be6a6e6124b0c3ed7cd11b2)) +* **lib:** expand internationalization ([0e8c89b](https://github.com/dariomatias-dev/sql_studio_app/commit/0e8c89bb8f2f83693dad3845c021e5bf73c907c6)) +* **lib:** implement `WorkspaceLayoutNotifier` ([df983a6](https://github.com/dariomatias-dev/sql_studio_app/commit/df983a60b0ad7dd4698883a5b020908c9d817405)) +* **lib:** implement language switching ([22ddfee](https://github.com/dariomatias-dev/sql_studio_app/commit/22ddfee162728e0de1117499c07dec028dbaebaf)) +* **lib:** implemented `AppVersionNotifier` ([d231cf9](https://github.com/dariomatias-dev/sql_studio_app/commit/d231cf903573fcbbef0ac20496f98f86aac946d7)) +* **lib:** implemented `SqlBasicSuggestionsNotifier` ([8648bfb](https://github.com/dariomatias-dev/sql_studio_app/commit/8648bfb35f2719b4777b84276cb23c66daa83162)) +* **lib:** provided `DatabaseNotifier` in the widget tree and added data loading ([936cf3d](https://github.com/dariomatias-dev/sql_studio_app/commit/936cf3d9735718927becd7fcee1c582a8d79de2b)) +* **lib:** refactored navigation notifier ([ff00f22](https://github.com/dariomatias-dev/sql_studio_app/commit/ff00f2202015614d7fbdece677b7c30eb6a92436)) +* **lib:** rename workspace configuration screen ([7e35244](https://github.com/dariomatias-dev/sql_studio_app/commit/7e3524408d7603293c11a44dd61f2f64c6a682c5)) +* **lib:** renamed `SqlCommandsNotifier` ([f371d4f](https://github.com/dariomatias-dev/sql_studio_app/commit/f371d4f69dc10bf20112ff16fb73cffa22a4e89e)) +* **lib:** renamed language controller ([860f347](https://github.com/dariomatias-dev/sql_studio_app/commit/860f347cac7d9f0de60bdf3791587ffd8599da41)) +* **lib:** repositioned `SharedPreferencesService` loading and app language initialization ([a908234](https://github.com/dariomatias-dev/sql_studio_app/commit/a90823409d2455a4ccd76b5b55377745fc29f552)) +* **lib:** simplified `SqlEditorWidget` ([1ce7e6b](https://github.com/dariomatias-dev/sql_studio_app/commit/1ce7e6b2ec45734b4bf485469bce0cc645fb1e67)) +* remove use of `SnackBar` ([7684dd0](https://github.com/dariomatias-dev/sql_studio_app/commit/7684dd0fa31da9fc7d9a3c6eb2b451c54807f08b)) +* replace code field package and fix keyboard opening bug ([543e659](https://github.com/dariomatias-dev/sql_studio_app/commit/543e6594c363bbad6c03b7f520363dd7378b1e52)) +* **screenshots:** generate localized screenshots for each README ([3e9de6f](https://github.com/dariomatias-dev/sql_studio_app/commit/3e9de6fb3c1a2da6299372d436f1c70ff4413eb9)) +* **scripts:** add automated application screenshot capture ([3008e82](https://github.com/dariomatias-dev/sql_studio_app/commit/3008e8267a93619631c1ac5d076d22b38cf2e60a)) +* set app icon and name ([95b2990](https://github.com/dariomatias-dev/sql_studio_app/commit/95b29907bf451efdd7f9eb594e6c9c4f4937dc2a)) +* **settings:** add light, dark, and system theme support ([bd7f265](https://github.com/dariomatias-dev/sql_studio_app/commit/bd7f2653bdd025331c67a518cb12bcfb6381219e)) +* **sql-editor:** add download and load last query actions ([6da1365](https://github.com/dariomatias-dev/sql_studio_app/commit/6da136548d245c0e07683bebbbbd200b94c01171)) +* **theme:** add spacing and duration tokens ([71f9495](https://github.com/dariomatias-dev/sql_studio_app/commit/71f949537d9ba2b01716b65baa353b4b0776618c)) +* **ui:** add standardized state widgets ([67f1444](https://github.com/dariomatias-dev/sql_studio_app/commit/67f14445693a33e2bcfe866314b4cd277019ee3f)) +* **ui:** add transitions and micro-animations ([23e398e](https://github.com/dariomatias-dev/sql_studio_app/commit/23e398e3522978d1c55f626ee0fba6979edb0ded)) +* **ui:** apply black-and-white minimal redesign to suggestion settings ([8c79316](https://github.com/dariomatias-dev/sql_studio_app/commit/8c793165cfe4ab1a38f7f96324060c8fc82076b0)) +* update app icon ([abc9a2c](https://github.com/dariomatias-dev/sql_studio_app/commit/abc9a2cb868fd7a8a0330d42d18a90247876f2ba)) + + +### Bug Fixes + +* **android:** fall back to the debug signing key when key.properties is absent ([24a2f44](https://github.com/dariomatias-dev/sql_studio_app/commit/24a2f44cf045de5146d7ac37c84e3b061668156a)) +* **app-icon:** improve adaptive icon rendering ([0bb3cdd](https://github.com/dariomatias-dev/sql_studio_app/commit/0bb3cdd658b46abeb25d41b162bf2eddc358dede)) +* **ci:** use a root-relative path in app_ui's coverage check ([8a042ab](https://github.com/dariomatias-dev/sql_studio_app/commit/8a042ab9e6144c8ed1624dab5ca6d7ad3f115cd0)) +* close stale database handles and improve SQL safety ([262f016](https://github.com/dariomatias-dev/sql_studio_app/commit/262f0164c821977c7e372929a88942b8898056b4)) +* complete repaint checks and detect SQL types after comments ([8a9256c](https://github.com/dariomatias-dev/sql_studio_app/commit/8a9256caef4a3d4757a3bfcef0b1a9e28631f520)) +* **create-database-dialog:** validate the original database name ([59fb58c](https://github.com/dariomatias-dev/sql_studio_app/commit/59fb58cd426c07ce806ff5529493847bcc70b156)) +* **database_visualizer:** stop caching usecase in late field ([28f27a7](https://github.com/dariomatias-dev/sql_studio_app/commit/28f27a7ff26eb869b6b348f1e23357ce532d8ba8)) +* **database-card:** improve table name preview ([3c7faca](https://github.com/dariomatias-dev/sql_studio_app/commit/3c7facab95b2cfa118679271e9b8a2f87b30c339)) +* **database-visualizer:** adopt the `Result`/`Failure` repository contract ([cc3a27e](https://github.com/dariomatias-dev/sql_studio_app/commit/cc3a27e211f0899ce7aeee33dacbe2d776c5a064)) +* **database-visualizer:** auto-dispose the view model provider ([6c3d02b](https://github.com/dariomatias-dev/sql_studio_app/commit/6c3d02bfe7559eea0ed3da016409b3135bf36ed1)) +* **database-visualizer:** preserve table name casing ([03fe0ab](https://github.com/dariomatias-dev/sql_studio_app/commit/03fe0abcd63f7b2daf77ab50d61f66269b56aa7d)) +* **database-visualizer:** recover from load errors ([f885551](https://github.com/dariomatias-dev/sql_studio_app/commit/f885551471a46d747b995245fdcf240a18d42088)) +* **database:** cache initialization future to prevent duplicate connections ([d5c42b3](https://github.com/dariomatias-dev/sql_studio_app/commit/d5c42b3332830e0d55de18610f5b64bb171a6bc1)) +* **database:** move database filtering into a view model ([871b0a9](https://github.com/dariomatias-dev/sql_studio_app/commit/871b0a9ced1bb459913f6c155bb9ddc52121bca2)) +* **database:** quote table identifier in `dropTable` ([35eb0dd](https://github.com/dariomatias-dev/sql_studio_app/commit/35eb0dd1e6e5c94301b380f73240e95400302464)) +* **default-database:** preserve user edits across a seed version bump ([7cf5b8d](https://github.com/dariomatias-dev/sql_studio_app/commit/7cf5b8dd9fd2e5d22f70e9a2c97adbe04342cd97)) +* **default-database:** reuse the statement splitter ([4dd57ca](https://github.com/dariomatias-dev/sql_studio_app/commit/4dd57ca41f58d3b251c4ad921c75c29e188a4dd0)) +* **default-database:** stop swallowing seed failures ([8c5f1a5](https://github.com/dariomatias-dev/sql_studio_app/commit/8c5f1a5b06aec2e7a83c31ff6ec5b6d4db0b88b1)) +* **docs:** reorder the README language selector ([c76dcdd](https://github.com/dariomatias-dev/sql_studio_app/commit/c76dcdd4a6336271db3611e251901d2c35b798a4)) +* **editor:** point Flutter SDK to FVM stable symlink ([3541125](https://github.com/dariomatias-dev/sql_studio_app/commit/3541125cf52876c09e79188ad9cf83c504c4aec2)) +* **l10n:** localize the dialog action label ([f1d6b3d](https://github.com/dariomatias-dev/sql_studio_app/commit/f1d6b3d4382a0461fecc4962701e8dc29aeadea6)) +* **l10n:** rename suggestion settings labels ([5bf93eb](https://github.com/dariomatias-dev/sql_studio_app/commit/5bf93ebb20aa209fe131d19a321332998e3ff62d)) +* **lib/l10n:** fixed translations ([e798cbd](https://github.com/dariomatias-dev/sql_studio_app/commit/e798cbdaf57d0187435b4b388276ac9fc326e256)) +* **lib/src/notifiers/sql_suggestions_notifiers/sql_advanced_suggestions_notifier.dart:** remove default value from `SqlAdvancedSuggestionsNotifier` ([1b5f271](https://github.com/dariomatias-dev/sql_studio_app/commit/1b5f2710c539fe1862209b12aedf6632e415e7ae)) +* **lib/src/screens/main/widgets/drawer/create_database_dialog_widget.dart:** refactor database creation flow ([305f845](https://github.com/dariomatias-dev/sql_studio_app/commit/305f845e9a6e7ef6c3ef61c987253a8a2cdae189)) +* **lib/src/shared/widgets/sql_workspace/console/console_widget.dart:** fixed loading element ([a3037ad](https://github.com/dariomatias-dev/sql_studio_app/commit/a3037ad14ee176fd6c961f673e65e08c21529593)) +* **lib/src:** corrected screens folder name ([e6845b5](https://github.com/dariomatias-dev/sql_studio_app/commit/e6845b5e174f8ef9ebcbbd97856b43d2cd2e72d5)) +* **logging:** stop logging SQL statements and database names ([e8068ea](https://github.com/dariomatias-dev/sql_studio_app/commit/e8068eaa829e681ebaf73314a8f00b3ccdd6d034)) +* **navigation:** keep bottom navigation labels within the selection pill ([cf62b18](https://github.com/dariomatias-dev/sql_studio_app/commit/cf62b18e9426f891bbfb873e44caf09f0b19557e)) +* **navigation:** keep the editor unfocused after overlays close ([defdfcb](https://github.com/dariomatias-dev/sql_studio_app/commit/defdfcbab85fda4e2803ffc00199e12c7efafe67)) +* **navigation:** remove duplicated bottom padding ([4a69972](https://github.com/dariomatias-dev/sql_studio_app/commit/4a69972b7c7625a3cb6ab8636d4d9f148e34f1ee)) +* **navigation:** select database by name instead of label ([4941726](https://github.com/dariomatias-dev/sql_studio_app/commit/4941726c79657f9a60455cfb4fcbb32946c277c9)) +* **providers:** remove duplicate SQL execution service provider ([d8aa383](https://github.com/dariomatias-dev/sql_studio_app/commit/d8aa383db3538038d93a057011596401b16bd992)) +* remove duplicate favorite toggle logic and incorrect error argument ([4f1cc48](https://github.com/dariomatias-dev/sql_studio_app/commit/4f1cc4818848aecd8cecddc59d95ae86e43584bc)) +* reuse shared database connections for seed and reset ([eb5ddf4](https://github.com/dariomatias-dev/sql_studio_app/commit/eb5ddf42f9f9f823ac9a9c874209582bc9030cea)) +* **root-drawer:** improve state handling and dialog behavior ([89c6758](https://github.com/dariomatias-dev/sql_studio_app/commit/89c6758bda60f0bbf809e97e39c07e59e65d4054)) +* scope `DatabaseManager` through dependency injection ([262755c](https://github.com/dariomatias-dev/sql_studio_app/commit/262755c8b0ce86595e80fc7c2033bb66cb9d5778)) +* **settings:** distinguish external links from internal navigation ([345e37a](https://github.com/dariomatias-dev/sql_studio_app/commit/345e37a58bde40344c5ae7eda9115289647a5d2e)) +* **shared-widgets:** prevent stuck loading state and dispose controllers ([5a5d76b](https://github.com/dariomatias-dev/sql_studio_app/commit/5a5d76b2f729e368b6154a24f771a0cd80ba3c4e)) +* **sql-editor:** dispose the code controller ([d0e28af](https://github.com/dariomatias-dev/sql_studio_app/commit/d0e28af37a9752c9a2f1f390795f5d3303af6357)) +* **sql-execution:** close cached connections on dispose ([b668377](https://github.com/dariomatias-dev/sql_studio_app/commit/b6683779fc062778d160aeef0dfc7447c875ecd0)) +* **sql-execution:** ignore semicolons inside SQL comments ([bc4f5a2](https://github.com/dariomatias-dev/sql_studio_app/commit/bc4f5a2ae052d73cdc14e1eefaeb59c3fe485e7c)) +* **sql-execution:** safely split statements and reuse connections ([1b2d6f0](https://github.com/dariomatias-dev/sql_studio_app/commit/1b2d6f0934e01073b1b3b90e029117c9a518b83c)) +* **sql-suggestions:** adopt the `Result`/`Failure` repository contract ([ed7daef](https://github.com/dariomatias-dev/sql_studio_app/commit/ed7daef6f93d7f7ad97ae3c455a462ca9c5c9b90)) +* **sql-suggestions:** seed default advanced suggestions ([09c7f9d](https://github.com/dariomatias-dev/sql_studio_app/commit/09c7f9dd6df515813ec6d20557f5def3f7129807)) +* **suggestions-settings:** reset change baseline after updates ([dcf07a6](https://github.com/dariomatias-dev/sql_studio_app/commit/dcf07a6638c9ac9d12d26683ef2f5fe37639fdbd)) +* **theme:** replace default purple with the app color palette ([4d605b1](https://github.com/dariomatias-dev/sql_studio_app/commit/4d605b174a782207d99e397ec50990e51647b527)) +* **workspace-layout:** remove rounded clipping from the workspace ([c006940](https://github.com/dariomatias-dev/sql_studio_app/commit/c0069406d77262e130dd1771c20304a0cb32688e)) +* **workspace:** improve floating navigation bar behavior ([1c6b2cd](https://github.com/dariomatias-dev/sql_studio_app/commit/1c6b2cdf42c00e0d5e29000e4042c205230a640e)) + + +### Performance Improvements + +* **console:** stop building every result row eagerly ([378c207](https://github.com/dariomatias-dev/sql_studio_app/commit/378c207163c432be2ad35815ad80e08206d2fb60)) +* run per-table PRAGMA queries concurrently ([9168f52](https://github.com/dariomatias-dev/sql_studio_app/commit/9168f525ddba49997f0530c51f97b311e538b511)) + ## [0.3.0] - 2026-07-31 ### Added diff --git a/CLAUDE.md b/CLAUDE.md index 6f0fcd62..728bb14a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,7 +13,7 @@ fvm flutter test --coverage # tests with coverage/lcov.info fvm flutter gen-l10n # regenerate lib/l10n/app_localizations*.dart scripts/check_l10n.sh # ARB key parity across locales -scripts/check_coverage.sh coverage/lcov.info 86 # coverage floor, lib/l10n excluded +scripts/check_coverage.sh coverage/lcov.info 91 # coverage floor, lib/l10n excluded scripts/verify.sh # the full local gate, mirrors CI scripts/verify.sh --skip-tests # fast pass; never the final gate ``` @@ -48,6 +48,15 @@ Code shared across features lives in `lib/src/shared/`. App-wide services, routing, theming and cross-feature providers live in `lib/src/core/` (`core/providers/core_providers.dart` for anything more than one feature reads). +Design tokens and presentation-agnostic widgets (buttons, cards, dialogs, +states) live in `packages/app_ui`, a local package imported as +`package:app_ui/app_ui.dart`. A widget belongs there only if it has no +dependency on `AppLocalizations`, a Riverpod provider, routing, or any other +app-specific type; one that needs an app default takes it as a parameter +(`ErrorDialogWidget.dismissLabel`) instead of reading it internally. Anything +with that coupling — `CancelButtonWidget`, for one — stays in +`lib/src/shared/` even if it looks generic. + A feature never imports another feature's `presentation/`. If two features need the same view model or service, it belongs in `core/` or `shared/`. @@ -65,6 +74,39 @@ the same view model or service, it belongs in `core/` or `shared/`. - Log through `AppLogger` (`core/logging/`), never construct a `Logger` directly. Never log SQL text, database names or other user data. +## Tests + +Anything with logic gets a test, in the mirrored path under `test/`. + +| What | Test | +| --- | --- | +| Repository, mapper, use case, service | Unit test against a fake or an in-memory implementation. | +| View model | Unit test driving the notifier through its states, with provider overrides. | +| Screen or widget | Widget test, using the fixtures in `test/test_helpers/`. | +| End-to-end flow across features | `integration_test/`, using the harness in `integration_test/test_helpers/app_harness.dart`. Runs on a real Android device or the CI emulator; assert on rendered content, never on a toast or on `go_router`'s navigation state. | + +Coverage is enforced, not advisory: 91% minimum, checked by `scripts/check_coverage.sh` (`lib/l10n/` excluded). + +## Ripple effects + +Ask these on every change, and act on the ones that apply: + +- **A user-visible capability changed?** Update `README.md`, `README.es.md` and + `README.pt-BR.md`. +- **The structure, a layer boundary or a convention changed?** Update + `docs/architecture.md`, in all three languages. +- **The workflow, the checks or the tooling changed?** Update + `docs/contributing.md`, in all three languages. +- **The test count or the coverage floor changed?** The README's Testing + section quotes both. +- **A script gained, lost or changed behavior?** The README's Scripts table. +- **A dependency was added or removed?** `pubspec.yaml`. +- **A CI job changed?** `docs/contributing.md` documents the jobs; `act + pull_request` (pinned by `.actrc`) runs them locally first. + +Documentation ships in English, Spanish and Portuguese (BR). A doc change in +one language and not the other two is a broken change. + ## Commit messages Conventional Commits, enforced by `.githooks/commit-msg` @@ -74,7 +116,7 @@ Conventional Commits, enforced by `.githooks/commit-msg` `workspace-layout`, `app-version` - core areas: `core`, `navigation`, `routes`, `theme`, `l10n`, `sql-execution`, `default-database`, `shared-preferences` -- cross-cutting: `shared`, `deps`, `ci`, `scripts`, `docs`, `release` +- cross-cutting: `shared`, `app-ui`, `deps`, `ci`, `scripts`, `docs`, `release` Scope is optional; prefer the narrowest one that fits. Lowercase and hyphenated, never underscored (`sql-editor`, not `sql_editor`). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index d740357f..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,64 +0,0 @@ -# Contributing - -## Setup - -After cloning, enable the repo's git hooks: - -```bash -git config core.hooksPath .githooks -``` - -This activates a `commit-msg` hook that rejects commits not following the -convention below. - -## Commit convention - -This project follows [Conventional Commits](https://www.conventionalcommits.org): - -``` -(): -``` - -- **type**: `feat`, `fix`, `refactor`, `docs`, `style`, `test`, `chore`, `perf`, `build`, `ci`, `revert` -- **scope**: optional, lowercase, e.g. `database`, `sql_editor`, `settings` -- **subject**: imperative mood, no trailing period, ≤72 chars - -Examples: - -``` -feat(database): add favorite toggle to database card -fix(database_visualizer): stop caching usecase in late field -refactor(ui): centralize color and shadow tokens -``` - -Body (optional) explains *why*, not *what* — the diff already shows what -changed. - -## Branching - -- `main` is protected: no direct pushes, merges only via pull request. -- Branch names: `/` (e.g. `feat/database-favorites`, - `fix/visualizer-crash`). - -## Pull requests - -- One logical change per PR; keep it small and reviewable. -- CI (`analyze-and-test`) must pass before merge. -- Squash or rebase merge only — no merge commits, to keep history linear - and each entry a valid Conventional Commit. -- For this repo (single maintainer), self-merge after CI passes is allowed; - branch protection still requires the PR flow and passing checks. - -## Code style - -- Follows `very_good_analysis` lints — run `flutter analyze` before pushing. -- Run `dart format lib/src lib/main.dart test` before committing (`lib/l10n` - is generated by `flutter gen-l10n` and excluded from formatting/lint, - same as in `analysis_options.yaml`). - -## Tests - -- `flutter test` runs the unit test suite in `test/`. -- Prefer testing use cases with real branching logic and view models over - use cases that only forward to a single repository method — those add - no coverage beyond the mock itself. diff --git a/README.es.md b/README.es.md index f14aae0e..d78a8b7c 100644 --- a/README.es.md +++ b/README.es.md @@ -1,38 +1,49 @@
-Flutter -Dart -SQLite -Android + Flutter + Dart + SQLite + Android +
+
+
+ CI + Coverage + very_good_analysis + MIT License

-English · Português (BR) · Español + English · Español · Português (BR)

SQL Studio

-Una app Android para practicar SQL en bases de datos SQLite locales, editables y totalmente offline. -
-Explora la documentación » -
-
-Reportar Error -· -Solicitar Funcionalidad + Una app Android para practicar SQL en bases de datos SQLite locales, editables y totalmente offline. +
+ Explora la documentación » +
+
+ Reportar Error + · + Solicitar Funcionalidad

## Índice - [Acerca del Proyecto](#acerca-del-proyecto) +- [Vista Previa](#vista-previa) - [Funcionalidades](#funcionalidades) -- [Capturas de Pantalla](#capturas-de-pantalla) -- [Construido Con](#construido-con) +- [Stack Tecnológico](#stack-tecnológico) +- [Arquitectura](#arquitectura) - [Cómo Empezar](#cómo-empezar) - [Scripts](#scripts) +- [Pruebas](#pruebas) +- [Documentación](#documentación) - [Contribuir](#contribuir) +- [Seguridad](#seguridad) - [Licencia](#licencia) - [Autor](#autor) @@ -42,6 +53,21 @@ Una app Android para practicar SQL en bases de datos SQLite locales, editables y Cada base de datos incluye un esquema y datos de seed predefinidos. Escribes y ejecutas SQL real contra ella en un editor de código con resaltado de sintaxis, inspeccionas los resultados en una consola y puedes restablecer la base de datos a su estado original en cualquier momento. Una vista de esquema visual te permite inspeccionar tablas, columnas y relaciones sin escribir una consulta. +## Vista Previa + +
+ Inicio / editor SQL + Bases de datos + Editor SQL con una base de datos activa + Menú de navegación + Visualizador de base de datos + Configuración + Selector de idioma + Selector de tema + Configuración de sugerencias de SQL + Configuración de diseño del workspace +
+ ## Funcionalidades - **Bases de Datos SQLite Offline**: Un catálogo de bases de datos locales, cada una con su propio esquema y datos de seed, listas para consultar de inmediato. @@ -55,22 +81,7 @@ Cada base de datos incluye un esquema y datos de seed predefinidos. Escribes y e - **Selección de Tema**: Tema claro, oscuro o según el sistema. - **Búsqueda de Bases de Datos**: Filtra la lista de bases de datos por nombre. -## Capturas de Pantalla - -
-Inicio / editor SQL -Bases de datos -Editor SQL con una base de datos activa -Menú de navegación -Visualizador de base de datos -Configuración -Selector de idioma -Selector de tema -Configuración de sugerencias de SQL -Configuración de diseño del workspace -
- -## Construido Con +## Stack Tecnológico - **[Flutter](https://flutter.dev/)**: El toolkit de UI de Google para crear aplicaciones compiladas de forma nativa desde una única base de código. - **[Dart](https://dart.dev/)**: El lenguaje de programación detrás de Flutter. @@ -79,6 +90,19 @@ Cada base de datos incluye un esquema y datos de seed predefinidos. Escribes y e - **[sqflite](https://pub.dev/packages/sqflite)**: Motor de base de datos SQLite para Flutter. - **[flutter_code_editor](https://pub.dev/packages/flutter_code_editor)** y **[flutter_highlight](https://pub.dev/packages/flutter_highlight)**: El editor de código y el resaltado de sintaxis usados en el editor SQL. +## Arquitectura + +La app está organizada por funcionalidad (`lib/src/features/`), cada una con sus propias capas `data`, `domain` y `presentation`, siguiendo una Clean Architecture simplificada y MVVM: + +- **database**: el catálogo de bases de datos, creación, eliminación, favoritos y búsqueda. +- **database-visualizer**: la vista visual del esquema de una base de datos, sus tablas, columnas y relaciones. +- **sql-editor**: el editor de código, su consola, y la ejecución de consultas. +- **sql-suggestions**: los fragmentos básicos y avanzados de SQL mostrados al escribir una consulta. +- **workspace-layout**: cómo se organizan en pantalla el editor, la consola y el visualizador. +- **app-version**: la información de versión de la app mostrada en Configuración. + +El estado se gestiona con Riverpod (clases `Notifier`/`AsyncNotifier` expuestas a través de providers), el enrutamiento con `go_router`, y la persistencia mediante `sqflite` y `shared_preferences`. Las preocupaciones transversales (navegación, logging, manejo de errores) viven en `lib/src/core`; los widgets y utilidades compartidos por más de una funcionalidad, pero aún acoplados a esta app, viven en `lib/src/shared`. Los tokens de diseño y los componentes agnósticos de presentación (botones, tarjetas, diálogos, estados) viven en su propio paquete local, `packages/app_ui`. Consulta [`docs/architecture.es.md`](docs/architecture.es.md) para el detalle completo. + ## Cómo Empezar El proyecto fija la versión del Flutter SDK mediante [FVM](https://fvm.app/), por lo que todos los comandos siguientes usan `fvm flutter` en lugar de una instalación simple de `flutter`. @@ -100,15 +124,40 @@ fvm flutter run Los scripts de utilidad están en `scripts/`. -| Script | Comando | Descripción | -| ------------ | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Script | Comando | Descripción | +| --- | --- | --- | | `screenshot` | `scripts/screenshot.sh [device-id]` | Recorre la app por sus pantallas principales en un dispositivo o emulador conectado y guarda una captura de cada una en `screenshots//`, usado en el README, la ficha de Play Store y el sitio oficial. | +| `verify` | `scripts/verify.sh [--skip-tests]` | El gate de calidad local completo, replicando CI: regenera localizaciones, verifica la paridad de claves ARB, formato, análisis, tests, y el umbral de cobertura, para la app y `packages/app_ui`. | +| `check_l10n` | `scripts/check_l10n.sh` | Compara las claves de mensaje de cada `app_*.arb` contra la plantilla en inglés y falla si falta o sobra una. | +| `check_coverage` | `scripts/check_coverage.sh ` | Analiza un reporte lcov, excluye `lib/l10n/`, y falla por debajo del mínimo dado. | + +## Pruebas + +El proyecto tiene 91 archivos de prueba que cubren repositorios, view models y widgets, más 15 en `packages/app_ui` para sus propios componentes, y 9 suites en `integration_test/` que cubren el primer arranque y el seeding, la creación y eliminación de bases de datos, la edición y el restablecimiento de la base de datos predeterminada, favoritos, la persistencia del tema y del diseño del workspace, y el cambio de idioma. El CI exige un mínimo de 91% de cobertura de líneas para la app y su propio umbral para `packages/app_ui`, junto con el conjunto estricto de lints de `very_good_analysis` y `dart format`. + +Cada ejecución del CI sube sus reportes `lcov` a [Codecov](https://codecov.io/gh/dariomatias-dev/sql_studio_app) como dos flags, `app` y `app_ui`, que comentan la variación de cobertura en cada pull request. Para un reporte línea por línea localmente, genera uno a partir del mismo archivo: + +```sh +fvm flutter test --coverage +genhtml coverage/lcov.info -o coverage/html # necesita lcov instalado +``` + +## Documentación + +- [Arquitectura](docs/architecture.es.md): cómo está organizado el código y por qué. +- [Contribuir](docs/contributing.es.md): configuración local, convenciones, qué verifica CI. +- [Política de Seguridad](docs/security.es.md): cómo reportar una vulnerabilidad. +- [Código de Conducta](docs/code_of_conduct.es.md). ## Contribuir Las contribuciones hacen de la comunidad de código abierto un lugar excelente para aprender y crear. Toda contribución es bienvenida. -Antes de abrir un pull request, consulta [CONTRIBUTING.md](CONTRIBUTING.md) para la configuración local, la convención de mensajes de commit (Conventional Commits) y las reglas de branching de este proyecto. +Antes de abrir un pull request, consulta [docs/contributing.es.md](docs/contributing.es.md) para la configuración local, la convención de mensajes de commit (Conventional Commits) y qué verifica CI. + +## Seguridad + +Por favor, no abras un issue público para reportar una vulnerabilidad de seguridad; consulta [docs/security.es.md](docs/security.es.md) para saber cómo reportarla. ## Licencia diff --git a/README.md b/README.md index 21b4884b..ed22f472 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,73 @@
-Flutter -Dart -SQLite -Android + Flutter + Dart + SQLite + Android +
+
+
+ CI + Coverage + very_good_analysis + MIT License

-English · Português (BR) · Español + English · Español · Português (BR)

SQL Studio

-An Android app to practice SQL on local, editable, fully offline SQLite databases. -
-Explore the docs » -
-
-Report Bug -· -Request Feature + An Android app to practice SQL on local, editable, fully offline SQLite databases. +
+ Explore the docs » +
+
+ Report Bug + · + Request Feature

## Table of Contents -- [About The Project](#about-the-project) +- [About the Project](#about-the-project) +- [Preview](#preview) - [Features](#features) -- [Screenshots](#screenshots) -- [Built With](#built-with) +- [Tech Stack](#tech-stack) +- [Architecture](#architecture) - [Getting Started](#getting-started) - [Scripts](#scripts) +- [Testing](#testing) +- [Documentation](#documentation) - [Contributing](#contributing) +- [Security](#security) - [License](#license) - [Author](#author) -## About The Project +## About the Project **SQL Studio** is a mobile SQL playground: a set of ready-made SQLite databases you can query, edit, and reset, right on your phone, with no server or account required. Each database ships with a predefined schema and seed data. You write and run real SQL against it in a code editor with syntax highlighting, inspect results in a console, and can always reset the database back to its original state. A visual schema view lets you inspect tables, columns, and relationships without writing a query. +## Preview + +
+ Home / SQL editor + Databases + SQL editor with an active database + Navigation drawer + Database visualizer + Settings + Language selector + Theme selector + SQL suggestions settings + Workspace layout settings +
+ ## Features - **Offline SQLite Databases**: A catalog of local databases, each with its own schema and seed data, ready to query immediately. @@ -55,22 +81,7 @@ Each database ships with a predefined schema and seed data. You write and run re - **Theme Selection**: Light, dark, or system theme. - **Database Search**: Filter the databases list by name. -## Screenshots - -
-Home / SQL editor -Databases -SQL editor with an active database -Navigation drawer -Database visualizer -Settings -Language selector -Theme selector -SQL suggestions settings -Workspace layout settings -
- -## Built With +## Tech Stack - **[Flutter](https://flutter.dev/)**: Google's UI toolkit for building natively compiled applications from a single codebase. - **[Dart](https://dart.dev/)**: The programming language behind Flutter. @@ -79,6 +90,19 @@ Each database ships with a predefined schema and seed data. You write and run re - **[sqflite](https://pub.dev/packages/sqflite)**: SQLite database engine for Flutter. - **[flutter_code_editor](https://pub.dev/packages/flutter_code_editor)** & **[flutter_highlight](https://pub.dev/packages/flutter_highlight)**: The code editor and syntax highlighting used for the SQL editor. +## Architecture + +The app is organized by feature (`lib/src/features/`), each with its own `data`, `domain`, and `presentation` layers, following a simplified Clean Architecture and MVVM: + +- **database**: the database catalog, creation, deletion, favoriting, and search. +- **database-visualizer**: the visual schema view of a database's tables, columns, and relationships. +- **sql-editor**: the code editor, its console, and running queries. +- **sql-suggestions**: the basic and advanced SQL snippets shown while writing a query. +- **workspace-layout**: how the editor, console, and visualizer are arranged on screen. +- **app-version**: the app's own version info shown in Settings. + +State is managed with Riverpod (`Notifier`/`AsyncNotifier` classes exposed through providers), routing with `go_router`, and persistence through `sqflite` and `shared_preferences`. Cross-cutting concerns (navigation, logging, error handling) live under `lib/src/core`; widgets and utilities shared by more than one feature, but still coupled to this app, live under `lib/src/shared`. Presentation-agnostic design tokens and components (buttons, cards, dialogs, states) live in their own local package, `packages/app_ui`. See [`docs/architecture.md`](docs/architecture.md) for the full breakdown. + ## Getting Started The project pins its Flutter SDK version via [FVM](https://fvm.app/), so all commands below use `fvm flutter` rather than a bare `flutter` install. @@ -100,15 +124,40 @@ fvm flutter run Utility scripts live under `scripts/`. -| Script | Command | Description | -| ------------ | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Script | Command | Description | +| --- | --- | --- | | `screenshot` | `scripts/screenshot.sh [device-id]` | Drives the app through its main screens on a connected device or emulator and saves a screenshot of each one into `screenshots//`, used for the README, Play Store listing, and official website. | +| `verify` | `scripts/verify.sh [--skip-tests]` | The full local quality gate, mirroring CI: regenerates localizations, checks ARB key parity, formatting, analysis, tests, and the coverage threshold, for the app and `packages/app_ui`. | +| `check_l10n` | `scripts/check_l10n.sh` | Compares the message keys of every `app_*.arb` against the English template and fails on a missing or extra key. | +| `check_coverage` | `scripts/check_coverage.sh ` | Parses an lcov report, excludes `lib/l10n/`, and fails below the given minimum. | + +## Testing + +The project has 91 test files covering repositories, view models, and widgets, plus 15 in `packages/app_ui` for its own components, and 9 `integration_test/` suites covering first run and seeding, database creation and deletion, editing and resetting the default database, favoriting, theme and workspace layout persistence, and language switching. CI enforces a minimum line coverage of 91% for the app and its own floor for `packages/app_ui`, alongside the strict `very_good_analysis` lint set and `dart format`. + +Every CI run uploads its `lcov` reports to [Codecov](https://codecov.io/gh/dariomatias-dev/sql_studio_app) as two flags, `app` and `app_ui`, which comment the coverage delta on each pull request. For a line-by-line report locally, generate one from the same file: + +```sh +fvm flutter test --coverage +genhtml coverage/lcov.info -o coverage/html # needs lcov installed +``` + +## Documentation + +- [Architecture](docs/architecture.md): how the codebase is organized and why. +- [Contributing](docs/contributing.md): local setup, conventions, what CI checks. +- [Security Policy](docs/security.md): how to report a vulnerability. +- [Code of Conduct](docs/code_of_conduct.md). ## Contributing Contributions make the open-source community an amazing place to learn and create. Any contributions you make are greatly appreciated. -Before opening a pull request, see [CONTRIBUTING.md](CONTRIBUTING.md) for the local setup, commit message convention (Conventional Commits), and branching rules this project follows. +Before opening a pull request, see [docs/contributing.md](docs/contributing.md) for the local setup, commit message convention (Conventional Commits), and what CI checks. + +## Security + +Please do not open a public issue to report a security vulnerability; see [docs/security.md](docs/security.md) for how to report one. ## License diff --git a/README.pt-BR.md b/README.pt-BR.md index 86c4c4d1..57a8cbe4 100644 --- a/README.pt-BR.md +++ b/README.pt-BR.md @@ -1,38 +1,49 @@
-Flutter -Dart -SQLite -Android + Flutter + Dart + SQLite + Android +
+
+
+ CI + Coverage + very_good_analysis + MIT License

-English · Português (BR) · Español + English · Español · Português (BR)

SQL Studio

-Um app Android para praticar SQL em bancos SQLite locais, editáveis e totalmente offline. -
-Explore a documentação » -
-
-Reportar Bug -· -Solicitar Funcionalidade + Um app Android para praticar SQL em bancos SQLite locais, editáveis e totalmente offline. +
+ Explore a documentação » +
+
+ Reportar Bug + · + Solicitar Funcionalidade

## Índice - [Sobre o Projeto](#sobre-o-projeto) +- [Preview](#preview) - [Funcionalidades](#funcionalidades) -- [Screenshots](#screenshots) -- [Construído Com](#construído-com) +- [Stack Tecnológico](#stack-tecnológico) +- [Arquitetura](#arquitetura) - [Como Começar](#como-começar) - [Scripts](#scripts) +- [Testes](#testes) +- [Documentação](#documentação) - [Contribuindo](#contribuindo) +- [Segurança](#segurança) - [Licença](#licença) - [Autor](#autor) @@ -42,6 +53,21 @@ Um app Android para praticar SQL em bancos SQLite locais, editáveis e totalment Cada banco de dados vem com um schema e dados de seed predefinidos. Você escreve e executa SQL real contra ele em um editor de código com destaque de sintaxe, inspeciona os resultados em um console, e pode resetar o banco ao estado original a qualquer momento. Uma visualização de esquema visual permite inspecionar tabelas, colunas e relacionamentos sem escrever uma query. +## Preview + +
+ Home / editor SQL + Bancos de dados + Editor SQL com banco de dados ativo + Menu de navegação + Visualizador de banco de dados + Configurações + Seletor de idioma + Seletor de tema + Configurações de sugestões de SQL + Configurações de layout do workspace +
+ ## Funcionalidades - **Bancos de Dados SQLite Offline**: Um catálogo de bancos locais, cada um com seu próprio schema e dados de seed, prontos para consulta imediata. @@ -55,22 +81,7 @@ Cada banco de dados vem com um schema e dados de seed predefinidos. Você escrev - **Seleção de Tema**: Tema claro, escuro ou de acordo com o sistema. - **Busca de Bancos de Dados**: Filtre a lista de bancos de dados por nome. -## Screenshots - -
-Home / editor SQL -Bancos de dados -Editor SQL com banco de dados ativo -Menu de navegação -Visualizador de banco de dados -Configurações -Seletor de idioma -Seletor de tema -Configurações de sugestões de SQL -Configurações de layout do workspace -
- -## Construído Com +## Stack Tecnológico - **[Flutter](https://flutter.dev/)**: Toolkit de UI do Google para construir aplicações nativamente compiladas a partir de uma única base de código. - **[Dart](https://dart.dev/)**: A linguagem de programação por trás do Flutter. @@ -79,6 +90,19 @@ Cada banco de dados vem com um schema e dados de seed predefinidos. Você escrev - **[sqflite](https://pub.dev/packages/sqflite)**: Motor de banco de dados SQLite para Flutter. - **[flutter_code_editor](https://pub.dev/packages/flutter_code_editor)** e **[flutter_highlight](https://pub.dev/packages/flutter_highlight)**: O editor de código e o destaque de sintaxe usados no editor SQL. +## Arquitetura + +O app é organizado por funcionalidade (`lib/src/features/`), cada uma com suas próprias camadas `data`, `domain` e `presentation`, seguindo uma Clean Architecture simplificada e MVVM: + +- **database**: o catálogo de bancos de dados, criação, exclusão, favoritos e busca. +- **database-visualizer**: a visualização visual do esquema de um banco de dados, suas tabelas, colunas e relacionamentos. +- **sql-editor**: o editor de código, seu console, e a execução de queries. +- **sql-suggestions**: os snippets básicos e avançados de SQL mostrados ao escrever uma query. +- **workspace-layout**: como o editor, console e visualizador são organizados na tela. +- **app-version**: as informações de versão do app mostradas em Configurações. + +O estado é gerenciado com Riverpod (classes `Notifier`/`AsyncNotifier` expostas através de providers), a navegação com `go_router`, e a persistência através de `sqflite` e `shared_preferences`. Preocupações transversais (navegação, logging, tratamento de erros) ficam em `lib/src/core`; widgets e utilitários compartilhados por mais de uma funcionalidade, mas ainda acoplados a este app, ficam em `lib/src/shared`. Tokens de design e componentes agnósticos de apresentação (botões, cards, diálogos, estados) ficam em um pacote local próprio, `packages/app_ui`. Veja [`docs/architecture.pt-BR.md`](docs/architecture.pt-BR.md) para o detalhamento completo. + ## Como Começar O projeto fixa a versão do Flutter SDK via [FVM](https://fvm.app/), então todos os comandos abaixo usam `fvm flutter` em vez de uma instalação simples do `flutter`. @@ -100,15 +124,40 @@ fvm flutter run Scripts utilitários ficam em `scripts/`. -| Script | Comando | Descrição | -| ------------ | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Script | Comando | Descrição | +| --- | --- | --- | | `screenshot` | `scripts/screenshot.sh [device-id]` | Navega o app sozinho pelas telas principais em um dispositivo ou emulador conectado e salva um screenshot de cada uma em `screenshots//`, usado no README, na listagem da Play Store e no site oficial. | +| `verify` | `scripts/verify.sh [--skip-tests]` | O gate de qualidade local completo, espelhando o CI: regenera localizações, verifica a paridade de chaves ARB, formatação, análise, testes, e o limiar de cobertura, para o app e `packages/app_ui`. | +| `check_l10n` | `scripts/check_l10n.sh` | Compara as chaves de mensagem de cada `app_*.arb` contra o template em inglês e falha se faltar ou sobrar uma. | +| `check_coverage` | `scripts/check_coverage.sh ` | Analisa um relatório lcov, exclui `lib/l10n/`, e falha abaixo do mínimo dado. | + +## Testes + +O projeto tem 91 arquivos de teste cobrindo repositórios, view models e widgets, mais 15 em `packages/app_ui` para seus próprios componentes, e 9 suítes em `integration_test/` cobrindo a primeira execução e o seeding, criação e exclusão de banco de dados, edição e reset do banco de dados padrão, favoritos, persistência de tema e layout do workspace, e troca de idioma. O CI exige um mínimo de 91% de cobertura de linhas para o app e seu próprio limiar para `packages/app_ui`, além do conjunto rigoroso de lints do `very_good_analysis` e do `dart format`. + +Toda execução do CI envia seus relatórios `lcov` para o [Codecov](https://codecov.io/gh/dariomatias-dev/sql_studio_app) como duas flags, `app` e `app_ui`, que comentam a variação de cobertura em cada pull request. Para um relatório linha a linha localmente, gere um a partir do mesmo arquivo: + +```sh +fvm flutter test --coverage +genhtml coverage/lcov.info -o coverage/html # precisa do lcov instalado +``` + +## Documentação + +- [Arquitetura](docs/architecture.pt-BR.md): como o código está organizado e por quê. +- [Contribuindo](docs/contributing.pt-BR.md): setup local, convenções, o que o CI verifica. +- [Política de Segurança](docs/security.pt-BR.md): como reportar uma vulnerabilidade. +- [Código de Conduta](docs/code_of_conduct.pt-BR.md). ## Contribuindo Contribuições tornam a comunidade de código aberto um lugar excelente para aprender e criar. Toda contribuição é bem-vinda. -Antes de abrir um pull request, consulte o [CONTRIBUTING.md](CONTRIBUTING.md) para o setup local, a convenção de mensagens de commit (Conventional Commits) e as regras de branching deste projeto. +Antes de abrir um pull request, consulte [docs/contributing.pt-BR.md](docs/contributing.pt-BR.md) para o setup local, a convenção de mensagens de commit (Conventional Commits) e o que o CI verifica. + +## Segurança + +Não abra uma issue pública para reportar uma vulnerabilidade de segurança; veja [docs/security.pt-BR.md](docs/security.pt-BR.md) para saber como reportar. ## Licença diff --git a/analysis_options.yaml b/analysis_options.yaml index ff845d95..2e002d33 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -2,5 +2,6 @@ include: package:very_good_analysis/analysis_options.yaml analyzer: exclude: + - build/** - lib/l10n/** - "**/*.g.dart" diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..aebb7bdc --- /dev/null +++ b/codecov.yml @@ -0,0 +1,49 @@ +# Codecov configuration. +# +# Coverage uploads as two independent flags, one per package, since the two +# suites run as separate CI jobs with different thresholds. The targets mirror +# their scripts/check_coverage.sh calls, which stay the authority: Codecov +# reports and annotates, the scripts are what actually fail the build. + +codecov: + require_ci_to_pass: true + +coverage: + precision: 1 + round: down + range: "70...100" + status: + project: + default: false + app: + flags: [app] + target: 91% + threshold: 0.5% + app_ui: + flags: [app_ui] + target: 79% + threshold: 0.5% + patch: + default: + target: auto + threshold: 0.5% + +ignore: + - "lib/l10n/**" + +flags: + app: + paths: + - lib/ + carryforward: true + app_ui: + paths: + - packages/app_ui/lib/ + carryforward: true + +comment: + layout: "condensed_header, diff, flags, files" + require_changes: true + +github_checks: + annotations: true diff --git a/docs/architecture.es.md b/docs/architecture.es.md new file mode 100644 index 00000000..ff8ac45e --- /dev/null +++ b/docs/architecture.es.md @@ -0,0 +1,90 @@ +# Arquitectura + +

+English · Español · Português (BR) +

+ +Este documento profundiza un nivel más que la vista general del README. Está pensado para quien vaya a modificar código en este repositorio: dónde va cada archivo, por qué existe cada capa y cómo se comunican las piezas entre sí. + +## Estructura + +``` +packages/ + app_ui/ # el design system: paquete local, ver abajo +lib/ + main.dart # raíz de composición: límite de errores, ProviderScope, fallback de arranque + src/ + core/ # aspectos transversales, compartidos por todas las features + database/ # DatabaseManager (el archivo SQLite propio de la app) + repositorio CRUD genérico + error/ # Result/Failure, los manejadores globales de errores + logging/ # el contrato AppLogger y su implementación + navigation/ # barra de navegación, drawer, shell de pestañas + providers/ # core_providers.dart: servicios que leen más de una feature + routes/ # configuración de go_router, helpers de navegación tipados + screens/ # ajustes, acerca de, splash, no encontrado, fallo de arranque + services/ # SqlExecutionService, DefaultDatabaseService, SharedPreferencesService, LocalStateService + sql/ # el divisor de sentencias SQL compartido + features/ + / + domain/ + entities/ # clases simples que describen los datos de la feature + repositories/ # contratos abstractos + usecases/ # solo donde la lógica real compone más de una llamada a repositorio + data/ + mappers/ # fromMap/toMap entre una entidad y su mapa de persistencia + datasources/ # con qué habla una implementación de repositorio + repositories/ # la implementación del repositorio + providers/ # _data_providers.dart: DI de datasource/repositorio + presentation/ + view_models/ # un Notifier/AsyncNotifier más su estado inmutable + screens/, widgets/ # UI + _providers.dart # providers de view model, use cases locales a la feature + shared/ # código compartido entre features, pero acoplado a esta app: widgets, utilidades +``` + +Features: `database` (las bases de datos guardadas por el usuario), `database_visualizer` (el diagrama de esquema), `sql_editor` (el editor de código y la consola), `sql_suggestions` (snippets básicos/avanzados), `workspace_layout_settings`, `app_version`. + +## Capas (Clean Architecture + MVVM) + +- **`domain`**: Dart puro. Entidades, contratos de repositorio abstractos, y clases de caso de uso para el puñado de operaciones con lógica ramificada real (`DeleteDatabaseUseCase` cierra la conexión, elimina el archivo y borra el registro; los casos de uso de reordenar/reiniciar/guardar-todo de sugerencias avanzadas componen más de una llamada a repositorio). Todo lo demás es un método de repositorio que un view model llama directamente: una clase que solo reenvía una llamada a un repositorio no es un caso de uso. +- **`data`**: implementa los contratos de `domain` contra una fuente de datos concreta (un `DatabaseRepository` ligado a una tabla, `SharedPreferencesService`, o `SqlExecutionService`). Los mappers convierten entre un mapa de persistencia y una entidad de dominio. +- **`presentation`**: pantallas y widgets, más view models: clases `Notifier`/`AsyncNotifier` de Riverpod expuestas a través de providers. Una pantalla observa un provider; solo lee un repositorio directamente para una llamada puntual disparada por una acción del usuario, nunca un caso de uso que no existe. + +Una feature nunca importa el `presentation/` de otra feature. Lo que más de una feature necesita compartir va en `core/` (un servicio, un provider transversal) o en `shared/` (un widget, una utilidad). + +## Design system (`packages/app_ui`) + +Los tokens de diseño (`AppColors`, `AppSpacing`, `AppRadii`, `AppShadows`, `AppDurations`, `AppTheme`) y un conjunto de widgets agnósticos de presentación (botones, tarjetas, inputs, diálogos, estados de carga/vacío/error) viven en `packages/app_ui`, un paquete local del que la app depende mediante una entrada `path:` en `pubspec.yaml`. Tiene su propio `pubspec.yaml`, `analysis_options.yaml`, tests y job de CI, y se importa como `package:app_ui/app_ui.dart` (el barrel), nunca por una ruta profunda. + +El límite es el acoplamiento, no la complejidad visual: un widget solo entra en `app_ui` si no depende del `AppLocalizations` de esta app, de los providers de Riverpod, del enrutamiento, ni de ningún otro tipo específico de la app. Un widget que necesita algo de eso (lee un string localizado, llama al `Navigator`, observa un provider) se queda en `lib/src/shared/widgets/`, aunque parezca genérico. `CancelButtonWidget` es el ejemplo más claro: visualmente es un envoltorio delgado alrededor del `ButtonWidget` de `app_ui`, pero lee `AppLocalizations.of(context).cancel` y llama a `Navigator.pop`, así que es código de la app. Donde un componente compartido necesita ambas cosas (un widget base más un valor por defecto específico de la app), el lado de `app_ui` recibe el valor como parámetro (`ConfirmationDialogWidget.cancelButton`, `ErrorDialogWidget.dismissLabel`) en vez de recurrir a `AppLocalizations` directamente, y la app lo provee en el sitio de la llamada. + +## Gestión de estado + +[Riverpod](https://riverpod.dev/) de punta a punta, escrito a mano (sin generación de código): `Provider` para dependencias sin estado, `NotifierProvider` para cualquier cosa con comportamiento. Los providers se agrupan por rol, no un archivo por provider: `_providers.dart` para view models, `data/providers/_data_providers.dart` para datasources y repositorios, `core/providers/core_providers.dart` para lo que lee más de una feature (el logger, el `DatabaseManager` compartido, `SqlExecutionService`, `DefaultDatabaseService`). + +## Navegación + +[go_router](https://pub.dev/packages/go_router), con una barra de navegación inferior para las tres pestañas principales (Inicio, Bases de datos, Ajustes) y rutas apiladas para todo lo demás (el visualizador de base de datos, las subpantallas de ajustes). `AppRoutes` (`lib/src/core/routes/`) envuelve cada llamada de navegación en un método tipado en vez de una ruta como cadena de texto suelta. + +## Persistencia + +Dos cosas independientes viven en el disco, ambas a través de [sqflite](https://pub.dev/packages/sqflite): + +- **La base de datos propia de la app** (`DatabaseManager`, `lib/src/core/database/`): un archivo SQLite pequeño con las tablas administrativas de la app: la lista de bases de datos creadas por el usuario y las sugerencias SQL avanzadas. `DatabaseRepository` es una capa CRUD genérica ligada a un nombre de tabla, compartida por cada feature que persiste aquí. +- **Las bases de datos de ejemplo y las del usuario**: cada una es su propio archivo SQLite, abierto bajo demanda por `SqlExecutionService` (`lib/src/core/services/`), que guarda en caché una conexión por cada base de datos abierta y las cierra todas al desecharse. `DefaultDatabaseService` siembra las 14 bases de datos de ejemplo incluidas desde `assets/sql/schemas/` y `assets/sql/seeds/`, versionadas **por base de datos**, no de forma global: subir la versión de un ejemplo vuelve a sembrar solo esa base de datos, dejando intactas las ediciones del usuario en las otras trece. Una instalación que actualiza desde la antigua clave de versión global única la migra a las claves por base de datos sin volver a sembrar nada. + +Las preferencias del usuario que no necesitan consultarse (tema, idioma, disposición del espacio de trabajo, activadores de sugerencias) pasan por `shared_preferences` detrás de `SharedPreferencesService`. + +Tanto el cargador de assets de esquema/semilla como el ejecutor de sentencias de la consola comparten un único divisor de sentencias SQL (`core/sql/sql_statement_splitter.dart`), así que un punto y coma dentro de un literal de texto, un comentario o el cuerpo `BEGIN...END` de un trigger se maneja una sola vez, no se reimplementa por cada quien lo llame. + +## Manejo de errores + +`main.dart` instala `FlutterError.onError` y `PlatformDispatcher.onError` antes de que corra cualquier otra cosa, ambos enrutando hacia `AppLogger`, y sustituye el `ErrorWidget.builder` por uno neutral en builds de release. `startApp()` protege `SharedPreferencesService.create()` y el resto del arranque: un fallo ahí recurre a `StartupFailureApp`, una app mínima (sin contenedor de providers, sin localizaciones más allá de las que necesita) que ofrece reintentar y, para un estado que falla sin importar cuántas veces se reintente, un borrado confirmado de los datos locales a través de `LocalStateService`. + +Dentro de la app, `Result` (`lib/src/core/error/result.dart`) es un tipo sellado: un repositorio o servicio devuelve `SuccessResult`/`FailureResult`, y quien lo llama lo compara con `when`/`switch`, nunca una cadena de `is`. Un `Failure` lleva una clave de localización más argumentos de interpolación; `LocalizationExtension.key()` la resuelve a un mensaje real, y un test verifica que cada `AppLocalizationsKey` tenga una entrada ahí, así que una clave añadida sin mensaje falla ruidosamente en los tests en vez de silenciosamente en producción. `handleError()` (`shared/utils/`) es el único lugar donde el resultado de una vista se convierte en un diálogo de error. + +El registro de logs pasa por `AppLogger` (`core/logging/`), nunca un `Logger` construido directamente. `SqlExecutionService` nunca registra el texto SQL que ejecuta ni el nombre de la base de datos contra la que corre, solo el tipo de sentencia y el número de filas, y adjunta la excepción cruda (que incorpora el SQL que falló) solo en builds de debug. + +## Tests + +Los tests unitarios y de widgets viven bajo `test/`, siguiendo la misma estructura que `lib/`; `packages/app_ui/test/` sigue a `packages/app_ui/lib/` de la misma forma, ejecutándose y cubriéndose de forma independiente. `integration_test/` cubre flujos de punta a punta contra el almacenamiento real de SQLite y `SharedPreferences` en el dispositivo: sembrado en el primer arranque, crear una base de datos y consultarla, editar una base de datos de ejemplo que sobrevive a un reinicio simulado, un reinicio deliberado, borrar una base de datos, cambiar de idioma, y ajustes (tema, disposición del espacio de trabajo, marcar una base de datos como favorita) que sobreviven a un reinicio. `test/core/providers/provider_graph_test.dart` construye el contenedor de providers completo de producción y lee cada provider, detectando un error de cableado que de otro modo solo aparecería en un dispositivo. Consulta el README para los conteos de tests actuales y el umbral de cobertura. diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 00000000..a29ea45c --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,90 @@ +# Architecture + +

+English · Español · Português (BR) +

+ +This document goes one level deeper than the README's overview. It's aimed at anyone changing code in this repo: where a file belongs, why a layer exists, and how the pieces talk to each other. + +## Layout + +``` +packages/ + app_ui/ # the design system: local package, see below +lib/ + main.dart # composition root: error boundary, ProviderScope, startup fallback + src/ + core/ # cross-cutting concerns, shared by every feature + database/ # DatabaseManager (the app's own SQLite file) + generic CRUD repository + error/ # Result/Failure, the global error handlers + logging/ # AppLogger contract and its implementation + navigation/ # nav bar, drawer, tab shell + providers/ # core_providers.dart: services more than one feature reads + routes/ # go_router config, typed navigation helpers + screens/ # settings, about, splash, not-found, startup failure + services/ # SqlExecutionService, DefaultDatabaseService, SharedPreferencesService, LocalStateService + sql/ # the shared SQL statement splitter + features/ + / + domain/ + entities/ # plain classes describing the feature's data + repositories/ # abstract contracts + usecases/ # only where real logic composes more than one repository call + data/ + mappers/ # fromMap/toMap between an entity and its persistence map + datasources/ # what a repository implementation talks to + repositories/ # the repository implementation + providers/ # _data_providers.dart: datasource/repository DI + presentation/ + view_models/ # a Notifier/AsyncNotifier plus its immutable state + screens/, widgets/ # UI + _providers.dart # view model providers, feature-local use case providers + shared/ # code shared across features, but coupled to this app: widgets, utils +``` + +Features: `database` (the user's saved databases), `database_visualizer` (the schema diagram), `sql_editor` (the code editor and console), `sql_suggestions` (basic/advanced snippets), `workspace_layout_settings`, `app_version`. + +## Layering (Clean Architecture + MVVM) + +- **`domain`**: plain Dart. Entities, abstract repository interfaces, and use-case classes for the handful of operations with real branching logic (`DeleteDatabaseUseCase` closes the connection, drops the file, and removes the record; the advanced-suggestion reorder/reset/save-all use cases compose more than one repository call). Everything else is a repository method a view model calls directly: a class that only forwards one call to one repository is not a use case. +- **`data`**: implements the `domain` repository interfaces against a concrete data source (a `DatabaseRepository` bound to a table, `SharedPreferencesService`, or `SqlExecutionService`). Mappers convert between a persistence map and a domain entity. +- **`presentation`**: screens and widgets, plus view models: Riverpod `Notifier`/`AsyncNotifier` classes exposed through providers. A screen watches a provider; it reads a repository directly only for a one-off call triggered by a user action, never a use case that doesn't exist. + +A feature never imports another feature's `presentation/`. Something more than one feature needs to share belongs in `core/` (a service, a cross-feature provider) or `shared/` (a widget, a util). + +## Design system (`packages/app_ui`) + +The design tokens (`AppColors`, `AppSpacing`, `AppRadii`, `AppShadows`, `AppDurations`, `AppTheme`) and a set of presentation-agnostic widgets (buttons, cards, inputs, dialogs, loading/empty/error states) live in `packages/app_ui`, a local package the app depends on via a `path:` entry in `pubspec.yaml`. It has its own `pubspec.yaml`, `analysis_options.yaml`, tests, and CI job, and is imported as `package:app_ui/app_ui.dart` (the barrel), never by a deep path. + +The boundary is coupling, not visual complexity: a widget belongs in `app_ui` only if it has no dependency on this app's `AppLocalizations`, Riverpod providers, routing, or any other app-specific type. A widget that needs one of those (reads a localized string, calls `Navigator`, watches a provider) stays in `lib/src/shared/widgets/`, even if it looks generic. `CancelButtonWidget` is the clearest example: visually a thin wrapper around `app_ui`'s `ButtonWidget`, but it reads `AppLocalizations.of(context).cancel` and calls `Navigator.pop`, so it's app code. Where a shared component needs both (a base widget plus an app-specific default), the `app_ui` side takes the value as a parameter (`ConfirmationDialogWidget.cancelButton`, `ErrorDialogWidget.dismissLabel`) instead of reaching for `AppLocalizations` itself, and the app supplies it at the call site. + +## State management + +[Riverpod](https://riverpod.dev/) end to end, written by hand (no code generation): `Provider` for stateless dependencies, `NotifierProvider` for anything with behavior. Providers are grouped by role, not one file per provider: `_providers.dart` for view models, `data/providers/_data_providers.dart` for datasources and repositories, `core/providers/core_providers.dart` for anything more than one feature reads (the logger, the shared `DatabaseManager`, `SqlExecutionService`, `DefaultDatabaseService`). + +## Navigation + +[go_router](https://pub.dev/packages/go_router), with a bottom nav bar for the three main tabs (Home, Databases, Settings) and pushed routes for everything else (the database visualizer, the settings sub-screens). `AppRoutes` (`lib/src/core/routes/`) wraps every navigation call in a typed method instead of a raw path string. + +## Persistence + +Two independent things live on disk, both through [sqflite](https://pub.dev/packages/sqflite): + +- **The app's own database** (`DatabaseManager`, `lib/src/core/database/`): one small SQLite file holding the app's own bookkeeping tables: the list of user-created databases and the advanced SQL suggestions. `DatabaseRepository` is a generic CRUD layer bound to a table name, shared by every feature that persists here. +- **The sample and user databases themselves**: each is its own SQLite file, opened on demand by `SqlExecutionService` (`lib/src/core/services/`), which caches one connection per open database and closes them all on dispose. `DefaultDatabaseService` seeds the 14 bundled sample databases from `assets/sql/schemas/` and `assets/sql/seeds/`, versioned **per database**, not globally: bumping one sample's version re-seeds only that database, leaving the user's edits to the other thirteen untouched. An install upgrading from the old single global version key migrates it into the per-database keys without re-seeding anything. + +User preferences that don't need querying (theme, locale, workspace layout, suggestion toggles) go through `shared_preferences` behind `SharedPreferencesService`. + +Both the schema/seed asset loader and the console's own statement runner share one SQL statement splitter (`core/sql/sql_statement_splitter.dart`), so a semicolon inside a string literal, a comment, or a trigger's `BEGIN...END` body is handled once, not reimplemented per caller. + +## Error handling + +`main.dart` installs `FlutterError.onError` and `PlatformDispatcher.onError` before anything else runs, both routing into `AppLogger`, and swaps in a neutral `ErrorWidget.builder` for release builds. `startApp()` guards `SharedPreferencesService.create()` and the rest of startup: a failure there falls back to `StartupFailureApp`, a minimal app (no provider container, no localizations beyond what it needs) offering a retry and, for state that fails no matter how many times it's retried, a confirmed wipe of local data through `LocalStateService`. + +Within the app, `Result` (`lib/src/core/error/result.dart`) is a sealed type: a repository or service returns `SuccessResult`/`FailureResult`, and a caller matches it with `when`/`switch`, never an `is` chain. A `Failure` carries a localization key plus interpolation args; `LocalizationExtension.key()` resolves it to a real message, and a test asserts every `AppLocalizationsKey` has an entry there, so a key added without a message falls back loudly in tests instead of silently in production. `handleError()` (`shared/utils/`) is the one place a view result becomes an error dialog. + +Logging goes through `AppLogger` (`core/logging/`), never a constructed `Logger` directly. `SqlExecutionService` never logs the SQL text it runs or the database name it runs against, only the statement kind and the row count, and attaches the raw exception (which embeds the failing SQL) only in debug builds. + +## Testing + +Unit and widget tests live under `test/`, mirroring `lib/`'s structure; `packages/app_ui/test/` mirrors `packages/app_ui/lib/` the same way, run and covered independently. `integration_test/` covers end-to-end flows against real on-device SQLite and `SharedPreferences` storage: first-run seeding, creating a database and querying it, editing a default database surviving a simulated restart, a deliberate reset, deleting a database, switching locale, and settings (theme, workspace layout, favoriting a database) surviving a restart. `test/core/providers/provider_graph_test.dart` builds the full production provider container and reads every provider, catching a wiring mistake that would otherwise only surface on a device. See the README for current test counts and the coverage threshold. diff --git a/docs/architecture.pt-BR.md b/docs/architecture.pt-BR.md new file mode 100644 index 00000000..eee749b3 --- /dev/null +++ b/docs/architecture.pt-BR.md @@ -0,0 +1,90 @@ +# Arquitetura + +

+English · Español · Português (BR) +

+ +Este documento vai um nível além da visão geral do README. É voltado a quem for mexer no código deste repositório: onde cada arquivo entra, por que cada camada existe e como as peças conversam entre si. + +## Estrutura + +``` +packages/ + app_ui/ # o design system: pacote local, veja abaixo +lib/ + main.dart # raiz de composição: limite de erro, ProviderScope, fallback de inicialização + src/ + core/ # aspectos transversais, compartilhados por todas as features + database/ # DatabaseManager (o arquivo SQLite próprio do app) + repositório CRUD genérico + error/ # Result/Failure, os handlers globais de erro + logging/ # o contrato AppLogger e sua implementação + navigation/ # barra de navegação, drawer, shell de abas + providers/ # core_providers.dart: serviços lidos por mais de uma feature + routes/ # configuração do go_router, helpers de navegação tipados + screens/ # ajustes, sobre, splash, não encontrado, falha de inicialização + services/ # SqlExecutionService, DefaultDatabaseService, SharedPreferencesService, LocalStateService + sql/ # o divisor de instruções SQL compartilhado + features/ + / + domain/ + entities/ # classes simples descrevendo os dados da feature + repositories/ # contratos abstratos + usecases/ # só onde a lógica real compõe mais de uma chamada de repositório + data/ + mappers/ # fromMap/toMap entre uma entidade e seu mapa de persistência + datasources/ # com o que uma implementação de repositório conversa + repositories/ # a implementação do repositório + providers/ # _data_providers.dart: DI de datasource/repositório + presentation/ + view_models/ # um Notifier/AsyncNotifier mais seu estado imutável + screens/, widgets/ # UI + _providers.dart # providers de view model, use cases locais à feature + shared/ # código compartilhado entre features, mas acoplado a este app: widgets, utilitários +``` + +Features: `database` (os bancos salvos pelo usuário), `database_visualizer` (o diagrama de esquema), `sql_editor` (o editor de código e o console), `sql_suggestions` (snippets básicos/avançados), `workspace_layout_settings`, `app_version`. + +## Camadas (Clean Architecture + MVVM) + +- **`domain`**: Dart puro. Entidades, contratos de repositório abstratos, e classes de caso de uso para o punhado de operações com lógica ramificada real (`DeleteDatabaseUseCase` fecha a conexão, apaga o arquivo e remove o registro; os casos de uso de reordenar/resetar/salvar-tudo das sugestões avançadas compõem mais de uma chamada de repositório). Todo o resto é um método de repositório que um view model chama diretamente: uma classe que só repassa uma chamada para um repositório não é um caso de uso. +- **`data`**: implementa os contratos de `domain` contra uma fonte de dados concreta (um `DatabaseRepository` ligado a uma tabela, `SharedPreferencesService`, ou `SqlExecutionService`). Mappers convertem entre um mapa de persistência e uma entidade de domínio. +- **`presentation`**: telas e widgets, mais view models: classes `Notifier`/`AsyncNotifier` do Riverpod expostas por providers. Uma tela observa um provider; só lê um repositório diretamente para uma chamada pontual disparada por uma ação do usuário, nunca um caso de uso que não existe. + +Uma feature nunca importa o `presentation/` de outra feature. O que mais de uma feature precisa compartilhar vai em `core/` (um serviço, um provider transversal) ou em `shared/` (um widget, um utilitário). + +## Design system (`packages/app_ui`) + +Os tokens de design (`AppColors`, `AppSpacing`, `AppRadii`, `AppShadows`, `AppDurations`, `AppTheme`) e um conjunto de widgets agnósticos de apresentação (botões, cards, inputs, diálogos, estados de carregamento/vazio/erro) vivem em `packages/app_ui`, um pacote local do qual o app depende via uma entrada `path:` no `pubspec.yaml`. Ele tem seu próprio `pubspec.yaml`, `analysis_options.yaml`, testes e job de CI, e é importado como `package:app_ui/app_ui.dart` (o barrel), nunca por um caminho profundo. + +O limite é o acoplamento, não a complexidade visual: um widget só entra em `app_ui` se não tiver dependência do `AppLocalizations` deste app, dos providers do Riverpod, do roteamento, ou de qualquer outro tipo específico do app. Um widget que precisa de algo disso (lê uma string localizada, chama o `Navigator`, observa um provider) fica em `lib/src/shared/widgets/`, mesmo que pareça genérico. `CancelButtonWidget` é o exemplo mais claro: visualmente é um wrapper fino em volta do `ButtonWidget` do `app_ui`, mas lê `AppLocalizations.of(context).cancel` e chama `Navigator.pop`, então é código do app. Onde um componente compartilhado precisa das duas coisas (um widget base mais um padrão específico do app), o lado do `app_ui` recebe o valor como parâmetro (`ConfirmationDialogWidget.cancelButton`, `ErrorDialogWidget.dismissLabel`) em vez de recorrer ao `AppLocalizations` diretamente, e o app fornece isso no local da chamada. + +## Gerenciamento de estado + +[Riverpod](https://riverpod.dev/) de ponta a ponta, escrito à mão (sem geração de código): `Provider` para dependências sem estado, `NotifierProvider` para qualquer coisa com comportamento. Os providers são agrupados por papel, não um arquivo por provider: `_providers.dart` para view models, `data/providers/_data_providers.dart` para datasources e repositórios, `core/providers/core_providers.dart` para o que é lido por mais de uma feature (o logger, o `DatabaseManager` compartilhado, `SqlExecutionService`, `DefaultDatabaseService`). + +## Navegação + +[go_router](https://pub.dev/packages/go_router), com uma barra de navegação inferior para as três abas principais (Início, Bancos de dados, Ajustes) e rotas empilhadas para o resto (o visualizador de banco de dados, as subtelas de ajustes). `AppRoutes` (`lib/src/core/routes/`) embrulha cada chamada de navegação num método tipado em vez de uma string de rota solta. + +## Persistência + +Duas coisas independentes vivem em disco, ambas via [sqflite](https://pub.dev/packages/sqflite): + +- **O banco de dados próprio do app** (`DatabaseManager`, `lib/src/core/database/`): um arquivo SQLite pequeno com as tabelas administrativas do app: a lista de bancos criados pelo usuário e as sugestões SQL avançadas. `DatabaseRepository` é uma camada CRUD genérica ligada a um nome de tabela, compartilhada por toda feature que persiste aqui. +- **Os bancos de exemplo e os do usuário**: cada um é seu próprio arquivo SQLite, aberto sob demanda pelo `SqlExecutionService` (`lib/src/core/services/`), que mantém em cache uma conexão por banco aberto e fecha todas ao ser descartado. `DefaultDatabaseService` semeia os 14 bancos de exemplo empacotados a partir de `assets/sql/schemas/` e `assets/sql/seeds/`, versionados **por banco**, não globalmente: subir a versão de um exemplo re-semeia só aquele banco, deixando intactas as edições do usuário nos outros treze. Uma instalação atualizando da antiga chave de versão global única migra para as chaves por banco sem re-semear nada. + +Preferências do usuário que não precisam de consulta (tema, idioma, disposição do workspace, ativação de sugestões) passam por `shared_preferences` atrás de `SharedPreferencesService`. + +Tanto o carregador de assets de esquema/seed quanto o executor de instruções do console compartilham um único divisor de instruções SQL (`core/sql/sql_statement_splitter.dart`), então um ponto e vírgula dentro de um literal de texto, um comentário, ou o corpo `BEGIN...END` de um trigger é tratado uma vez só, não reimplementado a cada chamador. + +## Tratamento de erros + +`main.dart` instala `FlutterError.onError` e `PlatformDispatcher.onError` antes de qualquer outra coisa rodar, ambos roteando para `AppLogger`, e troca o `ErrorWidget.builder` por um neutro em builds de release. `startApp()` protege `SharedPreferencesService.create()` e o resto da inicialização: uma falha ali recorre ao `StartupFailureApp`, um app mínimo (sem container de providers, sem localizações além do necessário) que oferece tentar de novo e, para um estado que falha não importa quantas vezes seja tentado, uma limpeza confirmada dos dados locais via `LocalStateService`. + +Dentro do app, `Result` (`lib/src/core/error/result.dart`) é um tipo selado: um repositório ou serviço retorna `SuccessResult`/`FailureResult`, e quem chama compara com `when`/`switch`, nunca uma cadeia de `is`. Um `Failure` carrega uma chave de localização mais argumentos de interpolação; `LocalizationExtension.key()` resolve isso para uma mensagem real, e um teste garante que toda `AppLocalizationsKey` tenha uma entrada ali, então uma chave adicionada sem mensagem falha ruidosamente nos testes em vez de silenciosamente em produção. `handleError()` (`shared/utils/`) é o único lugar onde o resultado de uma tela vira um diálogo de erro. + +O log passa por `AppLogger` (`core/logging/`), nunca um `Logger` construído diretamente. `SqlExecutionService` nunca registra o texto SQL executado nem o nome do banco contra o qual roda, só o tipo de instrução e a contagem de linhas, e anexa a exceção crua (que embute o SQL que falhou) somente em builds de debug. + +## Testes + +Testes unitários e de widget vivem em `test/`, espelhando a estrutura de `lib/`; `packages/app_ui/test/` espelha `packages/app_ui/lib/` da mesma forma, rodando e sendo coberto de forma independente. `integration_test/` cobre fluxos de ponta a ponta contra o armazenamento real de SQLite e `SharedPreferences` no dispositivo: semeadura na primeira execução, criar um banco e consultá-lo, editar um banco padrão que sobrevive a um reinício simulado, um reset deliberado, apagar um banco, trocar de idioma, e ajustes (tema, disposição do workspace, favoritar um banco) que sobrevivem a um reinício. `test/core/providers/provider_graph_test.dart` monta o container de providers completo de produção e lê cada provider, pegando um erro de conexão que de outra forma só apareceria num dispositivo. Veja o README para as contagens de teste atuais e o limiar de cobertura. diff --git a/docs/code_of_conduct.es.md b/docs/code_of_conduct.es.md new file mode 100644 index 00000000..6f2f2410 --- /dev/null +++ b/docs/code_of_conduct.es.md @@ -0,0 +1,45 @@ +# Código de Conducta + +

+English · Español · Português (BR) +

+ +Adaptado del [Contributor Covenant](https://www.contributor-covenant.org/), versión 2.1. + +## Nuestro Compromiso + +Como miembros, colaboradores y mantenedores, nos comprometemos a hacer de la participación en este proyecto una experiencia libre de acoso para todos, sin importar edad, tamaño corporal, discapacidad visible o invisible, etnia, características sexuales, identidad y expresión de género, nivel de experiencia, educación, nivel socioeconómico, nacionalidad, apariencia personal, raza, religión, u orientación e identidad sexual. + +## Nuestros Estándares + +Ejemplos de comportamiento que contribuyen a un ambiente positivo: + +- Mostrar empatía y amabilidad hacia otras personas. +- Ser respetuoso con opiniones, puntos de vista y experiencias diferentes. +- Dar y aceptar con elegancia comentarios constructivos. +- Aceptar la responsabilidad, disculparse por los errores y aprender de ellos. +- Enfocarse en lo que es mejor para la comunidad y el proyecto en su conjunto. + +Ejemplos de comportamiento inaceptable: + +- Lenguaje o imágenes sexualizadas, y atención o insinuaciones sexuales no deseadas. +- Trolling, comentarios insultantes o despectivos, y ataques personales o políticos. +- Acoso público o privado. +- Publicar información privada de terceros sin permiso explícito. +- Otra conducta que razonablemente pueda considerarse inapropiada en un entorno profesional. + +## Responsabilidades de Cumplimiento + +El mantenedor es responsable de aclarar y hacer cumplir estos estándares, y tomará las medidas correctivas apropiadas y justas en respuesta a cualquier comportamiento que considere inapropiado, amenazante, ofensivo o dañino. El mantenedor tiene el derecho de eliminar, editar o rechazar comentarios, commits, código, issues y otras contribuciones que no se alineen con este Código de Conducta. + +## Alcance + +Este Código de Conducta aplica dentro de todos los espacios del proyecto (issues, pull requests, discusiones) y cuando una persona representa oficialmente al proyecto en otro lugar. + +## Cumplimiento + +Los casos de comportamiento abusivo, acosador o de cualquier otra forma inaceptable pueden reportarse al mantenedor en [dariomatias.dev@gmail.com](mailto:dariomatias.dev@gmail.com). Todas las quejas serán revisadas e investigadas de forma pronta y justa. + +## Atribución + +Este Código de Conducta está adaptado del [Contributor Covenant](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html), versión 2.1. diff --git a/docs/code_of_conduct.md b/docs/code_of_conduct.md new file mode 100644 index 00000000..5b069cd2 --- /dev/null +++ b/docs/code_of_conduct.md @@ -0,0 +1,45 @@ +# Code of Conduct + +

+English · Español · Português (BR) +

+ +Adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 2.1. + +## Our Pledge + +We as members, contributors, and maintainers pledge to make participation in this project a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to a positive environment: + +- Showing empathy and kindness toward other people. +- Being respectful of differing opinions, viewpoints, and experiences. +- Giving and gracefully accepting constructive feedback. +- Accepting responsibility, apologizing for mistakes, and learning from them. +- Focusing on what is best for the community and the project as a whole. + +Examples of unacceptable behavior: + +- Sexualized language or imagery, and unwelcome sexual attention or advances. +- Trolling, insulting or derogatory comments, and personal or political attacks. +- Public or private harassment. +- Publishing others' private information without explicit permission. +- Other conduct that could reasonably be considered inappropriate in a professional setting. + +## Enforcement Responsibilities + +The maintainer is responsible for clarifying and enforcing these standards, and will take appropriate and fair corrective action in response to any behavior deemed inappropriate, threatening, offensive, or harmful. The maintainer has the right to remove, edit, or reject comments, commits, code, issues, and other contributions not aligned with this Code of Conduct. + +## Scope + +This Code of Conduct applies within all project spaces (issues, pull requests, discussions) and when an individual is officially representing the project elsewhere. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the maintainer at [dariomatias.dev@gmail.com](mailto:dariomatias.dev@gmail.com). All complaints will be reviewed and investigated promptly and fairly. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html), version 2.1. diff --git a/docs/code_of_conduct.pt-BR.md b/docs/code_of_conduct.pt-BR.md new file mode 100644 index 00000000..ff14f675 --- /dev/null +++ b/docs/code_of_conduct.pt-BR.md @@ -0,0 +1,45 @@ +# Código de Conduta + +

+English · Español · Português (BR) +

+ +Adaptado do [Contributor Covenant](https://www.contributor-covenant.org/), versão 2.1. + +## Nosso Compromisso + +Como membros, colaboradores e mantenedores, nos comprometemos a tornar a participação neste projeto uma experiência livre de assédio para todos, independentemente de idade, tamanho corporal, deficiência visível ou invisível, etnia, características sexuais, identidade e expressão de gênero, nível de experiência, educação, condição socioeconômica, nacionalidade, aparência pessoal, raça, religião, ou identidade e orientação sexual. + +## Nossos Padrões + +Exemplos de comportamento que contribuem para um ambiente positivo: + +- Demonstrar empatia e gentileza com outras pessoas. +- Respeitar opiniões, pontos de vista e experiências diferentes. +- Dar e aceitar com elegância feedback construtivo. +- Assumir responsabilidade, pedir desculpas por erros e aprender com eles. +- Focar no que é melhor para a comunidade e para o projeto como um todo. + +Exemplos de comportamento inaceitável: + +- Linguagem ou imagens sexualizadas, e atenção ou investidas sexuais indesejadas. +- Trolling, comentários insultuosos ou depreciativos, e ataques pessoais ou políticos. +- Assédio público ou privado. +- Publicar informações privadas de terceiros sem permissão explícita. +- Outra conduta que possa razoavelmente ser considerada inadequada em um ambiente profissional. + +## Responsabilidades de Aplicação + +O mantenedor é responsável por esclarecer e aplicar esses padrões, e tomará as medidas corretivas apropriadas e justas em resposta a qualquer comportamento considerado inadequado, ameaçador, ofensivo ou prejudicial. O mantenedor tem o direito de remover, editar ou rejeitar comentários, commits, código, issues e outras contribuições que não estejam alinhadas com este Código de Conduta. + +## Escopo + +Este Código de Conduta se aplica a todos os espaços do projeto (issues, pull requests, discussões) e quando uma pessoa está representando oficialmente o projeto em outro lugar. + +## Aplicação + +Casos de comportamento abusivo, de assédio ou de qualquer outra forma inaceitável podem ser reportados ao mantenedor em [dariomatias.dev@gmail.com](mailto:dariomatias.dev@gmail.com). Todas as reclamações serão revisadas e investigadas de forma pronta e justa. + +## Atribuição + +Este Código de Conduta é adaptado do [Contributor Covenant](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html), versão 2.1. diff --git a/docs/contributing.es.md b/docs/contributing.es.md new file mode 100644 index 00000000..e5a567c7 --- /dev/null +++ b/docs/contributing.es.md @@ -0,0 +1,125 @@ +# Contribuir + +

+English · Español · Português (BR) +

+ +Gracias por considerar contribuir. Este documento cubre la configuración, las convenciones, y qué necesita un pull request antes de estar listo para revisión. Para cómo está organizado el código, ver [`architecture.md`](architecture.es.md). + +## Configuración + +El proyecto fija su versión del SDK de Flutter vía [FVM](https://fvm.app/), así que todos los comandos de abajo usan `fvm flutter`/`fvm dart` en vez de una instalación directa de `flutter`. + +```sh +git clone https://github.com/dariomatias-dev/sql_studio_app.git +cd sql_studio_app +fvm install +fvm flutter pub get +git config core.hooksPath .githooks +``` + +Esa última línea apunta git hacia [`.githooks/`](../.githooks), donde un hook `commit-msg` rechaza una línea de asunto que no siga la convención de abajo, y un hook `pre-push` ejecuta `./scripts/verify.sh` antes de un push con commits, así un cambio roto aparece localmente en vez de como una ejecución de CI en rojo minutos después. `git push --no-verify` lo omite; la protección de rama en `main` es lo que realmente bloquea un cambio roto. Git no comparte hooks a través de un clon, así que es un comando por checkout. + +Las localizaciones no se versionan pregeneradas en cada cambio. Regenéralas después de actualizar o editar cualquier archivo bajo `lib/l10n/*.arb`: + +```sh +fvm flutter gen-l10n +``` + +Ejecuta la app en un dispositivo o emulador conectado con `fvm flutter run`. + +## Antes de abrir un pull request + +- **Abre un issue primero** para discutir el cambio, a menos que sea una corrección pequeña y obvia. +- **Sigue la estructura existente**: feature-first, capas `domain`/`data`/`presentation`, Riverpod para el estado, sin patrones nuevos sin discutirlos antes. Ver [`architecture.md`](architecture.es.md). +- **Un caso de uso se justifica solo cuando compone más de una llamada a repositorio.** En cualquier otro caso, llama al repositorio directamente desde el view model. +- **Respeta el sistema de diseño**: sin colores, espaciados, radios, duraciones o estilos de texto en línea. Usa los tokens de `package:app_ui` (`AppColors`, `AppSpacing`, `AppRadii`, `AppShadows`, `AppDurations`). Ver [`architecture.es.md`](architecture.es.md#design-system-packagesapp_ui) para qué entra en `packages/app_ui` y qué se queda en la app. +- **Sin cadenas de texto fijas para el usuario**: agrega la clave a los tres archivos ARB (`app_en.arb`, `app_es.arb`, `app_pt.arb`) con una `description`, y luego ejecuta `gen-l10n`. [`scripts/check_l10n.sh`](../scripts/check_l10n.sh) hace cumplir la paridad de claves entre ellos, en `verify.sh` y en CI. +- **Agrega tests** para cualquier cosa con lógica: un método de repositorio, un caso de uso, un view model, el comportamiento de un widget. Una corrección de bug debería llevar un test que falle sin la corrección. +- **Ejecuta la verificación completa localmente** antes de hacer push: + + ```sh + ./scripts/verify.sh + ``` + + Corre lo mismo que CI: regenera las localizaciones y falla si eso cambió algo, `check_l10n.sh`, formato, análisis, tests, y el umbral de cobertura, para la app y para `packages/app_ui`, cada uno contra su propio umbral. Usa `fvm` cuando está configurado para el proyecto, y las herramientas sin `fvm` en caso contrario. Agrega `--skip-tests` para una pasada parcial más rápida mientras iteras; nunca es el gate final, ya que borra el sello de la última pasada local en vez de escribirlo. + + Los mismos chequeos a mano: + + ```sh + fvm flutter analyze + fvm dart format --output=none --set-exit-if-changed lib/ test/ + fvm flutter test --coverage + ./scripts/check_coverage.sh coverage/lcov.info 91 + ``` + + A mano, ejecuta los mismos chequeos de nuevo desde dentro de `packages/app_ui` para un cambio que lo toque, contra su propio umbral de cobertura (ver el job `app_ui` en [`ci.yaml`](../.github/workflows/ci.yaml)). + +- **Los mensajes de commit** siguen [Conventional Commits](https://www.conventionalcommits.org/), reforzado por el hook `commit-msg` habilitado durante la configuración: + + ``` + (): + ``` + + El tipo es uno de `build`, `chore`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `style` o `test`. El scope, cuando lo hay, va en minúsculas y con guiones (`sql-editor`, no `sql_editor`): + + | | Scopes | + | --- | --- | + | features | `database`, `database-visualizer`, `sql-editor`, `sql-suggestions`, `workspace-layout`, `app-version` | + | áreas del core | `core`, `navigation`, `routes`, `theme`, `l10n`, `sql-execution`, `default-database`, `shared-preferences` | + | transversales | `shared`, `deps`, `ci`, `scripts`, `docs`, `release` | + + El subject es corto e imperativo, empieza en minúscula, y no lleva punto final. Agrega `!` antes de los dos puntos para un cambio disruptivo. + + El hook también mantiene el mensaje en la forma que las herramientas de git esperan: toda la línea de asunto se mantiene dentro de **72 caracteres**, que es donde `git log --oneline` y GitHub truncan; un cuerpo se separa del asunto por una línea en blanco, y sus líneas se ajustan a **100**. Las URLs y trailers como `Co-Authored-By:` están exentos, ya que ajustarlos rompería su significado. Los commits de merge y revert se dejan sin tocar. + +## Qué verifica CI + +Cada push y pull request ejecuta [`.github/workflows/ci.yaml`](../.github/workflows/ci.yaml), en cinco jobs: + +| Job | Qué hace | +| --- | --- | +| `quality` | Instala dependencias, regenera las localizaciones, y luego **falla si esa regeneración produjo un diff**, ya que los archivos generados deben estar versionados y actualizados. Después formato, análisis, tests, y el gate de cobertura, y sube el reporte a Codecov bajo la flag `app`. | +| `app_ui` | Los mismos chequeos de formato, análisis, tests y cobertura, restringidos a `packages/app_ui`, con su propio umbral de cobertura, y subidos bajo la flag `app_ui`. | +| `build_apk` | Corre después de que `quality` pase y compila un APK de release, compilable sin un secreto de firma (ya que `android/app/build.gradle.kts` recurre a la keystore de debug cuando falta `key.properties`), subido como artefacto del workflow, conservado 14 días. | +| `integration` | Corre después de que `quality` pase, inicia un emulador Android fijo (API 35) y ejecuta cada suite de `integration_test/` en él, forzando el cierre de la app entre suites para que cada una empiece en frío. Estas necesitan un dispositivo real: ejercitan el almacenamiento real de SQLite y `SharedPreferences`, incluyendo estado que sobrevive a un reinicio simulado. El job habilita KVM primero y compila un APK de debug antes de iniciar el emulador, ya que una compilación Android en frío por sí sola puede superar el límite de tiempo por suite. | +| `osv-scanner` | Escanea `pubspec.lock` y `packages/app_ui/pubspec.lock` contra la base de datos OSV. Corre de forma independiente a los otros jobs: un aviso recién publicado sin corrección disponible todavía no es motivo para detener el reporte de los tests. | + +Los releases los genera [release-please](https://github.com/googleapis/release-please). Lee los Conventional Commits llegados a `main` y mantiene abierto un pull request con la siguiente versión y la entrada de `CHANGELOG.md` derivada de ellos. Al fusionar ese pull request se escribe la versión en `pubspec.yaml`, se etiqueta el commit, y se publica el release de GitHub. + +[`.github/workflows/release.yml`](../.github/workflows/release.yml) luego ejecuta el mismo gate de calidad y adjunta el APK de release. Lo llama directamente [`release_please.yml`](../.github/workflows/release_please.yml), ya que GitHub no inicia un workflow desde un tag empujado con el token predeterminado, y también responde a un tag `v*.*.*` empujado a mano, creando el release por su cuenta en ese caso. + +### Probar workflows localmente + +[`.actrc`](../.actrc) fija la imagen de runner que usa [act](https://github.com/nektos/act), así `act pull_request` (o `act push`) ejecuta los mismos jobs que CI sin esperar a que se suba un commit. Útil al tocar `.github/workflows/`. + +### Actualizaciones de dependencias + +[Renovate](https://docs.renovatebot.com/) abre un pull request por cada dependencia desactualizada, según el cronograma y el prefijo de commit definidos en [`renovate.json`](../renovate.json). No sustituye leer qué cambió: revisa las notas de la versión enlazadas antes de fusionar, sobre todo para una versión mayor. + +### Reportes de cobertura + +[`scripts/check_coverage.sh`](../scripts/check_coverage.sh) es lo que hace fallar una build, excluyendo `lib/l10n/` antes de medir; [Codecov](https://codecov.io/gh/dariomatias-dev/sql_studio_app) es lo que hace el número legible en un pull request, como dos flags independientes (`app`, `app_ui`), ya que las dos suites corren en jobs separados con umbrales distintos; ver [`codecov.yml`](../codecov.yml). Las subidas se autentican con un secreto de repositorio `CODECOV_TOKEN`; los pull requests de forks no pueden leerlo, así que el paso está deliberadamente configurado con `fail_ci_if_error: false`: una subida fallida es un reporte faltante, nunca una build fallida. + +Para lo mismo localmente, sin cuenta, renderiza el archivo `lcov` a HTML: + +```sh +fvm flutter test --coverage +genhtml coverage/lcov.info -o coverage/html # apt install lcov +xdg-open coverage/html/index.html +``` + +Lo mismo funciona dentro de `packages/app_ui`, contra su propio `coverage/lcov.info`. + +## Trabajar con un agente de IA + +El repositorio lleva su propia configuración de agente, así que un asistente sigue el mismo proceso que un colaborador humano en vez de improvisar uno por prompt: + +- [`CLAUDE.md`](../CLAUDE.md) es el acuerdo de trabajo: dónde va cada código, las convenciones vigentes, el vocabulario de scopes de arriba, y la regla de un-paso-un-commit que sigue el historial de este proyecto. +- [`.claude/settings.json`](../.claude/settings.json) conecta dos hooks: cada archivo Dart escrito se formatea de inmediato, y un hook `Stop` se niega a terminar un turno mientras los cambios de código no hayan pasado `./scripts/verify.sh`. + +Nada de esto reemplaza a CI, que sigue siendo la autoridad. Existe para que la pasada local coincida con lo que dirá CI. Cambiar el acuerdo o los hooks es un cambio normal: actualiza `CLAUDE.md` junto con él. + +## Código de Conducta + +La participación en este proyecto se rige por el [Código de Conducta](code_of_conduct.es.md). diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 00000000..87c4de69 --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,125 @@ +# Contributing + +

+English · Español · Português (BR) +

+ +Thanks for considering a contribution. This document covers setup, conventions, and what a pull request needs before it's ready for review. For how the codebase is organized, see [`architecture.md`](architecture.md). + +## Setup + +The project pins its Flutter SDK version via [FVM](https://fvm.app/), so every command below uses `fvm flutter`/`fvm dart` rather than a bare install. + +```sh +git clone https://github.com/dariomatias-dev/sql_studio_app.git +cd sql_studio_app +fvm install +fvm flutter pub get +git config core.hooksPath .githooks +``` + +That last line points git at [`.githooks/`](../.githooks), where a `commit-msg` hook rejects a subject line that doesn't follow the convention below, and a `pre-push` hook runs `./scripts/verify.sh` before a push carrying commits, so a broken change surfaces locally rather than as a red CI run minutes later. `git push --no-verify` skips it; branch protection on `main` is what actually blocks a broken change. Git does not share hooks through a clone, so it is one command per checkout. + +Localizations aren't committed pre-built for every change. Regenerate them after pulling or editing anything under `lib/l10n/*.arb`: + +```sh +fvm flutter gen-l10n +``` + +Run the app on a connected device or emulator with `fvm flutter run`. + +## Before opening a pull request + +- **Open an issue first** to discuss the change, unless it's a small, obvious fix. +- **Follow the existing structure**: feature-first, `domain`/`data`/`presentation` layers, Riverpod for state, no new patterns introduced without discussion. See [`architecture.md`](architecture.md). +- **A use case earns its place only when it composes more than one repository call.** Otherwise call the repository directly from the view model. +- **Match the design system**: no inline colors, spacing, radii, durations, or text styles. Use the tokens from `package:app_ui` (`AppColors`, `AppSpacing`, `AppRadii`, `AppShadows`, `AppDurations`). See [`architecture.md`](architecture.md#design-system-packagesapp_ui) for what belongs in `packages/app_ui` versus the app. +- **No hardcoded user-facing strings**: add the key to all three ARB files (`app_en.arb`, `app_es.arb`, `app_pt.arb`) with a `description`, then run `gen-l10n`. [`scripts/check_l10n.sh`](../scripts/check_l10n.sh) enforces key parity across them, in `verify.sh` and in CI. +- **Add tests** for anything with logic: a repository method, a use case, a view model, a widget's behavior. A bug fix should carry a test that fails without the fix. +- **Run the full check locally** before pushing: + + ```sh + ./scripts/verify.sh + ``` + + It runs what CI runs: regenerates localizations and fails if that changed anything, `check_l10n.sh`, formatting, analysis, tests, and the coverage threshold, for the app and for `packages/app_ui`, each against its own floor. Uses `fvm` when set up for the project, the bare `flutter`/`dart` otherwise. Add `--skip-tests` for a faster partial pass while iterating; it is never the final gate, since it clears the local pass stamp instead of writing it. + + The same checks by hand: + + ```sh + fvm flutter analyze + fvm dart format --output=none --set-exit-if-changed lib/ test/ + fvm flutter test --coverage + ./scripts/check_coverage.sh coverage/lcov.info 91 + ``` + + By hand, run the same checks again from inside `packages/app_ui` for a change touching it, against its own coverage floor (see [`ci.yaml`](../.github/workflows/ci.yaml)'s `app_ui` job). + +- **Commit messages** follow [Conventional Commits](https://www.conventionalcommits.org/), enforced by the `commit-msg` hook enabled during setup: + + ``` + (): + ``` + + The type is one of `build`, `chore`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `style` or `test`. The scope, when there is one, is lowercase and hyphenated (`sql-editor`, not `sql_editor`): + + | | Scopes | + | --- | --- | + | features | `database`, `database-visualizer`, `sql-editor`, `sql-suggestions`, `workspace-layout`, `app-version` | + | core areas | `core`, `navigation`, `routes`, `theme`, `l10n`, `sql-execution`, `default-database`, `shared-preferences` | + | cross-cutting | `shared`, `deps`, `ci`, `scripts`, `docs`, `release` | + + The subject is short and imperative, starts lowercase, and carries no trailing period. Append `!` before the colon for a breaking change. + + The hook also holds the message to the shape git tooling expects: the whole subject line stays within **72 characters**, which is where `git log --oneline` and GitHub truncate it; a body is separated from the subject by a blank line, and its lines wrap at **100**. URLs and trailers such as `Co-Authored-By:` are exempt, since wrapping those breaks what they mean. Merge and revert commits are left alone. + +## What CI checks + +Every push and pull request runs [`.github/workflows/ci.yaml`](../.github/workflows/ci.yaml), in five jobs: + +| Job | What it does | +| --- | --- | +| `quality` | Installs dependencies, regenerates localizations, then **fails if that regeneration produced a diff**, since generated files must be committed and up to date. Then `check_l10n.sh`, formatting, analysis, tests, and the coverage gate, and uploads the report to Codecov under the `app` flag. | +| `app_ui` | The same formatting, analysis, tests and coverage gate, scoped to `packages/app_ui`, its own coverage floor, and uploaded under the `app_ui` flag. | +| `build_apk` | Runs after `quality` passes and builds a release APK, buildable without a signing secret (since `android/app/build.gradle.kts` falls back to the debug keystore when `key.properties` is absent), uploaded as a workflow artifact kept for 14 days. | +| `integration` | Runs after `quality` passes, boots a pinned Android emulator (API 35) and runs every `integration_test/` suite on it, force-stopping the app between suites so each starts cold. These need a real device: they exercise real SQLite and `SharedPreferences` storage, including state surviving a simulated restart. The job enables KVM first and builds a debug APK before booting the emulator, since a cold Android build on its own can outrun a per-suite timeout. | +| `osv-scanner` | Scans `pubspec.lock` and `packages/app_ui/pubspec.lock` against the OSV database. Runs independently of the other jobs: a newly disclosed advisory with no fix available yet is not a reason to stop the tests from reporting. | + +Releases are cut by [release-please](https://github.com/googleapis/release-please). It reads the Conventional Commits landed on `main` and keeps a pull request open carrying the next version and the `CHANGELOG.md` entry it derived from them. Merging that pull request writes the version into `pubspec.yaml`, tags the commit, and publishes the GitHub release. + +[`.github/workflows/release.yml`](../.github/workflows/release.yml) then runs the same quality gate and attaches the release APK. It is called directly by [`release_please.yml`](../.github/workflows/release_please.yml), since GitHub does not start a workflow from a tag pushed with the default token, and it still answers a `v*.*.*` tag pushed by hand, creating the release itself in that case. + +### Testing workflows locally + +[`.actrc`](../.actrc) pins the runner image [act](https://github.com/nektos/act) uses, so `act pull_request` (or `act push`) runs the same jobs as CI without waiting on a pushed commit. Useful when touching `.github/workflows/`. + +### Dependency updates + +[Renovate](https://docs.renovatebot.com/) opens a pull request for each outdated dependency, on the schedule and with the commit prefix set in [`renovate.json`](../renovate.json). It is not a substitute for reading what changed: check the linked release notes before merging, especially for a major version. + +### Coverage reports + +[`scripts/check_coverage.sh`](../scripts/check_coverage.sh) is what fails a build, excluding `lib/l10n/` before measuring; [Codecov](https://codecov.io/gh/dariomatias-dev/sql_studio_app) is what makes the number readable on a pull request, as two independent flags (`app`, `app_ui`) since the two suites run in separate jobs with different floors; see [`codecov.yml`](../codecov.yml). Uploads authenticate with a `CODECOV_TOKEN` repository secret; pull requests from forks cannot read it, so the step is deliberately set to `fail_ci_if_error: false`: a failed upload is a missing report, never a failed build. + +For the same thing locally, without an account, render the `lcov` file to HTML: + +```sh +fvm flutter test --coverage +genhtml coverage/lcov.info -o coverage/html # apt install lcov +xdg-open coverage/html/index.html +``` + +The same works inside `packages/app_ui`, against its own `coverage/lcov.info`. + +## Working with an AI agent + +The repo carries its own agent configuration, so an assistant follows the same process a contributor does instead of improvising one per prompt: + +- [`CLAUDE.md`](../CLAUDE.md) is the working agreement: where code belongs, the conventions in effect, the scope vocabulary above, and the one-step-one-commit rule this project's history follows. +- [`.claude/settings.json`](../.claude/settings.json) wires two hooks: every Dart file written is formatted immediately, and a `Stop` hook refuses to end a turn while code changes have not passed `./scripts/verify.sh`. + +None of it replaces CI, which stays the authority. It exists so the local pass matches what CI will say. Changing the agreement or the hooks is a normal change: update `CLAUDE.md` along with it. + +## Code of Conduct + +Participation in this project is governed by the [Code of Conduct](code_of_conduct.md). diff --git a/docs/contributing.pt-BR.md b/docs/contributing.pt-BR.md new file mode 100644 index 00000000..4a6ae152 --- /dev/null +++ b/docs/contributing.pt-BR.md @@ -0,0 +1,125 @@ +# Contribuindo + +

+English · Español · Português (BR) +

+ +Obrigado por considerar contribuir. Este documento cobre a configuração, as convenções, e o que um pull request precisa antes de estar pronto para revisão. Para como o código está organizado, veja [`architecture.md`](architecture.pt-BR.md). + +## Configuração + +O projeto fixa a versão do SDK do Flutter via [FVM](https://fvm.app/), então todos os comandos abaixo usam `fvm flutter`/`fvm dart` em vez de uma instalação direta do `flutter`. + +```sh +git clone https://github.com/dariomatias-dev/sql_studio_app.git +cd sql_studio_app +fvm install +fvm flutter pub get +git config core.hooksPath .githooks +``` + +Essa última linha aponta o git para [`.githooks/`](../.githooks), onde um hook `commit-msg` rejeita uma linha de assunto que não siga a convenção abaixo, e um hook `pre-push` roda `./scripts/verify.sh` antes de um push com commits, então uma mudança quebrada aparece localmente em vez de num CI vermelho minutos depois. `git push --no-verify` pula esse hook; a proteção de branch na `main` é o que de fato bloqueia uma mudança quebrada. O git não compartilha hooks entre clones, então é um comando por checkout. + +As localizações não são versionadas pré-geradas a cada mudança. Regenere-as depois de atualizar ou editar qualquer arquivo em `lib/l10n/*.arb`: + +```sh +fvm flutter gen-l10n +``` + +Rode o app em um dispositivo ou emulador conectado com `fvm flutter run`. + +## Antes de abrir um pull request + +- **Abra uma issue primeiro** para discutir a mudança, a menos que seja uma correção pequena e óbvia. +- **Siga a estrutura existente**: feature-first, camadas `domain`/`data`/`presentation`, Riverpod para estado, sem padrões novos sem discussão prévia. Veja [`architecture.md`](architecture.pt-BR.md). +- **Um caso de uso só se justifica quando compõe mais de uma chamada de repositório.** Caso contrário, chame o repositório diretamente do view model. +- **Respeite o design system**: sem cores, espaçamentos, raios, durações ou estilos de texto inline. Use os tokens de `package:app_ui` (`AppColors`, `AppSpacing`, `AppRadii`, `AppShadows`, `AppDurations`). Veja [`architecture.pt-BR.md`](architecture.pt-BR.md#design-system-packagesapp_ui) para o que entra em `packages/app_ui` e o que fica no app. +- **Sem strings fixas voltadas ao usuário**: adicione a chave aos três arquivos ARB (`app_en.arb`, `app_es.arb`, `app_pt.arb`) com uma `description`, depois rode `gen-l10n`. [`scripts/check_l10n.sh`](../scripts/check_l10n.sh) garante a paridade de chaves entre eles, no `verify.sh` e no CI. +- **Adicione testes** para qualquer coisa com lógica: um método de repositório, um caso de uso, um view model, o comportamento de um widget. Uma correção de bug deve vir com um teste que falha sem a correção. +- **Rode a verificação completa localmente** antes de dar push: + + ```sh + ./scripts/verify.sh + ``` + + Roda o mesmo que o CI: regenera as localizações e falha se isso mudou algo, `check_l10n.sh`, formatação, análise, testes, e o limiar de cobertura, para o app e para `packages/app_ui`, cada um contra o próprio limiar. Usa `fvm` quando configurado para o projeto, e as ferramentas soltas caso contrário. Adicione `--skip-tests` para uma passada parcial mais rápida enquanto você itera; nunca é o gate final, já que apaga o carimbo da última passada local em vez de escrevê-lo. + + Os mesmos checks manualmente: + + ```sh + fvm flutter analyze + fvm dart format --output=none --set-exit-if-changed lib/ test/ + fvm flutter test --coverage + ./scripts/check_coverage.sh coverage/lcov.info 91 + ``` + + Manualmente, rode os mesmos checks de novo de dentro de `packages/app_ui` para uma mudança que o toque, contra o próprio limiar de cobertura (veja o job `app_ui` em [`ci.yaml`](../.github/workflows/ci.yaml)). + +- **Mensagens de commit** seguem o [Conventional Commits](https://www.conventionalcommits.org/), reforçado pelo hook `commit-msg` habilitado na configuração: + + ``` + (): + ``` + + O tipo é um de `build`, `chore`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `style` ou `test`. O escopo, quando existe, é minúsculo e com hífen (`sql-editor`, não `sql_editor`): + + | | Escopos | + | --- | --- | + | features | `database`, `database-visualizer`, `sql-editor`, `sql-suggestions`, `workspace-layout`, `app-version` | + | áreas do core | `core`, `navigation`, `routes`, `theme`, `l10n`, `sql-execution`, `default-database`, `shared-preferences` | + | transversais | `shared`, `deps`, `ci`, `scripts`, `docs`, `release` | + + O subject é curto e imperativo, começa em minúscula, e não leva ponto final. Adicione `!` antes dos dois-pontos para uma mudança que quebra compatibilidade. + + O hook também mantém a mensagem no formato que as ferramentas do git esperam: a linha inteira do subject fica dentro de **72 caracteres**, onde `git log --oneline` e o GitHub truncam; um corpo é separado do subject por uma linha em branco, e suas linhas quebram em **100**. URLs e trailers como `Co-Authored-By:` são isentos, já que quebrá-los estragaria o que significam. Commits de merge e revert ficam intocados. + +## O que o CI verifica + +Todo push e pull request roda [`.github/workflows/ci.yaml`](../.github/workflows/ci.yaml), em cinco jobs: + +| Job | O que faz | +| --- | --- | +| `quality` | Instala dependências, regenera as localizações, e então **falha se essa regeneração produziu um diff**, já que arquivos gerados precisam estar versionados e atualizados. Depois formatação, análise, testes, e o gate de cobertura, e sobe o relatório para o Codecov sob a flag `app`. | +| `app_ui` | Os mesmos checks de formatação, análise, testes e cobertura, restritos a `packages/app_ui`, com seu próprio limiar de cobertura, e enviados sob a flag `app_ui`. | +| `build_apk` | Roda depois que `quality` passa e compila um APK de release, compilável sem um segredo de assinatura (já que `android/app/build.gradle.kts` recorre à keystore de debug quando `key.properties` está ausente), enviado como artefato do workflow, mantido por 14 dias. | +| `integration` | Roda depois que `quality` passa, inicia um emulador Android fixo (API 35) e roda cada suíte de `integration_test/` nele, forçando o encerramento do app entre suítes para que cada uma comece a frio. Essas precisam de um dispositivo real: exercitam o armazenamento real de SQLite e `SharedPreferences`, incluindo estado que sobrevive a um reinício simulado. O job habilita o KVM primeiro e compila um APK de debug antes de iniciar o emulador, já que uma build Android a frio sozinha pode estourar o limite de tempo por suíte. | +| `osv-scanner` | Escaneia o `pubspec.lock` e `packages/app_ui/pubspec.lock` contra a base OSV. Roda independente dos outros jobs: um aviso recém-publicado sem correção disponível ainda não é motivo para parar o relato dos testes. | + +Releases são feitas pelo [release-please](https://github.com/googleapis/release-please). Ele lê os Conventional Commits chegados na `main` e mantém aberto um pull request com a próxima versão e a entrada do `CHANGELOG.md` derivada deles. Ao mesclar esse pull request, a versão é escrita em `pubspec.yaml`, o commit é marcado com tag, e a release do GitHub é publicada. + +[`.github/workflows/release.yml`](../.github/workflows/release.yml) então roda o mesmo gate de qualidade e anexa o APK de release. Ele é chamado diretamente pelo [`release_please.yml`](../.github/workflows/release_please.yml), já que o GitHub não inicia um workflow a partir de uma tag empurrada com o token padrão, e também responde a uma tag `v*.*.*` empurrada manualmente, criando a release por conta própria nesse caso. + +### Testando workflows localmente + +[`.actrc`](../.actrc) fixa a imagem de runner que o [act](https://github.com/nektos/act) usa, então `act pull_request` (ou `act push`) roda os mesmos jobs do CI sem esperar um commit ser enviado. Útil ao mexer em `.github/workflows/`. + +### Atualizações de dependências + +O [Renovate](https://docs.renovatebot.com/) abre um pull request para cada dependência desatualizada, no cronograma e com o prefixo de commit definidos em [`renovate.json`](../renovate.json). Não substitui a leitura do que mudou: confira as release notes vinculadas antes de mesclar, especialmente para uma versão major. + +### Relatórios de cobertura + +[`scripts/check_coverage.sh`](../scripts/check_coverage.sh) é o que faz uma build falhar, excluindo `lib/l10n/` antes de medir; o [Codecov](https://codecov.io/gh/dariomatias-dev/sql_studio_app) é o que deixa o número legível num pull request, como duas flags independentes (`app`, `app_ui`), já que as duas suítes rodam em jobs separados com limiares diferentes; veja [`codecov.yml`](../codecov.yml). Os envios se autenticam com um secret de repositório `CODECOV_TOKEN`; pull requests de forks não conseguem lê-lo, então o passo está deliberadamente configurado com `fail_ci_if_error: false`: um envio falho é um relatório faltando, nunca uma build falha. + +Para o mesmo localmente, sem conta, renderize o arquivo `lcov` em HTML: + +```sh +fvm flutter test --coverage +genhtml coverage/lcov.info -o coverage/html # apt install lcov +xdg-open coverage/html/index.html +``` + +O mesmo funciona dentro de `packages/app_ui`, contra o próprio `coverage/lcov.info`. + +## Trabalhando com um agente de IA + +O repositório carrega sua própria configuração de agente, então um assistente segue o mesmo processo que um colaborador humano em vez de improvisar um por prompt: + +- [`CLAUDE.md`](../CLAUDE.md) é o acordo de trabalho: onde cada código entra, as convenções em vigor, o vocabulário de escopos acima, e a regra de um-passo-um-commit que o histórico deste projeto segue. +- [`.claude/settings.json`](../.claude/settings.json) conecta dois hooks: todo arquivo Dart escrito é formatado imediatamente, e um hook `Stop` se recusa a encerrar um turno enquanto as mudanças de código não passaram por `./scripts/verify.sh`. + +Nada disso substitui o CI, que continua sendo a autoridade. Existe para que a passada local combine com o que o CI vai dizer. Mudar o acordo ou os hooks é uma mudança normal: atualize `CLAUDE.md` junto com ela. + +## Código de Conduta + +A participação neste projeto é regida pelo [Código de Conduta](code_of_conduct.pt-BR.md). diff --git a/docs/security.es.md b/docs/security.es.md new file mode 100644 index 00000000..72e1c271 --- /dev/null +++ b/docs/security.es.md @@ -0,0 +1,25 @@ +# Política de Seguridad + +

+English · Español · Português (BR) +

+ +## Versiones Soportadas + +**SQL Studio** se mantiene como código fuente. La única versión soportada es la rama `main` actual: las correcciones de seguridad se aplican ahí y no se retroportan a commits anteriores. + +## Reportar una Vulnerabilidad + +Por favor, **no** abras un issue público de GitHub para reportar una vulnerabilidad de seguridad. + +En su lugar, envía un correo a [dariomatias.dev@gmail.com](mailto:dariomatias.dev@gmail.com) que contenga: + +- Una descripción de la vulnerabilidad y su impacto potencial. +- Pasos para reproducirla (un ejemplo mínimo es especialmente útil). +- El commit desde el que compilaste y la versión de Android en la que probaste. + +Este es un proyecto de código abierto mantenido en solitario, a escala de hobby: no hay un equipo de seguridad dedicado ni un SLA formal, pero cada reporte se toma en serio y se confirma la recepción lo antes posible. Una vez publicada la corrección, se acreditará a quien reportó, salvo que prefiera permanecer anónimo. + +## Alcance + +La app funciona completamente sin conexión: mantiene un conjunto fijo de bases de datos de ejemplo incluidas y las que el usuario cree, guarda todo localmente (SQLite vía `sqflite`, `shared_preferences`), y no hace peticiones de red. El tipo de problema más relevante es, por lo tanto, local: el SQL que la propia app ejecuta (tanto los assets de esquema/semilla incluidos como el divisor de sentencias de la consola, que debe manejar un punto y coma o una palabra clave dentro de un literal de texto o comentario sin interpretar mal la sentencia) y cómo la app almacena datos en el dispositivo. Los problemas relacionados con servidor o cuenta no aplican, ya que no hay ni servidor ni cuenta. diff --git a/docs/security.md b/docs/security.md new file mode 100644 index 00000000..97d44cc4 --- /dev/null +++ b/docs/security.md @@ -0,0 +1,25 @@ +# Security Policy + +

+English · Español · Português (BR) +

+ +## Supported Versions + +**SQL Studio** is maintained as source. The only supported version is the current `main` branch: security fixes are applied there and are not backported to older commits. + +## Reporting a Vulnerability + +Please do **not** open a public GitHub issue to report a security vulnerability. + +Instead, send an email to [dariomatias.dev@gmail.com](mailto:dariomatias.dev@gmail.com) containing: + +- A description of the vulnerability and its potential impact. +- Steps to reproduce it (a minimal example is especially helpful). +- The commit you built from and the Android version you tested on. + +This is a solo-maintained, hobby-scale open-source project: there is no dedicated security team and no formal SLA, but every report is taken seriously and acknowledged as soon as possible. Once a fix is published, the reporter will be credited unless they prefer to remain anonymous. + +## Scope + +The app runs fully offline: it holds a fixed set of bundled sample databases and whatever databases the user creates, stores everything locally (SQLite via `sqflite`, `shared_preferences`), and makes no network requests of its own (external links open in the system browser). The most relevant class of issue is therefore local: the SQL the app itself runs (both the bundled schema/seed assets and the console's statement splitter, which has to handle a semicolon or a keyword inside a string literal or comment without misparsing the statement) and how the app stores data on the device. Server- and account-related issues do not apply, since there is neither a server nor an account. diff --git a/docs/security.pt-BR.md b/docs/security.pt-BR.md new file mode 100644 index 00000000..b84d103c --- /dev/null +++ b/docs/security.pt-BR.md @@ -0,0 +1,25 @@ +# Política de Segurança + +

+English · Español · Português (BR) +

+ +## Versões Suportadas + +O **SQL Studio** é mantido como código-fonte. A única versão suportada é a branch `main` atual: correções de segurança são aplicadas ali e não são retroportadas para commits anteriores. + +## Reportando uma Vulnerabilidade + +Por favor, **não** abra uma issue pública no GitHub para reportar uma vulnerabilidade de segurança. + +Em vez disso, envie um e-mail para [dariomatias.dev@gmail.com](mailto:dariomatias.dev@gmail.com) contendo: + +- Uma descrição da vulnerabilidade e seu impacto potencial. +- Passos para reproduzi-la (um exemplo mínimo ajuda bastante). +- O commit a partir do qual você compilou e a versão do Android em que testou. + +Este é um projeto open source mantido por uma única pessoa, em escala de hobby: não há uma equipe de segurança dedicada nem um SLA formal, mas todo reporte é levado a sério e confirmado o quanto antes. Assim que uma correção for publicada, quem reportou será creditado, a menos que prefira permanecer anônimo. + +## Escopo + +O app funciona totalmente offline: mantém um conjunto fixo de bancos de dados de exemplo empacotados e os que o usuário criar, guarda tudo localmente (SQLite via `sqflite`, `shared_preferences`), e não faz nenhuma requisição de rede. O tipo de problema mais relevante é, portanto, local: o SQL que o próprio app executa (tanto os assets de esquema/seed empacotados quanto o divisor de instruções do console, que precisa lidar com um ponto e vírgula ou palavra-chave dentro de um literal de texto ou comentário sem interpretar mal a instrução) e como o app armazena dados no dispositivo. Problemas relacionados a servidor ou conta não se aplicam, já que não há nem servidor nem conta. diff --git a/integration_test/locale_switch_test.dart b/integration_test/locale_switch_test.dart index daf4cf52..49809d43 100644 --- a/integration_test/locale_switch_test.dart +++ b/integration_test/locale_switch_test.dart @@ -1,9 +1,9 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; import 'package:sql_studio/src/core/providers/app_localization_provider.dart'; -import 'package:sql_studio/src/shared/widgets/states/empty_state_widget.dart'; import 'test_helpers/app_harness.dart'; diff --git a/lib/src/core/navigation/root_navigation.dart b/lib/src/core/navigation/root_navigation.dart index 6566fa6e..869200a1 100644 --- a/lib/src/core/navigation/root_navigation.dart +++ b/lib/src/core/navigation/root_navigation.dart @@ -1,10 +1,9 @@ import 'dart:async'; +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:sql_studio/src/core/app_durations.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; import 'package:sql_studio/src/core/navigation/widgets/root_drawer/drawer_widget.dart'; import 'package:sql_studio/src/core/navigation/widgets/root_nav_bar_widget.dart'; import 'package:sql_studio/src/core/navigation/widgets/root_swipe_wrapper_widget.dart'; diff --git a/lib/src/core/navigation/widgets/root_drawer/create_database_dialog_widget.dart b/lib/src/core/navigation/widgets/root_drawer/create_database_dialog_widget.dart index 0155882b..ebc02889 100644 --- a/lib/src/core/navigation/widgets/root_drawer/create_database_dialog_widget.dart +++ b/lib/src/core/navigation/widgets/root_drawer/create_database_dialog_widget.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; @@ -8,11 +9,7 @@ import 'package:sql_studio/src/features/database/presentation/database_providers import 'package:sql_studio/src/features/sql_editor/presentation/sql_editor_providers.dart'; import 'package:sql_studio/src/shared/utils/app_toast.dart'; import 'package:sql_studio/src/shared/utils/handle_error.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/button_widget.dart'; import 'package:sql_studio/src/shared/widgets/buttons/cancel_button_widget.dart'; -import 'package:sql_studio/src/shared/widgets/dialogs/dialog_widget.dart'; -import 'package:sql_studio/src/shared/widgets/dialogs/error_dialog_widget.dart'; -import 'package:sql_studio/src/shared/widgets/input_widget.dart'; /// Dialog that lets the user create a new database by entering its label /// and name. @@ -75,7 +72,9 @@ class _CreateDatabaseDialogWidgetState await ErrorDialogWidget.show( context, + title: appLocalizations.error, description: appLocalizations.databaseAlreadyExists(name), + dismissLabel: appLocalizations.ok, ); } }, diff --git a/lib/src/core/navigation/widgets/root_drawer/drawer_widget.dart b/lib/src/core/navigation/widgets/root_drawer/drawer_widget.dart index 65e35df0..563833bf 100644 --- a/lib/src/core/navigation/widgets/root_drawer/drawer_widget.dart +++ b/lib/src/core/navigation/widgets/root_drawer/drawer_widget.dart @@ -1,17 +1,11 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_durations.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; import 'package:sql_studio/src/core/navigation/widgets/root_drawer/create_database_dialog_widget.dart'; import 'package:sql_studio/src/core/navigation/widgets/root_drawer/root_drawer_database_group/root_drawer_database_group_widget.dart'; import 'package:sql_studio/src/features/database/presentation/database_providers.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/button_widget.dart'; -import 'package:sql_studio/src/shared/widgets/input_widget.dart'; -import 'package:sql_studio/src/shared/widgets/states/empty_state_widget.dart'; -import 'package:sql_studio/src/shared/widgets/states/loading_state_widget.dart'; /// App-wide navigation drawer listing favorite and other databases, with /// search and database creation entry points. diff --git a/lib/src/core/navigation/widgets/root_drawer/root_drawer_database_group/database_delete_dialog_widget.dart b/lib/src/core/navigation/widgets/root_drawer/root_drawer_database_group/database_delete_dialog_widget.dart index 8b1238a8..42d458a6 100644 --- a/lib/src/core/navigation/widgets/root_drawer/root_drawer_database_group/database_delete_dialog_widget.dart +++ b/lib/src/core/navigation/widgets/root_drawer/root_drawer_database_group/database_delete_dialog_widget.dart @@ -1,9 +1,9 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/button_widget.dart'; -import 'package:sql_studio/src/shared/widgets/dialogs/confirmation_dialog_widget.dart'; +import 'package:sql_studio/src/shared/widgets/buttons/cancel_button_widget.dart'; /// Confirmation dialog shown before permanently deleting a database. class DatabaseDeleteDialogWidget extends StatelessWidget { @@ -33,6 +33,7 @@ class DatabaseDeleteDialogWidget extends StatelessWidget { return ConfirmationDialogWidget( title: appLocalizations.attention, description: appLocalizations.deleteDatabaseConfirmation, + cancelButton: const CancelButtonWidget(), confirmButton: ButtonWidget( onPressed: onDeleteDatabase, text: appLocalizations.delete, diff --git a/lib/src/core/navigation/widgets/root_drawer/root_drawer_database_group/root_drawer_database_card_widget.dart b/lib/src/core/navigation/widgets/root_drawer/root_drawer_database_group/root_drawer_database_card_widget.dart index 0c8fda6a..69e14ff4 100644 --- a/lib/src/core/navigation/widgets/root_drawer/root_drawer_database_group/root_drawer_database_card_widget.dart +++ b/lib/src/core/navigation/widgets/root_drawer/root_drawer_database_group/root_drawer_database_card_widget.dart @@ -1,14 +1,12 @@ import 'dart:async'; +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_radii.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; import 'package:sql_studio/src/core/constants/shared_preferences_keys.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; import 'package:sql_studio/src/core/navigation/widgets/root_drawer/root_drawer_database_group/database_delete_dialog_widget.dart'; import 'package:sql_studio/src/core/providers/core_providers.dart'; import 'package:sql_studio/src/core/providers/navigation_provider.dart'; @@ -39,7 +37,7 @@ class _RootDrawerDatabaseCardWidgetState void _selectDatabase() { ref.read(sqlCommandsViewModelProvider.notifier) - ..activeDatabase = widget.database.label + ..activeDatabase = widget.database.name ..clearResult(); ref.read(navigationViewModelProvider.notifier).index = 0; @@ -97,7 +95,7 @@ class _RootDrawerDatabaseCardWidgetState Widget build(BuildContext context) { final l10n = AppLocalizations.of(context); final commandsState = ref.watch(sqlCommandsViewModelProvider); - final isActive = commandsState.activeDatabase == widget.database.label; + final isActive = commandsState.activeDatabase == widget.database.name; return Padding( padding: const EdgeInsets.only(bottom: 6), diff --git a/lib/src/core/navigation/widgets/root_drawer/root_drawer_database_group/root_drawer_database_group_widget.dart b/lib/src/core/navigation/widgets/root_drawer/root_drawer_database_group/root_drawer_database_group_widget.dart index c3c66083..9af341f0 100644 --- a/lib/src/core/navigation/widgets/root_drawer/root_drawer_database_group/root_drawer_database_group_widget.dart +++ b/lib/src/core/navigation/widgets/root_drawer/root_drawer_database_group/root_drawer_database_group_widget.dart @@ -1,7 +1,6 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; import 'package:sql_studio/src/core/navigation/widgets/root_drawer/root_drawer_database_group/root_drawer_database_card_widget.dart'; import 'package:sql_studio/src/features/database/domain/entities/database_entity.dart'; diff --git a/lib/src/core/navigation/widgets/root_nav_bar_widget.dart b/lib/src/core/navigation/widgets/root_nav_bar_widget.dart index b9b6e5d7..0af6f028 100644 --- a/lib/src/core/navigation/widgets/root_nav_bar_widget.dart +++ b/lib/src/core/navigation/widgets/root_nav_bar_widget.dart @@ -1,12 +1,8 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_durations.dart'; -import 'package:sql_studio/src/core/app_radii.dart'; -import 'package:sql_studio/src/core/app_shadows.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; import 'package:sql_studio/src/core/providers/navigation_provider.dart'; /// Bottom navigation bar switching between the app's main pages. diff --git a/lib/src/core/providers/core_providers.dart b/lib/src/core/providers/core_providers.dart index 965988d9..2d8819c9 100644 --- a/lib/src/core/providers/core_providers.dart +++ b/lib/src/core/providers/core_providers.dart @@ -20,7 +20,7 @@ final databaseManagerProvider = Provider( /// Shared [SharedPreferencesService] instance, resolved during app /// bootstrap in `main()`. /// -/// Must be overridden with a resolved instance before use — see +/// Must be overridden with a resolved instance before use; see /// `main.dart` for the production override and /// `test/test_helpers/shared_preferences_test_helper.dart` for tests. final sharedPreferencesServiceProvider = Provider(( diff --git a/lib/src/core/routes/app_router.dart b/lib/src/core/routes/app_router.dart index d39b1783..0bf327c2 100644 --- a/lib/src/core/routes/app_router.dart +++ b/lib/src/core/routes/app_router.dart @@ -1,7 +1,7 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import 'package:sql_studio/src/core/app_durations.dart'; import 'package:sql_studio/src/core/navigation/root_navigation.dart'; import 'package:sql_studio/src/core/routes/route_names.dart'; import 'package:sql_studio/src/core/routes/route_paths.dart'; diff --git a/lib/src/core/screens/about/about_screen.dart b/lib/src/core/screens/about/about_screen.dart index 3baaefbf..34bf75cc 100644 --- a/lib/src/core/screens/about/about_screen.dart +++ b/lib/src/core/screens/about/about_screen.dart @@ -1,18 +1,15 @@ import 'dart:async'; +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_radii.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; import 'package:sql_studio/src/core/constants/urls.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; import 'package:sql_studio/src/core/screens/about/licenses_screen.dart'; import 'package:sql_studio/src/core/screens/settings/widgets/settings_section/settings_card_widget.dart'; import 'package:sql_studio/src/core/screens/settings/widgets/settings_section/settings_section_widget.dart'; import 'package:sql_studio/src/features/app_version/presentation/app_version_providers.dart'; -import 'package:sql_studio/src/shared/widgets/dialogs/error_dialog_widget.dart'; import 'package:sql_studio/src/shared/widgets/scaffold_widget.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -32,6 +29,7 @@ class AboutScreen extends ConsumerWidget { context, title: l10n.errorOpeningUrl, description: l10n.errorOpeningUrlDescription(url), + dismissLabel: l10n.ok, ); } } diff --git a/lib/src/core/screens/about/license_detail_screen.dart b/lib/src/core/screens/about/license_detail_screen.dart index 7550ae40..34240dae 100644 --- a/lib/src/core/screens/about/license_detail_screen.dart +++ b/lib/src/core/screens/about/license_detail_screen.dart @@ -1,8 +1,7 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; import 'package:sql_studio/src/shared/widgets/scaffold_widget.dart'; /// Shows the full license text of a single package, respecting each diff --git a/lib/src/core/screens/about/licenses_screen.dart b/lib/src/core/screens/about/licenses_screen.dart index e2cbc278..e3419e3f 100644 --- a/lib/src/core/screens/about/licenses_screen.dart +++ b/lib/src/core/screens/about/licenses_screen.dart @@ -1,12 +1,10 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; import 'package:sql_studio/src/core/screens/about/license_detail_screen.dart'; import 'package:sql_studio/src/shared/widgets/scaffold_widget.dart'; -import 'package:sql_studio/src/shared/widgets/states/loading_state_widget.dart'; class _PackageLicense { _PackageLicense(this.name); diff --git a/lib/src/core/screens/not_found/not_found_screen.dart b/lib/src/core/screens/not_found/not_found_screen.dart index 65a9c67d..5c7dbdd2 100644 --- a/lib/src/core/screens/not_found/not_found_screen.dart +++ b/lib/src/core/screens/not_found/not_found_screen.dart @@ -1,10 +1,8 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/button_widget.dart'; /// Screen shown when a navigation route could not be resolved. class NotFoundScreen extends StatelessWidget { diff --git a/lib/src/core/screens/settings/settings_screen.dart b/lib/src/core/screens/settings/settings_screen.dart index daabd4b5..13c1b3ef 100644 --- a/lib/src/core/screens/settings/settings_screen.dart +++ b/lib/src/core/screens/settings/settings_screen.dart @@ -1,12 +1,11 @@ import 'dart:async'; +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; import 'package:sql_studio/src/core/constants/urls.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; import 'package:sql_studio/src/core/providers/app_localization_provider.dart'; import 'package:sql_studio/src/core/providers/app_theme_mode_provider.dart'; import 'package:sql_studio/src/core/routes/app_routes.dart'; @@ -15,7 +14,6 @@ import 'package:sql_studio/src/core/screens/settings/widgets/language_selector_s import 'package:sql_studio/src/core/screens/settings/widgets/settings_section/settings_card_widget.dart'; import 'package:sql_studio/src/core/screens/settings/widgets/settings_section/settings_section_widget.dart'; import 'package:sql_studio/src/core/screens/settings/widgets/theme_selector_sheet/theme_selector_sheet_widget.dart'; -import 'package:sql_studio/src/shared/widgets/dialogs/error_dialog_widget.dart'; import 'package:sql_studio/src/shared/widgets/scaffold_widget.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -63,6 +61,7 @@ class _SettingsScreenState extends ConsumerState context, title: l10n.errorOpeningUrl, description: l10n.errorOpeningUrlDescription(url), + dismissLabel: l10n.ok, ); } } diff --git a/lib/src/core/screens/settings/widgets/app_version_widget.dart b/lib/src/core/screens/settings/widgets/app_version_widget.dart index fc813b56..30c8da6b 100644 --- a/lib/src/core/screens/settings/widgets/app_version_widget.dart +++ b/lib/src/core/screens/settings/widgets/app_version_widget.dart @@ -1,7 +1,7 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; import 'package:sql_studio/src/features/app_version/presentation/app_version_providers.dart'; /// Small caption that displays the current app build version. diff --git a/lib/src/core/screens/settings/widgets/language_selector_sheet/language_selector_sheet_option_widget.dart b/lib/src/core/screens/settings/widgets/language_selector_sheet/language_selector_sheet_option_widget.dart index 1874b868..a4059fff 100644 --- a/lib/src/core/screens/settings/widgets/language_selector_sheet/language_selector_sheet_option_widget.dart +++ b/lib/src/core/screens/settings/widgets/language_selector_sheet/language_selector_sheet_option_widget.dart @@ -1,13 +1,10 @@ import 'dart:async'; +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_durations.dart'; -import 'package:sql_studio/src/core/app_radii.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; import 'package:sql_studio/src/core/providers/app_localization_provider.dart'; import 'package:sql_studio/src/shared/utils/app_toast.dart'; diff --git a/lib/src/core/screens/settings/widgets/language_selector_sheet/language_selector_sheet_widget.dart b/lib/src/core/screens/settings/widgets/language_selector_sheet/language_selector_sheet_widget.dart index a433b04e..d262057b 100644 --- a/lib/src/core/screens/settings/widgets/language_selector_sheet/language_selector_sheet_widget.dart +++ b/lib/src/core/screens/settings/widgets/language_selector_sheet/language_selector_sheet_widget.dart @@ -1,11 +1,8 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_radii.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; -import 'package:sql_studio/src/core/extensions/list_extension.dart'; import 'package:sql_studio/src/core/screens/settings/widgets/language_selector_sheet/language_selector_sheet_option_widget.dart'; /// Display names for each supported language, in the same order as diff --git a/lib/src/core/screens/settings/widgets/settings_section/settings_card_widget.dart b/lib/src/core/screens/settings/widgets/settings_section/settings_card_widget.dart index 194d4055..a152bbe9 100644 --- a/lib/src/core/screens/settings/widgets/settings_section/settings_card_widget.dart +++ b/lib/src/core/screens/settings/widgets/settings_section/settings_card_widget.dart @@ -1,7 +1,6 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; - /// A tappable row used to present a single settings entry, with a /// leading icon, title, and trailing chevron. class SettingsCardWidget extends StatelessWidget { diff --git a/lib/src/core/screens/settings/widgets/settings_section/settings_section_widget.dart b/lib/src/core/screens/settings/widgets/settings_section/settings_section_widget.dart index 8f38ac1e..9cb905ec 100644 --- a/lib/src/core/screens/settings/widgets/settings_section/settings_section_widget.dart +++ b/lib/src/core/screens/settings/widgets/settings_section/settings_section_widget.dart @@ -1,8 +1,6 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; - /// Groups related settings entries under a titled section header, each /// entry rendered directly on the page background and separated from /// the next by a hairline divider. diff --git a/lib/src/core/screens/settings/widgets/theme_selector_sheet/theme_selector_sheet_option_widget.dart b/lib/src/core/screens/settings/widgets/theme_selector_sheet/theme_selector_sheet_option_widget.dart index 89d9c549..947438cc 100644 --- a/lib/src/core/screens/settings/widgets/theme_selector_sheet/theme_selector_sheet_option_widget.dart +++ b/lib/src/core/screens/settings/widgets/theme_selector_sheet/theme_selector_sheet_option_widget.dart @@ -1,13 +1,10 @@ import 'dart:async'; +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_durations.dart'; -import 'package:sql_studio/src/core/app_radii.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; import 'package:sql_studio/src/core/providers/app_theme_mode_provider.dart'; import 'package:sql_studio/src/shared/utils/app_toast.dart'; diff --git a/lib/src/core/screens/settings/widgets/theme_selector_sheet/theme_selector_sheet_widget.dart b/lib/src/core/screens/settings/widgets/theme_selector_sheet/theme_selector_sheet_widget.dart index 719c9342..393e1899 100644 --- a/lib/src/core/screens/settings/widgets/theme_selector_sheet/theme_selector_sheet_widget.dart +++ b/lib/src/core/screens/settings/widgets/theme_selector_sheet/theme_selector_sheet_widget.dart @@ -1,10 +1,8 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_radii.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; import 'package:sql_studio/src/core/screens/settings/widgets/theme_selector_sheet/theme_selector_sheet_option_widget.dart'; /// Bottom sheet that lists the theme modes supported by the app for diff --git a/lib/src/core/screens/splash/splash_screen.dart b/lib/src/core/screens/splash/splash_screen.dart index 8dbfbf51..9aff1ddf 100644 --- a/lib/src/core/screens/splash/splash_screen.dart +++ b/lib/src/core/screens/splash/splash_screen.dart @@ -1,12 +1,11 @@ import 'dart:async'; +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_durations.dart'; import 'package:sql_studio/src/core/constants/shared_preferences_keys.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; import 'package:sql_studio/src/core/providers/core_providers.dart'; import 'package:sql_studio/src/core/routes/app_routes.dart'; import 'package:sql_studio/src/features/app_version/presentation/app_version_providers.dart'; diff --git a/lib/src/core/screens/startup_failure/startup_failure_app.dart b/lib/src/core/screens/startup_failure/startup_failure_app.dart index de847c82..6ca81871 100644 --- a/lib/src/core/screens/startup_failure/startup_failure_app.dart +++ b/lib/src/core/screens/startup_failure/startup_failure_app.dart @@ -1,9 +1,8 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/core/screens/startup_failure/startup_failure_screen.dart'; -import 'package:sql_studio/src/shared/utils/text_scaling.dart'; /// Minimal app shown when startup fails, before any provider container /// exists: theme and localization only, hosting [StartupFailureScreen]. diff --git a/lib/src/core/screens/startup_failure/startup_failure_screen.dart b/lib/src/core/screens/startup_failure/startup_failure_screen.dart index f83b94a9..63d67b95 100644 --- a/lib/src/core/screens/startup_failure/startup_failure_screen.dart +++ b/lib/src/core/screens/startup_failure/startup_failure_screen.dart @@ -1,10 +1,9 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/button_widget.dart'; -import 'package:sql_studio/src/shared/widgets/dialogs/confirmation_dialog_widget.dart'; + +import 'package:sql_studio/src/shared/widgets/buttons/cancel_button_widget.dart'; /// Shown when the app cannot load its local state on startup, offering a /// retry and, for state that will not load however often it is retried, @@ -50,6 +49,7 @@ class _StartupFailureScreenState extends State { context, title: appLocalizations.clearAppData, description: appLocalizations.clearAppDataConfirmation, + cancelButton: const CancelButtonWidget(), confirmButton: Builder( builder: (dialogContext) => ButtonWidget( onPressed: () => Navigator.pop(dialogContext, true), diff --git a/lib/src/core/services/sql_execution_service.dart b/lib/src/core/services/sql_execution_service.dart index d665694b..b3b33b40 100644 --- a/lib/src/core/services/sql_execution_service.dart +++ b/lib/src/core/services/sql_execution_service.dart @@ -1,9 +1,9 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/foundation.dart'; import 'package:path/path.dart'; import 'package:sqflite/sqflite.dart'; import 'package:sql_studio/src/core/enums/app_localizations_key.dart'; import 'package:sql_studio/src/core/error/result.dart'; -import 'package:sql_studio/src/core/extensions/list_extension.dart'; import 'package:sql_studio/src/core/logging/app_logger.dart'; import 'package:sql_studio/src/core/sql/sql_statement_splitter.dart'; import 'package:sql_studio/src/features/database_visualizer/domain/entities/table_info_entity.dart'; diff --git a/lib/src/features/database/presentation/screens/databases_screen.dart b/lib/src/features/database/presentation/screens/databases_screen.dart index 3c840311..1a114fe8 100644 --- a/lib/src/features/database/presentation/screens/databases_screen.dart +++ b/lib/src/features/database/presentation/screens/databases_screen.dart @@ -1,14 +1,12 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; import 'package:sql_studio/src/core/extensions/localization_extension.dart'; import 'package:sql_studio/src/features/database/presentation/database_providers.dart'; import 'package:sql_studio/src/features/database/presentation/widgets/database_card_widget.dart'; -import 'package:sql_studio/src/shared/widgets/input_widget.dart'; import 'package:sql_studio/src/shared/widgets/scaffold_widget.dart'; -import 'package:sql_studio/src/shared/widgets/states/empty_state_widget.dart'; /// Screen that lists the available default databases. class DatabasesScreen extends ConsumerStatefulWidget { diff --git a/lib/src/features/database/presentation/widgets/database_card_widget.dart b/lib/src/features/database/presentation/widgets/database_card_widget.dart index f55ae225..4d24974f 100644 --- a/lib/src/features/database/presentation/widgets/database_card_widget.dart +++ b/lib/src/features/database/presentation/widgets/database_card_widget.dart @@ -1,22 +1,18 @@ import 'dart:async'; +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_radii.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; import 'package:sql_studio/src/core/database/default_database_model.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; import 'package:sql_studio/src/core/extensions/localization_extension.dart'; import 'package:sql_studio/src/core/providers/navigation_provider.dart'; import 'package:sql_studio/src/core/routes/app_routes.dart'; import 'package:sql_studio/src/features/sql_editor/presentation/sql_editor_providers.dart'; import 'package:sql_studio/src/shared/utils/app_toast.dart'; -import 'package:sql_studio/src/shared/widgets/card_widget.dart'; import 'package:sql_studio/src/shared/widgets/popup_menu_button_widget.dart'; -import 'package:sql_studio/src/shared/widgets/popup_menu_section_header_widget.dart'; /// Card that presents a default database and its available actions. class DatabaseCardWidget extends ConsumerStatefulWidget { diff --git a/lib/src/features/database_visualizer/presentation/painters/grid_background_painter.dart b/lib/src/features/database_visualizer/presentation/painters/grid_background_painter.dart index 61b1b786..5e0558eb 100644 --- a/lib/src/features/database_visualizer/presentation/painters/grid_background_painter.dart +++ b/lib/src/features/database_visualizer/presentation/painters/grid_background_painter.dart @@ -1,5 +1,5 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; -import 'package:sql_studio/src/core/app_colors.dart'; /// Paints the faint dot/line grid behind the database visualizer canvas. class GridBackgroundPainter extends CustomPainter { diff --git a/lib/src/features/database_visualizer/presentation/painters/table_relation_painter.dart b/lib/src/features/database_visualizer/presentation/painters/table_relation_painter.dart index cade87ad..d57fef4f 100644 --- a/lib/src/features/database_visualizer/presentation/painters/table_relation_painter.dart +++ b/lib/src/features/database_visualizer/presentation/painters/table_relation_painter.dart @@ -1,5 +1,5 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; -import 'package:sql_studio/src/core/app_colors.dart'; import 'package:sql_studio/src/features/database_visualizer/domain/entities/table_info_entity.dart'; /// Paints the foreign-key relation arrows between visualized tables. diff --git a/lib/src/features/database_visualizer/presentation/screens/database_visualizer_screen.dart b/lib/src/features/database_visualizer/presentation/screens/database_visualizer_screen.dart index dc52ea21..a5481732 100644 --- a/lib/src/features/database_visualizer/presentation/screens/database_visualizer_screen.dart +++ b/lib/src/features/database_visualizer/presentation/screens/database_visualizer_screen.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; @@ -11,8 +12,6 @@ import 'package:sql_studio/src/features/database_visualizer/presentation/widgets import 'package:sql_studio/src/features/sql_editor/presentation/sql_editor_providers.dart'; import 'package:sql_studio/src/shared/utils/handle_error.dart'; import 'package:sql_studio/src/shared/widgets/scaffold_widget.dart'; -import 'package:sql_studio/src/shared/widgets/states/empty_state_widget.dart'; -import 'package:sql_studio/src/shared/widgets/states/loading_state_widget.dart'; /// Screen that renders an interactive diagram of a database's tables and /// their relations. diff --git a/lib/src/features/database_visualizer/presentation/widgets/database_visualizer_canvas_widget.dart b/lib/src/features/database_visualizer/presentation/widgets/database_visualizer_canvas_widget.dart index ebca86ba..cc6481ce 100644 --- a/lib/src/features/database_visualizer/presentation/widgets/database_visualizer_canvas_widget.dart +++ b/lib/src/features/database_visualizer/presentation/widgets/database_visualizer_canvas_widget.dart @@ -1,7 +1,7 @@ import 'dart:math' as math; +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; import 'package:sql_studio/src/features/database_visualizer/domain/entities/table_info_entity.dart'; import 'package:sql_studio/src/features/database_visualizer/presentation/painters/grid_background_painter.dart'; import 'package:sql_studio/src/features/database_visualizer/presentation/painters/table_relation_painter.dart'; diff --git a/lib/src/features/database_visualizer/presentation/widgets/database_visualizer_table_widget.dart b/lib/src/features/database_visualizer/presentation/widgets/database_visualizer_table_widget.dart index 9b07ac1a..7530a2b2 100644 --- a/lib/src/features/database_visualizer/presentation/widgets/database_visualizer_table_widget.dart +++ b/lib/src/features/database_visualizer/presentation/widgets/database_visualizer_table_widget.dart @@ -1,11 +1,7 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_durations.dart'; -import 'package:sql_studio/src/core/app_radii.dart'; -import 'package:sql_studio/src/core/app_shadows.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; import 'package:sql_studio/src/features/database_visualizer/domain/entities/table_info_entity.dart'; /// Card that renders a single table's name and columns in the database diff --git a/lib/src/features/database_visualizer/presentation/widgets/zoom_controls_widget.dart b/lib/src/features/database_visualizer/presentation/widgets/zoom_controls_widget.dart index 7034787e..35ba4f3b 100644 --- a/lib/src/features/database_visualizer/presentation/widgets/zoom_controls_widget.dart +++ b/lib/src/features/database_visualizer/presentation/widgets/zoom_controls_widget.dart @@ -1,10 +1,7 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_radii.dart'; -import 'package:sql_studio/src/core/app_shadows.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; /// Floating zoom in/out/reset controls for the database visualizer canvas. class ZoomControlsWidget extends StatelessWidget { diff --git a/lib/src/features/sql_suggestions/presentation/screens/sql_suggestion_settings_screen.dart b/lib/src/features/sql_suggestions/presentation/screens/sql_suggestion_settings_screen.dart index 535f147a..92288c0d 100644 --- a/lib/src/features/sql_suggestions/presentation/screens/sql_suggestion_settings_screen.dart +++ b/lib/src/features/sql_suggestions/presentation/screens/sql_suggestion_settings_screen.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -13,8 +14,6 @@ import 'package:sql_studio/src/features/sql_suggestions/presentation/widgets/sql import 'package:sql_studio/src/features/sql_suggestions/presentation/widgets/sql_suggestion_settings_title_option_widget.dart'; import 'package:sql_studio/src/shared/utils/app_toast.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/button_widget.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/loading_button_widget.dart'; import 'package:sql_studio/src/shared/widgets/scaffold_widget.dart'; /// Screen where the user enables or disables the available SQL suggestion diff --git a/lib/src/features/sql_suggestions/presentation/widgets/dialogs/create_sql_basic_suggestion_dialog_widget.dart b/lib/src/features/sql_suggestions/presentation/widgets/dialogs/create_sql_basic_suggestion_dialog_widget.dart index 55f65a5e..31201bfa 100644 --- a/lib/src/features/sql_suggestions/presentation/widgets/dialogs/create_sql_basic_suggestion_dialog_widget.dart +++ b/lib/src/features/sql_suggestions/presentation/widgets/dialogs/create_sql_basic_suggestion_dialog_widget.dart @@ -1,3 +1,4 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -6,7 +7,7 @@ import 'package:sql_studio/l10n/app_localizations.dart'; import 'package:sql_studio/src/features/sql_suggestions/presentation/sql_suggestions_providers.dart'; import 'package:sql_studio/src/shared/utils/handle_error.dart'; -import 'package:sql_studio/src/shared/widgets/dialogs/input_dialog_widget.dart'; +import 'package:sql_studio/src/shared/widgets/buttons/cancel_button_widget.dart'; /// Dialog for creating a new basic SQL suggestion. class CreateSqlBasicSuggestionDialogWidget extends ConsumerStatefulWidget { @@ -49,6 +50,7 @@ class _CreateSqlBasicSuggestionDialogWidgetState title: appLocalizations.createSuggestion, controller: _controller, label: appLocalizations.suggestionName, + cancelButton: const CancelButtonWidget(), submitText: appLocalizations.create, validator: (value) { if (value == null || value.trim().isEmpty) { diff --git a/lib/src/features/sql_suggestions/presentation/widgets/dialogs/delete_sql_advanced_suggestion_dialog_widget.dart b/lib/src/features/sql_suggestions/presentation/widgets/dialogs/delete_sql_advanced_suggestion_dialog_widget.dart index 3965e6cd..1cf4a16d 100644 --- a/lib/src/features/sql_suggestions/presentation/widgets/dialogs/delete_sql_advanced_suggestion_dialog_widget.dart +++ b/lib/src/features/sql_suggestions/presentation/widgets/dialogs/delete_sql_advanced_suggestion_dialog_widget.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; @@ -7,9 +8,7 @@ import 'package:sql_studio/l10n/app_localizations.dart'; import 'package:sql_studio/src/features/sql_suggestions/presentation/sql_suggestions_providers.dart'; import 'package:sql_studio/src/shared/utils/app_toast.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/button_widget.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/loading_button_widget.dart'; -import 'package:sql_studio/src/shared/widgets/dialogs/confirmation_dialog_widget.dart'; +import 'package:sql_studio/src/shared/widgets/buttons/cancel_button_widget.dart'; /// Confirmation dialog for deleting an existing advanced SQL suggestion. class DeleteSqlAdvancedSuggestionDialogWidget extends ConsumerWidget { @@ -50,6 +49,7 @@ class DeleteSqlAdvancedSuggestionDialogWidget extends ConsumerWidget { return ConfirmationDialogWidget( title: appLocalizations.removeSuggestion, description: appLocalizations.deleteSuggestionConfirmation(label), + cancelButton: const CancelButtonWidget(), confirmButton: LoadingButtonWidget( onPressed: () async { final viewModel = ref.read( diff --git a/lib/src/features/sql_suggestions/presentation/widgets/dialogs/remove_sql_basic_suggestion_dialog_widget.dart b/lib/src/features/sql_suggestions/presentation/widgets/dialogs/remove_sql_basic_suggestion_dialog_widget.dart index 7fdf312b..6f5906e1 100644 --- a/lib/src/features/sql_suggestions/presentation/widgets/dialogs/remove_sql_basic_suggestion_dialog_widget.dart +++ b/lib/src/features/sql_suggestions/presentation/widgets/dialogs/remove_sql_basic_suggestion_dialog_widget.dart @@ -1,3 +1,4 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; @@ -6,9 +7,7 @@ import 'package:sql_studio/l10n/app_localizations.dart'; import 'package:sql_studio/src/features/sql_suggestions/presentation/sql_suggestions_providers.dart'; import 'package:sql_studio/src/shared/utils/handle_error.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/button_widget.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/loading_button_widget.dart'; -import 'package:sql_studio/src/shared/widgets/dialogs/confirmation_dialog_widget.dart'; +import 'package:sql_studio/src/shared/widgets/buttons/cancel_button_widget.dart'; /// Confirmation dialog for removing a single basic SQL suggestion. class RemoveSqlBasicSuggestionDialogWidget extends ConsumerStatefulWidget { @@ -50,6 +49,7 @@ class _RemoveSqlBasicSuggestionDialogWidgetState return ConfirmationDialogWidget( title: appLocalizations.removeSuggestion, description: appLocalizations.removeSuggestionDescription, + cancelButton: const CancelButtonWidget(), confirmButton: LoadingButtonWidget( onPressed: () async { final result = await ref diff --git a/lib/src/features/sql_suggestions/presentation/widgets/dialogs/reset_sql_advanced_suggestions_dialog_widget.dart b/lib/src/features/sql_suggestions/presentation/widgets/dialogs/reset_sql_advanced_suggestions_dialog_widget.dart index 55327901..a5675a6f 100644 --- a/lib/src/features/sql_suggestions/presentation/widgets/dialogs/reset_sql_advanced_suggestions_dialog_widget.dart +++ b/lib/src/features/sql_suggestions/presentation/widgets/dialogs/reset_sql_advanced_suggestions_dialog_widget.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -8,9 +9,7 @@ import 'package:sql_studio/l10n/app_localizations.dart'; import 'package:sql_studio/src/features/sql_suggestions/presentation/sql_suggestions_providers.dart'; import 'package:sql_studio/src/shared/utils/app_toast.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/button_widget.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/loading_button_widget.dart'; -import 'package:sql_studio/src/shared/widgets/dialogs/confirmation_dialog_widget.dart'; +import 'package:sql_studio/src/shared/widgets/buttons/cancel_button_widget.dart'; /// Confirmation dialog for resetting all advanced SQL suggestions to their /// default set. @@ -36,6 +35,7 @@ class ResetSqlAdvancedSuggestionsDialogWidget extends ConsumerWidget { return ConfirmationDialogWidget( title: appLocalizations.resetSuggestions, description: appLocalizations.resetSuggestionsConfirm, + cancelButton: const CancelButtonWidget(), confirmButton: LoadingButtonWidget( onPressed: () async { Navigator.pop(context); diff --git a/lib/src/features/sql_suggestions/presentation/widgets/dialogs/reset_sql_basic_suggestions_dialog_widget.dart b/lib/src/features/sql_suggestions/presentation/widgets/dialogs/reset_sql_basic_suggestions_dialog_widget.dart index fb6f32e2..86b3eee9 100644 --- a/lib/src/features/sql_suggestions/presentation/widgets/dialogs/reset_sql_basic_suggestions_dialog_widget.dart +++ b/lib/src/features/sql_suggestions/presentation/widgets/dialogs/reset_sql_basic_suggestions_dialog_widget.dart @@ -1,3 +1,4 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; @@ -9,9 +10,7 @@ import 'package:sql_studio/src/core/constants/default_sql_suggestions/default_sq import 'package:sql_studio/src/features/sql_suggestions/presentation/sql_suggestions_providers.dart'; import 'package:sql_studio/src/shared/utils/handle_error.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/button_widget.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/loading_button_widget.dart'; -import 'package:sql_studio/src/shared/widgets/dialogs/confirmation_dialog_widget.dart'; +import 'package:sql_studio/src/shared/widgets/buttons/cancel_button_widget.dart'; /// Confirmation dialog for resetting basic SQL suggestions back to the /// default list. @@ -45,6 +44,7 @@ class _ResetSqlBasicSuggestionsDialogWidgetState return ConfirmationDialogWidget( title: appLocalizations.resetSuggestions, description: appLocalizations.resetSuggestionsDescription, + cancelButton: const CancelButtonWidget(), confirmButton: LoadingButtonWidget( onPressed: () async { final result = await ref diff --git a/lib/src/features/sql_suggestions/presentation/widgets/dialogs/sql_advanced_suggestion_form_dialog_widget.dart b/lib/src/features/sql_suggestions/presentation/widgets/dialogs/sql_advanced_suggestion_form_dialog_widget.dart index 8b02e0f8..f45dda1c 100644 --- a/lib/src/features/sql_suggestions/presentation/widgets/dialogs/sql_advanced_suggestion_form_dialog_widget.dart +++ b/lib/src/features/sql_suggestions/presentation/widgets/dialogs/sql_advanced_suggestion_form_dialog_widget.dart @@ -1,13 +1,10 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; import 'package:sql_studio/src/features/sql_suggestions/domain/entities/sql_advanced_suggestion_entity.dart'; import 'package:sql_studio/src/features/sql_suggestions/presentation/sql_suggestions_providers.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/button_widget.dart'; import 'package:sql_studio/src/shared/widgets/buttons/cancel_button_widget.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/loading_button_widget.dart'; -import 'package:sql_studio/src/shared/widgets/dialogs/dialog_widget.dart'; -import 'package:sql_studio/src/shared/widgets/input_widget.dart'; /// Shared form dialog used to create or update an advanced SQL suggestion, /// collecting its label, SQL code, and optional selectable text. diff --git a/lib/src/features/sql_suggestions/presentation/widgets/sql_advanced_suggestion_card_widget.dart b/lib/src/features/sql_suggestions/presentation/widgets/sql_advanced_suggestion_card_widget.dart index f8265fb3..bc4eefe9 100644 --- a/lib/src/features/sql_suggestions/presentation/widgets/sql_advanced_suggestion_card_widget.dart +++ b/lib/src/features/sql_suggestions/presentation/widgets/sql_advanced_suggestion_card_widget.dart @@ -1,15 +1,12 @@ import 'dart:async'; +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_radii.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; import 'package:sql_studio/src/features/sql_suggestions/domain/entities/sql_advanced_suggestion_entity.dart'; import 'package:sql_studio/src/features/sql_suggestions/presentation/widgets/dialogs/delete_sql_advanced_suggestion_dialog_widget.dart'; import 'package:sql_studio/src/features/sql_suggestions/presentation/widgets/dialogs/update_sql_advanced_suggestion_dialog_widget.dart'; -import 'package:sql_studio/src/shared/widgets/card_widget.dart'; /// Card that displays an advanced SQL suggestion with edit and delete /// actions. diff --git a/lib/src/features/sql_suggestions/presentation/widgets/sql_basic_suggestion_card_widget.dart b/lib/src/features/sql_suggestions/presentation/widgets/sql_basic_suggestion_card_widget.dart index 40192538..b61de2c7 100644 --- a/lib/src/features/sql_suggestions/presentation/widgets/sql_basic_suggestion_card_widget.dart +++ b/lib/src/features/sql_suggestions/presentation/widgets/sql_basic_suggestion_card_widget.dart @@ -1,13 +1,10 @@ import 'dart:async'; +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_radii.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; import 'package:sql_studio/src/features/sql_suggestions/presentation/widgets/dialogs/remove_sql_basic_suggestion_dialog_widget.dart'; -import 'package:sql_studio/src/shared/widgets/card_widget.dart'; /// Card that displays a basic SQL suggestion with a delete action. class SqlBasicSuggestionCardWidget extends StatelessWidget { diff --git a/lib/src/features/sql_suggestions/presentation/widgets/sql_suggestion_settings_card_widget.dart b/lib/src/features/sql_suggestions/presentation/widgets/sql_suggestion_settings_card_widget.dart index c2077e5b..7da24bb3 100644 --- a/lib/src/features/sql_suggestions/presentation/widgets/sql_suggestion_settings_card_widget.dart +++ b/lib/src/features/sql_suggestions/presentation/widgets/sql_suggestion_settings_card_widget.dart @@ -1,12 +1,7 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_durations.dart'; -import 'package:sql_studio/src/core/app_radii.dart'; -import 'package:sql_studio/src/core/app_shadows.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/button_widget.dart'; -import 'package:sql_studio/src/shared/widgets/switch_widget.dart'; /// Toggleable card representing a single SQL suggestion mode, with an /// optional configure action shown while active. diff --git a/lib/src/features/sql_suggestions/presentation/widgets/sql_suggestion_settings_title_option_widget.dart b/lib/src/features/sql_suggestions/presentation/widgets/sql_suggestion_settings_title_option_widget.dart index 021ef7d2..2b7272d8 100644 --- a/lib/src/features/sql_suggestions/presentation/widgets/sql_suggestion_settings_title_option_widget.dart +++ b/lib/src/features/sql_suggestions/presentation/widgets/sql_suggestion_settings_title_option_widget.dart @@ -1,5 +1,5 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; /// Section title label used within the SQL suggestion settings screen. class SqlSuggestionSettingsTitleOptionWidget extends StatelessWidget { diff --git a/lib/src/features/workspace_layout_settings/presentation/screens/workspace_layout_settings_screen.dart b/lib/src/features/workspace_layout_settings/presentation/screens/workspace_layout_settings_screen.dart index e334f3e3..596e2ad0 100644 --- a/lib/src/features/workspace_layout_settings/presentation/screens/workspace_layout_settings_screen.dart +++ b/lib/src/features/workspace_layout_settings/presentation/screens/workspace_layout_settings_screen.dart @@ -1,12 +1,11 @@ import 'dart:async'; +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; import 'package:sql_studio/src/core/error/result.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; import 'package:sql_studio/src/core/types/workspace_layout_type.dart'; import 'package:sql_studio/src/features/workspace_layout_settings/presentation/widgets/workspace_layout_settings_option_card_widget.dart'; import 'package:sql_studio/src/features/workspace_layout_settings/presentation/workspace_layout_providers.dart'; diff --git a/lib/src/features/workspace_layout_settings/presentation/widgets/workspace_layout_settings_option_card_widget.dart b/lib/src/features/workspace_layout_settings/presentation/widgets/workspace_layout_settings_option_card_widget.dart index 5be804b5..4fb55f02 100644 --- a/lib/src/features/workspace_layout_settings/presentation/widgets/workspace_layout_settings_option_card_widget.dart +++ b/lib/src/features/workspace_layout_settings/presentation/widgets/workspace_layout_settings_option_card_widget.dart @@ -1,8 +1,6 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; -import 'package:sql_studio/src/shared/widgets/card_widget.dart'; - /// Selectable card representing a single workspace layout option. class WorkspaceLayoutSettingsOptionCardWidget extends StatelessWidget { /// Creates a workspace layout option card. diff --git a/lib/src/shared/utils/handle_error.dart b/lib/src/shared/utils/handle_error.dart index cbd38781..dafd0688 100644 --- a/lib/src/shared/utils/handle_error.dart +++ b/lib/src/shared/utils/handle_error.dart @@ -1,9 +1,8 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; import 'package:sql_studio/src/core/error/result.dart'; import 'package:sql_studio/src/core/extensions/localization_extension.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/button_widget.dart'; -import 'package:sql_studio/src/shared/widgets/dialogs/dialog_widget.dart'; /// Shows an error dialog when [result] is a failure, or invokes /// [onSuccess] with the unwrapped value otherwise. diff --git a/lib/src/shared/widgets/buttons/cancel_button_widget.dart b/lib/src/shared/widgets/buttons/cancel_button_widget.dart index a05c0fe1..385f8ad7 100644 --- a/lib/src/shared/widgets/buttons/cancel_button_widget.dart +++ b/lib/src/shared/widgets/buttons/cancel_button_widget.dart @@ -1,9 +1,8 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/button_widget.dart'; - /// A button that pops the current route, labeled with the localized /// "cancel" text. class CancelButtonWidget extends StatelessWidget { diff --git a/lib/src/shared/widgets/dialogs/error_dialog_widget.dart b/lib/src/shared/widgets/dialogs/error_dialog_widget.dart deleted file mode 100644 index 51a5c4e8..00000000 --- a/lib/src/shared/widgets/dialogs/error_dialog_widget.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:sql_studio/l10n/app_localizations.dart'; - -import 'package:sql_studio/src/shared/widgets/buttons/button_widget.dart'; -import 'package:sql_studio/src/shared/widgets/dialogs/dialog_widget.dart'; - -/// A dialog displaying an error message with a single dismiss button. -class ErrorDialogWidget extends StatelessWidget { - /// Creates an error dialog with the given [description] and - /// optional [title]. - const ErrorDialogWidget({required this.description, super.key, this.title}); - - /// Title displayed at the top of the dialog. Defaults to the - /// localized "error" text when omitted. - final String? title; - - /// Descriptive body text explaining the error. - final String description; - - /// Displays an [ErrorDialogWidget] with the given [description] - /// and optional [title]. - static Future show( - BuildContext context, { - required String description, - String? title, - }) async { - await showDialog( - context: context, - builder: (context) { - return ErrorDialogWidget(title: title, description: description); - }, - ); - } - - @override - Widget build(BuildContext context) { - final appLocalizations = AppLocalizations.of(context); - - return DialogWidget( - title: title ?? appLocalizations.error, - content: Text(description, textAlign: TextAlign.center), - actions: [ - ButtonWidget( - onPressed: () { - Navigator.pop(context); - }, - style: ButtonStyleType.red, - text: appLocalizations.ok, - ), - ], - ); - } -} diff --git a/lib/src/shared/widgets/popup_menu_button_widget.dart b/lib/src/shared/widgets/popup_menu_button_widget.dart index ed037213..31a37116 100644 --- a/lib/src/shared/widgets/popup_menu_button_widget.dart +++ b/lib/src/shared/widgets/popup_menu_button_widget.dart @@ -1,8 +1,7 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_radii.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; /// A three-dot overflow menu button with the app's rounded styling. class PopupMenuButtonWidget extends StatelessWidget { diff --git a/lib/src/shared/widgets/scaffold_widget.dart b/lib/src/shared/widgets/scaffold_widget.dart index 7affd0d9..b73750bc 100644 --- a/lib/src/shared/widgets/scaffold_widget.dart +++ b/lib/src/shared/widgets/scaffold_widget.dart @@ -1,8 +1,8 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; /// The app's base [Scaffold] wrapper with a consistent app bar and /// back button. diff --git a/lib/src/shared/widgets/sql_workspace/console/console_widget.dart b/lib/src/shared/widgets/sql_workspace/console/console_widget.dart index fd124992..f48d1566 100644 --- a/lib/src/shared/widgets/sql_workspace/console/console_widget.dart +++ b/lib/src/shared/widgets/sql_workspace/console/console_widget.dart @@ -1,18 +1,14 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_durations.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; import 'package:sql_studio/src/core/error/result.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; import 'package:sql_studio/src/core/extensions/localization_extension.dart'; import 'package:sql_studio/src/features/sql_editor/presentation/sql_editor_providers.dart'; import 'package:sql_studio/src/shared/widgets/sql_workspace/console/console_controller.dart'; import 'package:sql_studio/src/shared/widgets/sql_workspace/console/styled_data_table_widget.dart'; import 'package:sql_studio/src/shared/widgets/sql_workspace/panel_widget.dart'; -import 'package:sql_studio/src/shared/widgets/states/empty_state_widget.dart'; -import 'package:sql_studio/src/shared/widgets/states/error_state_widget.dart'; /// Displays the result of the last executed SQL command, showing a loading /// indicator, an error message, or the resulting data table as appropriate. diff --git a/lib/src/shared/widgets/sql_workspace/console/styled_data_table_widget.dart b/lib/src/shared/widgets/sql_workspace/console/styled_data_table_widget.dart index 50455f76..48eafcb6 100644 --- a/lib/src/shared/widgets/sql_workspace/console/styled_data_table_widget.dart +++ b/lib/src/shared/widgets/sql_workspace/console/styled_data_table_widget.dart @@ -1,9 +1,7 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; -import 'package:sql_studio/src/core/extensions/list_extension.dart'; /// A [DataTable] with a consistent black-and-white style used to render /// SQL query results, one page at a time so a large result set does not diff --git a/lib/src/shared/widgets/sql_workspace/divider_bar_widget.dart b/lib/src/shared/widgets/sql_workspace/divider_bar_widget.dart index d470c42b..60f058af 100644 --- a/lib/src/shared/widgets/sql_workspace/divider_bar_widget.dart +++ b/lib/src/shared/widgets/sql_workspace/divider_bar_widget.dart @@ -1,6 +1,5 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; -import 'package:sql_studio/src/core/app_radii.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; /// A draggable horizontal bar used to resize the panels above and below it /// in a workspace split view. diff --git a/lib/src/shared/widgets/sql_workspace/panel_widget.dart b/lib/src/shared/widgets/sql_workspace/panel_widget.dart index 9bccf0a0..8dcca62f 100644 --- a/lib/src/shared/widgets/sql_workspace/panel_widget.dart +++ b/lib/src/shared/widgets/sql_workspace/panel_widget.dart @@ -1,7 +1,7 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; /// A common panel frame used by the SQL workspace widgets, providing a /// header with title, database name, action buttons, and a body area. diff --git a/lib/src/shared/widgets/sql_workspace/sql_editor/sql_editor_widget.dart b/lib/src/shared/widgets/sql_workspace/sql_editor/sql_editor_widget.dart index f2ffddb5..173f20e6 100644 --- a/lib/src/shared/widgets/sql_workspace/sql_editor/sql_editor_widget.dart +++ b/lib/src/shared/widgets/sql_workspace/sql_editor/sql_editor_widget.dart @@ -1,19 +1,18 @@ import 'dart:async'; +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_code_editor/flutter_code_editor.dart'; import 'package:flutter_highlight/themes/atom-one-dark.dart'; import 'package:flutter_highlight/themes/github.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; import 'package:sql_studio/src/core/routes/app_routes.dart'; import 'package:sql_studio/src/features/sql_editor/presentation/sql_editor_providers.dart'; import 'package:sql_studio/src/features/sql_suggestions/presentation/sql_suggestions_providers.dart'; import 'package:sql_studio/src/shared/utils/app_toast.dart'; import 'package:sql_studio/src/shared/utils/handle_error.dart'; import 'package:sql_studio/src/shared/widgets/popup_menu_button_widget.dart'; -import 'package:sql_studio/src/shared/widgets/popup_menu_section_header_widget.dart'; import 'package:sql_studio/src/shared/widgets/sql_workspace/panel_widget.dart'; import 'package:sql_studio/src/shared/widgets/sql_workspace/sql_editor/sql_editor_controller.dart'; import 'package:sql_studio/src/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_advanced_suggestions_bar_widget.dart'; diff --git a/lib/src/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_character_bar_widget.dart b/lib/src/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_character_bar_widget.dart index a84f9a56..43edede0 100644 --- a/lib/src/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_character_bar_widget.dart +++ b/lib/src/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_character_bar_widget.dart @@ -1,6 +1,6 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; import 'package:sql_studio/src/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_suggestions_bar_widget.dart'; /// Special characters commonly used in SQL statements, shown as quick diff --git a/lib/src/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_suggestions_bar_widget.dart b/lib/src/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_suggestions_bar_widget.dart index 0608d858..f5815cd3 100644 --- a/lib/src/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_suggestions_bar_widget.dart +++ b/lib/src/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_suggestions_bar_widget.dart @@ -1,9 +1,6 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; -import 'package:sql_studio/src/core/app_radii.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; - /// Base layout for a horizontally scrollable row of tappable suggestion /// chips, shared by the SQL suggestions bar variants. class SqlSuggestionsBarBaseWidget extends StatelessWidget { diff --git a/lib/src/shared/widgets/sql_workspace/sql_workspace_widget.dart b/lib/src/shared/widgets/sql_workspace/sql_workspace_widget.dart index d561e39a..eeef1d37 100644 --- a/lib/src/shared/widgets/sql_workspace/sql_workspace_widget.dart +++ b/lib/src/shared/widgets/sql_workspace/sql_workspace_widget.dart @@ -1,7 +1,7 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; import 'package:sql_studio/src/core/types/workspace_layout_type.dart'; import 'package:sql_studio/src/features/workspace_layout_settings/presentation/workspace_layout_providers.dart'; diff --git a/lib/src/shared/widgets/suggestions_settings_layout/suggestions_settings_layout_widget.dart b/lib/src/shared/widgets/suggestions_settings_layout/suggestions_settings_layout_widget.dart index 5672eca9..0481909c 100644 --- a/lib/src/shared/widgets/suggestions_settings_layout/suggestions_settings_layout_widget.dart +++ b/lib/src/shared/widgets/suggestions_settings_layout/suggestions_settings_layout_widget.dart @@ -1,15 +1,8 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_radii.dart'; -import 'package:sql_studio/src/core/app_shadows.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/button_widget.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/loading_button_widget.dart'; import 'package:sql_studio/src/shared/widgets/scaffold_widget.dart'; -import 'package:sql_studio/src/shared/widgets/states/empty_state_widget.dart'; -import 'package:sql_studio/src/shared/widgets/states/loading_state_widget.dart'; import 'package:sql_studio/src/shared/widgets/suggestions_settings_layout/suggestions_settings_layout_controller.dart'; /// Generic screen for reordering, saving, resetting, and adding suggestion diff --git a/lib/src/shared/widgets/unexpected_error_widget.dart b/lib/src/shared/widgets/unexpected_error_widget.dart index b1b4b342..ba82a1c1 100644 --- a/lib/src/shared/widgets/unexpected_error_widget.dart +++ b/lib/src/shared/widgets/unexpected_error_widget.dart @@ -1,7 +1,7 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; /// Neutral replacement for the framework's red error screen, shown when a /// widget fails to build in release. diff --git a/lib/src/sql_studio_app.dart b/lib/src/sql_studio_app.dart index 59078dac..09bc4817 100644 --- a/lib/src/sql_studio_app.dart +++ b/lib/src/sql_studio_app.dart @@ -1,11 +1,10 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/core/providers/app_localization_provider.dart'; import 'package:sql_studio/src/core/providers/app_theme_mode_provider.dart'; import 'package:sql_studio/src/core/routes/app_router.dart'; -import 'package:sql_studio/src/shared/utils/text_scaling.dart'; /// Root widget of the SQL Studio application, wiring up routing and /// localization for the whole app. diff --git a/packages/app_ui/analysis_options.yaml b/packages/app_ui/analysis_options.yaml new file mode 100644 index 00000000..911bffb2 --- /dev/null +++ b/packages/app_ui/analysis_options.yaml @@ -0,0 +1,5 @@ +include: package:very_good_analysis/analysis_options.yaml + +analyzer: + exclude: + - build/** diff --git a/packages/app_ui/lib/app_ui.dart b/packages/app_ui/lib/app_ui.dart new file mode 100644 index 00000000..f6f23644 --- /dev/null +++ b/packages/app_ui/lib/app_ui.dart @@ -0,0 +1,26 @@ +/// Design system of SQL Studio. +library; + +export 'src/components/button_widget.dart'; +export 'src/components/card_widget.dart'; +export 'src/components/confirmation_dialog_widget.dart'; +export 'src/components/dialog_widget.dart'; +export 'src/components/empty_state_widget.dart'; +export 'src/components/error_dialog_widget.dart'; +export 'src/components/error_state_widget.dart'; +export 'src/components/input_dialog_widget.dart'; +export 'src/components/input_widget.dart'; +export 'src/components/loading_button_widget.dart'; +export 'src/components/loading_state_widget.dart'; +export 'src/components/popup_menu_section_header_widget.dart'; +export 'src/components/switch_widget.dart'; +export 'src/extensions/list_extension.dart'; +export 'src/theme/app_theme.dart'; +export 'src/theme/build_context_extension.dart'; +export 'src/tokens/app_colors.dart'; +export 'src/tokens/app_durations.dart'; +export 'src/tokens/app_radii.dart'; +export 'src/tokens/app_shadows.dart'; +export 'src/tokens/app_spacing.dart'; +export 'src/utils/button_style_util.dart'; +export 'src/utils/text_scaling.dart'; diff --git a/lib/src/shared/widgets/buttons/button_widget.dart b/packages/app_ui/lib/src/components/button_widget.dart similarity index 92% rename from lib/src/shared/widgets/buttons/button_widget.dart rename to packages/app_ui/lib/src/components/button_widget.dart index 36268856..bfba5a8a 100644 --- a/lib/src/shared/widgets/buttons/button_widget.dart +++ b/packages/app_ui/lib/src/components/button_widget.dart @@ -1,10 +1,9 @@ +import 'package:app_ui/src/theme/build_context_extension.dart'; +import 'package:app_ui/src/tokens/app_radii.dart'; +import 'package:app_ui/src/tokens/app_spacing.dart'; +import 'package:app_ui/src/utils/button_style_util.dart'; import 'package:flutter/material.dart'; -import 'package:sql_studio/src/core/app_radii.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; -import 'package:sql_studio/src/shared/utils/button_style_util.dart'; - /// Visual style presets shared by the button widgets. enum ButtonStyleType { /// Solid black background with white text. diff --git a/lib/src/shared/widgets/card_widget.dart b/packages/app_ui/lib/src/components/card_widget.dart similarity index 85% rename from lib/src/shared/widgets/card_widget.dart rename to packages/app_ui/lib/src/components/card_widget.dart index 5d715de7..7c264a36 100644 --- a/lib/src/shared/widgets/card_widget.dart +++ b/packages/app_ui/lib/src/components/card_widget.dart @@ -1,9 +1,6 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; -import 'package:sql_studio/src/core/app_radii.dart'; -import 'package:sql_studio/src/core/app_shadows.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; - /// A rounded, elevated surface that optionally responds to taps. class CardWidget extends StatelessWidget { /// Creates a card wrapping [child]. diff --git a/lib/src/shared/widgets/dialogs/confirmation_dialog_widget.dart b/packages/app_ui/lib/src/components/confirmation_dialog_widget.dart similarity index 73% rename from lib/src/shared/widgets/dialogs/confirmation_dialog_widget.dart rename to packages/app_ui/lib/src/components/confirmation_dialog_widget.dart index cdc48865..a9805dc0 100644 --- a/lib/src/shared/widgets/dialogs/confirmation_dialog_widget.dart +++ b/packages/app_ui/lib/src/components/confirmation_dialog_widget.dart @@ -1,15 +1,14 @@ +import 'package:app_ui/src/components/dialog_widget.dart'; import 'package:flutter/material.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/cancel_button_widget.dart'; -import 'package:sql_studio/src/shared/widgets/dialogs/dialog_widget.dart'; - /// A dialog asking the user to confirm or cancel an action. class ConfirmationDialogWidget extends StatelessWidget { /// Creates a confirmation dialog with the given [title], - /// [description] and [confirmButton]. + /// [description], [cancelButton] and [confirmButton]. const ConfirmationDialogWidget({ required this.title, required this.description, + required this.cancelButton, required this.confirmButton, super.key, }); @@ -20,8 +19,12 @@ class ConfirmationDialogWidget extends StatelessWidget { /// Descriptive body text explaining the action to confirm. final String description; - /// Widget triggering the confirmed action, shown alongside a cancel - /// button. + /// Widget dismissing the dialog without confirming, shown alongside + /// [confirmButton]. + final Widget cancelButton; + + /// Widget triggering the confirmed action, shown alongside + /// [cancelButton]. final Widget confirmButton; /// Displays a [ConfirmationDialogWidget] and returns the result @@ -30,6 +33,7 @@ class ConfirmationDialogWidget extends StatelessWidget { BuildContext context, { required String title, required String description, + required Widget cancelButton, required Widget confirmButton, bool barrierDismissible = true, }) async { @@ -40,6 +44,7 @@ class ConfirmationDialogWidget extends StatelessWidget { return ConfirmationDialogWidget( title: title, description: description, + cancelButton: cancelButton, confirmButton: confirmButton, ); }, @@ -51,7 +56,7 @@ class ConfirmationDialogWidget extends StatelessWidget { return DialogWidget( title: title, content: Text(description, textAlign: TextAlign.center), - actions: [const CancelButtonWidget(), confirmButton], + actions: [cancelButton, confirmButton], ); } } diff --git a/lib/src/shared/widgets/dialogs/dialog_widget.dart b/packages/app_ui/lib/src/components/dialog_widget.dart similarity index 91% rename from lib/src/shared/widgets/dialogs/dialog_widget.dart rename to packages/app_ui/lib/src/components/dialog_widget.dart index 06887ccd..a682b1b4 100644 --- a/lib/src/shared/widgets/dialogs/dialog_widget.dart +++ b/packages/app_ui/lib/src/components/dialog_widget.dart @@ -1,11 +1,10 @@ +import 'package:app_ui/src/extensions/list_extension.dart'; +import 'package:app_ui/src/theme/build_context_extension.dart'; +import 'package:app_ui/src/tokens/app_radii.dart'; +import 'package:app_ui/src/tokens/app_shadows.dart'; +import 'package:app_ui/src/tokens/app_spacing.dart'; import 'package:flutter/material.dart'; -import 'package:sql_studio/src/core/app_radii.dart'; -import 'package:sql_studio/src/core/app_shadows.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; -import 'package:sql_studio/src/core/extensions/list_extension.dart'; - /// The app's base dialog chrome: title, scrollable content and /// action buttons. class DialogWidget extends StatelessWidget { diff --git a/lib/src/shared/widgets/states/empty_state_widget.dart b/packages/app_ui/lib/src/components/empty_state_widget.dart similarity index 92% rename from lib/src/shared/widgets/states/empty_state_widget.dart rename to packages/app_ui/lib/src/components/empty_state_widget.dart index f406aff2..34ac4bd4 100644 --- a/lib/src/shared/widgets/states/empty_state_widget.dart +++ b/packages/app_ui/lib/src/components/empty_state_widget.dart @@ -1,8 +1,6 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; - /// Standardized "no data" placeholder, with an optional action slot. class EmptyStateWidget extends StatelessWidget { /// Creates an empty state displaying [message]. diff --git a/packages/app_ui/lib/src/components/error_dialog_widget.dart b/packages/app_ui/lib/src/components/error_dialog_widget.dart new file mode 100644 index 00000000..10e7df20 --- /dev/null +++ b/packages/app_ui/lib/src/components/error_dialog_widget.dart @@ -0,0 +1,61 @@ +import 'package:app_ui/src/components/button_widget.dart'; +import 'package:app_ui/src/components/dialog_widget.dart'; +import 'package:flutter/material.dart'; + +/// A dialog displaying an error message with a single dismiss button. +class ErrorDialogWidget extends StatelessWidget { + /// Creates an error dialog with the given [title], [description] + /// and [dismissLabel]. + const ErrorDialogWidget({ + required this.title, + required this.description, + required this.dismissLabel, + super.key, + }); + + /// Title displayed at the top of the dialog. + final String title; + + /// Descriptive body text explaining the error. + final String description; + + /// Label of the button that dismisses the dialog. + final String dismissLabel; + + /// Displays an [ErrorDialogWidget] with the given [title], + /// [description] and [dismissLabel]. + static Future show( + BuildContext context, { + required String title, + required String description, + required String dismissLabel, + }) async { + await showDialog( + context: context, + builder: (context) { + return ErrorDialogWidget( + title: title, + description: description, + dismissLabel: dismissLabel, + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return DialogWidget( + title: title, + content: Text(description, textAlign: TextAlign.center), + actions: [ + ButtonWidget( + onPressed: () { + Navigator.pop(context); + }, + style: ButtonStyleType.red, + text: dismissLabel, + ), + ], + ); + } +} diff --git a/lib/src/shared/widgets/states/error_state_widget.dart b/packages/app_ui/lib/src/components/error_state_widget.dart similarity index 88% rename from lib/src/shared/widgets/states/error_state_widget.dart rename to packages/app_ui/lib/src/components/error_state_widget.dart index 4ae40400..920d4180 100644 --- a/lib/src/shared/widgets/states/error_state_widget.dart +++ b/packages/app_ui/lib/src/components/error_state_widget.dart @@ -1,10 +1,9 @@ +import 'package:app_ui/src/components/button_widget.dart'; +import 'package:app_ui/src/components/dialog_widget.dart'; +import 'package:app_ui/src/theme/build_context_extension.dart'; +import 'package:app_ui/src/tokens/app_spacing.dart'; import 'package:flutter/material.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/button_widget.dart'; -import 'package:sql_studio/src/shared/widgets/dialogs/dialog_widget.dart'; - /// Standardized inline error state, with an optional retry action. /// /// This is for errors embedded directly in a screen's layout (e.g. a diff --git a/lib/src/shared/widgets/dialogs/input_dialog_widget.dart b/packages/app_ui/lib/src/components/input_dialog_widget.dart similarity index 69% rename from lib/src/shared/widgets/dialogs/input_dialog_widget.dart rename to packages/app_ui/lib/src/components/input_dialog_widget.dart index 6794c00b..a377a3ca 100644 --- a/lib/src/shared/widgets/dialogs/input_dialog_widget.dart +++ b/packages/app_ui/lib/src/components/input_dialog_widget.dart @@ -1,23 +1,22 @@ +import 'package:app_ui/src/components/button_widget.dart'; +import 'package:app_ui/src/components/dialog_widget.dart'; +import 'package:app_ui/src/components/input_widget.dart'; +import 'package:app_ui/src/components/loading_button_widget.dart'; import 'package:flutter/material.dart'; -import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/button_widget.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/cancel_button_widget.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/loading_button_widget.dart'; -import 'package:sql_studio/src/shared/widgets/dialogs/dialog_widget.dart'; -import 'package:sql_studio/src/shared/widgets/input_widget.dart'; /// A dialog prompting the user for a single text input value. class InputDialogWidget extends StatefulWidget { /// Creates an input dialog with the given [title], [controller], - /// [label] and [onSubmit] callback. + /// [label], [cancelButton], [submitText] and [onSubmit] callback. const InputDialogWidget({ required this.title, required this.controller, required this.label, + required this.cancelButton, + required this.submitText, required this.onSubmit, super.key, this.validator, - this.submitText, }); /// Title displayed at the top of the dialog. @@ -29,16 +28,19 @@ class InputDialogWidget extends StatefulWidget { /// Label displayed on the input field. final String label; + /// Widget dismissing the dialog without submitting. + final Widget cancelButton; + /// Optional validation function returning an error message, or - /// `null` when the value is valid. + /// `null` when the value is valid or no validation applies. final String? Function(String? value)? validator; /// Called with the submitted value. Return `true` to close the /// dialog, or `false` to keep it open. final Future Function(String value) onSubmit; - /// Overrides the default localized submit button label. - final String? submitText; + /// Label of the submit button. + final String submitText; /// Displays an [InputDialogWidget] with the given parameters. static Future show( @@ -46,9 +48,10 @@ class InputDialogWidget extends StatefulWidget { required String title, required TextEditingController controller, required String label, + required Widget cancelButton, + required String submitText, required Future Function(String value) onSubmit, String? Function(String? value)? validator, - String? submitText, }) async { await showDialog( context: context, @@ -57,9 +60,10 @@ class InputDialogWidget extends StatefulWidget { title: title, controller: controller, label: label, + cancelButton: cancelButton, + submitText: submitText, onSubmit: onSubmit, validator: validator, - submitText: submitText, ); }, ); @@ -84,8 +88,6 @@ class _InputDialogWidgetState extends State { @override Widget build(BuildContext context) { - final appLocalizations = AppLocalizations.of(context); - return DialogWidget( title: widget.title, content: Form( @@ -94,22 +96,14 @@ class _InputDialogWidgetState extends State { controller: widget.controller, labelText: widget.label, onChanged: (value) => setState(() {}), - validator: - widget.validator ?? - (value) { - if (value == null || value.trim().isEmpty) { - return appLocalizations.thisFieldIsRequired; - } - - return null; - }, + validator: widget.validator, ), ), actions: [ - const CancelButtonWidget(), + widget.cancelButton, LoadingButtonWidget( onPressed: _handleSubmit, - text: widget.submitText ?? appLocalizations.submit, + text: widget.submitText, style: ButtonStyleType.black, ), ], diff --git a/lib/src/shared/widgets/input_widget.dart b/packages/app_ui/lib/src/components/input_widget.dart similarity index 93% rename from lib/src/shared/widgets/input_widget.dart rename to packages/app_ui/lib/src/components/input_widget.dart index fbf9ed98..3e465333 100644 --- a/lib/src/shared/widgets/input_widget.dart +++ b/packages/app_ui/lib/src/components/input_widget.dart @@ -1,9 +1,6 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; -import 'package:sql_studio/src/core/app_radii.dart'; -import 'package:sql_studio/src/core/app_spacing.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; - /// A styled text form field used throughout the app's forms. class InputWidget extends StatelessWidget { /// Creates an input field. diff --git a/lib/src/shared/widgets/buttons/loading_button_widget.dart b/packages/app_ui/lib/src/components/loading_button_widget.dart similarity index 91% rename from lib/src/shared/widgets/buttons/loading_button_widget.dart rename to packages/app_ui/lib/src/components/loading_button_widget.dart index d8156cfe..254a33ec 100644 --- a/lib/src/shared/widgets/buttons/loading_button_widget.dart +++ b/packages/app_ui/lib/src/components/loading_button_widget.dart @@ -1,9 +1,8 @@ +import 'package:app_ui/src/components/button_widget.dart'; +import 'package:app_ui/src/theme/build_context_extension.dart'; +import 'package:app_ui/src/utils/button_style_util.dart'; import 'package:flutter/material.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; -import 'package:sql_studio/src/shared/utils/button_style_util.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/button_widget.dart'; - /// A button that shows a spinner and disables itself while its /// asynchronous [onPressed] callback is running. class LoadingButtonWidget extends StatefulWidget { diff --git a/lib/src/shared/widgets/states/loading_state_widget.dart b/packages/app_ui/lib/src/components/loading_state_widget.dart similarity index 94% rename from lib/src/shared/widgets/states/loading_state_widget.dart rename to packages/app_ui/lib/src/components/loading_state_widget.dart index e1c2a874..bda54567 100644 --- a/lib/src/shared/widgets/states/loading_state_widget.dart +++ b/packages/app_ui/lib/src/components/loading_state_widget.dart @@ -1,5 +1,5 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; /// Standardized full-area loading indicator, optionally labeled. class LoadingStateWidget extends StatelessWidget { diff --git a/lib/src/shared/widgets/popup_menu_section_header_widget.dart b/packages/app_ui/lib/src/components/popup_menu_section_header_widget.dart similarity index 89% rename from lib/src/shared/widgets/popup_menu_section_header_widget.dart rename to packages/app_ui/lib/src/components/popup_menu_section_header_widget.dart index 04cf15b3..72d47f41 100644 --- a/lib/src/shared/widgets/popup_menu_section_header_widget.dart +++ b/packages/app_ui/lib/src/components/popup_menu_section_header_widget.dart @@ -1,5 +1,5 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; /// A non-interactive label grouping related entries within a popup menu. class PopupMenuSectionHeaderWidget extends PopupMenuItem { diff --git a/lib/src/shared/widgets/switch_widget.dart b/packages/app_ui/lib/src/components/switch_widget.dart similarity index 91% rename from lib/src/shared/widgets/switch_widget.dart rename to packages/app_ui/lib/src/components/switch_widget.dart index ade68d74..9c143310 100644 --- a/lib/src/shared/widgets/switch_widget.dart +++ b/packages/app_ui/lib/src/components/switch_widget.dart @@ -1,9 +1,6 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; -import 'package:sql_studio/src/core/app_radii.dart'; -import 'package:sql_studio/src/core/app_shadows.dart'; -import 'package:sql_studio/src/core/extensions/build_context_extension.dart'; - /// A compact pill switch matching the app's own design system instead of /// Material's stock [Switch] shape. class SwitchWidget extends StatelessWidget { diff --git a/lib/src/core/extensions/list_extension.dart b/packages/app_ui/lib/src/extensions/list_extension.dart similarity index 100% rename from lib/src/core/extensions/list_extension.dart rename to packages/app_ui/lib/src/extensions/list_extension.dart diff --git a/lib/src/core/app_theme.dart b/packages/app_ui/lib/src/theme/app_theme.dart similarity index 96% rename from lib/src/core/app_theme.dart rename to packages/app_ui/lib/src/theme/app_theme.dart index fd5b97de..7d8ddb67 100644 --- a/lib/src/core/app_theme.dart +++ b/packages/app_ui/lib/src/theme/app_theme.dart @@ -1,5 +1,5 @@ +import 'package:app_ui/src/tokens/app_colors.dart'; import 'package:flutter/material.dart'; -import 'package:sql_studio/src/core/app_colors.dart'; /// Centralized [ThemeData] for the app, so Material components (text /// selection, tab indicators, progress indicators, ...) follow diff --git a/lib/src/core/extensions/build_context_extension.dart b/packages/app_ui/lib/src/theme/build_context_extension.dart similarity index 84% rename from lib/src/core/extensions/build_context_extension.dart rename to packages/app_ui/lib/src/theme/build_context_extension.dart index 6f8e27b1..3c75b9b5 100644 --- a/lib/src/core/extensions/build_context_extension.dart +++ b/packages/app_ui/lib/src/theme/build_context_extension.dart @@ -1,5 +1,5 @@ +import 'package:app_ui/src/tokens/app_colors.dart'; import 'package:flutter/material.dart'; -import 'package:sql_studio/src/core/app_colors.dart'; /// Convenience accessors for values derived from [BuildContext]. extension BuildContextExtension on BuildContext { diff --git a/lib/src/core/app_colors.dart b/packages/app_ui/lib/src/tokens/app_colors.dart similarity index 98% rename from lib/src/core/app_colors.dart rename to packages/app_ui/lib/src/tokens/app_colors.dart index 8f15b788..56dd4561 100644 --- a/lib/src/core/app_colors.dart +++ b/packages/app_ui/lib/src/tokens/app_colors.dart @@ -59,7 +59,7 @@ class AppColors extends ThemeExtension { /// Muted secondary text and icons. final Color textMuted; - /// Drag handles and inactive control fills — one step stronger than + /// Drag handles and inactive control fills, one step stronger than /// [border] for elements that need to read as interactive/structural. final Color controlInactive; diff --git a/lib/src/core/app_durations.dart b/packages/app_ui/lib/src/tokens/app_durations.dart similarity index 100% rename from lib/src/core/app_durations.dart rename to packages/app_ui/lib/src/tokens/app_durations.dart diff --git a/lib/src/core/app_radii.dart b/packages/app_ui/lib/src/tokens/app_radii.dart similarity index 100% rename from lib/src/core/app_radii.dart rename to packages/app_ui/lib/src/tokens/app_radii.dart diff --git a/lib/src/core/app_shadows.dart b/packages/app_ui/lib/src/tokens/app_shadows.dart similarity index 100% rename from lib/src/core/app_shadows.dart rename to packages/app_ui/lib/src/tokens/app_shadows.dart diff --git a/lib/src/core/app_spacing.dart b/packages/app_ui/lib/src/tokens/app_spacing.dart similarity index 100% rename from lib/src/core/app_spacing.dart rename to packages/app_ui/lib/src/tokens/app_spacing.dart diff --git a/lib/src/shared/utils/button_style_util.dart b/packages/app_ui/lib/src/utils/button_style_util.dart similarity index 92% rename from lib/src/shared/utils/button_style_util.dart rename to packages/app_ui/lib/src/utils/button_style_util.dart index fd3c538a..16b6be0f 100644 --- a/lib/src/shared/utils/button_style_util.dart +++ b/packages/app_ui/lib/src/utils/button_style_util.dart @@ -1,7 +1,6 @@ +import 'package:app_ui/src/components/button_widget.dart'; +import 'package:app_ui/src/tokens/app_colors.dart'; import 'package:flutter/material.dart'; -import 'package:sql_studio/src/core/app_colors.dart'; - -import 'package:sql_studio/src/shared/widgets/buttons/button_widget.dart'; /// Resolved color set used to render a button. class ButtonStyleData { diff --git a/lib/src/shared/utils/text_scaling.dart b/packages/app_ui/lib/src/utils/text_scaling.dart similarity index 100% rename from lib/src/shared/utils/text_scaling.dart rename to packages/app_ui/lib/src/utils/text_scaling.dart diff --git a/packages/app_ui/pubspec.lock b/packages/app_ui/pubspec.lock new file mode 100644 index 00000000..d8ffdb4d --- /dev/null +++ b/packages/app_ui/pubspec.lock @@ -0,0 +1,197 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" + source: hosted + version: "0.12.19" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + url: "https://pub.dev" + source: hosted + version: "0.7.10" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + very_good_analysis: + dependency: "direct dev" + description: + name: very_good_analysis + sha256: d1cb1d66a5aae2c702d68caca6c8347306d35e728fd94555fa21fa0448a972e0 + url: "https://pub.dev" + source: hosted + version: "10.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "5f37239c4851efcef929cea7824e76df7f2f0970aef85d66bbc430afa40e72f0" + url: "https://pub.dev" + source: hosted + version: "15.3.0" +sdks: + dart: ">=3.11.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/packages/app_ui/pubspec.yaml b/packages/app_ui/pubspec.yaml new file mode 100644 index 00000000..36eaa108 --- /dev/null +++ b/packages/app_ui/pubspec.yaml @@ -0,0 +1,19 @@ +name: app_ui +description: "Design system of SQL Studio." +version: 0.0.1 +publish_to: "none" + +environment: + sdk: ^3.9.2 + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + very_good_analysis: ^10.2.0 + +flutter: + uses-material-design: true diff --git a/test/shared/widgets/buttons/button_widget_test.dart b/packages/app_ui/test/src/components/button_widget_test.dart similarity index 91% rename from test/shared/widgets/buttons/button_widget_test.dart rename to packages/app_ui/test/src/components/button_widget_test.dart index 2c0d821f..1b276de1 100644 --- a/test/shared/widgets/buttons/button_widget_test.dart +++ b/packages/app_ui/test/src/components/button_widget_test.dart @@ -1,7 +1,6 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/button_widget.dart'; void main() { Widget wrap(Widget child) => MaterialApp( diff --git a/test/shared/widgets/card_widget_test.dart b/packages/app_ui/test/src/components/card_widget_test.dart similarity index 90% rename from test/shared/widgets/card_widget_test.dart rename to packages/app_ui/test/src/components/card_widget_test.dart index b11ff68d..0ca9345c 100644 --- a/test/shared/widgets/card_widget_test.dart +++ b/packages/app_ui/test/src/components/card_widget_test.dart @@ -1,7 +1,6 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; -import 'package:sql_studio/src/shared/widgets/card_widget.dart'; void main() { Widget wrap(Widget child) => MaterialApp( diff --git a/test/shared/widgets/dialogs/confirmation_dialog_widget_test.dart b/packages/app_ui/test/src/components/confirmation_dialog_widget_test.dart similarity index 79% rename from test/shared/widgets/dialogs/confirmation_dialog_widget_test.dart rename to packages/app_ui/test/src/components/confirmation_dialog_widget_test.dart index 31c70c31..a4e34f07 100644 --- a/test/shared/widgets/dialogs/confirmation_dialog_widget_test.dart +++ b/packages/app_ui/test/src/components/confirmation_dialog_widget_test.dart @@ -1,19 +1,15 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; -import 'package:sql_studio/src/shared/widgets/dialogs/confirmation_dialog_widget.dart'; void main() { Widget wrap(Widget child) => MaterialApp( theme: AppTheme.light, - supportedLocales: AppLocalizations.supportedLocales, - localizationsDelegates: AppLocalizations.localizationsDelegates, home: Scaffold(body: child), ); group('ConfirmationDialogWidget', () { - testWidgets('renders title, description and confirm button', ( + testWidgets('renders title, description, cancel and confirm buttons', ( tester, ) async { await tester.pumpWidget( @@ -21,6 +17,10 @@ void main() { ConfirmationDialogWidget( title: 'Delete database', description: 'This action cannot be undone.', + cancelButton: ElevatedButton( + onPressed: () {}, + child: const Text('Cancel'), + ), confirmButton: ElevatedButton( onPressed: () {}, child: const Text('Delete'), @@ -31,6 +31,7 @@ void main() { expect(find.text('Delete database'), findsOneWidget); expect(find.text('This action cannot be undone.'), findsOneWidget); + expect(find.text('Cancel'), findsOneWidget); expect(find.text('Delete'), findsOneWidget); }); @@ -49,6 +50,10 @@ void main() { context, title: 'Confirm', description: 'Proceed?', + cancelButton: ElevatedButton( + onPressed: () => Navigator.pop(context, false), + child: const Text('No'), + ), confirmButton: ElevatedButton( onPressed: () => Navigator.pop(context, true), child: const Text('Yes'), diff --git a/test/shared/widgets/dialogs/dialog_widget_test.dart b/packages/app_ui/test/src/components/dialog_widget_test.dart similarity index 96% rename from test/shared/widgets/dialogs/dialog_widget_test.dart rename to packages/app_ui/test/src/components/dialog_widget_test.dart index 851f12bc..ecd0db53 100644 --- a/test/shared/widgets/dialogs/dialog_widget_test.dart +++ b/packages/app_ui/test/src/components/dialog_widget_test.dart @@ -1,7 +1,6 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; -import 'package:sql_studio/src/shared/widgets/dialogs/dialog_widget.dart'; void main() { Widget wrap(Widget child) => MaterialApp( diff --git a/test/shared/widgets/states/empty_state_widget_test.dart b/packages/app_ui/test/src/components/empty_state_widget_test.dart similarity index 93% rename from test/shared/widgets/states/empty_state_widget_test.dart rename to packages/app_ui/test/src/components/empty_state_widget_test.dart index 0cbe7846..72cd08b9 100644 --- a/test/shared/widgets/states/empty_state_widget_test.dart +++ b/packages/app_ui/test/src/components/empty_state_widget_test.dart @@ -1,7 +1,6 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; -import 'package:sql_studio/src/shared/widgets/states/empty_state_widget.dart'; void main() { Widget wrap(Widget child) => MaterialApp( diff --git a/test/shared/widgets/dialogs/error_dialog_widget_test.dart b/packages/app_ui/test/src/components/error_dialog_widget_test.dart similarity index 59% rename from test/shared/widgets/dialogs/error_dialog_widget_test.dart rename to packages/app_ui/test/src/components/error_dialog_widget_test.dart index 20a87aa0..a712d638 100644 --- a/test/shared/widgets/dialogs/error_dialog_widget_test.dart +++ b/packages/app_ui/test/src/components/error_dialog_widget_test.dart @@ -1,45 +1,34 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; -import 'package:sql_studio/src/shared/widgets/dialogs/error_dialog_widget.dart'; void main() { Widget wrap(Widget child) => MaterialApp( theme: AppTheme.light, - supportedLocales: AppLocalizations.supportedLocales, - localizationsDelegates: AppLocalizations.localizationsDelegates, home: Scaffold(body: child), ); group('ErrorDialogWidget', () { - testWidgets('renders the given description and a default title', ( + testWidgets('renders the given title, description and dismiss label', ( tester, ) async { - await tester.pumpWidget( - wrap(const ErrorDialogWidget(description: 'Something went wrong')), - ); - await tester.pumpAndSettle(); - - expect(find.text('Something went wrong'), findsOneWidget); - expect(find.text('OK'), findsOneWidget); - }); - - testWidgets('renders a custom title when provided', (tester) async { await tester.pumpWidget( wrap( const ErrorDialogWidget( - title: 'Connection failed', - description: 'Could not reach the database', + title: 'Error', + description: 'Something went wrong', + dismissLabel: 'OK', ), ), ); await tester.pumpAndSettle(); - expect(find.text('Connection failed'), findsOneWidget); + expect(find.text('Error'), findsOneWidget); + expect(find.text('Something went wrong'), findsOneWidget); + expect(find.text('OK'), findsOneWidget); }); - testWidgets('show displays the dialog and Ok dismisses it', ( + testWidgets('show displays the dialog and dismiss closes it', ( tester, ) async { await tester.pumpWidget( @@ -49,7 +38,9 @@ void main() { return ElevatedButton( onPressed: () => ErrorDialogWidget.show( context, + title: 'Error', description: 'Failed to save', + dismissLabel: 'OK', ), child: const Text('Trigger'), ); diff --git a/test/shared/widgets/states/error_state_widget_test.dart b/packages/app_ui/test/src/components/error_state_widget_test.dart similarity index 91% rename from test/shared/widgets/states/error_state_widget_test.dart rename to packages/app_ui/test/src/components/error_state_widget_test.dart index 638f9036..1c52e7d1 100644 --- a/test/shared/widgets/states/error_state_widget_test.dart +++ b/packages/app_ui/test/src/components/error_state_widget_test.dart @@ -1,8 +1,6 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/button_widget.dart'; -import 'package:sql_studio/src/shared/widgets/states/error_state_widget.dart'; void main() { Widget wrap(Widget child) => MaterialApp( diff --git a/test/shared/widgets/dialogs/input_dialog_widget_test.dart b/packages/app_ui/test/src/components/input_dialog_widget_test.dart similarity index 77% rename from test/shared/widgets/dialogs/input_dialog_widget_test.dart rename to packages/app_ui/test/src/components/input_dialog_widget_test.dart index 9c6f4544..096745fd 100644 --- a/test/shared/widgets/dialogs/input_dialog_widget_test.dart +++ b/packages/app_ui/test/src/components/input_dialog_widget_test.dart @@ -1,19 +1,18 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; -import 'package:sql_studio/src/shared/widgets/dialogs/input_dialog_widget.dart'; void main() { Widget wrap(Widget child) => MaterialApp( theme: AppTheme.light, - supportedLocales: AppLocalizations.supportedLocales, - localizationsDelegates: AppLocalizations.localizationsDelegates, home: Scaffold(body: child), ); + Widget cancelButton() => + ElevatedButton(onPressed: () {}, child: const Text('Cancel')); + group('InputDialogWidget', () { - testWidgets('renders title and label', (tester) async { + testWidgets('renders title, label and submit text', (tester) async { final controller = TextEditingController(); await tester.pumpWidget( @@ -22,6 +21,8 @@ void main() { title: 'New database', controller: controller, label: 'Name', + cancelButton: cancelButton(), + submitText: 'Submit', onSubmit: (value) async => true, ), ), @@ -29,11 +30,10 @@ void main() { expect(find.text('New database'), findsOneWidget); expect(find.text('Name'), findsOneWidget); + expect(find.text('Submit'), findsOneWidget); }); - testWidgets('shows validation error for empty input by default', ( - tester, - ) async { + testWidgets('runs a caller-supplied validator on submit', (tester) async { final controller = TextEditingController(); await tester.pumpWidget( @@ -42,6 +42,11 @@ void main() { title: 'New database', controller: controller, label: 'Name', + cancelButton: cancelButton(), + submitText: 'Submit', + validator: (value) => (value == null || value.trim().isEmpty) + ? 'This field is required' + : null, onSubmit: (value) async => true, ), ), @@ -69,6 +74,8 @@ void main() { title: 'New database', controller: controller, label: 'Name', + cancelButton: cancelButton(), + submitText: 'Submit', onSubmit: (value) async { submittedValue = value; return true; @@ -103,6 +110,8 @@ void main() { title: 'New database', controller: controller, label: 'Name', + cancelButton: cancelButton(), + submitText: 'Submit', onSubmit: (value) async => false, ), ), @@ -115,7 +124,9 @@ void main() { expect(find.text('New database'), findsOneWidget); }); - testWidgets('uses the custom submitText when provided', (tester) async { + testWidgets('renders the cancel button passed by the caller', ( + tester, + ) async { final controller = TextEditingController(); await tester.pumpWidget( @@ -124,14 +135,15 @@ void main() { title: 'Rename', controller: controller, label: 'Name', + cancelButton: cancelButton(), submitText: 'Save', onSubmit: (value) async => true, ), ), ); + expect(find.text('Cancel'), findsOneWidget); expect(find.text('Save'), findsOneWidget); - expect(find.text('Submit'), findsNothing); }); }); } diff --git a/test/shared/widgets/input_widget_test.dart b/packages/app_ui/test/src/components/input_widget_test.dart similarity index 93% rename from test/shared/widgets/input_widget_test.dart rename to packages/app_ui/test/src/components/input_widget_test.dart index 5272e3f8..b58d1c44 100644 --- a/test/shared/widgets/input_widget_test.dart +++ b/packages/app_ui/test/src/components/input_widget_test.dart @@ -1,7 +1,6 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; -import 'package:sql_studio/src/shared/widgets/input_widget.dart'; void main() { Widget wrap(Widget child) => MaterialApp( diff --git a/test/shared/widgets/buttons/loading_button_widget_test.dart b/packages/app_ui/test/src/components/loading_button_widget_test.dart similarity index 93% rename from test/shared/widgets/buttons/loading_button_widget_test.dart rename to packages/app_ui/test/src/components/loading_button_widget_test.dart index bf89a8a5..2e99f1eb 100644 --- a/test/shared/widgets/buttons/loading_button_widget_test.dart +++ b/packages/app_ui/test/src/components/loading_button_widget_test.dart @@ -1,9 +1,8 @@ import 'dart:async'; +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/loading_button_widget.dart'; void main() { Widget wrap(Widget child) => MaterialApp( diff --git a/test/shared/widgets/states/loading_state_widget_test.dart b/packages/app_ui/test/src/components/loading_state_widget_test.dart similarity index 91% rename from test/shared/widgets/states/loading_state_widget_test.dart rename to packages/app_ui/test/src/components/loading_state_widget_test.dart index 5a8f32dc..77bac0a7 100644 --- a/test/shared/widgets/states/loading_state_widget_test.dart +++ b/packages/app_ui/test/src/components/loading_state_widget_test.dart @@ -1,7 +1,6 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; -import 'package:sql_studio/src/shared/widgets/states/loading_state_widget.dart'; void main() { Widget wrap(Widget child) => MaterialApp( diff --git a/test/shared/widgets/popup_menu_section_header_widget_test.dart b/packages/app_ui/test/src/components/popup_menu_section_header_widget_test.dart similarity index 87% rename from test/shared/widgets/popup_menu_section_header_widget_test.dart rename to packages/app_ui/test/src/components/popup_menu_section_header_widget_test.dart index 325ff78e..8367e957 100644 --- a/test/shared/widgets/popup_menu_section_header_widget_test.dart +++ b/packages/app_ui/test/src/components/popup_menu_section_header_widget_test.dart @@ -1,7 +1,6 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; -import 'package:sql_studio/src/shared/widgets/popup_menu_section_header_widget.dart'; void main() { Widget wrap(WidgetBuilder builder) => MaterialApp( diff --git a/test/shared/widgets/switch_widget_test.dart b/packages/app_ui/test/src/components/switch_widget_test.dart similarity index 89% rename from test/shared/widgets/switch_widget_test.dart rename to packages/app_ui/test/src/components/switch_widget_test.dart index fda15263..f30b6d88 100644 --- a/test/shared/widgets/switch_widget_test.dart +++ b/packages/app_ui/test/src/components/switch_widget_test.dart @@ -1,7 +1,6 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; -import 'package:sql_studio/src/shared/widgets/switch_widget.dart'; void main() { Widget wrap(Widget child) => MaterialApp( diff --git a/test/shared/utils/button_style_util_test.dart b/packages/app_ui/test/src/utils/button_style_util_test.dart similarity index 90% rename from test/shared/utils/button_style_util_test.dart rename to packages/app_ui/test/src/utils/button_style_util_test.dart index 1952b85e..289bc224 100644 --- a/test/shared/utils/button_style_util_test.dart +++ b/packages/app_ui/test/src/utils/button_style_util_test.dart @@ -1,8 +1,6 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:sql_studio/src/core/app_colors.dart'; -import 'package:sql_studio/src/shared/utils/button_style_util.dart'; -import 'package:sql_studio/src/shared/widgets/buttons/button_widget.dart'; void main() { const colors = AppColors.light; diff --git a/test/shared/utils/text_scaling_test.dart b/packages/app_ui/test/src/utils/text_scaling_test.dart similarity index 95% rename from test/shared/utils/text_scaling_test.dart rename to packages/app_ui/test/src/utils/text_scaling_test.dart index 66747fdd..1a8bb56f 100644 --- a/test/shared/utils/text_scaling_test.dart +++ b/packages/app_ui/test/src/utils/text_scaling_test.dart @@ -1,6 +1,6 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:sql_studio/src/shared/utils/text_scaling.dart'; void main() { Future scalerUnder(WidgetTester tester, double platform) async { diff --git a/pubspec.lock b/pubspec.lock index 89b5c448..1c323845 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,6 +17,13 @@ packages: url: "https://pub.dev" source: hosted version: "10.0.1" + app_ui: + dependency: "direct main" + description: + path: "packages/app_ui" + relative: true + source: path + version: "0.0.1" archive: dependency: transitive description: @@ -1002,7 +1009,7 @@ packages: source: hosted version: "3.2.5" url_launcher_platform_interface: - dependency: transitive + dependency: "direct dev" description: name: url_launcher_platform_interface sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" diff --git a/pubspec.yaml b/pubspec.yaml index 28a29d11..e84e2074 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,12 +1,14 @@ name: sql_studio description: "Practice SQL on your phone with local, editable, fully offline SQLite databases." publish_to: "none" -version: 0.3.0+4 +version: 0.4.0+5 environment: sdk: ^3.9.2 dependencies: + app_ui: + path: packages/app_ui cross_file: ^0.3.5 flutter: sdk: flutter @@ -37,6 +39,7 @@ dev_dependencies: mocktail: ^1.0.4 share_plus_platform_interface: ^7.2.0 sqflite_common_ffi: ^2.3.4+4 + url_launcher_platform_interface: ^2.3.2 very_good_analysis: ^10.2.0 flutter: diff --git a/renovate.json b/renovate.json new file mode 100644 index 00000000..987b969a --- /dev/null +++ b/renovate.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:recommended", ":semanticCommits"], + "timezone": "America/Sao_Paulo", + "schedule": ["before 6am on monday"], + "labels": ["dependencies"], + "dependencyDashboard": true, + "commitMessagePrefix": "build(deps):", + "packageRules": [ + { + "description": "Actions are safe to take without review noise.", + "matchManagers": ["github-actions"], + "matchUpdateTypes": ["minor", "patch"], + "groupName": "github actions" + } + ] +} diff --git a/scripts/verify.sh b/scripts/verify.sh index 927bd069..f28bd9a3 100755 --- a/scripts/verify.sh +++ b/scripts/verify.sh @@ -1,7 +1,8 @@ #!/usr/bin/env bash # Local quality gate mirroring CI, step for step: regenerate l10n and # fail if it changed anything, check_l10n.sh, format, analyze, test with -# coverage, check_coverage.sh. +# coverage, check_coverage.sh, for the root app and, the same four +# checks, for packages/app_ui. # # Uses `fvm flutter`/`fvm dart` when FVM is set up for this project, and # the bare `flutter`/`dart` otherwise, so contributors without FVM see @@ -18,7 +19,10 @@ set -euo pipefail cd "$(dirname "${BASH_SOURCE[0]}")/.." +# Kept in sync by hand with ci.yaml's `quality` and `app_ui` jobs; nothing +# enforces the two match, so a change to either floor needs both updated. COVERAGE_MINIMUM=91 +APP_UI_COVERAGE_MINIMUM=79 SKIP_TESTS=false for arg in "$@"; do @@ -45,6 +49,36 @@ step() { echo "── $1 ──" } +# Runs format, analyze, and (unless --skip-tests) test with the coverage +# gate inside one package directory. +# +# $1 directory to run in, relative to the repo root +# $2 minimum line coverage percentage +# $3 human-readable name, for the step headings +verify_package() { + local dir="$1" minimum="$2" name="$3" + + step "$name: flutter pub get" + (cd "$dir" && "${FLUTTER[@]}" pub get) + + step "$name: dart format --set-exit-if-changed" + (cd "$dir" && "${DART[@]}" format --set-exit-if-changed lib/ test/) + + step "$name: flutter analyze" + (cd "$dir" && "${FLUTTER[@]}" analyze) + + if [[ "$SKIP_TESTS" == true ]]; then + step "$name: tests skipped (--skip-tests)" + return + fi + + step "$name: flutter test --coverage" + (cd "$dir" && "${FLUTTER[@]}" test --coverage) + + step "$name: check_coverage.sh" + scripts/check_coverage.sh "$dir/coverage/lcov.info" "$minimum" +} + step "flutter pub get" "${FLUTTER[@]}" pub get @@ -65,6 +99,8 @@ step "dart format --set-exit-if-changed" step "flutter analyze" "${FLUTTER[@]}" analyze +verify_package packages/app_ui "$APP_UI_COVERAGE_MINIMUM" "packages/app_ui" + if [[ "$SKIP_TESTS" == true ]]; then echo "" echo "Skipped tests and coverage (--skip-tests). This is not the final gate." diff --git a/scripts/workspace_hash.sh b/scripts/workspace_hash.sh index 72414f72..b8203195 100755 --- a/scripts/workspace_hash.sh +++ b/scripts/workspace_hash.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # Prints a hash of the current source tree: every tracked file plus -# every untracked, non-ignored file under lib/, test/ and the config -# files verify.sh checks. Used to tell whether the workspace has +# every untracked, non-ignored file under lib/, test/, packages/ and the +# config files verify.sh checks. Used to tell whether the workspace has # changed since the last successful verify.sh run. # # Usage: @@ -12,6 +12,6 @@ set -euo pipefail cd "$(dirname "${BASH_SOURCE[0]}")/.." { - git ls-files -z -- lib test pubspec.yaml pubspec.lock l10n.yaml analysis_options.yaml - git ls-files -z --others --exclude-standard -- lib test + git ls-files -z -- lib test packages pubspec.yaml pubspec.lock l10n.yaml analysis_options.yaml + git ls-files -z --others --exclude-standard -- lib test packages } | LC_ALL=C sort -zu | xargs -0 sha256sum | sha256sum | cut -d' ' -f1 diff --git a/test/core/navigation/root_navigation_test.dart b/test/core/navigation/root_navigation_test.dart index 56a0d678..c6dd4c7e 100644 --- a/test/core/navigation/root_navigation_test.dart +++ b/test/core/navigation/root_navigation_test.dart @@ -1,8 +1,8 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/core/navigation/root_navigation.dart'; import 'package:sql_studio/src/core/providers/core_providers.dart'; import 'package:sql_studio/src/core/providers/navigation_provider.dart'; diff --git a/test/core/navigation/widgets/root_drawer/create_database_dialog_widget_test.dart b/test/core/navigation/widgets/root_drawer/create_database_dialog_widget_test.dart index 33bc3dfa..43847e18 100644 --- a/test/core/navigation/widgets/root_drawer/create_database_dialog_widget_test.dart +++ b/test/core/navigation/widgets/root_drawer/create_database_dialog_widget_test.dart @@ -1,10 +1,10 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/core/error/result.dart'; import 'package:sql_studio/src/core/navigation/widgets/root_drawer/create_database_dialog_widget.dart'; import 'package:sql_studio/src/core/providers/core_providers.dart'; diff --git a/test/core/navigation/widgets/root_drawer/drawer_widget_test.dart b/test/core/navigation/widgets/root_drawer/drawer_widget_test.dart index e5c967a8..382db45a 100644 --- a/test/core/navigation/widgets/root_drawer/drawer_widget_test.dart +++ b/test/core/navigation/widgets/root_drawer/drawer_widget_test.dart @@ -1,11 +1,11 @@ import 'dart:async'; +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/core/error/result.dart'; import 'package:sql_studio/src/core/navigation/widgets/root_drawer/create_database_dialog_widget.dart'; import 'package:sql_studio/src/core/navigation/widgets/root_drawer/drawer_widget.dart'; diff --git a/test/core/navigation/widgets/root_drawer/root_drawer_database_group/database_delete_dialog_widget_test.dart b/test/core/navigation/widgets/root_drawer/root_drawer_database_group/database_delete_dialog_widget_test.dart index 13f97665..8cbd2382 100644 --- a/test/core/navigation/widgets/root_drawer/root_drawer_database_group/database_delete_dialog_widget_test.dart +++ b/test/core/navigation/widgets/root_drawer/root_drawer_database_group/database_delete_dialog_widget_test.dart @@ -1,7 +1,7 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/core/navigation/widgets/root_drawer/root_drawer_database_group/database_delete_dialog_widget.dart'; void main() { diff --git a/test/core/navigation/widgets/root_drawer/root_drawer_database_group/root_drawer_database_card_widget_test.dart b/test/core/navigation/widgets/root_drawer/root_drawer_database_group/root_drawer_database_card_widget_test.dart index aab0032a..1d45d07a 100644 --- a/test/core/navigation/widgets/root_drawer/root_drawer_database_group/root_drawer_database_card_widget_test.dart +++ b/test/core/navigation/widgets/root_drawer/root_drawer_database_group/root_drawer_database_card_widget_test.dart @@ -1,9 +1,9 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/core/enums/app_localizations_key.dart'; import 'package:sql_studio/src/core/error/result.dart'; import 'package:sql_studio/src/core/navigation/widgets/root_drawer/root_drawer_database_group/database_delete_dialog_widget.dart'; @@ -96,7 +96,7 @@ void main() { expect( container.read(sqlCommandsViewModelProvider).activeDatabase, - 'Todo List', + 'todo_list', ); expect(container.read(navigationViewModelProvider), 0); expect(scaffoldState.isDrawerOpen, isFalse); diff --git a/test/core/navigation/widgets/root_drawer/root_drawer_database_group/root_drawer_database_group_widget_test.dart b/test/core/navigation/widgets/root_drawer/root_drawer_database_group/root_drawer_database_group_widget_test.dart index 99ca74a6..9929f711 100644 --- a/test/core/navigation/widgets/root_drawer/root_drawer_database_group/root_drawer_database_group_widget_test.dart +++ b/test/core/navigation/widgets/root_drawer/root_drawer_database_group/root_drawer_database_group_widget_test.dart @@ -1,8 +1,8 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/core/navigation/widgets/root_drawer/root_drawer_database_group/root_drawer_database_card_widget.dart'; import 'package:sql_studio/src/core/navigation/widgets/root_drawer/root_drawer_database_group/root_drawer_database_group_widget.dart'; import 'package:sql_studio/src/core/providers/core_providers.dart'; diff --git a/test/core/navigation/widgets/root_nav_bar_widget_test.dart b/test/core/navigation/widgets/root_nav_bar_widget_test.dart index 9892330a..559e862b 100644 --- a/test/core/navigation/widgets/root_nav_bar_widget_test.dart +++ b/test/core/navigation/widgets/root_nav_bar_widget_test.dart @@ -1,8 +1,8 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/core/navigation/widgets/root_nav_bar_widget.dart'; import 'package:sql_studio/src/core/providers/navigation_provider.dart'; diff --git a/test/core/screens/about/about_screen_test.dart b/test/core/screens/about/about_screen_test.dart index d0cca8da..6f3ab64d 100644 --- a/test/core/screens/about/about_screen_test.dart +++ b/test/core/screens/about/about_screen_test.dart @@ -1,14 +1,17 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/misc.dart' show Override; import 'package:flutter_test/flutter_test.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/core/screens/about/about_screen.dart'; import 'package:sql_studio/src/core/screens/about/licenses_screen.dart'; import 'package:sql_studio/src/features/app_version/presentation/app_version_providers.dart'; import 'package:sql_studio/src/features/app_version/presentation/view_models/app_version_state.dart'; import 'package:sql_studio/src/features/app_version/presentation/view_models/app_version_view_model.dart'; +import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; + +import '../../../test_helpers/fake_url_launcher_platform.dart'; class _FixedAppVersionViewModel extends AppVersionViewModel { @override @@ -63,4 +66,22 @@ void main() { expect(find.byType(LicensesScreen), findsOneWidget); }); + + testWidgets('shows an error dialog when opening the developer link fails', ( + tester, + ) async { + final originalPlatform = UrlLauncherPlatform.instance; + addTearDown(() => UrlLauncherPlatform.instance = originalPlatform); + UrlLauncherPlatform.instance = FakeUrlLauncherPlatform( + shouldLaunch: false, + ); + + await tester.pumpWidget(wrap(const AboutScreen())); + + await tester.tap(find.textContaining('Dario Matias')); + await tester.pumpAndSettle(); + + final l10n = await AppLocalizations.delegate.load(const Locale('en')); + expect(find.text(l10n.errorOpeningUrl), findsOneWidget); + }); } diff --git a/test/core/screens/about/license_detail_screen_test.dart b/test/core/screens/about/license_detail_screen_test.dart index 66167480..f2e3587e 100644 --- a/test/core/screens/about/license_detail_screen_test.dart +++ b/test/core/screens/about/license_detail_screen_test.dart @@ -1,8 +1,8 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/core/screens/about/license_detail_screen.dart'; void main() { diff --git a/test/core/screens/about/licenses_screen_test.dart b/test/core/screens/about/licenses_screen_test.dart index 5266766b..8f0d4d46 100644 --- a/test/core/screens/about/licenses_screen_test.dart +++ b/test/core/screens/about/licenses_screen_test.dart @@ -1,9 +1,9 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/core/screens/about/license_detail_screen.dart'; import 'package:sql_studio/src/core/screens/about/licenses_screen.dart'; diff --git a/test/core/screens/home/home_screen_test.dart b/test/core/screens/home/home_screen_test.dart index 80e07138..0c64189b 100644 --- a/test/core/screens/home/home_screen_test.dart +++ b/test/core/screens/home/home_screen_test.dart @@ -1,8 +1,8 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/core/providers/core_providers.dart'; import 'package:sql_studio/src/core/screens/home/home_screen.dart'; import 'package:sql_studio/src/core/services/shared_preferences_service.dart'; diff --git a/test/core/screens/not_found/not_found_screen_test.dart b/test/core/screens/not_found/not_found_screen_test.dart index ce1c1ab6..38ef8997 100644 --- a/test/core/screens/not_found/not_found_screen_test.dart +++ b/test/core/screens/not_found/not_found_screen_test.dart @@ -1,8 +1,8 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:go_router/go_router.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/core/screens/not_found/not_found_screen.dart'; void main() { diff --git a/test/core/screens/settings/settings_screen_test.dart b/test/core/screens/settings/settings_screen_test.dart index 3a16c050..4a26b167 100644 --- a/test/core/screens/settings/settings_screen_test.dart +++ b/test/core/screens/settings/settings_screen_test.dart @@ -1,13 +1,15 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/core/constants/shared_preferences_keys.dart'; import 'package:sql_studio/src/core/providers/core_providers.dart'; import 'package:sql_studio/src/core/screens/settings/settings_screen.dart'; import 'package:sql_studio/src/core/services/shared_preferences_service.dart'; +import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; +import '../../../test_helpers/fake_url_launcher_platform.dart'; import '../../../test_helpers/shared_preferences_test_helper.dart'; void main() { @@ -67,4 +69,24 @@ void main() { expect(find.text('Español'), findsNothing); expect(find.text('Português'), findsNothing); }); + + testWidgets('shows an error dialog when opening a link fails', ( + tester, + ) async { + final originalPlatform = UrlLauncherPlatform.instance; + addTearDown(() => UrlLauncherPlatform.instance = originalPlatform); + UrlLauncherPlatform.instance = FakeUrlLauncherPlatform( + shouldLaunch: false, + ); + + await initPrefs(); + + await tester.pumpWidget(wrap(const SettingsScreen())); + + await tester.tap(find.text('Official Website')); + await tester.pumpAndSettle(); + + final l10n = await AppLocalizations.delegate.load(const Locale('en')); + expect(find.text(l10n.errorOpeningUrl), findsOneWidget); + }); } diff --git a/test/core/screens/settings/widgets/language_selector_sheet_widget_test.dart b/test/core/screens/settings/widgets/language_selector_sheet_widget_test.dart index 69e72fda..b050030d 100644 --- a/test/core/screens/settings/widgets/language_selector_sheet_widget_test.dart +++ b/test/core/screens/settings/widgets/language_selector_sheet_widget_test.dart @@ -1,9 +1,9 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/core/providers/app_localization_provider.dart'; import 'package:sql_studio/src/core/providers/core_providers.dart'; import 'package:sql_studio/src/core/screens/settings/widgets/language_selector_sheet/language_selector_sheet_widget.dart'; diff --git a/test/core/screens/settings/widgets/theme_selector_sheet_widget_test.dart b/test/core/screens/settings/widgets/theme_selector_sheet_widget_test.dart index b980a9ab..bc8e5647 100644 --- a/test/core/screens/settings/widgets/theme_selector_sheet_widget_test.dart +++ b/test/core/screens/settings/widgets/theme_selector_sheet_widget_test.dart @@ -1,9 +1,9 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/core/providers/app_theme_mode_provider.dart'; import 'package:sql_studio/src/core/providers/core_providers.dart'; import 'package:sql_studio/src/core/screens/settings/widgets/theme_selector_sheet/theme_selector_sheet_widget.dart'; diff --git a/test/core/screens/splash/splash_screen_test.dart b/test/core/screens/splash/splash_screen_test.dart index a7192019..15552149 100644 --- a/test/core/screens/splash/splash_screen_test.dart +++ b/test/core/screens/splash/splash_screen_test.dart @@ -1,9 +1,9 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:go_router/go_router.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/core/error/result.dart'; import 'package:sql_studio/src/core/providers/core_providers.dart'; import 'package:sql_studio/src/core/screens/splash/splash_screen.dart'; @@ -30,7 +30,7 @@ import '../../../test_helpers/shared_preferences_test_helper.dart'; /// These fakes let the splash screen's real loading sequence run to /// completion without touching platform channels, sqflite, or the -/// filesystem — every dependency it drives during `_loadResources` +/// filesystem, so every dependency it drives during `_loadResources` /// resolves instantly. class _FakeAppVersionViewModel extends AppVersionViewModel { @override diff --git a/test/features/database/presentation/screens/databases_screen_test.dart b/test/features/database/presentation/screens/databases_screen_test.dart index 78eeb4aa..f04ec003 100644 --- a/test/features/database/presentation/screens/databases_screen_test.dart +++ b/test/features/database/presentation/screens/databases_screen_test.dart @@ -1,8 +1,8 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/features/database/presentation/screens/databases_screen.dart'; void main() { diff --git a/test/features/database/presentation/widgets/database_card_widget_test.dart b/test/features/database/presentation/widgets/database_card_widget_test.dart index 0bc691c0..e3facaa2 100644 --- a/test/features/database/presentation/widgets/database_card_widget_test.dart +++ b/test/features/database/presentation/widgets/database_card_widget_test.dart @@ -1,9 +1,9 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/core/constants/default_databases.dart'; import 'package:sql_studio/src/core/providers/core_providers.dart'; import 'package:sql_studio/src/core/providers/navigation_provider.dart'; diff --git a/test/features/database_visualizer/presentation/painters/grid_background_painter_test.dart b/test/features/database_visualizer/presentation/painters/grid_background_painter_test.dart index 92d8a8cd..6bcef763 100644 --- a/test/features/database_visualizer/presentation/painters/grid_background_painter_test.dart +++ b/test/features/database_visualizer/presentation/painters/grid_background_painter_test.dart @@ -1,5 +1,5 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:sql_studio/src/core/app_colors.dart'; import 'package:sql_studio/src/features/database_visualizer/presentation/painters/grid_background_painter.dart'; void main() { diff --git a/test/features/database_visualizer/presentation/painters/table_relation_painter_test.dart b/test/features/database_visualizer/presentation/painters/table_relation_painter_test.dart index 1f2e1ca0..e69c687a 100644 --- a/test/features/database_visualizer/presentation/painters/table_relation_painter_test.dart +++ b/test/features/database_visualizer/presentation/painters/table_relation_painter_test.dart @@ -1,6 +1,8 @@ +import 'dart:ui' as ui; + +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:sql_studio/src/core/app_colors.dart'; import 'package:sql_studio/src/features/database_visualizer/domain/entities/table_info_entity.dart'; import 'package:sql_studio/src/features/database_visualizer/presentation/painters/table_relation_painter.dart'; @@ -61,4 +63,114 @@ void main() { ); }); }); + + group('TableRelationPainter.paint', () { + Canvas recordingCanvas() => Canvas(ui.PictureRecorder()); + + test('skips relations whose source or target rect is missing', () { + final withRelation = [ + TableInfoEntity( + name: 'orders', + columns: [ + ColumnInfoEntity( + name: 'user_id', + type: 'INTEGER', + foreignTable: 'users', + ), + ], + ), + ]; + + expect( + () => painter(tables_: withRelation, tableRects_: {}).paint( + recordingCanvas(), + const Size(200, 200), + ), + returnsNormally, + ); + }); + + test('draws a relation with no selection', () { + final tables_ = [ + TableInfoEntity( + name: 'orders', + columns: [ + ColumnInfoEntity( + name: 'user_id', + type: 'INTEGER', + foreignTable: 'users', + ), + ], + ), + ]; + final tableRects_ = { + 'orders': const Rect.fromLTWH(0, 0, 100, 100), + 'users': const Rect.fromLTWH(200, 0, 100, 100), + }; + + expect( + () => painter( + tables_: tables_, + tableRects_: tableRects_, + ).paint(recordingCanvas(), const Size(400, 200)), + returnsNormally, + ); + }); + + test('highlights a relation touching the selected table', () { + final tables_ = [ + TableInfoEntity( + name: 'orders', + columns: [ + ColumnInfoEntity( + name: 'user_id', + type: 'INTEGER', + foreignTable: 'users', + ), + ], + ), + ]; + final tableRects_ = { + 'orders': const Rect.fromLTWH(200, 0, 100, 100), + 'users': const Rect.fromLTWH(0, 0, 100, 100), + }; + + expect( + () => painter( + tables_: tables_, + tableRects_: tableRects_, + selectedTable: 'users', + ).paint(recordingCanvas(), const Size(400, 200)), + returnsNormally, + ); + }); + + test('dims a relation that does not touch the selected table', () { + final tables_ = [ + TableInfoEntity( + name: 'orders', + columns: [ + ColumnInfoEntity( + name: 'user_id', + type: 'INTEGER', + foreignTable: 'users', + ), + ], + ), + ]; + final tableRects_ = { + 'orders': const Rect.fromLTWH(0, 0, 100, 100), + 'users': const Rect.fromLTWH(200, 0, 100, 100), + }; + + expect( + () => painter( + tables_: tables_, + tableRects_: tableRects_, + selectedTable: 'products', + ).paint(recordingCanvas(), const Size(400, 200)), + returnsNormally, + ); + }); + }); } diff --git a/test/features/database_visualizer/presentation/view_models/database_visualizer_view_model_test.dart b/test/features/database_visualizer/presentation/view_models/database_visualizer_view_model_test.dart index 1a22a815..0fca6f62 100644 --- a/test/features/database_visualizer/presentation/view_models/database_visualizer_view_model_test.dart +++ b/test/features/database_visualizer/presentation/view_models/database_visualizer_view_model_test.dart @@ -77,7 +77,7 @@ void main() { }); test( - 'load() works again after the autoDispose provider is recreated — ' + 'load() works again after the autoDispose provider is recreated: ' 'regression test for the LateInitializationError previously thrown ' 'by a cached late field', () async { diff --git a/test/features/database_visualizer/presentation/widgets/database_visualizer_table_widget_test.dart b/test/features/database_visualizer/presentation/widgets/database_visualizer_table_widget_test.dart index 347c8223..c100bc67 100644 --- a/test/features/database_visualizer/presentation/widgets/database_visualizer_table_widget_test.dart +++ b/test/features/database_visualizer/presentation/widgets/database_visualizer_table_widget_test.dart @@ -1,7 +1,7 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/features/database_visualizer/domain/entities/table_info_entity.dart'; import 'package:sql_studio/src/features/database_visualizer/presentation/widgets/database_visualizer_table_widget.dart'; diff --git a/test/features/database_visualizer/presentation/widgets/zoom_controls_widget_test.dart b/test/features/database_visualizer/presentation/widgets/zoom_controls_widget_test.dart index 750ac561..69e8d715 100644 --- a/test/features/database_visualizer/presentation/widgets/zoom_controls_widget_test.dart +++ b/test/features/database_visualizer/presentation/widgets/zoom_controls_widget_test.dart @@ -1,7 +1,7 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/features/database_visualizer/presentation/widgets/zoom_controls_widget.dart'; void main() { diff --git a/test/shared/utils/handle_error_test.dart b/test/shared/utils/handle_error_test.dart index cad24e41..0d586b3d 100644 --- a/test/shared/utils/handle_error_test.dart +++ b/test/shared/utils/handle_error_test.dart @@ -1,7 +1,7 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/core/enums/app_localizations_key.dart'; import 'package:sql_studio/src/core/error/result.dart'; import 'package:sql_studio/src/shared/utils/handle_error.dart'; diff --git a/test/shared/widgets/buttons/cancel_button_widget_test.dart b/test/shared/widgets/buttons/cancel_button_widget_test.dart index d3c63f8b..ae1d5cdc 100644 --- a/test/shared/widgets/buttons/cancel_button_widget_test.dart +++ b/test/shared/widgets/buttons/cancel_button_widget_test.dart @@ -1,7 +1,7 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/shared/widgets/buttons/cancel_button_widget.dart'; void main() { diff --git a/test/shared/widgets/popup_menu_button_widget_test.dart b/test/shared/widgets/popup_menu_button_widget_test.dart index 92c527de..53b37d51 100644 --- a/test/shared/widgets/popup_menu_button_widget_test.dart +++ b/test/shared/widgets/popup_menu_button_widget_test.dart @@ -1,7 +1,7 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/shared/widgets/popup_menu_button_widget.dart'; void main() { diff --git a/test/shared/widgets/scaffold_widget_test.dart b/test/shared/widgets/scaffold_widget_test.dart index 1f45fbab..76b12b0f 100644 --- a/test/shared/widgets/scaffold_widget_test.dart +++ b/test/shared/widgets/scaffold_widget_test.dart @@ -1,8 +1,8 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:go_router/go_router.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/shared/widgets/scaffold_widget.dart'; void main() { diff --git a/test/shared/widgets/sql_workspace/console/console_widget_test.dart b/test/shared/widgets/sql_workspace/console/console_widget_test.dart index 375552ec..323e77d6 100644 --- a/test/shared/widgets/sql_workspace/console/console_widget_test.dart +++ b/test/shared/widgets/sql_workspace/console/console_widget_test.dart @@ -1,11 +1,11 @@ import 'dart:async'; +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/core/enums/app_localizations_key.dart'; import 'package:sql_studio/src/core/error/result.dart'; import 'package:sql_studio/src/core/providers/core_providers.dart'; diff --git a/test/shared/widgets/sql_workspace/console/styled_data_table_widget_test.dart b/test/shared/widgets/sql_workspace/console/styled_data_table_widget_test.dart index e366eb0c..e1c0fd8e 100644 --- a/test/shared/widgets/sql_workspace/console/styled_data_table_widget_test.dart +++ b/test/shared/widgets/sql_workspace/console/styled_data_table_widget_test.dart @@ -1,7 +1,7 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/shared/widgets/sql_workspace/console/styled_data_table_widget.dart'; void main() { diff --git a/test/shared/widgets/sql_workspace/divider_bar_widget_test.dart b/test/shared/widgets/sql_workspace/divider_bar_widget_test.dart index f2ac20b3..95f326cf 100644 --- a/test/shared/widgets/sql_workspace/divider_bar_widget_test.dart +++ b/test/shared/widgets/sql_workspace/divider_bar_widget_test.dart @@ -1,6 +1,6 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/shared/widgets/sql_workspace/divider_bar_widget.dart'; void main() { diff --git a/test/shared/widgets/sql_workspace/panel_widget_test.dart b/test/shared/widgets/sql_workspace/panel_widget_test.dart index de07f634..3c77ff82 100644 --- a/test/shared/widgets/sql_workspace/panel_widget_test.dart +++ b/test/shared/widgets/sql_workspace/panel_widget_test.dart @@ -1,7 +1,7 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/shared/widgets/sql_workspace/panel_widget.dart'; void main() { diff --git a/test/shared/widgets/sql_workspace/sql_editor/sql_editor_widget_test.dart b/test/shared/widgets/sql_workspace/sql_editor/sql_editor_widget_test.dart index f959e3ee..c0b20823 100644 --- a/test/shared/widgets/sql_workspace/sql_editor/sql_editor_widget_test.dart +++ b/test/shared/widgets/sql_workspace/sql_editor/sql_editor_widget_test.dart @@ -1,10 +1,10 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/core/error/result.dart'; import 'package:sql_studio/src/core/providers/core_providers.dart'; import 'package:sql_studio/src/features/sql_editor/data/providers/sql_editor_data_providers.dart'; diff --git a/test/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_advanced_suggestions_bar_widget_test.dart b/test/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_advanced_suggestions_bar_widget_test.dart index 82d4423d..ee0b9123 100644 --- a/test/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_advanced_suggestions_bar_widget_test.dart +++ b/test/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_advanced_suggestions_bar_widget_test.dart @@ -1,7 +1,7 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/features/sql_suggestions/domain/entities/sql_advanced_suggestion_entity.dart'; import 'package:sql_studio/src/features/sql_suggestions/presentation/sql_suggestions_providers.dart'; import 'package:sql_studio/src/features/sql_suggestions/presentation/view_models/sql_advanced_suggestions_state.dart'; diff --git a/test/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_basic_suggestions_bar_widget_test.dart b/test/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_basic_suggestions_bar_widget_test.dart index e4eef4ad..f7fa495d 100644 --- a/test/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_basic_suggestions_bar_widget_test.dart +++ b/test/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_basic_suggestions_bar_widget_test.dart @@ -1,7 +1,7 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/core/constants/default_sql_suggestions/default_sql_basic_suggestions.dart'; import 'package:sql_studio/src/features/sql_suggestions/presentation/sql_suggestions_providers.dart'; import 'package:sql_studio/src/features/sql_suggestions/presentation/view_models/sql_basic_suggestions_state.dart'; diff --git a/test/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_character_bar_widget_test.dart b/test/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_character_bar_widget_test.dart index ca806ba2..243d7742 100644 --- a/test/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_character_bar_widget_test.dart +++ b/test/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_character_bar_widget_test.dart @@ -1,6 +1,6 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_character_bar_widget.dart'; void main() { diff --git a/test/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_suggestions_bar_widget_test.dart b/test/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_suggestions_bar_widget_test.dart index 868673df..b28e97e1 100644 --- a/test/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_suggestions_bar_widget_test.dart +++ b/test/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_suggestions_bar_widget_test.dart @@ -1,6 +1,6 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/shared/widgets/sql_workspace/sql_editor/sql_suggestions_bars/sql_suggestions_bar_widget.dart'; void main() { diff --git a/test/shared/widgets/sql_workspace/sql_workspace_widget_test.dart b/test/shared/widgets/sql_workspace/sql_workspace_widget_test.dart index b7de9cc4..f1198a0d 100644 --- a/test/shared/widgets/sql_workspace/sql_workspace_widget_test.dart +++ b/test/shared/widgets/sql_workspace/sql_workspace_widget_test.dart @@ -1,8 +1,8 @@ +import 'package:app_ui/app_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:sql_studio/l10n/app_localizations.dart'; -import 'package:sql_studio/src/core/app_theme.dart'; import 'package:sql_studio/src/core/providers/core_providers.dart'; import 'package:sql_studio/src/core/services/shared_preferences_service.dart'; import 'package:sql_studio/src/shared/widgets/sql_workspace/console/console_widget.dart'; diff --git a/test/test_helpers/fake_url_launcher_platform.dart b/test/test_helpers/fake_url_launcher_platform.dart new file mode 100644 index 00000000..c0cb1510 --- /dev/null +++ b/test/test_helpers/fake_url_launcher_platform.dart @@ -0,0 +1,21 @@ +import 'package:url_launcher_platform_interface/link.dart'; +import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; + +/// A [UrlLauncherPlatform] whose [launchUrl] outcome is fixed at +/// construction, for testing the success and failure paths of code that +/// calls the top-level `launchUrl` without a real platform channel. +class FakeUrlLauncherPlatform extends UrlLauncherPlatform { + /// Creates a fake platform that resolves [launchUrl] to [shouldLaunch]. + FakeUrlLauncherPlatform({required this.shouldLaunch}); + + /// The result every [launchUrl] call resolves to. + final bool shouldLaunch; + + @override + LinkDelegate? get linkDelegate => null; + + @override + Future launchUrl(String url, LaunchOptions options) async { + return shouldLaunch; + } +}