diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..bd1093a
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,47 @@
+# ============================================================================
+# Bug Report Template
+# ============================================================================
+---
+name: Bug Report
+about: Create a report to help us improve
+title: '[BUG]'
+labels: bug
+assignees: ''
+---
+
+## ๐ Bug Description
+
+
+## Environment
+- **Device**
+- **OS Version**:
+- **App Version**:
+- **Build**: Dev/Staging/Production
+
+## ๐ Steps To Reproduce
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+## โ
Expected Behavior
+
+
+## โ Actual Behavior
+
+
+## ๐ธ Screenshots
+
+
+## ๐ Logs
+
+
+```
+Paste logs here
+```
+
+## ๐ก Possible Solution
+
+
+## ๐ Additional Context
+
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..14ed8d4
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,28 @@
+# ============================================================================
+# Feature Request Template
+# ============================================================================
+---
+name: Feature Request
+about: Suggest a feature for this project
+title: '[FEATURE] '
+labels: enhancement
+assignees: ''
+---
+
+## โจ Feature Description
+
+
+## ๐ฏ Problem/Motivation
+
+
+## ๐ก Proposed Solution
+
+
+## ๐ Alternatives Considered
+
+
+## ๐ธ Mockups/Examples
+
+
+## ๐ Additional Context
+
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..6c12a64
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,50 @@
+#=======================================================================
+# Pull Request Template
+#=======================================================================
+## ๐ Description
+
+
+## ๐ฏ Type of Change
+- [ ] ๐ Bug fix (non-breaking change that fixes an issue)
+- [ ] โจ New feature (non-breaking change that adds functionality)
+- [ ] ๐ฅ Breaking change (fix or feature that causes existing functionality to change)
+- [ ] ๐ Documentation update
+- [ ] ๐จ UI/UX improvement
+- [ ] โก Performance improvement
+- [ ] ๐ Security fix
+
+## ๐งช Testing
+- [ ] Unit tests pass locally
+- [ ] Integration tests pass (if applicable)
+- [ ] Manual testing completed
+- [ ] Tested on physical device
+
+## Test Coverage
+- [ ] Added tests for new features
+- [ ] Updated existing tests
+- [ ] Coverage increased/maintained
+
+## ๐ฑ Tested On
+- [ ] Android (API level: ___)
+- [ ] iOS (version: ___)
+- [ ] Physical device (model: ___)
+- [ ] Emulator/Simulator
+
+## ๐ธ Screenshots (if UI changes)
+
+
+## โ
Checklist
+- [ ] Code follows project style guidelines
+- [ ] Self-reviewed the code
+- [ ] Commented complex code sections
+- [ ] Updated documentation (if needed)
+- [ ] No new warnings generated
+- [ ] Added tests that prove fix/feature works
+- [ ] All tests pass locally
+- [ ] Conflicts resolved with base branch
+
+## ๐ Related Issues
+
+
+## ๐ Additional Notes
+
\ No newline at end of file
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..5578626
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,23 @@
+# ============================================================================
+# .github/dependabot.yml
+# Automated dependency updates
+# ============================================================================
+version: 2
+updates:
+ # GitHub Actions
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ labels:
+ - "dependencies"
+ - "github-actions"
+
+ # Gradle (Android)
+ - package-ecosystem: "gradle"
+ directory: "/android"
+ schedule:
+ interval: "weekly"
+ labels:
+ - "dependencies"
+ - "android"
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e69de29..5978a31 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,153 @@
+# ============================================================================
+# CONTINUOUS INTEGRATION PIPELINE
+# Runs on every PR and push to develop/main
+# ============================================================================
+name: Continuous Integration
+
+on:
+ pull_request:
+ branches: [develop, main]
+ push:
+ branches: [develop, main]
+ workflow_dispatch:
+
+env:
+ FLUTTER_VERSION: '3.35.7'
+ JAVA_VERSION: '17'
+
+jobs:
+ # ============================================================================
+ # CODE QUALITY CHECKS
+ # ============================================================================
+ code-quality:
+ name: Code Quality Analysis
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+
+ steps:
+ - name: ๐ฆ Checkout Code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: ๐ฏ Setup Flutter
+ uses: subosito/flutter-action@v2
+ with:
+ flutter-version: ${{ env.FLUTTER_VERSION }}
+ channel: 'stable'
+ cache: true
+
+ - name: ๐ Get Dependencies
+ run: flutter pub get
+
+ - name: ๐ Run Flutter Analyzer
+ run: flutter analyze --no-fatal-infos
+
+ - name: ๐จ Check Code Formatting
+ run: dart format --set-exit-if-changed .
+
+ - name: ๐ Check for Secrets
+ uses: trufflesecurity/trufflehog@main
+ with:
+ path: ./
+ base: ${{ github.event.repository.default_branch }}
+ head: HEAD
+
+
+ # ============================================================================
+ # UNIT & WIDGET TESTS
+ # ============================================================================
+ tests:
+ name: Run Tests
+ runs-on: ubuntu-latest
+ needs: code-quality
+ timeout-minutes: 15
+
+ steps:
+ - name: ๐ฆ Checkout Code
+ uses: actions/checkout@v4
+
+ - name: ๐ฏ Setup Flutter
+ uses: subosito/flutter-action@v2
+ with:
+ flutter-version: ${{ env.FLUTTER_VERSION }}
+ channel: 'stable'
+ cache: true
+
+ - name: ๐ Get Dependencies
+ run: flutter pub get
+
+ - name: ๐งช Run Tests with Coverage
+ run: flutter test --coverage --reporter expanded
+
+ - name: ๐ Upload Coverage to Codecov
+ uses: codecov/codecov-action@v3
+ with:
+ files: ./coverage/lcov.info
+ fail_ci_if_error: false
+ verbose: true
+
+ - name: ๐ Generate Coverage Report
+ run: |
+ flutter pub global activate coverage
+ flutter pub global run coverage:format_coverage --lcov --in=coverage --out=coverage/lcov.info --report-on=lib
+
+
+ # ============================================================================
+ # BUILD VERIFICATION (Debug APK)
+ # ============================================================================
+ build-verification:
+ name: Build Verification
+ runs-on: ubuntu-latest
+ needs: tests
+ timeout-minutes: 20
+
+ strategy:
+ matrix:
+ flavor: [dev, staging, prod]
+
+ steps:
+ - name: ๐ฆ Checkout Code
+ uses: actions/checkout@v4
+
+ - name: ๐ฏ Setup Flutter
+ uses: subosito/flutter-action@v2
+ with:
+ flutter-version: ${{ env.FLUTTER_VERSION }}
+ channel: 'stable'
+ cache: true
+
+ - name: โ Setup Java
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'zulu'
+ java-version: ${{ env.JAVA_VERSION }}
+ cache: 'gradle'
+
+ - name: ๐ฆ Get Dependencies
+ run: flutter pub get
+
+ - name: ๐จ Run Code Generation
+ run: flutter pub run build_runner build --delete-conflicting-outputs
+
+ - name: ๐ Create Environment File
+ run: |
+ mkdir -p assets/config
+ echo "APP_NAME=Password Manager ${{ matrix.flavor }}" > assets/config/.env.${{ matrix.flavor }}
+ echo "APP_ENV=${{ matrix.flavor }}" >> assets/config/.env.${{ matrix.flavor }}
+ echo "API_BASE_URL=${{ secrets[format('{0}_API_BASE_URL', matrix.flavor)] }}" >> assets/config/.env.${{ matrix.flavor }}
+
+ - name: ๐จ Build Debug APK
+ run: |
+ flutter build apk \
+ -t lib/main_${{ matrix.flavor }}.dart \
+ --debug \
+ --flavor ${{ matrix.flavor }} \
+ --dart-define=FLAVOR=${{ matrix.flavor }}
+
+ - name: ๐ค Upload Build Artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: apk-${{ matrix.flavor }}-debug
+ path: build/app/outputs/flutter-apk/app-${{ matrix.flavor }}-debug.apk
+ retention-days: 7
diff --git a/.github/workflows/deploy_dev.yml b/.github/workflows/deploy_dev.yml
index 8ef3de7..9b5b4c1 100644
--- a/.github/workflows/deploy_dev.yml
+++ b/.github/workflows/deploy_dev.yml
@@ -354,16 +354,17 @@ jobs:
- Branch
+ Branch
${{ github.ref_name }}
- Commit
+ Commit
${{ github.sha }}
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
index ec826fd..16afabb 100644
Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
index 49477c8..2537235 100644
Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
index 3c9cf09..4e73f43 100644
Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
index 3713ba1..f3b505e 100644
Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
index 28d335a..2ce5d49 100644
Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/assets/logo/app_logo.png b/assets/logo/app_logo.png
index 10beb10..5711861 100644
Binary files a/assets/logo/app_logo.png and b/assets/logo/app_logo.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
index 3fda7f7..2f9ac6c 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
index 8c6ed0b..e5ddf5d 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
index 0cd034a..dcf1bc1 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
index d3cf792..e17e1aa 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
index 16d8472..b149f64 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
index 9638b54..4e4dfa7 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
index 35d71b7..d7c524d 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
index 0cd034a..dcf1bc1 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
index 3abaa05..5b039c5 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
index dbc0baa..aac70a7 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png
index f61f19e..90bd46a 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png
index e821bb9..536106b 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png
index 76f519c..2571431 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png
index 729803c..a2a804d 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
index dbc0baa..aac70a7 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
index 56c8eee..1d03075 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png
index ec826fd..16afabb 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png
index 3713ba1..f3b505e 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
index 9357e4c..d4896ed 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
index c0eb1b8..bc5403c 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
index 3bd6186..f625bb3 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ
diff --git a/lib/config/routes/app_router.dart b/lib/config/routes/app_router.dart
index b3007fb..6593154 100644
--- a/lib/config/routes/app_router.dart
+++ b/lib/config/routes/app_router.dart
@@ -8,6 +8,7 @@ import 'package:password_manager/feature/auth/presentation/pages/master-password
import 'package:password_manager/feature/auth/presentation/pages/master-password-success/master_password_success.dart';
import 'package:password_manager/feature/auth/presentation/pages/unlock-vault/unlock_vault.dart';
import 'package:password_manager/feature/home/domain/entities/password_entry.dart';
+import 'package:password_manager/feature/home/presentation/mainscreen/pages/camera_screen.dart';
import 'package:password_manager/feature/home/presentation/mainscreen/pages/main_screen.dart';
import 'package:password_manager/feature/home/presentation/password_detail/password_add_screen.dart';
import 'package:password_manager/feature/home/presentation/password_detail/password_detail_screen.dart';
@@ -179,15 +180,15 @@ class AppRouter {
// // ======================================================================
// // CATEGORIES
// // ======================================================================
- // GoRoute(
- // path: Routes.categories,
- // name: RouteNames.categories,
- // pageBuilder: (context, state) => CustomTransitionPage(
- // key: state.pageKey,
- // child: const CategoriesPage(),
- // transitionsBuilder: RouteTransitions.slide,
- // ),
- // ),
+ GoRoute(
+ path: Routes.camera,
+ name: RouteNames.camera,
+ pageBuilder: (context, state) => CustomTransitionPage(
+ key: state.pageKey,
+ child: SnapCameraPro(),
+ transitionsBuilder: RouteTransitions.slide,
+ ),
+ ),
// GoRoute(
// path: Routes.addCategory,
diff --git a/lib/config/routes/route_guards.dart b/lib/config/routes/route_guards.dart
index 5fb7b0e..470b795 100644
--- a/lib/config/routes/route_guards.dart
+++ b/lib/config/routes/route_guards.dart
@@ -203,6 +203,7 @@ class AuthGuard {
Routes.masterPasswordSetup,
Routes.masterPasswordSuccess,
Routes.unlock,
+ Routes.camera,
];
return publicRoutes.contains(path);
diff --git a/lib/config/routes/route_names.dart b/lib/config/routes/route_names.dart
index cab0b30..1ac141e 100644
--- a/lib/config/routes/route_names.dart
+++ b/lib/config/routes/route_names.dart
@@ -2,6 +2,8 @@
class Routes {
Routes._();
+ static const String camera ='/camera';
+
// Core App Routes
static const String splash = '/';
static const String onboarding = '/onboarding';
@@ -43,6 +45,8 @@ class Routes {
class RouteNames {
RouteNames._();
+ static const String camera= 'camera';
+
static const String splash = 'splash';
static const String onboarding = 'onboarding';
static const String masterPasswordSetup = 'masterPasswordSetup';
diff --git a/lib/feature/home/presentation/mainscreen/pages/camera_screen.dart b/lib/feature/home/presentation/mainscreen/pages/camera_screen.dart
new file mode 100644
index 0000000..00aa928
--- /dev/null
+++ b/lib/feature/home/presentation/mainscreen/pages/camera_screen.dart
@@ -0,0 +1,544 @@
+import 'dart:async';
+import 'dart:io';
+import 'dart:math';
+import 'package:camera/camera.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+
+// ---------------------------------------------------------
+// CUSTOM PAINTER โ Optimized progress drawing
+// ---------------------------------------------------------
+class RecordProgressPainter extends CustomPainter {
+ final double progress;
+ final Color color;
+
+ RecordProgressPainter({required this.progress, required this.color});
+
+ @override
+ void paint(Canvas canvas, Size size) {
+ const strokeWidth = 6.0;
+
+ final paint = Paint()
+ ..color = color
+ ..strokeWidth = strokeWidth
+ ..style = PaintingStyle.stroke
+ ..strokeCap = StrokeCap.round;
+
+ canvas.drawArc(
+ Rect.fromLTWH(0, 0, size.width, size.height).deflate(strokeWidth / 2),
+ -pi / 2,
+ 2 * pi * progress,
+ false,
+ paint,
+ );
+ }
+
+ @override
+ bool shouldRepaint(covariant RecordProgressPainter oldDelegate) {
+ return oldDelegate.progress != progress;
+ }
+}
+
+// ---------------------------------------------------------
+// MAIN CAMERA WIDGET
+// ---------------------------------------------------------
+class SnapCameraPro extends StatefulWidget {
+ const SnapCameraPro({super.key});
+
+ @override
+ State createState() => _SnapCameraProState();
+}
+
+class _SnapCameraProState extends State
+ with SingleTickerProviderStateMixin, WidgetsBindingObserver {
+ CameraController? _controller;
+ List _cameras = [];
+
+ bool _isRecording = false;
+ bool _isInitialized = false;
+ bool _isSwitchingCamera = false;
+ String? _errorMessage;
+
+ double _zoomLevel = 1.0;
+ double _minZoom = 1.0;
+ double _maxZoom = 1.0;
+
+ // Recording timer & progress
+ Timer? _recordTimer;
+ double _progress = 0;
+ static const int _maxSeconds = 10;
+ int _currentSeconds = 0;
+
+ late AnimationController _scaleController;
+ late Animation _scaleAnimation;
+
+ @override
+ void initState() {
+ super.initState();
+ WidgetsBinding.instance.addObserver(this);
+ _initializeAnimations();
+ _initCamera();
+ }
+
+ @override
+ void dispose() {
+ WidgetsBinding.instance.removeObserver(this);
+ _recordTimer?.cancel();
+ _controller?.dispose();
+ _scaleController.dispose();
+ super.dispose();
+ }
+
+ // Handle app lifecycle changes
+ @override
+ void didChangeAppLifecycleState(AppLifecycleState state) {
+ final controller = _controller;
+ if (controller == null || !controller.value.isInitialized) {
+ return;
+ }
+
+ if (state == AppLifecycleState.inactive) {
+ controller.dispose();
+ } else if (state == AppLifecycleState.resumed) {
+ _initCamera(_controller?.description);
+ }
+ }
+
+ // ---------------------------------------------------------
+ // ANIMATION SETUP
+ // ---------------------------------------------------------
+ void _initializeAnimations() {
+ _scaleController = AnimationController(
+ vsync: this,
+ duration: const Duration(milliseconds: 250),
+ );
+
+ _scaleAnimation = Tween(begin: 1.0, end: 1.3).animate(
+ CurvedAnimation(parent: _scaleController, curve: Curves.easeOutBack),
+ );
+ }
+
+ // ---------------------------------------------------------
+ // CAMERA INITIALIZATION - Optimized with error handling
+ // ---------------------------------------------------------
+ Future _initCamera([CameraDescription? cam]) async {
+ try {
+ setState(() {
+ _isInitialized = false;
+ _errorMessage = null;
+ });
+
+ // Get available cameras
+ _cameras = await availableCameras();
+
+ if (_cameras.isEmpty) {
+ throw CameraException('NO_CAMERA', 'No cameras available');
+ }
+
+ // Dispose previous controller if exists
+ await _controller?.dispose();
+
+ // Initialize new controller
+ final camera = cam ?? _cameras.first;
+ final controller = CameraController(
+ camera,
+ ResolutionPreset
+ .high, // Use 'high' instead of 'max' for better performance
+ enableAudio: true,
+ imageFormatGroup: ImageFormatGroup.jpeg,
+ );
+
+ _controller = controller;
+ await controller.initialize();
+
+ // Get zoom levels
+ _minZoom = await controller.getMinZoomLevel();
+ _maxZoom = await controller.getMaxZoomLevel();
+ _zoomLevel = _minZoom;
+
+ if (mounted) {
+ setState(() => _isInitialized = true);
+ }
+ } catch (e) {
+ _handleError('Failed to initialize camera: $e');
+ }
+ }
+
+ // ---------------------------------------------------------
+ // ERROR HANDLING
+ // ---------------------------------------------------------
+ void _handleError(String message) {
+ debugPrint('Camera Error: $message');
+ if (mounted) {
+ setState(() => _errorMessage = message);
+
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text(message),
+ backgroundColor: Colors.red,
+ duration: const Duration(seconds: 3),
+ ),
+ );
+ }
+ }
+
+ // ---------------------------------------------------------
+ // PHOTO CAPTURE - Optimized
+ // ---------------------------------------------------------
+ Future _takePhoto() async {
+ if (_isRecording || !_isInitialized) return;
+
+ try {
+ await HapticFeedback.mediumImpact();
+ final file = await _controller!.takePicture();
+ debugPrint('๐ธ Photo saved: ${file.path}');
+
+ // Show feedback
+ if (mounted) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text('Photo saved: ${file.path}'),
+ duration: const Duration(seconds: 2),
+ ),
+ );
+ }
+ } catch (e) {
+ _handleError('Failed to take photo: $e');
+ }
+ }
+
+ // ---------------------------------------------------------
+ // VIDEO RECORDING - START
+ // ---------------------------------------------------------
+ Future _startRecording() async {
+ if (_isRecording || !_isInitialized) return;
+
+ try {
+ await HapticFeedback.mediumImpact();
+
+ setState(() {
+ _isRecording = true;
+ _progress = 0;
+ _currentSeconds = 0;
+ });
+
+ _scaleController.forward();
+
+ await _controller!.startVideoRecording();
+
+ // Use 100ms intervals for smooth progress
+ _recordTimer = Timer.periodic(const Duration(milliseconds: 100), (_) {
+ if (!mounted) return;
+
+ setState(() {
+ _progress += 0.01; // 0.01 * 100 calls = 1.0 in 10 seconds
+ _currentSeconds = (_progress * _maxSeconds).toInt();
+ });
+
+ if (_progress >= 1.0) {
+ _stopRecording();
+ }
+ });
+ } catch (e) {
+ _handleError('Failed to start recording: $e');
+ setState(() => _isRecording = false);
+ _scaleController.reverse();
+ }
+ }
+
+ // ---------------------------------------------------------
+ // VIDEO RECORDING - STOP
+ // ---------------------------------------------------------
+ Future _stopRecording() async {
+ if (!_isRecording) return;
+
+ try {
+ // Cancel timer and update UI first
+ _recordTimer?.cancel();
+ _recordTimer = null;
+
+ // Update state before stopping recording
+ if (mounted) {
+ setState(() => _isRecording = false);
+ }
+
+ await HapticFeedback.mediumImpact();
+ _scaleController.reverse();
+
+ // Check if controller is still recording
+ if (_controller != null && _controller!.value.isRecordingVideo) {
+ final file = await _controller!.stopVideoRecording();
+ debugPrint('๐ฅ Video saved: ${file.path}');
+
+ if (mounted) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text('Video saved: ${file.path}'),
+ duration: const Duration(seconds: 2),
+ ),
+ );
+ }
+ }
+ } catch (e) {
+ debugPrint('Error stopping recording: $e');
+ // Don't show error to user for stop recording issues
+ // Just ensure state is cleaned up
+ if (mounted) {
+ setState(() => _isRecording = false);
+ }
+ }
+ }
+
+ // ---------------------------------------------------------
+ // CAMERA SWITCH - Smooth with loading state
+ // ---------------------------------------------------------
+ Future _switchCamera() async {
+ if (_cameras.length < 2 || _isSwitchingCamera || _isRecording) return;
+
+ try {
+ setState(() => _isSwitchingCamera = true);
+ await HapticFeedback.lightImpact();
+
+ final isFront =
+ _controller!.description.lensDirection == CameraLensDirection.front;
+
+ final newCamera = _cameras.firstWhere(
+ (cam) =>
+ cam.lensDirection ==
+ (isFront ? CameraLensDirection.back : CameraLensDirection.front),
+ orElse: () => _cameras.first,
+ );
+
+ await _initCamera(newCamera);
+ } catch (e) {
+ _handleError('Failed to switch camera: $e');
+ } finally {
+ if (mounted) {
+ setState(() => _isSwitchingCamera = false);
+ }
+ }
+ }
+
+ // ---------------------------------------------------------
+ // ZOOM CONTROL - Optimized
+ // ---------------------------------------------------------
+ void _setZoom(double value) {
+ final clampedValue = value.clamp(_minZoom, _maxZoom);
+ setState(() => _zoomLevel = clampedValue);
+ _controller?.setZoomLevel(clampedValue);
+ }
+
+ // ---------------------------------------------------------
+ // UI BUILD
+ // ---------------------------------------------------------
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(backgroundColor: Colors.black, body: _buildBody());
+ }
+
+ Widget _buildBody() {
+ if (_errorMessage != null) {
+ return _buildErrorView();
+ }
+
+ if (!_isInitialized || _controller == null) {
+ return _buildLoadingView();
+ }
+
+ return Stack(
+ fit: StackFit.expand,
+ children: [
+ _buildCameraPreview(),
+ _buildZoomSlider(),
+ _buildTopControls(),
+ _buildBottomControls(),
+ if (_isSwitchingCamera) _buildSwitchingOverlay(),
+ ],
+ );
+ }
+
+ // Full-screen camera preview
+ Widget _buildCameraPreview() {
+ return SizedBox.expand(
+ child: FittedBox(
+ fit: BoxFit.cover,
+ child: SizedBox(
+ width: _controller!.value.previewSize?.height ?? 1,
+ height: _controller!.value.previewSize?.width ?? 1,
+ child: CameraPreview(_controller!),
+ ),
+ ),
+ );
+ }
+
+ // Error view
+ Widget _buildErrorView() {
+ return Center(
+ child: Padding(
+ padding: const EdgeInsets.all(24.0),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ const Icon(Icons.error_outline, color: Colors.red, size: 64),
+ const SizedBox(height: 16),
+ Text(
+ _errorMessage ?? 'Unknown error',
+ style: const TextStyle(color: Colors.white, fontSize: 16),
+ textAlign: TextAlign.center,
+ ),
+ const SizedBox(height: 24),
+ ElevatedButton(
+ onPressed: () => _initCamera(),
+ child: const Text('Retry'),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ // Loading view
+ Widget _buildLoadingView() {
+ return const Center(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ CircularProgressIndicator(color: Colors.white),
+ SizedBox(height: 16),
+ Text('Initializing camera...', style: TextStyle(color: Colors.white)),
+ ],
+ ),
+ );
+ }
+
+ // Switching camera overlay
+ Widget _buildSwitchingOverlay() {
+ return Container(
+ color: Colors.black54,
+ child: const Center(
+ child: CircularProgressIndicator(color: Colors.white),
+ ),
+ );
+ }
+
+ // Zoom slider
+ Widget _buildZoomSlider() {
+ return Positioned(
+ right: 20,
+ top: 350,
+ bottom: 250,
+ child: RotatedBox(
+ quarterTurns: 3,
+ child: SliderTheme(
+ data: SliderThemeData(
+ activeTrackColor: Colors.white,
+ inactiveTrackColor: Colors.white30,
+ thumbColor: Colors.white,
+ overlayColor: Colors.white24,
+ ),
+ child: Slider(
+ value: _zoomLevel,
+ min: _minZoom,
+ max: _maxZoom,
+ onChanged: _setZoom,
+ ),
+ ),
+ ),
+ );
+ }
+
+ // Top controls (switch camera, timer)
+ Widget _buildTopControls() {
+ return Positioned(
+ top: MediaQuery.of(context).padding.top + 10,
+ left: 0,
+ right: 0,
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 20),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ if (_isRecording)
+ Container(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 12,
+ vertical: 6,
+ ),
+ decoration: BoxDecoration(
+ color: Colors.red,
+ borderRadius: BorderRadius.circular(20),
+ ),
+ child: Row(
+ children: [
+ const Icon(
+ Icons.fiber_manual_record,
+ color: Colors.white,
+ size: 12,
+ ),
+ const SizedBox(width: 6),
+ Text(
+ '${_currentSeconds}s / ${_maxSeconds}s',
+ style: const TextStyle(
+ color: Colors.white,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ],
+ ),
+ )
+ else
+ const SizedBox.shrink(),
+ ],
+ ),
+ ),
+ );
+ }
+
+ // Bottom controls (capture button)
+ Widget _buildBottomControls() {
+ return Positioned(
+ bottom: 40,
+ left: 0,
+ right: 0,
+ child: Center(
+ child: GestureDetector(
+ onTap: !_isRecording ? _takePhoto : null,
+ onLongPressStart: (_) => _startRecording(),
+ onLongPressEnd: (_) => _stopRecording(),
+ child: AnimatedBuilder(
+ animation: _scaleAnimation,
+ builder: (context, child) {
+ return Transform.scale(
+ scale: _scaleAnimation.value,
+ child: Container(
+ width: 80,
+ height: 80,
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ color: Colors.black26,
+ ),
+ child: CustomPaint(
+ painter: _isRecording
+ ? RecordProgressPainter(
+ progress: _progress,
+ color: Colors.red,
+ )
+ : null,
+ child: Container(
+ margin: const EdgeInsets.all(8),
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ color: _isRecording ? Colors.red : Colors.white,
+ border: Border.all(color: Colors.white, width: 3),
+ ),
+ ),
+ ),
+ ),
+ );
+ },
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/feature/home/presentation/mainscreen/pages/widgets/main_screen_appbar.dart b/lib/feature/home/presentation/mainscreen/pages/widgets/main_screen_appbar.dart
index 6ddc82f..688e479 100644
--- a/lib/feature/home/presentation/mainscreen/pages/widgets/main_screen_appbar.dart
+++ b/lib/feature/home/presentation/mainscreen/pages/widgets/main_screen_appbar.dart
@@ -1,7 +1,5 @@
import 'package:flutter/material.dart';
-import 'package:go_router/go_router.dart';
import 'package:iconsax/iconsax.dart';
-import 'package:password_manager/config/routes/route_names.dart';
import 'package:password_manager/core/extension/context_extension.dart';
import 'package:password_manager/core/extension/font_extension.dart';
import 'package:password_manager/core/widgets/appbar.dart';
@@ -12,12 +10,7 @@ class MainScreenAppBar extends StatelessWidget {
Widget build(BuildContext context) {
return Appbar(
title: Text('My Vault', style: context.textTheme.titleLarge?.bold),
- actions: [
- GestureDetector(
- onTap: () => context.go(Routes.addPassword),
- child: Icon(Iconsax.setting),
- ),
- ],
+ actions: [GestureDetector(onTap: () {}, child: Icon(Iconsax.setting))],
);
}
}
diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift
index 3308b6f..ca53a60 100644
--- a/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -6,6 +6,7 @@ import FlutterMacOS
import Foundation
import connectivity_plus
+import firebase_core
import flutter_secure_storage_macos
import local_auth_darwin
import path_provider_foundation
@@ -14,6 +15,7 @@ import sqflite_darwin
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
+ FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
diff --git a/pubspec.lock b/pubspec.lock
index ce6877b..3119531 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -145,6 +145,46 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.1"
+ camera:
+ dependency: "direct main"
+ description:
+ name: camera
+ sha256: eefad89f262a873f38d21e5eec853461737ea074d7c9ede39f3ceb135d201cab
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.11.3"
+ camera_android_camerax:
+ dependency: transitive
+ description:
+ name: camera_android_camerax
+ sha256: d96bf774152dd2a0aee64c59ba6b914b5efb04ec5a25b56e17b7e898869cc07c
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.6.24+2"
+ camera_avfoundation:
+ dependency: transitive
+ description:
+ name: camera_avfoundation
+ sha256: "035b90c1e33c2efad7548f402572078f6e514d4f82be0a315cd6c6af7e855aa8"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.9.22+6"
+ camera_platform_interface:
+ dependency: transitive
+ description:
+ name: camera_platform_interface
+ sha256: "98cfc9357e04bad617671b4c1f78a597f25f08003089dd94050709ae54effc63"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.12.0"
+ camera_web:
+ dependency: transitive
+ description:
+ name: camera_web
+ sha256: "77e53acb64d9de8917424eeb32b5c7c73572d1e00954bbf54a1e609d79a751a2"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.3.5+1"
characters:
dependency: transitive
description:
@@ -225,6 +265,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.2"
+ cross_file:
+ dependency: transitive
+ description:
+ name: cross_file
+ sha256: "701dcfc06da0882883a2657c445103380e53e647060ad8d9dfb710c100996608"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.3.5+1"
crypto:
dependency: "direct main"
description:
@@ -329,6 +377,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.0.1"
+ firebase_core:
+ dependency: "direct main"
+ description:
+ name: firebase_core
+ sha256: "1f2dfd9f535d81f8b06d7a50ecda6eac1e6922191ed42e09ca2c84bd2288927c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.2.1"
+ firebase_core_platform_interface:
+ dependency: transitive
+ description:
+ name: firebase_core_platform_interface
+ sha256: cccb4f572325dc14904c02fcc7db6323ad62ba02536833dddb5c02cac7341c64
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.0.2"
+ firebase_core_web:
+ dependency: transitive
+ description:
+ name: firebase_core_web
+ sha256: ff18fabb0ad0ed3595d2f2c85007ecc794aadecdff5b3bb1460b7ee47cded398
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.3.0"
fixnum:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index a21d476..597a439 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -59,6 +59,8 @@ dependencies:
local_auth_android: ^1.0.56
flutter_svg: ^2.2.2
rxdart: ^0.28.0
+ firebase_core: ^4.2.1
+ camera: ^0.11.3
# ------------------------------------------------------------
# Dev Dependencies
diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc
index d7240f1..d785ab2 100644
--- a/windows/flutter/generated_plugin_registrant.cc
+++ b/windows/flutter/generated_plugin_registrant.cc
@@ -7,12 +7,15 @@
#include "generated_plugin_registrant.h"
#include
+#include
#include
#include
void RegisterPlugins(flutter::PluginRegistry* registry) {
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
+ FirebaseCorePluginCApiRegisterWithRegistrar(
+ registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
LocalAuthPluginRegisterWithRegistrar(
diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake
index 9b83ab5..572ceb4 100644
--- a/windows/flutter/generated_plugins.cmake
+++ b/windows/flutter/generated_plugins.cmake
@@ -4,6 +4,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
connectivity_plus
+ firebase_core
flutter_secure_storage_windows
local_auth_windows
)