diff --git a/.github/workflows/build_ios.yml b/.github/workflows/build_ios.yml deleted file mode 100644 index 2094ad3..0000000 --- a/.github/workflows/build_ios.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: iOS-ipa-build - -on: - workflow_dispatch: - # push: - # branches: - # - main - -jobs: - build-ios: - name: ๐ŸŽ‰ iOS Build - runs-on: macos-latest - steps: - - uses: actions/checkout@v3 - - - uses: subosito/flutter-action@v2 - with: - channel: 'stable' - architecture: x64 - - run: cd med_system_app && flutter pub get - - - - run: pod repo update - working-directory: med_system_app/ios - - - run: cd med_system_app && flutter build ios --release --no-codesign - - - run: mkdir Payload - working-directory: med_system_app/build/ios/iphoneos - - - run: mv Runner.app/ Payload - working-directory: med_system_app/build/ios/iphoneos - - - name: Zip output - run: zip -qq -r -9 FlutterIpaExport.ipa Payload - working-directory: med_system_app/build/ios/iphoneos - - - name: Upload binaries to release - uses: svenstaro/upload-release-action@v2 - with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - file: med_system_app/build/ios/iphoneos/FlutterIpaExport.ipa - tag: v1.0.${{ github.run_number }} - overwrite: true - body: "${{ github.event.commits }}" diff --git a/.github/workflows/flutter_cd.yml b/.github/workflows/flutter_cd.yml new file mode 100644 index 0000000..dd34c4c --- /dev/null +++ b/.github/workflows/flutter_cd.yml @@ -0,0 +1,61 @@ +name: Flutter CD - Android Build + +on: + release: + types: [published] + workflow_dispatch: # Permite executar manualmente + +jobs: + build: + name: Build APK + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '17' + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + channel: 'stable' + flutter-version: '3.19.4' + cache: false + + - name: Clean Dependencies + working-directory: med_system_app + run: | + rm -f pubspec.lock + flutter clean + flutter pub cache repair + + - name: Get Dependencies + working-directory: med_system_app + run: flutter pub get + + - name: Run Build Runner + working-directory: med_system_app + run: dart run build_runner build --delete-conflicting-outputs + + - name: Build APK + working-directory: med_system_app + run: flutter build apk --release + + - name: Upload APK Artifact + uses: actions/upload-artifact@v4 + with: + name: app-release + path: med_system_app/build/app/outputs/flutter-apk/app-release.apk + + - name: Upload APK to Release + if: github.event_name == 'release' + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: med_system_app/build/app/outputs/flutter-apk/app-release.apk + asset_name: med_system_app_${{ github.ref_name }}.apk + tag: ${{ github.ref }} diff --git a/.github/workflows/flutter_ci.yml b/.github/workflows/flutter_ci.yml new file mode 100644 index 0000000..857bd32 --- /dev/null +++ b/.github/workflows/flutter_ci.yml @@ -0,0 +1,59 @@ +name: Flutter CI + +on: + push: + branches: [ "main", "master", "develop" ] + pull_request: + branches: [ "main", "master", "develop" ] + +jobs: + build: + name: Build and Test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '17' + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + channel: 'stable' + flutter-version: '3.19.4' + cache: false + + - name: Verify Flutter Installation + run: | + flutter doctor -v + flutter --version + + - name: Clean Dependencies + working-directory: med_system_app + run: rm -f pubspec.lock + + - name: Get Dependencies + working-directory: med_system_app + run: flutter pub get + + - name: Run Build Runner + working-directory: med_system_app + run: dart run build_runner build --delete-conflicting-outputs + + - name: Analyze Code + working-directory: med_system_app + run: flutter analyze --no-fatal-infos + + - name: Run Tests + working-directory: med_system_app + run: flutter test --coverage + + - name: Upload Coverage + uses: codecov/codecov-action@v3 + with: + file: med_system_app/coverage/lcov.info + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/flutter_pr_build.yml b/.github/workflows/flutter_pr_build.yml new file mode 100644 index 0000000..8ee434c --- /dev/null +++ b/.github/workflows/flutter_pr_build.yml @@ -0,0 +1,53 @@ +name: Flutter PR Build + +on: + pull_request: + types: [opened, synchronize, reopened] + branches: [ "main", "master", "develop" ] + +jobs: + build-pr-apk: + name: Build PR APK + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'zulu' + java-version: '17' + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + channel: 'stable' + flutter-version: '3.19.4' + cache: false + + - name: Clean Dependencies + working-directory: med_system_app + run: | + rm -f pubspec.lock + flutter clean + flutter pub cache repair + + - name: Get Dependencies + working-directory: med_system_app + run: flutter pub get + + - name: Run Build Runner + working-directory: med_system_app + run: dart run build_runner build --delete-conflicting-outputs + + - name: Build APK (Release) + working-directory: med_system_app + run: flutter build apk --release + + - name: Upload APK Artifact + uses: actions/upload-artifact@v4 + with: + name: pr-${{ github.event.number }}-apk + path: med_system_app/build/app/outputs/flutter-apk/app-release.apk + retention-days: 14 diff --git a/README.md b/README.md index 37d371b..e4f1d2a 100644 --- a/README.md +++ b/README.md @@ -1 +1,1132 @@ -# med_system \ No newline at end of file +# Documentaรงรฃo do Projeto + +[![codecov](https://codecov.io/gh/espoo-dev/med_system/branch/main/graph/badge.svg)](https://codecov.io/gh/espoo-dev/med_system) + + +## Diagrama da Arquitetura - Feature de Autenticaรงรฃo + +## ๐Ÿ“ Visรฃo Geral da Clean Architecture + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ PRESENTATION LAYER โ”‚ +โ”‚ (UI + ViewModel + State) โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ SignInPage โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถโ”‚ SignInViewModel โ”‚ โ”‚ +โ”‚ โ”‚ (View/UI) โ”‚ โ”‚ (MobX) โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ - email โ”‚ โ”‚ +โ”‚ โ”‚ observa โ”‚ - password โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ - state โ”‚ โ”‚ +โ”‚ โ–ผ โ”‚ - currentUser โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ - isAuthenticated โ”‚ โ”‚ +โ”‚ โ”‚ Observer โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ (MobX) โ”‚ โ”‚ + signIn() โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ + loadCurrentUser() โ”‚ โ”‚ +โ”‚ โ”‚ + logout() โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ +26: โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ chama + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ DOMAIN LAYER โ”‚ +โ”‚ (Regras de Negรณcio Puras) โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ Use Cases โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ SignInUseCase โ”‚ โ”‚ GetCurrentUserUseCaseโ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ + call(params) โ”‚ โ”‚ + call(NoParams) โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ - Valida email โ”‚ โ”‚ - Obtรฉm usuรกrio โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ - Valida senha โ”‚ โ”‚ do storage โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ - Chama repo โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ LogoutUseCase โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ + call(NoParams) โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ - Limpa dados โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ usa โ”‚ +โ”‚ โ–ผ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ AuthRepository (Interface) โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ +โ”‚ โ”‚ + signIn(email, password): Either โ”‚ โ”‚ +โ”‚ โ”‚ + getCurrentUser(): Either โ”‚ โ”‚ +โ”‚ โ”‚ + logout(): Either โ”‚ โ”‚ +โ”‚ โ”‚ + isAuthenticated(): bool โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ–ฒ โ”‚ +โ”‚ โ”‚ implementa โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ Entities โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ +โ”‚ โ”‚ UserEntity โ”‚ โ”‚ +โ”‚ โ”‚ - token: String โ”‚ โ”‚ +โ”‚ โ”‚ - refreshToken: String โ”‚ โ”‚ +โ”‚ โ”‚ - expiresIn: int โ”‚ โ”‚ +โ”‚ โ”‚ - tokenType: String โ”‚ โ”‚ +โ”‚ โ”‚ - resourceOwner: ResourceOwner โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”‚ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ DATA LAYER โ”‚ +โ”‚ (Implementaรงรฃo de Acesso a Dados) โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ AuthRepositoryImpl โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ +โ”‚ โ”‚ - remoteDataSource: AuthRemoteDataSource โ”‚ โ”‚ +โ”‚ โ”‚ - localDataSource: AuthLocalDataSource โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ + signIn(email, password) โ”‚ โ”‚ +โ”‚ โ”‚ 1. Chama remoteDataSource.signIn() โ”‚ โ”‚ +โ”‚ โ”‚ 2. Salva via localDataSource.saveUser() โ”‚ โ”‚ +โ”‚ โ”‚ 3. Converte Model โ†’ Entity โ”‚ โ”‚ +โ”‚ โ”‚ 4. Trata exceรงรตes โ†’ Failures โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ + getCurrentUser() โ”‚ โ”‚ +โ”‚ โ”‚ 1. Chama localDataSource.getUser() โ”‚ โ”‚ +โ”‚ โ”‚ 2. Converte Model โ†’ Entity โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ + logout() โ”‚ โ”‚ +โ”‚ โ”‚ 1. Chama localDataSource.clearUser() โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ–ผ โ–ผ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ AuthRemoteDataSourceโ”‚ โ”‚ AuthLocalDataSource โ”‚ โ”‚ +โ”‚ โ”‚ (Interface) โ”‚ โ”‚ (Interface) โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ–ผ โ–ผ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ AuthRemoteDataSourceโ”‚ โ”‚ AuthLocalDataSource โ”‚ โ”‚ +โ”‚ โ”‚ Impl โ”‚ โ”‚ Impl โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ +โ”‚ โ”‚ + signIn() โ”‚ โ”‚ + saveUser() โ”‚ โ”‚ +โ”‚ โ”‚ - Usa Chopper โ”‚ โ”‚ - Usa Secure โ”‚ โ”‚ +โ”‚ โ”‚ - Chama API โ”‚ โ”‚ Storage โ”‚ โ”‚ +โ”‚ โ”‚ - Retorna Model โ”‚ โ”‚ + getUser() โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ + clearUser() โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ + hasUser() โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ–ผ โ–ผ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ Models (DTOs) โ”‚ โ”‚ FlutterSecure โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ Storage โ”‚ โ”‚ +โ”‚ โ”‚ UserModel โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ - fromJson() โ”‚ โ”‚ (Framework) โ”‚ โ”‚ +โ”‚ โ”‚ - toJson() โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ - toEntity() โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ SignInRequestModel โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ - toJson() โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +## ๐Ÿ”„ Fluxo de Dados - Login + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Usuรกrio โ”‚ +โ”‚ digita โ”‚ +โ”‚ credenci โ”‚ +โ”‚ ais โ”‚ +โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ SignInPage โ”‚ +โ”‚ (View) โ”‚ +โ”‚ โ”‚ +โ”‚ 1. Valida formulรกrio โ”‚ +โ”‚ 2. Chama viewModel โ”‚ +โ”‚ .signIn() โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ SignInViewModel โ”‚ +โ”‚ (Presentation) โ”‚ +โ”‚ โ”‚ +โ”‚ 1. Muda estado para โ”‚ +โ”‚ loading โ”‚ +โ”‚ 2. Chama SignInUseCaseโ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ SignInUseCase โ”‚ +โ”‚ (Domain) โ”‚ +โ”‚ โ”‚ +โ”‚ 1. Valida email โ”‚ +โ”‚ 2. Valida senha โ”‚ +โ”‚ 3. Chama repository โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ AuthRepositoryImpl โ”‚ +โ”‚ (Data) โ”‚ +โ”‚ โ”‚ +โ”‚ 1. Chama remote DS โ”‚ +โ”‚ 2. Salva local DS โ”‚ +โ”‚ 3. Converte Modelโ†’ โ”‚ +โ”‚ Entity โ”‚ +โ”‚ 4. Retorna Either โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ” + โ”‚ โ”‚ + โ–ผ โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Remote โ”‚ โ”‚ Local โ”‚ +โ”‚ DS โ”‚ โ”‚ DS โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ API โ”‚ โ”‚Storage โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ โ”‚ + โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Either โ”‚ +โ”‚ โ”‚ +โ”‚ Success: Right(User) โ”‚ +โ”‚ Error: Left(Failure) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ SignInViewModel โ”‚ +โ”‚ โ”‚ +โ”‚ fold( โ”‚ +โ”‚ error โ†’ state.error โ”‚ +โ”‚ user โ†’ state.successโ”‚ +โ”‚ ) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ SignInPage โ”‚ +โ”‚ โ”‚ +โ”‚ reaction() observa โ”‚ +โ”‚ mudanรงa de estado โ”‚ +โ”‚ โ”‚ +โ”‚ success โ†’ navega home โ”‚ +โ”‚ error โ†’ mostra toast โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +## ๐Ÿงช Pirรขmide de Testes + +``` + โ–ฒ + โ•ฑ โ•ฒ + โ•ฑ โ•ฒ + โ•ฑ E2E โ•ฒ + โ•ฑ Tests โ•ฒ + โ•ฑโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฒ + โ•ฑ โ•ฒ + โ•ฑ Integration โ•ฒ + โ•ฑ Tests โ•ฒ + โ•ฑโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ•ฒ + โ•ฑ โ•ฒ + โ•ฑ Unit Tests โ•ฒ + โ•ฑ (25 testes) โ•ฒ + โ•ฑโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฒ + โ•ฑ โ•ฒ + โ•ฑ โ€ข UseCase Tests (5) โ•ฒ + โ•ฑ โ€ข Repository Tests (9) โ•ฒ + โ•ฑ โ€ข ViewModel Tests (11) โ•ฒ + โ•ฑโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฒ +``` + +### Distribuiรงรฃo dos Testes + +- **Use Cases** (5 testes) + - โœ… Login bem-sucedido + - โœ… Validaรงรฃo de email + - โœ… Validaรงรฃo de senha + - โœ… Senha curta + - โœ… Credenciais invรกlidas + +- **Repository** (9 testes) + - โœ… Login remoto sucesso + - โœ… Credenciais invรกlidas + - โœ… Erro ao salvar localmente + - โœ… Obter usuรกrio atual + - โœ… Usuรกrio nรฃo encontrado + - โœ… Logout sucesso + - โœ… Erro ao fazer logout + - โœ… Verificar autenticaรงรฃo (3 cenรกrios) + +- **ViewModel** (11 testes) + - โœ… Atualizar email + - โœ… Atualizar senha + - โœ… Validaรงรฃo canSubmit (4 cenรกrios) + - โœ… Login (loading โ†’ success) + - โœ… Login (loading โ†’ error) + - โœ… Carregar usuรกrio atual (2 cenรกrios) + - โœ… Logout (2 cenรกrios) + - โœ… Reset de estado + +## ๐ŸŽฏ Princรญpios SOLID Aplicados + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ S - Single Responsibility Principle โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โœ… Cada Use Case tem uma รบnica responsabilidade โ”‚ +โ”‚ โœ… Data Sources separados (Remote vs Local) โ”‚ +โ”‚ โœ… ViewModel apenas gerencia estado da UI โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ O - Open/Closed Principle โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โœ… Aberto para extensรฃo: Novos use cases facilmente โ”‚ +โ”‚ โœ… Fechado para modificaรงรฃo: Interfaces estรกveis โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ L - Liskov Substitution Principle โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โœ… AuthRepositoryImpl substitui AuthRepository โ”‚ +โ”‚ โœ… Mocks substituem implementaรงรตes reais nos testes โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ I - Interface Segregation Principle โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โœ… Interfaces especรญficas (AuthRepository) โ”‚ +โ”‚ โœ… Data Sources com mรฉtodos focados โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ D - Dependency Inversion Principle โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โœ… Use Cases dependem de interfaces, nรฃo implementaรงรตesโ”‚ +โ”‚ โœ… Repository depende de abstraรงรตes de Data Sources โ”‚ +โ”‚ โœ… Injeรงรฃo de dependรชncias via GetIt โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +## ๐Ÿ“ฆ Injeรงรฃo de Dependรชncias + +``` +setupServiceLocator() + โ”‚ + โ””โ”€โ”€โ–ถ setupAuthInjection(getIt) + โ”‚ + โ”œโ”€โ”€โ–ถ FlutterSecureStorage (Singleton) + โ”‚ + โ”œโ”€โ”€โ–ถ AuthLocalDataSource (Lazy Singleton) + โ”‚ โ””โ”€โ”€ depende de FlutterSecureStorage + โ”‚ + โ”œโ”€โ”€โ–ถ AuthRemoteDataSource (Lazy Singleton) + โ”‚ + โ”œโ”€โ”€โ–ถ AuthRepository (Lazy Singleton) + โ”‚ โ”œโ”€โ”€ depende de AuthRemoteDataSource + โ”‚ โ””โ”€โ”€ depende de AuthLocalDataSource + โ”‚ + โ”œโ”€โ”€โ–ถ SignInUseCase (Lazy Singleton) + โ”‚ โ””โ”€โ”€ depende de AuthRepository + โ”‚ + โ”œโ”€โ”€โ–ถ GetCurrentUserUseCase (Lazy Singleton) + โ”‚ โ””โ”€โ”€ depende de AuthRepository + โ”‚ + โ”œโ”€โ”€โ–ถ LogoutUseCase (Lazy Singleton) + โ”‚ โ””โ”€โ”€ depende de AuthRepository + โ”‚ + โ””โ”€โ”€โ–ถ SignInViewModel (Lazy Singleton) + โ”œโ”€โ”€ depende de SignInUseCase + โ”œโ”€โ”€ depende de GetCurrentUserUseCase + โ””โ”€โ”€ depende de LogoutUseCase +``` + +## ๐Ÿ” Tratamento de Erros + +``` +Exception/Error + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Data Sources โ”‚ +โ”‚ lanรงam Exceptions โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Repository โ”‚ +โ”‚ captura Exceptions โ”‚ +โ”‚ converte em โ”‚ +โ”‚ Failures โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Either โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Use Case โ”‚ +โ”‚ retorna Either โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ViewModel โ”‚ +โ”‚ fold() para tratar โ”‚ +โ”‚ Left (erro) ou โ”‚ +โ”‚ Right (sucesso) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ View โ”‚ +โ”‚ reage ao estado โ”‚ +โ”‚ mostra UI โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +# Guia Prรกtico - Como Usar a Nova Arquitetura + +## ๐Ÿš€ Inรญcio Rรกpido + +### 1. Usando o ViewModel na UI + +```dart +import 'package:distrito_medico/features/auth/presentation/viewmodels/signin_viewmodel.dart'; +import 'package:distrito_medico/features/auth/presentation/pages/signin_page.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:get_it/get_it.dart'; + +class MyLoginPage extends StatefulWidget { + @override + State createState() => _MyLoginPageState(); +} + +class _MyLoginPageState extends State { + // Injetar o ViewModel + final viewModel = GetIt.I.get(); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Column( + children: [ + // Campo de Email + TextField( + onChanged: viewModel.setEmail, + decoration: InputDecoration(labelText: 'Email'), + ), + + // Campo de Senha + TextField( + onChanged: viewModel.setPassword, + obscureText: true, + decoration: InputDecoration(labelText: 'Senha'), + ), + + // Botรฃo de Login com estado reativo + Observer( + builder: (_) { + return ElevatedButton( + onPressed: viewModel.canSubmit + ? () async { + await viewModel.signIn(); + } + : null, + child: viewModel.isLoading + ? CircularProgressIndicator() + : Text('Entrar'), + ); + }, + ), + ], + ), + ); + } +} +``` + +### 2. Reagindo a Mudanรงas de Estado + +```dart +import 'package:mobx/mobx.dart'; + +class _MyLoginPageState extends State { + final viewModel = GetIt.I.get(); + final List _disposers = []; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + // Reaรงรฃo para navegar quando login for bem-sucedido + _disposers.add( + reaction( + (_) => viewModel.state, + (state) { + if (state == SignInState.success) { + // Navegar para home + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (_) => HomePage()), + ); + } else if (state == SignInState.error) { + // Mostrar erro + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(viewModel.errorMessage)), + ); + } + }, + ), + ); + } + + @override + void dispose() { + // Limpar reaรงรตes + for (var disposer in _disposers) { + disposer(); + } + super.dispose(); + } +} +``` + +### 3. Verificando Autenticaรงรฃo no Inรญcio do App + +```dart +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + // Configurar injeรงรฃo de dependรชncias + setupServiceLocator(); + + // Carregar usuรกrio atual + final viewModel = GetIt.I.get(); + await viewModel.loadCurrentUser(); + + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + final viewModel = GetIt.I.get(); + + return MaterialApp( + home: Observer( + builder: (_) { + // Mostrar home se autenticado, senรฃo login + return viewModel.isAuthenticated + ? HomePage() + : SignInPage(); + }, + ), + ); + } +} +``` + +### 4. Implementando Logout + +```dart +class ProfilePage extends StatelessWidget { + final viewModel = GetIt.I.get(); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Perfil'), + actions: [ + IconButton( + icon: Icon(Icons.logout), + onPressed: () async { + await viewModel.logout(); + + // Navegar para login + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute(builder: (_) => SignInPage()), + (route) => false, + ); + }, + ), + ], + ), + body: Observer( + builder: (_) { + final user = viewModel.currentUser; + + if (user == null) { + return Center(child: Text('Nรฃo autenticado')); + } + + return Column( + children: [ + Text('Email: ${user.resourceOwner.email}'), + Text('ID: ${user.resourceOwner.id}'), + ], + ); + }, + ), + ); + } +} +``` + +## ๐Ÿงช Escrevendo Testes + +### 1. Teste de Use Case + +```dart +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:dartz/dartz.dart'; + +class MockAuthRepository extends Mock implements AuthRepository {} + +void main() { + late MyUseCase useCase; + late MockAuthRepository mockRepository; + + setUp(() { + mockRepository = MockAuthRepository(); + useCase = MyUseCase(mockRepository); + }); + + test('deve retornar sucesso quando...', () async { + // Arrange + when(() => mockRepository.someMethod()) + .thenAnswer((_) async => Right(expectedResult)); + + // Act + final result = await useCase(params); + + // Assert + expect(result, Right(expectedResult)); + verify(() => mockRepository.someMethod()).called(1); + }); +} +``` + +### 2. Teste de Repository + +```dart +void main() { + late AuthRepositoryImpl repository; + late MockRemoteDataSource mockRemoteDS; + late MockLocalDataSource mockLocalDS; + + setUp(() { + mockRemoteDS = MockRemoteDataSource(); + mockLocalDS = MockLocalDataSource(); + repository = AuthRepositoryImpl( + remoteDataSource: mockRemoteDS, + localDataSource: mockLocalDS, + ); + }); + + test('deve salvar usuรกrio localmente apรณs login', () async { + // Arrange + when(() => mockRemoteDS.signIn( + email: any(named: 'email'), + password: any(named: 'password'), + )).thenAnswer((_) async => userModel); + + when(() => mockLocalDS.saveUser(any())) + .thenAnswer((_) async => {}); + + // Act + await repository.signIn(email: 'test@test.com', password: '1234'); + + // Assert + verify(() => mockLocalDS.saveUser(userModel)).called(1); + }); +} +``` + +### 3. Teste de ViewModel + +```dart +void main() { + late SignInViewModel viewModel; + late MockSignInUseCase mockUseCase; + + setUp(() { + mockUseCase = MockSignInUseCase(); + viewModel = SignInViewModel( + signInUseCase: mockUseCase, + // ... outros use cases + ); + }); + + test('deve mudar estado para loading ao fazer login', () async { + // Arrange + viewModel.setEmail('test@test.com'); + viewModel.setPassword('1234'); + + when(() => mockUseCase(any())) + .thenAnswer((_) async => Right(userEntity)); + + // Act + final future = viewModel.signIn(); + + // Assert - Estado loading + expect(viewModel.state, SignInState.loading); + expect(viewModel.isLoading, true); + + await future; + + // Assert - Estado success + expect(viewModel.state, SignInState.success); + }); +} +``` + +## ๐Ÿ”ง Criando uma Nova Feature + +### Passo 1: Estrutura de Pastas + +```bash +lib/features/minha_feature/ +โ”œโ”€โ”€ data/ +โ”‚ โ”œโ”€โ”€ datasources/ +โ”‚ โ”‚ โ”œโ”€โ”€ minha_feature_local_datasource.dart +โ”‚ โ”‚ โ””โ”€โ”€ minha_feature_remote_datasource.dart +โ”‚ โ”œโ”€โ”€ models/ +โ”‚ โ”‚ โ””โ”€โ”€ minha_model.dart +โ”‚ โ””โ”€โ”€ repositories/ +โ”‚ โ””โ”€โ”€ minha_repository_impl.dart +โ”œโ”€โ”€ domain/ +โ”‚ โ”œโ”€โ”€ entities/ +โ”‚ โ”‚ โ””โ”€โ”€ minha_entity.dart +โ”‚ โ”œโ”€โ”€ repositories/ +โ”‚ โ”‚ โ””โ”€โ”€ minha_repository.dart +โ”‚ โ””โ”€โ”€ usecases/ +โ”‚ โ””โ”€โ”€ meu_usecase.dart +โ”œโ”€โ”€ presentation/ +โ”‚ โ”œโ”€โ”€ pages/ +โ”‚ โ”‚ โ””โ”€โ”€ minha_page.dart +โ”‚ โ””โ”€โ”€ viewmodels/ +โ”‚ โ””โ”€โ”€ meu_viewmodel.dart +โ””โ”€โ”€ minha_feature_injection.dart +``` + +### Passo 2: Domain Layer + +```dart +// 1. Criar Entity +class MinhaEntity extends Equatable { + final String id; + final String nome; + + const MinhaEntity({required this.id, required this.nome}); + + @override + List get props => [id, nome]; +} + +// 2. Criar Repository Interface +abstract class MinhaRepository { + Future> buscar(String id); + Future>> listar(); + Future> salvar(MinhaEntity entity); +} + +// 3. Criar Use Case +class BuscarUseCase implements UseCase { + final MinhaRepository repository; + + BuscarUseCase(this.repository); + + @override + Future> call(String id) async { + if (id.isEmpty) { + return const Left(ValidationFailure(message: 'ID nรฃo pode ser vazio')); + } + return await repository.buscar(id); + } +} +``` + +### Passo 3: Data Layer + +```dart +// 1. Criar Model +class MinhaModel extends MinhaEntity { + const MinhaModel({required super.id, required super.nome}); + + factory MinhaModel.fromJson(Map json) { + return MinhaModel( + id: json['id'] as String, + nome: json['nome'] as String, + ); + } + + Map toJson() { + return {'id': id, 'nome': nome}; + } + + MinhaEntity toEntity() { + return MinhaEntity(id: id, nome: nome); + } +} + +// 2. Criar Remote Data Source +abstract class MinhaRemoteDataSource { + Future buscar(String id); +} + +class MinhaRemoteDataSourceImpl implements MinhaRemoteDataSource { + @override + Future buscar(String id) async { + try { + final response = await minhaService.buscar(id); + if (response.isSuccessful) { + return MinhaModel.fromJson(json.decode(response.body)); + } + throw ServerException(message: 'Erro ao buscar'); + } catch (e) { + throw ServerException(message: e.toString()); + } + } +} + +// 3. Criar Repository Implementation +class MinhaRepositoryImpl implements MinhaRepository { + final MinhaRemoteDataSource remoteDataSource; + + MinhaRepositoryImpl({required this.remoteDataSource}); + + @override + Future> buscar(String id) async { + try { + final model = await remoteDataSource.buscar(id); + return Right(model.toEntity()); + } on ServerException catch (e) { + return Left(ServerFailure(message: e.message)); + } catch (e) { + return Left(UnexpectedFailure(message: e.toString())); + } + } +} +``` + +### Passo 4: Presentation Layer + +```dart +// 1. Criar ViewModel +class MeuViewModel = _MeuViewModelBase with _$MeuViewModel; + +abstract class _MeuViewModelBase with Store { + final BuscarUseCase buscarUseCase; + + _MeuViewModelBase({required this.buscarUseCase}); + + @observable + MinhaEntity? item; + + @observable + bool isLoading = false; + + @observable + String errorMessage = ''; + + @action + Future buscar(String id) async { + isLoading = true; + errorMessage = ''; + + final result = await buscarUseCase(id); + + result.fold( + (failure) { + errorMessage = failure.message; + isLoading = false; + }, + (entity) { + item = entity; + isLoading = false; + }, + ); + } +} + +// 2. Criar Page +class MinhaPage extends StatelessWidget { + final viewModel = GetIt.I.get(); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Observer( + builder: (_) { + if (viewModel.isLoading) { + return CircularProgressIndicator(); + } + + if (viewModel.errorMessage.isNotEmpty) { + return Text('Erro: ${viewModel.errorMessage}'); + } + + final item = viewModel.item; + if (item == null) { + return Text('Nenhum item'); + } + + return Text('Nome: ${item.nome}'); + }, + ), + ); + } +} +``` + +### Passo 5: Injeรงรฃo de Dependรชncias + +```dart +void setupMinhaFeatureInjection(GetIt getIt) { + // Data Sources + getIt.registerLazySingleton( + () => MinhaRemoteDataSourceImpl(), + ); + + // Repositories + getIt.registerLazySingleton( + () => MinhaRepositoryImpl( + remoteDataSource: getIt(), + ), + ); + + // Use Cases + getIt.registerLazySingleton( + () => BuscarUseCase(getIt()), + ); + + // ViewModels + getIt.registerLazySingleton( + () => MeuViewModel( + buscarUseCase: getIt(), + ), + ); +} + +// No service_locator.dart +void setupServiceLocator() { + // ... outras configuraรงรตes + + setupMinhaFeatureInjection(getIt); +} +``` + +## ๐Ÿ’ก Dicas e Boas Prรกticas + +### 1. Sempre use Either para retornos de mรฉtodos assรญncronos + +```dart +// โŒ Evite +Future getUser(); + +// โœ… Prefira +Future> getUser(); +``` + +### 2. Mantenha as Entities puras (sem dependรชncias) + +```dart +// โŒ Evite +class User { + final String id; + + Future save() { + // Lรณgica de persistรชncia + } +} + +// โœ… Prefira +class User extends Equatable { + final String id; + + const User({required this.id}); + + @override + List get props => [id]; +} +``` + +### 3. Um Use Case = Uma Responsabilidade + +```dart +// โŒ Evite +class UserUseCase { + Future> signIn(); + Future> signUp(); + Future> logout(); +} + +// โœ… Prefira +class SignInUseCase { + Future> call(SignInParams params); +} + +class SignUpUseCase { + Future> call(SignUpParams params); +} + +class LogoutUseCase { + Future> call(NoParams params); +} +``` + +### 4. ViewModels nรฃo devem conhecer detalhes de implementaรงรฃo + +```dart +// โŒ Evite +class MyViewModel { + final AuthRepository repository; + + Future login() { + // Chamando repository diretamente + await repository.signIn(email, password); + } +} + +// โœ… Prefira +class MyViewModel { + final SignInUseCase signInUseCase; + + Future login() { + // Chamando use case + await signInUseCase(SignInParams(email: email, password: password)); + } +} +``` + +### 5. Sempre escreva testes + +```dart +// Para cada Use Case, escreva no mรญnimo: +// - 1 teste de sucesso +// - 1 teste de erro +// - Testes de validaรงรฃo (se houver) + +// Para cada Repository, escreva no mรญnimo: +// - 1 teste de sucesso +// - 1 teste de erro de servidor +// - 1 teste de erro de cache (se aplicรกvel) + +// Para cada ViewModel, escreva no mรญnimo: +// - Testes de mudanรงa de estado +// - Testes de propriedades computadas +// - Testes de interaรงรฃo com use cases +``` + +## ๐ŸŽฏ Checklist para Nova Feature + +- [ ] Criar estrutura de pastas (domain, data, presentation) +- [ ] Criar Entity no domain +- [ ] Criar Repository interface no domain +- [ ] Criar Use Cases no domain +- [ ] Criar Models no data +- [ ] Criar Data Sources (remote e/ou local) no data +- [ ] Criar Repository implementation no data +- [ ] Criar ViewModel no presentation +- [ ] Criar Page/Widget no presentation +- [ ] Configurar injeรงรฃo de dependรชncias +- [ ] Escrever testes unitรกrios +- [ ] Executar `flutter pub run build_runner build` +- [ ] Testar manualmente +- [ ] Documentar (README.md na pasta da feature) + +## ๐Ÿ“š Recursos Adicionais + +- [Clean Architecture - Uncle Bob](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) +- [Dartz Package](https://pub.dev/packages/dartz) +- [MobX Documentation](https://mobx.netlify.app/) +- [GetIt Documentation](https://pub.dev/packages/get_it) +- [Mocktail Documentation](https://pub.dev/packages/mocktail) + +## Como Testar as Features + +### 1. Testes Automatizados (Unit & Widget Tests) +Para rodar todos os testes do projeto e garantir que a refatora็ใo ou nova feature nใo quebrou funcionalidades existentes: + +`ash +flutter test +` + +Para rodar testes de uma feature especํfica (ex: medical_shifts): + +`ash +flutter test test/features/medical_shifts/ +` + +### 2. Testes Manuais - Fluxo de Medical Shifts +Recomendamos validar manualmente os seguintes cenแrios ap๓s rodar o projeto ('flutter run'): + +1. **Listagem e Filtros** + - Acesse a tela de Plant๕es (Home ou Menu). + - Verifique se a lista inicial carrega corretamente. + - Teste mudar o m๊s/ano no calendแrio. + - Aplique filtros por 'Pago', 'Nใo Pago' e nome do Hospital. + - Use o botใo 'Limpar Filtros' e verifique o reset. + +2. **Cadastro (CRUD)** + - Clique em '+' ou 'Novo Plantใo'. + - Tente salvar vazio -> Deve mostrar alerta. + - Preencha um plantใo simples (Hospital, Valor, Data, Hora). + - Salve -> Deve voltar เ lista e exibir Toast de Sucesso. + - Verifique se o novo item aparece na lista. + +3. **Recorr๊ncia** + - No cadastro, ative 'Recorrente'. + - Teste frequ๊ncia 'Semanal' -> Deve exibir dias da semana. + - Teste frequ๊ncia 'Mensal (Dia Fixo)' -> Deve exibir seletor de dia (1-31). + - Defina uma data final. + - Salve e verifique se m๚ltiplos plant๕es foram criados no calendแrio. + +4. **Edi็ใo e Exclusใo** + - Abra um plantใo existente. + - Edite o valor ou status de pagamento. + - Salve -> Verifique atualiza็ใo na lista. + - Deslize o item na lista para a esquerda -> Clique 'Deletar'. + - Se for recorrente, deve perguntar: 'Excluir apenas este ou a s้rie toda?'. + - Confirme e verifique a remo็ใo (Toast de Sucesso deve aparecer). + +5. **Gera็ใo de PDF** + - Na tela de listagem, clique no ํcone de PDF. + - Aplique filtros desejados e gere o relat๓rio. + - Verifique se o arquivo abre corretamente no visualizador. + diff --git a/med_system_app/ARCHITECTURE_DIAGRAM.md b/med_system_app/ARCHITECTURE_DIAGRAM.md new file mode 100644 index 0000000..f7756f6 --- /dev/null +++ b/med_system_app/ARCHITECTURE_DIAGRAM.md @@ -0,0 +1,409 @@ +# Diagrama da Arquitetura - Feature de Autenticaรงรฃo + +## ๐Ÿ“ Visรฃo Geral da Clean Architecture + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ PRESENTATION LAYER โ”‚ +โ”‚ (UI + ViewModel + State) โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ SignInPage โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถโ”‚ SignInViewModel โ”‚ โ”‚ +โ”‚ โ”‚ (View/UI) โ”‚ โ”‚ (MobX) โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ - email โ”‚ โ”‚ +โ”‚ โ”‚ observa โ”‚ - password โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ - state โ”‚ โ”‚ +โ”‚ โ–ผ โ”‚ - currentUser โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ - isAuthenticated โ”‚ โ”‚ +โ”‚ โ”‚ Observer โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ (MobX) โ”‚ โ”‚ + signIn() โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ + loadCurrentUser() โ”‚ โ”‚ +โ”‚ โ”‚ + logout() โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ chama + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ DOMAIN LAYER โ”‚ +โ”‚ (Regras de Negรณcio Puras) โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ Use Cases โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ SignInUseCase โ”‚ โ”‚ GetCurrentUserUseCaseโ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ + call(params) โ”‚ โ”‚ + call(NoParams) โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ - Valida email โ”‚ โ”‚ - Obtรฉm usuรกrio โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ - Valida senha โ”‚ โ”‚ do storage โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ - Chama repo โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ LogoutUseCase โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ + call(NoParams) โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ - Limpa dados โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ usa โ”‚ +โ”‚ โ–ผ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ AuthRepository (Interface) โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ +โ”‚ โ”‚ + signIn(email, password): Either โ”‚ โ”‚ +โ”‚ โ”‚ + getCurrentUser(): Either โ”‚ โ”‚ +โ”‚ โ”‚ + logout(): Either โ”‚ โ”‚ +โ”‚ โ”‚ + isAuthenticated(): bool โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ–ฒ โ”‚ +โ”‚ โ”‚ implementa โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ Entities โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ +โ”‚ โ”‚ UserEntity โ”‚ โ”‚ +โ”‚ โ”‚ - token: String โ”‚ โ”‚ +โ”‚ โ”‚ - refreshToken: String โ”‚ โ”‚ +โ”‚ โ”‚ - expiresIn: int โ”‚ โ”‚ +โ”‚ โ”‚ - tokenType: String โ”‚ โ”‚ +โ”‚ โ”‚ - resourceOwner: ResourceOwner โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”‚ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ DATA LAYER โ”‚ +โ”‚ (Implementaรงรฃo de Acesso a Dados) โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ AuthRepositoryImpl โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ +โ”‚ โ”‚ - remoteDataSource: AuthRemoteDataSource โ”‚ โ”‚ +โ”‚ โ”‚ - localDataSource: AuthLocalDataSource โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ + signIn(email, password) โ”‚ โ”‚ +โ”‚ โ”‚ 1. Chama remoteDataSource.signIn() โ”‚ โ”‚ +โ”‚ โ”‚ 2. Salva via localDataSource.saveUser() โ”‚ โ”‚ +โ”‚ โ”‚ 3. Converte Model โ†’ Entity โ”‚ โ”‚ +โ”‚ โ”‚ 4. Trata exceรงรตes โ†’ Failures โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ + getCurrentUser() โ”‚ โ”‚ +โ”‚ โ”‚ 1. Chama localDataSource.getUser() โ”‚ โ”‚ +โ”‚ โ”‚ 2. Converte Model โ†’ Entity โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ + logout() โ”‚ โ”‚ +โ”‚ โ”‚ 1. Chama localDataSource.clearUser() โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ–ผ โ–ผ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ AuthRemoteDataSourceโ”‚ โ”‚ AuthLocalDataSource โ”‚ โ”‚ +โ”‚ โ”‚ (Interface) โ”‚ โ”‚ (Interface) โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ–ผ โ–ผ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ AuthRemoteDataSourceโ”‚ โ”‚ AuthLocalDataSource โ”‚ โ”‚ +โ”‚ โ”‚ Impl โ”‚ โ”‚ Impl โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ +โ”‚ โ”‚ + signIn() โ”‚ โ”‚ + saveUser() โ”‚ โ”‚ +โ”‚ โ”‚ - Usa Chopper โ”‚ โ”‚ - Usa Secure โ”‚ โ”‚ +โ”‚ โ”‚ - Chama API โ”‚ โ”‚ Storage โ”‚ โ”‚ +โ”‚ โ”‚ - Retorna Model โ”‚ โ”‚ + getUser() โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ + clearUser() โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ + hasUser() โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ–ผ โ–ผ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ Models (DTOs) โ”‚ โ”‚ FlutterSecure โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ Storage โ”‚ โ”‚ +โ”‚ โ”‚ UserModel โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ - fromJson() โ”‚ โ”‚ (Framework) โ”‚ โ”‚ +โ”‚ โ”‚ - toJson() โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ - toEntity() โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ SignInRequestModel โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ - toJson() โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +## ๐Ÿ”„ Fluxo de Dados - Login + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Usuรกrio โ”‚ +โ”‚ digita โ”‚ +โ”‚ credenci โ”‚ +โ”‚ ais โ”‚ +โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ SignInPage โ”‚ +โ”‚ (View) โ”‚ +โ”‚ โ”‚ +โ”‚ 1. Valida formulรกrio โ”‚ +โ”‚ 2. Chama viewModel โ”‚ +โ”‚ .signIn() โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ SignInViewModel โ”‚ +โ”‚ (Presentation) โ”‚ +โ”‚ โ”‚ +โ”‚ 1. Muda estado para โ”‚ +โ”‚ loading โ”‚ +โ”‚ 2. Chama SignInUseCaseโ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ SignInUseCase โ”‚ +โ”‚ (Domain) โ”‚ +โ”‚ โ”‚ +โ”‚ 1. Valida email โ”‚ +โ”‚ 2. Valida senha โ”‚ +โ”‚ 3. Chama repository โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ AuthRepositoryImpl โ”‚ +โ”‚ (Data) โ”‚ +โ”‚ โ”‚ +โ”‚ 1. Chama remote DS โ”‚ +โ”‚ 2. Salva local DS โ”‚ +โ”‚ 3. Converte Modelโ†’ โ”‚ +โ”‚ Entity โ”‚ +โ”‚ 4. Retorna Either โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ” + โ”‚ โ”‚ + โ–ผ โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Remote โ”‚ โ”‚ Local โ”‚ +โ”‚ DS โ”‚ โ”‚ DS โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ API โ”‚ โ”‚Storage โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ โ”‚ + โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Either โ”‚ +โ”‚ โ”‚ +โ”‚ Success: Right(User) โ”‚ +โ”‚ Error: Left(Failure) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ SignInViewModel โ”‚ +โ”‚ โ”‚ +โ”‚ fold( โ”‚ +โ”‚ error โ†’ state.error โ”‚ +โ”‚ user โ†’ state.successโ”‚ +โ”‚ ) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ SignInPage โ”‚ +โ”‚ โ”‚ +โ”‚ reaction() observa โ”‚ +โ”‚ mudanรงa de estado โ”‚ +โ”‚ โ”‚ +โ”‚ success โ†’ navega home โ”‚ +โ”‚ error โ†’ mostra toast โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +## ๐Ÿงช Pirรขmide de Testes + +``` + โ–ฒ + โ•ฑ โ•ฒ + โ•ฑ โ•ฒ + โ•ฑ E2E โ•ฒ + โ•ฑ Tests โ•ฒ + โ•ฑโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฒ + โ•ฑ โ•ฒ + โ•ฑ Integration โ•ฒ + โ•ฑ Tests โ•ฒ + โ•ฑโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ•ฒ + โ•ฑ โ•ฒ + โ•ฑ Unit Tests โ•ฒ + โ•ฑ (25 testes) โ•ฒ + โ•ฑโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฒ + โ•ฑ โ•ฒ + โ•ฑ โ€ข UseCase Tests (5) โ•ฒ + โ•ฑ โ€ข Repository Tests (9) โ•ฒ + โ•ฑ โ€ข ViewModel Tests (11) โ•ฒ + โ•ฑโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฒ +``` + +### Distribuiรงรฃo dos Testes + +- **Use Cases** (5 testes) + - โœ… Login bem-sucedido + - โœ… Validaรงรฃo de email + - โœ… Validaรงรฃo de senha + - โœ… Senha curta + - โœ… Credenciais invรกlidas + +- **Repository** (9 testes) + - โœ… Login remoto sucesso + - โœ… Credenciais invรกlidas + - โœ… Erro ao salvar localmente + - โœ… Obter usuรกrio atual + - โœ… Usuรกrio nรฃo encontrado + - โœ… Logout sucesso + - โœ… Erro ao fazer logout + - โœ… Verificar autenticaรงรฃo (3 cenรกrios) + +- **ViewModel** (11 testes) + - โœ… Atualizar email + - โœ… Atualizar senha + - โœ… Validaรงรฃo canSubmit (4 cenรกrios) + - โœ… Login (loading โ†’ success) + - โœ… Login (loading โ†’ error) + - โœ… Carregar usuรกrio atual (2 cenรกrios) + - โœ… Logout (2 cenรกrios) + - โœ… Reset de estado + +## ๐ŸŽฏ Princรญpios SOLID Aplicados + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ S - Single Responsibility Principle โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โœ… Cada Use Case tem uma รบnica responsabilidade โ”‚ +โ”‚ โœ… Data Sources separados (Remote vs Local) โ”‚ +โ”‚ โœ… ViewModel apenas gerencia estado da UI โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ O - Open/Closed Principle โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โœ… Aberto para extensรฃo: Novos use cases facilmente โ”‚ +โ”‚ โœ… Fechado para modificaรงรฃo: Interfaces estรกveis โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ L - Liskov Substitution Principle โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โœ… AuthRepositoryImpl substitui AuthRepository โ”‚ +โ”‚ โœ… Mocks substituem implementaรงรตes reais nos testes โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ I - Interface Segregation Principle โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โœ… Interfaces especรญficas (AuthRepository) โ”‚ +โ”‚ โœ… Data Sources com mรฉtodos focados โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ D - Dependency Inversion Principle โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โœ… Use Cases dependem de interfaces, nรฃo implementaรงรตesโ”‚ +โ”‚ โœ… Repository depende de abstraรงรตes de Data Sources โ”‚ +โ”‚ โœ… Injeรงรฃo de dependรชncias via GetIt โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +## ๐Ÿ“ฆ Injeรงรฃo de Dependรชncias + +``` +setupServiceLocator() + โ”‚ + โ””โ”€โ”€โ–ถ setupAuthInjection(getIt) + โ”‚ + โ”œโ”€โ”€โ–ถ FlutterSecureStorage (Singleton) + โ”‚ + โ”œโ”€โ”€โ–ถ AuthLocalDataSource (Lazy Singleton) + โ”‚ โ””โ”€โ”€ depende de FlutterSecureStorage + โ”‚ + โ”œโ”€โ”€โ–ถ AuthRemoteDataSource (Lazy Singleton) + โ”‚ + โ”œโ”€โ”€โ–ถ AuthRepository (Lazy Singleton) + โ”‚ โ”œโ”€โ”€ depende de AuthRemoteDataSource + โ”‚ โ””โ”€โ”€ depende de AuthLocalDataSource + โ”‚ + โ”œโ”€โ”€โ–ถ SignInUseCase (Lazy Singleton) + โ”‚ โ””โ”€โ”€ depende de AuthRepository + โ”‚ + โ”œโ”€โ”€โ–ถ GetCurrentUserUseCase (Lazy Singleton) + โ”‚ โ””โ”€โ”€ depende de AuthRepository + โ”‚ + โ”œโ”€โ”€โ–ถ LogoutUseCase (Lazy Singleton) + โ”‚ โ””โ”€โ”€ depende de AuthRepository + โ”‚ + โ””โ”€โ”€โ–ถ SignInViewModel (Lazy Singleton) + โ”œโ”€โ”€ depende de SignInUseCase + โ”œโ”€โ”€ depende de GetCurrentUserUseCase + โ””โ”€โ”€ depende de LogoutUseCase +``` + +## ๐Ÿ” Tratamento de Erros + +``` +Exception/Error + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Data Sources โ”‚ +โ”‚ lanรงam Exceptions โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Repository โ”‚ +โ”‚ captura Exceptions โ”‚ +โ”‚ converte em โ”‚ +โ”‚ Failures โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Either โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Use Case โ”‚ +โ”‚ retorna Either โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ViewModel โ”‚ +โ”‚ fold() para tratar โ”‚ +โ”‚ Left (erro) ou โ”‚ +โ”‚ Right (sucesso) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ View โ”‚ +โ”‚ reage ao estado โ”‚ +โ”‚ mostra UI โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` diff --git a/med_system_app/PRACTICAL_GUIDE.md b/med_system_app/PRACTICAL_GUIDE.md new file mode 100644 index 0000000..8e81552 --- /dev/null +++ b/med_system_app/PRACTICAL_GUIDE.md @@ -0,0 +1,661 @@ +# Guia Prรกtico - Como Usar a Nova Arquitetura + +## ๐Ÿš€ Inรญcio Rรกpido + +### 1. Usando o ViewModel na UI + +```dart +import 'package:distrito_medico/features/auth/presentation/viewmodels/signin_viewmodel.dart'; +import 'package:distrito_medico/features/auth/presentation/pages/signin_page.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:get_it/get_it.dart'; + +class MyLoginPage extends StatefulWidget { + @override + State createState() => _MyLoginPageState(); +} + +class _MyLoginPageState extends State { + // Injetar o ViewModel + final viewModel = GetIt.I.get(); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Column( + children: [ + // Campo de Email + TextField( + onChanged: viewModel.setEmail, + decoration: InputDecoration(labelText: 'Email'), + ), + + // Campo de Senha + TextField( + onChanged: viewModel.setPassword, + obscureText: true, + decoration: InputDecoration(labelText: 'Senha'), + ), + + // Botรฃo de Login com estado reativo + Observer( + builder: (_) { + return ElevatedButton( + onPressed: viewModel.canSubmit + ? () async { + await viewModel.signIn(); + } + : null, + child: viewModel.isLoading + ? CircularProgressIndicator() + : Text('Entrar'), + ); + }, + ), + ], + ), + ); + } +} +``` + +### 2. Reagindo a Mudanรงas de Estado + +```dart +import 'package:mobx/mobx.dart'; + +class _MyLoginPageState extends State { + final viewModel = GetIt.I.get(); + final List _disposers = []; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + // Reaรงรฃo para navegar quando login for bem-sucedido + _disposers.add( + reaction( + (_) => viewModel.state, + (state) { + if (state == SignInState.success) { + // Navegar para home + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (_) => HomePage()), + ); + } else if (state == SignInState.error) { + // Mostrar erro + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(viewModel.errorMessage)), + ); + } + }, + ), + ); + } + + @override + void dispose() { + // Limpar reaรงรตes + for (var disposer in _disposers) { + disposer(); + } + super.dispose(); + } +} +``` + +### 3. Verificando Autenticaรงรฃo no Inรญcio do App + +```dart +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + // Configurar injeรงรฃo de dependรชncias + setupServiceLocator(); + + // Carregar usuรกrio atual + final viewModel = GetIt.I.get(); + await viewModel.loadCurrentUser(); + + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + final viewModel = GetIt.I.get(); + + return MaterialApp( + home: Observer( + builder: (_) { + // Mostrar home se autenticado, senรฃo login + return viewModel.isAuthenticated + ? HomePage() + : SignInPage(); + }, + ), + ); + } +} +``` + +### 4. Implementando Logout + +```dart +class ProfilePage extends StatelessWidget { + final viewModel = GetIt.I.get(); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Perfil'), + actions: [ + IconButton( + icon: Icon(Icons.logout), + onPressed: () async { + await viewModel.logout(); + + // Navegar para login + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute(builder: (_) => SignInPage()), + (route) => false, + ); + }, + ), + ], + ), + body: Observer( + builder: (_) { + final user = viewModel.currentUser; + + if (user == null) { + return Center(child: Text('Nรฃo autenticado')); + } + + return Column( + children: [ + Text('Email: ${user.resourceOwner.email}'), + Text('ID: ${user.resourceOwner.id}'), + ], + ); + }, + ), + ); + } +} +``` + +## ๐Ÿงช Escrevendo Testes + +### 1. Teste de Use Case + +```dart +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:dartz/dartz.dart'; + +class MockAuthRepository extends Mock implements AuthRepository {} + +void main() { + late MyUseCase useCase; + late MockAuthRepository mockRepository; + + setUp(() { + mockRepository = MockAuthRepository(); + useCase = MyUseCase(mockRepository); + }); + + test('deve retornar sucesso quando...', () async { + // Arrange + when(() => mockRepository.someMethod()) + .thenAnswer((_) async => Right(expectedResult)); + + // Act + final result = await useCase(params); + + // Assert + expect(result, Right(expectedResult)); + verify(() => mockRepository.someMethod()).called(1); + }); +} +``` + +### 2. Teste de Repository + +```dart +void main() { + late AuthRepositoryImpl repository; + late MockRemoteDataSource mockRemoteDS; + late MockLocalDataSource mockLocalDS; + + setUp(() { + mockRemoteDS = MockRemoteDataSource(); + mockLocalDS = MockLocalDataSource(); + repository = AuthRepositoryImpl( + remoteDataSource: mockRemoteDS, + localDataSource: mockLocalDS, + ); + }); + + test('deve salvar usuรกrio localmente apรณs login', () async { + // Arrange + when(() => mockRemoteDS.signIn( + email: any(named: 'email'), + password: any(named: 'password'), + )).thenAnswer((_) async => userModel); + + when(() => mockLocalDS.saveUser(any())) + .thenAnswer((_) async => {}); + + // Act + await repository.signIn(email: 'test@test.com', password: '1234'); + + // Assert + verify(() => mockLocalDS.saveUser(userModel)).called(1); + }); +} +``` + +### 3. Teste de ViewModel + +```dart +void main() { + late SignInViewModel viewModel; + late MockSignInUseCase mockUseCase; + + setUp(() { + mockUseCase = MockSignInUseCase(); + viewModel = SignInViewModel( + signInUseCase: mockUseCase, + // ... outros use cases + ); + }); + + test('deve mudar estado para loading ao fazer login', () async { + // Arrange + viewModel.setEmail('test@test.com'); + viewModel.setPassword('1234'); + + when(() => mockUseCase(any())) + .thenAnswer((_) async => Right(userEntity)); + + // Act + final future = viewModel.signIn(); + + // Assert - Estado loading + expect(viewModel.state, SignInState.loading); + expect(viewModel.isLoading, true); + + await future; + + // Assert - Estado success + expect(viewModel.state, SignInState.success); + }); +} +``` + +## ๐Ÿ”ง Criando uma Nova Feature + +### Passo 1: Estrutura de Pastas + +```bash +lib/features/minha_feature/ +โ”œโ”€โ”€ data/ +โ”‚ โ”œโ”€โ”€ datasources/ +โ”‚ โ”‚ โ”œโ”€โ”€ minha_feature_local_datasource.dart +โ”‚ โ”‚ โ””โ”€โ”€ minha_feature_remote_datasource.dart +โ”‚ โ”œโ”€โ”€ models/ +โ”‚ โ”‚ โ””โ”€โ”€ minha_model.dart +โ”‚ โ””โ”€โ”€ repositories/ +โ”‚ โ””โ”€โ”€ minha_repository_impl.dart +โ”œโ”€โ”€ domain/ +โ”‚ โ”œโ”€โ”€ entities/ +โ”‚ โ”‚ โ””โ”€โ”€ minha_entity.dart +โ”‚ โ”œโ”€โ”€ repositories/ +โ”‚ โ”‚ โ””โ”€โ”€ minha_repository.dart +โ”‚ โ””โ”€โ”€ usecases/ +โ”‚ โ””โ”€โ”€ meu_usecase.dart +โ”œโ”€โ”€ presentation/ +โ”‚ โ”œโ”€โ”€ pages/ +โ”‚ โ”‚ โ””โ”€โ”€ minha_page.dart +โ”‚ โ””โ”€โ”€ viewmodels/ +โ”‚ โ””โ”€โ”€ meu_viewmodel.dart +โ””โ”€โ”€ minha_feature_injection.dart +``` + +### Passo 2: Domain Layer + +```dart +// 1. Criar Entity +class MinhaEntity extends Equatable { + final String id; + final String nome; + + const MinhaEntity({required this.id, required this.nome}); + + @override + List get props => [id, nome]; +} + +// 2. Criar Repository Interface +abstract class MinhaRepository { + Future> buscar(String id); + Future>> listar(); + Future> salvar(MinhaEntity entity); +} + +// 3. Criar Use Case +class BuscarUseCase implements UseCase { + final MinhaRepository repository; + + BuscarUseCase(this.repository); + + @override + Future> call(String id) async { + if (id.isEmpty) { + return const Left(ValidationFailure(message: 'ID nรฃo pode ser vazio')); + } + return await repository.buscar(id); + } +} +``` + +### Passo 3: Data Layer + +```dart +// 1. Criar Model +class MinhaModel extends MinhaEntity { + const MinhaModel({required super.id, required super.nome}); + + factory MinhaModel.fromJson(Map json) { + return MinhaModel( + id: json['id'] as String, + nome: json['nome'] as String, + ); + } + + Map toJson() { + return {'id': id, 'nome': nome}; + } + + MinhaEntity toEntity() { + return MinhaEntity(id: id, nome: nome); + } +} + +// 2. Criar Remote Data Source +abstract class MinhaRemoteDataSource { + Future buscar(String id); +} + +class MinhaRemoteDataSourceImpl implements MinhaRemoteDataSource { + @override + Future buscar(String id) async { + try { + final response = await minhaService.buscar(id); + if (response.isSuccessful) { + return MinhaModel.fromJson(json.decode(response.body)); + } + throw ServerException(message: 'Erro ao buscar'); + } catch (e) { + throw ServerException(message: e.toString()); + } + } +} + +// 3. Criar Repository Implementation +class MinhaRepositoryImpl implements MinhaRepository { + final MinhaRemoteDataSource remoteDataSource; + + MinhaRepositoryImpl({required this.remoteDataSource}); + + @override + Future> buscar(String id) async { + try { + final model = await remoteDataSource.buscar(id); + return Right(model.toEntity()); + } on ServerException catch (e) { + return Left(ServerFailure(message: e.message)); + } catch (e) { + return Left(UnexpectedFailure(message: e.toString())); + } + } +} +``` + +### Passo 4: Presentation Layer + +```dart +// 1. Criar ViewModel +class MeuViewModel = _MeuViewModelBase with _$MeuViewModel; + +abstract class _MeuViewModelBase with Store { + final BuscarUseCase buscarUseCase; + + _MeuViewModelBase({required this.buscarUseCase}); + + @observable + MinhaEntity? item; + + @observable + bool isLoading = false; + + @observable + String errorMessage = ''; + + @action + Future buscar(String id) async { + isLoading = true; + errorMessage = ''; + + final result = await buscarUseCase(id); + + result.fold( + (failure) { + errorMessage = failure.message; + isLoading = false; + }, + (entity) { + item = entity; + isLoading = false; + }, + ); + } +} + +// 2. Criar Page +class MinhaPage extends StatelessWidget { + final viewModel = GetIt.I.get(); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Observer( + builder: (_) { + if (viewModel.isLoading) { + return CircularProgressIndicator(); + } + + if (viewModel.errorMessage.isNotEmpty) { + return Text('Erro: ${viewModel.errorMessage}'); + } + + final item = viewModel.item; + if (item == null) { + return Text('Nenhum item'); + } + + return Text('Nome: ${item.nome}'); + }, + ), + ); + } +} +``` + +### Passo 5: Injeรงรฃo de Dependรชncias + +```dart +void setupMinhaFeatureInjection(GetIt getIt) { + // Data Sources + getIt.registerLazySingleton( + () => MinhaRemoteDataSourceImpl(), + ); + + // Repositories + getIt.registerLazySingleton( + () => MinhaRepositoryImpl( + remoteDataSource: getIt(), + ), + ); + + // Use Cases + getIt.registerLazySingleton( + () => BuscarUseCase(getIt()), + ); + + // ViewModels + getIt.registerLazySingleton( + () => MeuViewModel( + buscarUseCase: getIt(), + ), + ); +} + +// No service_locator.dart +void setupServiceLocator() { + // ... outras configuraรงรตes + + setupMinhaFeatureInjection(getIt); +} +``` + +## ๐Ÿ’ก Dicas e Boas Prรกticas + +### 1. Sempre use Either para retornos de mรฉtodos assรญncronos + +```dart +// โŒ Evite +Future getUser(); + +// โœ… Prefira +Future> getUser(); +``` + +### 2. Mantenha as Entities puras (sem dependรชncias) + +```dart +// โŒ Evite +class User { + final String id; + + Future save() { + // Lรณgica de persistรชncia + } +} + +// โœ… Prefira +class User extends Equatable { + final String id; + + const User({required this.id}); + + @override + List get props => [id]; +} +``` + +### 3. Um Use Case = Uma Responsabilidade + +```dart +// โŒ Evite +class UserUseCase { + Future> signIn(); + Future> signUp(); + Future> logout(); +} + +// โœ… Prefira +class SignInUseCase { + Future> call(SignInParams params); +} + +class SignUpUseCase { + Future> call(SignUpParams params); +} + +class LogoutUseCase { + Future> call(NoParams params); +} +``` + +### 4. ViewModels nรฃo devem conhecer detalhes de implementaรงรฃo + +```dart +// โŒ Evite +class MyViewModel { + final AuthRepository repository; + + Future login() { + // Chamando repository diretamente + await repository.signIn(email, password); + } +} + +// โœ… Prefira +class MyViewModel { + final SignInUseCase signInUseCase; + + Future login() { + // Chamando use case + await signInUseCase(SignInParams(email: email, password: password)); + } +} +``` + +### 5. Sempre escreva testes + +```dart +// Para cada Use Case, escreva no mรญnimo: +// - 1 teste de sucesso +// - 1 teste de erro +// - Testes de validaรงรฃo (se houver) + +// Para cada Repository, escreva no mรญnimo: +// - 1 teste de sucesso +// - 1 teste de erro de servidor +// - 1 teste de erro de cache (se aplicรกvel) + +// Para cada ViewModel, escreva no mรญnimo: +// - Testes de mudanรงa de estado +// - Testes de propriedades computadas +// - Testes de interaรงรฃo com use cases +``` + +## ๐ŸŽฏ Checklist para Nova Feature + +- [ ] Criar estrutura de pastas (domain, data, presentation) +- [ ] Criar Entity no domain +- [ ] Criar Repository interface no domain +- [ ] Criar Use Cases no domain +- [ ] Criar Models no data +- [ ] Criar Data Sources (remote e/ou local) no data +- [ ] Criar Repository implementation no data +- [ ] Criar ViewModel no presentation +- [ ] Criar Page/Widget no presentation +- [ ] Configurar injeรงรฃo de dependรชncias +- [ ] Escrever testes unitรกrios +- [ ] Executar `flutter pub run build_runner build` +- [ ] Testar manualmente +- [ ] Documentar (README.md na pasta da feature) + +## ๐Ÿ“š Recursos Adicionais + +- [Clean Architecture - Uncle Bob](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) +- [Dartz Package](https://pub.dev/packages/dartz) +- [MobX Documentation](https://mobx.netlify.app/) +- [GetIt Documentation](https://pub.dev/packages/get_it) +- [Mocktail Documentation](https://pub.dev/packages/mocktail) diff --git a/med_system_app/README.md b/med_system_app/README.md index cdf5cf3..e4f1d2a 100644 --- a/med_system_app/README.md +++ b/med_system_app/README.md @@ -1,16 +1,1132 @@ -# distrito_medico +# Documentaรงรฃo do Projeto -A new Flutter project. +[![codecov](https://codecov.io/gh/espoo-dev/med_system/branch/main/graph/badge.svg)](https://codecov.io/gh/espoo-dev/med_system) -## Getting Started -This project is a starting point for a Flutter application. +## Diagrama da Arquitetura - Feature de Autenticaรงรฃo -A few resources to get you started if this is your first Flutter project: +## ๐Ÿ“ Visรฃo Geral da Clean Architecture -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ PRESENTATION LAYER โ”‚ +โ”‚ (UI + ViewModel + State) โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ SignInPage โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถโ”‚ SignInViewModel โ”‚ โ”‚ +โ”‚ โ”‚ (View/UI) โ”‚ โ”‚ (MobX) โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ - email โ”‚ โ”‚ +โ”‚ โ”‚ observa โ”‚ - password โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ - state โ”‚ โ”‚ +โ”‚ โ–ผ โ”‚ - currentUser โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ - isAuthenticated โ”‚ โ”‚ +โ”‚ โ”‚ Observer โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ (MobX) โ”‚ โ”‚ + signIn() โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ + loadCurrentUser() โ”‚ โ”‚ +โ”‚ โ”‚ + logout() โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ +26: โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ chama + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ DOMAIN LAYER โ”‚ +โ”‚ (Regras de Negรณcio Puras) โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ Use Cases โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ SignInUseCase โ”‚ โ”‚ GetCurrentUserUseCaseโ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ + call(params) โ”‚ โ”‚ + call(NoParams) โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ - Valida email โ”‚ โ”‚ - Obtรฉm usuรกrio โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ - Valida senha โ”‚ โ”‚ do storage โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ - Chama repo โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ LogoutUseCase โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ + call(NoParams) โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ - Limpa dados โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ usa โ”‚ +โ”‚ โ–ผ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ AuthRepository (Interface) โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ +โ”‚ โ”‚ + signIn(email, password): Either โ”‚ โ”‚ +โ”‚ โ”‚ + getCurrentUser(): Either โ”‚ โ”‚ +โ”‚ โ”‚ + logout(): Either โ”‚ โ”‚ +โ”‚ โ”‚ + isAuthenticated(): bool โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ–ฒ โ”‚ +โ”‚ โ”‚ implementa โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ Entities โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ +โ”‚ โ”‚ UserEntity โ”‚ โ”‚ +โ”‚ โ”‚ - token: String โ”‚ โ”‚ +โ”‚ โ”‚ - refreshToken: String โ”‚ โ”‚ +โ”‚ โ”‚ - expiresIn: int โ”‚ โ”‚ +โ”‚ โ”‚ - tokenType: String โ”‚ โ”‚ +โ”‚ โ”‚ - resourceOwner: ResourceOwner โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”‚ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ DATA LAYER โ”‚ +โ”‚ (Implementaรงรฃo de Acesso a Dados) โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ AuthRepositoryImpl โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ +โ”‚ โ”‚ - remoteDataSource: AuthRemoteDataSource โ”‚ โ”‚ +โ”‚ โ”‚ - localDataSource: AuthLocalDataSource โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ + signIn(email, password) โ”‚ โ”‚ +โ”‚ โ”‚ 1. Chama remoteDataSource.signIn() โ”‚ โ”‚ +โ”‚ โ”‚ 2. Salva via localDataSource.saveUser() โ”‚ โ”‚ +โ”‚ โ”‚ 3. Converte Model โ†’ Entity โ”‚ โ”‚ +โ”‚ โ”‚ 4. Trata exceรงรตes โ†’ Failures โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ + getCurrentUser() โ”‚ โ”‚ +โ”‚ โ”‚ 1. Chama localDataSource.getUser() โ”‚ โ”‚ +โ”‚ โ”‚ 2. Converte Model โ†’ Entity โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ + logout() โ”‚ โ”‚ +โ”‚ โ”‚ 1. Chama localDataSource.clearUser() โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ–ผ โ–ผ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ AuthRemoteDataSourceโ”‚ โ”‚ AuthLocalDataSource โ”‚ โ”‚ +โ”‚ โ”‚ (Interface) โ”‚ โ”‚ (Interface) โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ–ผ โ–ผ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ AuthRemoteDataSourceโ”‚ โ”‚ AuthLocalDataSource โ”‚ โ”‚ +โ”‚ โ”‚ Impl โ”‚ โ”‚ Impl โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ +โ”‚ โ”‚ + signIn() โ”‚ โ”‚ + saveUser() โ”‚ โ”‚ +โ”‚ โ”‚ - Usa Chopper โ”‚ โ”‚ - Usa Secure โ”‚ โ”‚ +โ”‚ โ”‚ - Chama API โ”‚ โ”‚ Storage โ”‚ โ”‚ +โ”‚ โ”‚ - Retorna Model โ”‚ โ”‚ + getUser() โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ + clearUser() โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ + hasUser() โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ–ผ โ–ผ โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ Models (DTOs) โ”‚ โ”‚ FlutterSecure โ”‚ โ”‚ +โ”‚ โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ Storage โ”‚ โ”‚ +โ”‚ โ”‚ UserModel โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ - fromJson() โ”‚ โ”‚ (Framework) โ”‚ โ”‚ +โ”‚ โ”‚ - toJson() โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ - toEntity() โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ SignInRequestModel โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ - toJson() โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +## ๐Ÿ”„ Fluxo de Dados - Login + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Usuรกrio โ”‚ +โ”‚ digita โ”‚ +โ”‚ credenci โ”‚ +โ”‚ ais โ”‚ +โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ SignInPage โ”‚ +โ”‚ (View) โ”‚ +โ”‚ โ”‚ +โ”‚ 1. Valida formulรกrio โ”‚ +โ”‚ 2. Chama viewModel โ”‚ +โ”‚ .signIn() โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ SignInViewModel โ”‚ +โ”‚ (Presentation) โ”‚ +โ”‚ โ”‚ +โ”‚ 1. Muda estado para โ”‚ +โ”‚ loading โ”‚ +โ”‚ 2. Chama SignInUseCaseโ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ SignInUseCase โ”‚ +โ”‚ (Domain) โ”‚ +โ”‚ โ”‚ +โ”‚ 1. Valida email โ”‚ +โ”‚ 2. Valida senha โ”‚ +โ”‚ 3. Chama repository โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ AuthRepositoryImpl โ”‚ +โ”‚ (Data) โ”‚ +โ”‚ โ”‚ +โ”‚ 1. Chama remote DS โ”‚ +โ”‚ 2. Salva local DS โ”‚ +โ”‚ 3. Converte Modelโ†’ โ”‚ +โ”‚ Entity โ”‚ +โ”‚ 4. Retorna Either โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ” + โ”‚ โ”‚ + โ–ผ โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Remote โ”‚ โ”‚ Local โ”‚ +โ”‚ DS โ”‚ โ”‚ DS โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ API โ”‚ โ”‚Storage โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ โ”‚ + โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Either โ”‚ +โ”‚ โ”‚ +โ”‚ Success: Right(User) โ”‚ +โ”‚ Error: Left(Failure) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ SignInViewModel โ”‚ +โ”‚ โ”‚ +โ”‚ fold( โ”‚ +โ”‚ error โ†’ state.error โ”‚ +โ”‚ user โ†’ state.successโ”‚ +โ”‚ ) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ SignInPage โ”‚ +โ”‚ โ”‚ +โ”‚ reaction() observa โ”‚ +โ”‚ mudanรงa de estado โ”‚ +โ”‚ โ”‚ +โ”‚ success โ†’ navega home โ”‚ +โ”‚ error โ†’ mostra toast โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +## ๐Ÿงช Pirรขmide de Testes + +``` + โ–ฒ + โ•ฑ โ•ฒ + โ•ฑ โ•ฒ + โ•ฑ E2E โ•ฒ + โ•ฑ Tests โ•ฒ + โ•ฑโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฒ + โ•ฑ โ•ฒ + โ•ฑ Integration โ•ฒ + โ•ฑ Tests โ•ฒ + โ•ฑโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ•ฒ + โ•ฑ โ•ฒ + โ•ฑ Unit Tests โ•ฒ + โ•ฑ (25 testes) โ•ฒ + โ•ฑโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฒ + โ•ฑ โ•ฒ + โ•ฑ โ€ข UseCase Tests (5) โ•ฒ + โ•ฑ โ€ข Repository Tests (9) โ•ฒ + โ•ฑ โ€ข ViewModel Tests (11) โ•ฒ + โ•ฑโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฒ +``` + +### Distribuiรงรฃo dos Testes + +- **Use Cases** (5 testes) + - โœ… Login bem-sucedido + - โœ… Validaรงรฃo de email + - โœ… Validaรงรฃo de senha + - โœ… Senha curta + - โœ… Credenciais invรกlidas + +- **Repository** (9 testes) + - โœ… Login remoto sucesso + - โœ… Credenciais invรกlidas + - โœ… Erro ao salvar localmente + - โœ… Obter usuรกrio atual + - โœ… Usuรกrio nรฃo encontrado + - โœ… Logout sucesso + - โœ… Erro ao fazer logout + - โœ… Verificar autenticaรงรฃo (3 cenรกrios) + +- **ViewModel** (11 testes) + - โœ… Atualizar email + - โœ… Atualizar senha + - โœ… Validaรงรฃo canSubmit (4 cenรกrios) + - โœ… Login (loading โ†’ success) + - โœ… Login (loading โ†’ error) + - โœ… Carregar usuรกrio atual (2 cenรกrios) + - โœ… Logout (2 cenรกrios) + - โœ… Reset de estado + +## ๐ŸŽฏ Princรญpios SOLID Aplicados + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ S - Single Responsibility Principle โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โœ… Cada Use Case tem uma รบnica responsabilidade โ”‚ +โ”‚ โœ… Data Sources separados (Remote vs Local) โ”‚ +โ”‚ โœ… ViewModel apenas gerencia estado da UI โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ O - Open/Closed Principle โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โœ… Aberto para extensรฃo: Novos use cases facilmente โ”‚ +โ”‚ โœ… Fechado para modificaรงรฃo: Interfaces estรกveis โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ L - Liskov Substitution Principle โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โœ… AuthRepositoryImpl substitui AuthRepository โ”‚ +โ”‚ โœ… Mocks substituem implementaรงรตes reais nos testes โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ I - Interface Segregation Principle โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โœ… Interfaces especรญficas (AuthRepository) โ”‚ +โ”‚ โœ… Data Sources com mรฉtodos focados โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ D - Dependency Inversion Principle โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ โœ… Use Cases dependem de interfaces, nรฃo implementaรงรตesโ”‚ +โ”‚ โœ… Repository depende de abstraรงรตes de Data Sources โ”‚ +โ”‚ โœ… Injeรงรฃo de dependรชncias via GetIt โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +## ๐Ÿ“ฆ Injeรงรฃo de Dependรชncias + +``` +setupServiceLocator() + โ”‚ + โ””โ”€โ”€โ–ถ setupAuthInjection(getIt) + โ”‚ + โ”œโ”€โ”€โ–ถ FlutterSecureStorage (Singleton) + โ”‚ + โ”œโ”€โ”€โ–ถ AuthLocalDataSource (Lazy Singleton) + โ”‚ โ””โ”€โ”€ depende de FlutterSecureStorage + โ”‚ + โ”œโ”€โ”€โ–ถ AuthRemoteDataSource (Lazy Singleton) + โ”‚ + โ”œโ”€โ”€โ–ถ AuthRepository (Lazy Singleton) + โ”‚ โ”œโ”€โ”€ depende de AuthRemoteDataSource + โ”‚ โ””โ”€โ”€ depende de AuthLocalDataSource + โ”‚ + โ”œโ”€โ”€โ–ถ SignInUseCase (Lazy Singleton) + โ”‚ โ””โ”€โ”€ depende de AuthRepository + โ”‚ + โ”œโ”€โ”€โ–ถ GetCurrentUserUseCase (Lazy Singleton) + โ”‚ โ””โ”€โ”€ depende de AuthRepository + โ”‚ + โ”œโ”€โ”€โ–ถ LogoutUseCase (Lazy Singleton) + โ”‚ โ””โ”€โ”€ depende de AuthRepository + โ”‚ + โ””โ”€โ”€โ–ถ SignInViewModel (Lazy Singleton) + โ”œโ”€โ”€ depende de SignInUseCase + โ”œโ”€โ”€ depende de GetCurrentUserUseCase + โ””โ”€โ”€ depende de LogoutUseCase +``` + +## ๐Ÿ” Tratamento de Erros + +``` +Exception/Error + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Data Sources โ”‚ +โ”‚ lanรงam Exceptions โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Repository โ”‚ +โ”‚ captura Exceptions โ”‚ +โ”‚ converte em โ”‚ +โ”‚ Failures โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Either โ”‚ +โ”‚ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Use Case โ”‚ +โ”‚ retorna Either โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ViewModel โ”‚ +โ”‚ fold() para tratar โ”‚ +โ”‚ Left (erro) ou โ”‚ +โ”‚ Right (sucesso) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ View โ”‚ +โ”‚ reage ao estado โ”‚ +โ”‚ mostra UI โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +--- + +# Guia Prรกtico - Como Usar a Nova Arquitetura + +## ๐Ÿš€ Inรญcio Rรกpido + +### 1. Usando o ViewModel na UI + +```dart +import 'package:distrito_medico/features/auth/presentation/viewmodels/signin_viewmodel.dart'; +import 'package:distrito_medico/features/auth/presentation/pages/signin_page.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:get_it/get_it.dart'; + +class MyLoginPage extends StatefulWidget { + @override + State createState() => _MyLoginPageState(); +} + +class _MyLoginPageState extends State { + // Injetar o ViewModel + final viewModel = GetIt.I.get(); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Column( + children: [ + // Campo de Email + TextField( + onChanged: viewModel.setEmail, + decoration: InputDecoration(labelText: 'Email'), + ), + + // Campo de Senha + TextField( + onChanged: viewModel.setPassword, + obscureText: true, + decoration: InputDecoration(labelText: 'Senha'), + ), + + // Botรฃo de Login com estado reativo + Observer( + builder: (_) { + return ElevatedButton( + onPressed: viewModel.canSubmit + ? () async { + await viewModel.signIn(); + } + : null, + child: viewModel.isLoading + ? CircularProgressIndicator() + : Text('Entrar'), + ); + }, + ), + ], + ), + ); + } +} +``` + +### 2. Reagindo a Mudanรงas de Estado + +```dart +import 'package:mobx/mobx.dart'; + +class _MyLoginPageState extends State { + final viewModel = GetIt.I.get(); + final List _disposers = []; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + // Reaรงรฃo para navegar quando login for bem-sucedido + _disposers.add( + reaction( + (_) => viewModel.state, + (state) { + if (state == SignInState.success) { + // Navegar para home + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (_) => HomePage()), + ); + } else if (state == SignInState.error) { + // Mostrar erro + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(viewModel.errorMessage)), + ); + } + }, + ), + ); + } + + @override + void dispose() { + // Limpar reaรงรตes + for (var disposer in _disposers) { + disposer(); + } + super.dispose(); + } +} +``` + +### 3. Verificando Autenticaรงรฃo no Inรญcio do App + +```dart +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + // Configurar injeรงรฃo de dependรชncias + setupServiceLocator(); + + // Carregar usuรกrio atual + final viewModel = GetIt.I.get(); + await viewModel.loadCurrentUser(); + + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + final viewModel = GetIt.I.get(); + + return MaterialApp( + home: Observer( + builder: (_) { + // Mostrar home se autenticado, senรฃo login + return viewModel.isAuthenticated + ? HomePage() + : SignInPage(); + }, + ), + ); + } +} +``` + +### 4. Implementando Logout + +```dart +class ProfilePage extends StatelessWidget { + final viewModel = GetIt.I.get(); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Perfil'), + actions: [ + IconButton( + icon: Icon(Icons.logout), + onPressed: () async { + await viewModel.logout(); + + // Navegar para login + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute(builder: (_) => SignInPage()), + (route) => false, + ); + }, + ), + ], + ), + body: Observer( + builder: (_) { + final user = viewModel.currentUser; + + if (user == null) { + return Center(child: Text('Nรฃo autenticado')); + } + + return Column( + children: [ + Text('Email: ${user.resourceOwner.email}'), + Text('ID: ${user.resourceOwner.id}'), + ], + ); + }, + ), + ); + } +} +``` + +## ๐Ÿงช Escrevendo Testes + +### 1. Teste de Use Case + +```dart +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:dartz/dartz.dart'; + +class MockAuthRepository extends Mock implements AuthRepository {} + +void main() { + late MyUseCase useCase; + late MockAuthRepository mockRepository; + + setUp(() { + mockRepository = MockAuthRepository(); + useCase = MyUseCase(mockRepository); + }); + + test('deve retornar sucesso quando...', () async { + // Arrange + when(() => mockRepository.someMethod()) + .thenAnswer((_) async => Right(expectedResult)); + + // Act + final result = await useCase(params); + + // Assert + expect(result, Right(expectedResult)); + verify(() => mockRepository.someMethod()).called(1); + }); +} +``` + +### 2. Teste de Repository + +```dart +void main() { + late AuthRepositoryImpl repository; + late MockRemoteDataSource mockRemoteDS; + late MockLocalDataSource mockLocalDS; + + setUp(() { + mockRemoteDS = MockRemoteDataSource(); + mockLocalDS = MockLocalDataSource(); + repository = AuthRepositoryImpl( + remoteDataSource: mockRemoteDS, + localDataSource: mockLocalDS, + ); + }); + + test('deve salvar usuรกrio localmente apรณs login', () async { + // Arrange + when(() => mockRemoteDS.signIn( + email: any(named: 'email'), + password: any(named: 'password'), + )).thenAnswer((_) async => userModel); + + when(() => mockLocalDS.saveUser(any())) + .thenAnswer((_) async => {}); + + // Act + await repository.signIn(email: 'test@test.com', password: '1234'); + + // Assert + verify(() => mockLocalDS.saveUser(userModel)).called(1); + }); +} +``` + +### 3. Teste de ViewModel + +```dart +void main() { + late SignInViewModel viewModel; + late MockSignInUseCase mockUseCase; + + setUp(() { + mockUseCase = MockSignInUseCase(); + viewModel = SignInViewModel( + signInUseCase: mockUseCase, + // ... outros use cases + ); + }); + + test('deve mudar estado para loading ao fazer login', () async { + // Arrange + viewModel.setEmail('test@test.com'); + viewModel.setPassword('1234'); + + when(() => mockUseCase(any())) + .thenAnswer((_) async => Right(userEntity)); + + // Act + final future = viewModel.signIn(); + + // Assert - Estado loading + expect(viewModel.state, SignInState.loading); + expect(viewModel.isLoading, true); + + await future; + + // Assert - Estado success + expect(viewModel.state, SignInState.success); + }); +} +``` + +## ๐Ÿ”ง Criando uma Nova Feature + +### Passo 1: Estrutura de Pastas + +```bash +lib/features/minha_feature/ +โ”œโ”€โ”€ data/ +โ”‚ โ”œโ”€โ”€ datasources/ +โ”‚ โ”‚ โ”œโ”€โ”€ minha_feature_local_datasource.dart +โ”‚ โ”‚ โ””โ”€โ”€ minha_feature_remote_datasource.dart +โ”‚ โ”œโ”€โ”€ models/ +โ”‚ โ”‚ โ””โ”€โ”€ minha_model.dart +โ”‚ โ””โ”€โ”€ repositories/ +โ”‚ โ””โ”€โ”€ minha_repository_impl.dart +โ”œโ”€โ”€ domain/ +โ”‚ โ”œโ”€โ”€ entities/ +โ”‚ โ”‚ โ””โ”€โ”€ minha_entity.dart +โ”‚ โ”œโ”€โ”€ repositories/ +โ”‚ โ”‚ โ””โ”€โ”€ minha_repository.dart +โ”‚ โ””โ”€โ”€ usecases/ +โ”‚ โ””โ”€โ”€ meu_usecase.dart +โ”œโ”€โ”€ presentation/ +โ”‚ โ”œโ”€โ”€ pages/ +โ”‚ โ”‚ โ””โ”€โ”€ minha_page.dart +โ”‚ โ””โ”€โ”€ viewmodels/ +โ”‚ โ””โ”€โ”€ meu_viewmodel.dart +โ””โ”€โ”€ minha_feature_injection.dart +``` + +### Passo 2: Domain Layer + +```dart +// 1. Criar Entity +class MinhaEntity extends Equatable { + final String id; + final String nome; + + const MinhaEntity({required this.id, required this.nome}); + + @override + List get props => [id, nome]; +} + +// 2. Criar Repository Interface +abstract class MinhaRepository { + Future> buscar(String id); + Future>> listar(); + Future> salvar(MinhaEntity entity); +} + +// 3. Criar Use Case +class BuscarUseCase implements UseCase { + final MinhaRepository repository; + + BuscarUseCase(this.repository); + + @override + Future> call(String id) async { + if (id.isEmpty) { + return const Left(ValidationFailure(message: 'ID nรฃo pode ser vazio')); + } + return await repository.buscar(id); + } +} +``` + +### Passo 3: Data Layer + +```dart +// 1. Criar Model +class MinhaModel extends MinhaEntity { + const MinhaModel({required super.id, required super.nome}); + + factory MinhaModel.fromJson(Map json) { + return MinhaModel( + id: json['id'] as String, + nome: json['nome'] as String, + ); + } + + Map toJson() { + return {'id': id, 'nome': nome}; + } + + MinhaEntity toEntity() { + return MinhaEntity(id: id, nome: nome); + } +} + +// 2. Criar Remote Data Source +abstract class MinhaRemoteDataSource { + Future buscar(String id); +} + +class MinhaRemoteDataSourceImpl implements MinhaRemoteDataSource { + @override + Future buscar(String id) async { + try { + final response = await minhaService.buscar(id); + if (response.isSuccessful) { + return MinhaModel.fromJson(json.decode(response.body)); + } + throw ServerException(message: 'Erro ao buscar'); + } catch (e) { + throw ServerException(message: e.toString()); + } + } +} + +// 3. Criar Repository Implementation +class MinhaRepositoryImpl implements MinhaRepository { + final MinhaRemoteDataSource remoteDataSource; + + MinhaRepositoryImpl({required this.remoteDataSource}); + + @override + Future> buscar(String id) async { + try { + final model = await remoteDataSource.buscar(id); + return Right(model.toEntity()); + } on ServerException catch (e) { + return Left(ServerFailure(message: e.message)); + } catch (e) { + return Left(UnexpectedFailure(message: e.toString())); + } + } +} +``` + +### Passo 4: Presentation Layer + +```dart +// 1. Criar ViewModel +class MeuViewModel = _MeuViewModelBase with _$MeuViewModel; + +abstract class _MeuViewModelBase with Store { + final BuscarUseCase buscarUseCase; + + _MeuViewModelBase({required this.buscarUseCase}); + + @observable + MinhaEntity? item; + + @observable + bool isLoading = false; + + @observable + String errorMessage = ''; + + @action + Future buscar(String id) async { + isLoading = true; + errorMessage = ''; + + final result = await buscarUseCase(id); + + result.fold( + (failure) { + errorMessage = failure.message; + isLoading = false; + }, + (entity) { + item = entity; + isLoading = false; + }, + ); + } +} + +// 2. Criar Page +class MinhaPage extends StatelessWidget { + final viewModel = GetIt.I.get(); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Observer( + builder: (_) { + if (viewModel.isLoading) { + return CircularProgressIndicator(); + } + + if (viewModel.errorMessage.isNotEmpty) { + return Text('Erro: ${viewModel.errorMessage}'); + } + + final item = viewModel.item; + if (item == null) { + return Text('Nenhum item'); + } + + return Text('Nome: ${item.nome}'); + }, + ), + ); + } +} +``` + +### Passo 5: Injeรงรฃo de Dependรชncias + +```dart +void setupMinhaFeatureInjection(GetIt getIt) { + // Data Sources + getIt.registerLazySingleton( + () => MinhaRemoteDataSourceImpl(), + ); + + // Repositories + getIt.registerLazySingleton( + () => MinhaRepositoryImpl( + remoteDataSource: getIt(), + ), + ); + + // Use Cases + getIt.registerLazySingleton( + () => BuscarUseCase(getIt()), + ); + + // ViewModels + getIt.registerLazySingleton( + () => MeuViewModel( + buscarUseCase: getIt(), + ), + ); +} + +// No service_locator.dart +void setupServiceLocator() { + // ... outras configuraรงรตes + + setupMinhaFeatureInjection(getIt); +} +``` + +## ๐Ÿ’ก Dicas e Boas Prรกticas + +### 1. Sempre use Either para retornos de mรฉtodos assรญncronos + +```dart +// โŒ Evite +Future getUser(); + +// โœ… Prefira +Future> getUser(); +``` + +### 2. Mantenha as Entities puras (sem dependรชncias) + +```dart +// โŒ Evite +class User { + final String id; + + Future save() { + // Lรณgica de persistรชncia + } +} + +// โœ… Prefira +class User extends Equatable { + final String id; + + const User({required this.id}); + + @override + List get props => [id]; +} +``` + +### 3. Um Use Case = Uma Responsabilidade + +```dart +// โŒ Evite +class UserUseCase { + Future> signIn(); + Future> signUp(); + Future> logout(); +} + +// โœ… Prefira +class SignInUseCase { + Future> call(SignInParams params); +} + +class SignUpUseCase { + Future> call(SignUpParams params); +} + +class LogoutUseCase { + Future> call(NoParams params); +} +``` + +### 4. ViewModels nรฃo devem conhecer detalhes de implementaรงรฃo + +```dart +// โŒ Evite +class MyViewModel { + final AuthRepository repository; + + Future login() { + // Chamando repository diretamente + await repository.signIn(email, password); + } +} + +// โœ… Prefira +class MyViewModel { + final SignInUseCase signInUseCase; + + Future login() { + // Chamando use case + await signInUseCase(SignInParams(email: email, password: password)); + } +} +``` + +### 5. Sempre escreva testes + +```dart +// Para cada Use Case, escreva no mรญnimo: +// - 1 teste de sucesso +// - 1 teste de erro +// - Testes de validaรงรฃo (se houver) + +// Para cada Repository, escreva no mรญnimo: +// - 1 teste de sucesso +// - 1 teste de erro de servidor +// - 1 teste de erro de cache (se aplicรกvel) + +// Para cada ViewModel, escreva no mรญnimo: +// - Testes de mudanรงa de estado +// - Testes de propriedades computadas +// - Testes de interaรงรฃo com use cases +``` + +## ๐ŸŽฏ Checklist para Nova Feature + +- [ ] Criar estrutura de pastas (domain, data, presentation) +- [ ] Criar Entity no domain +- [ ] Criar Repository interface no domain +- [ ] Criar Use Cases no domain +- [ ] Criar Models no data +- [ ] Criar Data Sources (remote e/ou local) no data +- [ ] Criar Repository implementation no data +- [ ] Criar ViewModel no presentation +- [ ] Criar Page/Widget no presentation +- [ ] Configurar injeรงรฃo de dependรชncias +- [ ] Escrever testes unitรกrios +- [ ] Executar `flutter pub run build_runner build` +- [ ] Testar manualmente +- [ ] Documentar (README.md na pasta da feature) + +## ๐Ÿ“š Recursos Adicionais + +- [Clean Architecture - Uncle Bob](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) +- [Dartz Package](https://pub.dev/packages/dartz) +- [MobX Documentation](https://mobx.netlify.app/) +- [GetIt Documentation](https://pub.dev/packages/get_it) +- [Mocktail Documentation](https://pub.dev/packages/mocktail) + +## Como Testar as Features + +### 1. Testes Automatizados (Unit & Widget Tests) +Para rodar todos os testes do projeto e garantir que a refatora็ใo ou nova feature nใo quebrou funcionalidades existentes: + +`ash +flutter test +` + +Para rodar testes de uma feature especํfica (ex: medical_shifts): + +`ash +flutter test test/features/medical_shifts/ +` + +### 2. Testes Manuais - Fluxo de Medical Shifts +Recomendamos validar manualmente os seguintes cenแrios ap๓s rodar o projeto ('flutter run'): + +1. **Listagem e Filtros** + - Acesse a tela de Plant๕es (Home ou Menu). + - Verifique se a lista inicial carrega corretamente. + - Teste mudar o m๊s/ano no calendแrio. + - Aplique filtros por 'Pago', 'Nใo Pago' e nome do Hospital. + - Use o botใo 'Limpar Filtros' e verifique o reset. + +2. **Cadastro (CRUD)** + - Clique em '+' ou 'Novo Plantใo'. + - Tente salvar vazio -> Deve mostrar alerta. + - Preencha um plantใo simples (Hospital, Valor, Data, Hora). + - Salve -> Deve voltar เ lista e exibir Toast de Sucesso. + - Verifique se o novo item aparece na lista. + +3. **Recorr๊ncia** + - No cadastro, ative 'Recorrente'. + - Teste frequ๊ncia 'Semanal' -> Deve exibir dias da semana. + - Teste frequ๊ncia 'Mensal (Dia Fixo)' -> Deve exibir seletor de dia (1-31). + - Defina uma data final. + - Salve e verifique se m๚ltiplos plant๕es foram criados no calendแrio. + +4. **Edi็ใo e Exclusใo** + - Abra um plantใo existente. + - Edite o valor ou status de pagamento. + - Salve -> Verifique atualiza็ใo na lista. + - Deslize o item na lista para a esquerda -> Clique 'Deletar'. + - Se for recorrente, deve perguntar: 'Excluir apenas este ou a s้rie toda?'. + - Confirme e verifique a remo็ใo (Toast de Sucesso deve aparecer). + +5. **Gera็ใo de PDF** + - Na tela de listagem, clique no ํcone de PDF. + - Aplique filtros desejados e gere o relat๓rio. + - Verifique se o arquivo abre corretamente no visualizador. -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples, guidance on mobile development, and a full API reference. diff --git a/med_system_app/android/app/build.gradle b/med_system_app/android/app/build.gradle index 6c040aa..044ca02 100644 --- a/med_system_app/android/app/build.gradle +++ b/med_system_app/android/app/build.gradle @@ -24,8 +24,8 @@ if (flutterVersionName == null) { android { namespace "dev.flutter.distrito_medico" - compileSdkVersion flutter.compileSdkVersion - ndkVersion flutter.ndkVersion + compileSdkVersion 36 + ndkVersion "27.0.12077973" compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 @@ -41,20 +41,20 @@ android { } defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "dev.flutter.distrito_medico" - // You can update the following values to match your application needs. - // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. - minSdkVersion 21 - targetSdkVersion flutter.targetSdkVersion + + // Android SDK versions - Updated for modern libraries + minSdkVersion 24 // Required by flutter_secure_storage v10+ + targetSdkVersion 36 // Latest Android API level + versionCode flutterVersionCode.toInteger() versionName flutterVersionName } buildTypes { release { - // TODO: Add your own signing config for the release build. - // Signing with the debug keys for now, so `flutter run --release` works. + // Using debug signing for development + // For production, configure proper signing in keystore.properties signingConfig signingConfigs.debug } } diff --git a/med_system_app/android/build.gradle b/med_system_app/android/build.gradle index e83fb5d..6d3ff83 100644 --- a/med_system_app/android/build.gradle +++ b/med_system_app/android/build.gradle @@ -1,11 +1,12 @@ buildscript { - ext.kotlin_version = '1.7.10' + ext.kotlin_version = '2.0.0' repositories { google() mavenCentral() } dependencies { + classpath 'com.android.tools.build:gradle:8.7.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/med_system_app/android/gradle/wrapper/gradle-wrapper.properties b/med_system_app/android/gradle/wrapper/gradle-wrapper.properties index 3c472b9..64dcb3c 100644 --- a/med_system_app/android/gradle/wrapper/gradle-wrapper.properties +++ b/med_system_app/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Fri Dec 12 13:40:53 BRT 2025 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/med_system_app/android/settings.gradle b/med_system_app/android/settings.gradle index 7cd7128..e6f71a6 100644 --- a/med_system_app/android/settings.gradle +++ b/med_system_app/android/settings.gradle @@ -23,7 +23,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "7.3.0" apply false + id "com.android.application" version "8.7.3" apply false } include ":app" diff --git a/med_system_app/lib/app.page.dart b/med_system_app/lib/app.page.dart index 391bfc0..28346ac 100644 --- a/med_system_app/lib/app.page.dart +++ b/med_system_app/lib/app.page.dart @@ -1,6 +1,6 @@ import 'package:distrito_medico/core/pages/splash/splash_page.dart'; import 'package:distrito_medico/features/home/pages/home_page.dart'; -import 'package:distrito_medico/features/signin/store/signin.store.dart'; +import 'package:distrito_medico/features/auth/presentation/viewmodels/signin_viewmodel.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; @@ -50,7 +50,7 @@ class Template extends StatefulWidget { } class _TemplateState extends State