├── .github └── workflows │ └── split_apk.yml ├── .gitignore ├── .metadata ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── viewus │ │ │ │ ├── hive_note_app │ │ │ │ └── MainActivity.kt │ │ │ │ └── v_notes │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-hdpi │ │ │ └── ic_stat_onesignal_default.png │ │ │ ├── drawable-mdpi │ │ │ └── ic_stat_onesignal_default.png │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable-xhdpi │ │ │ └── ic_stat_onesignal_default.png │ │ │ ├── drawable-xxhdpi │ │ │ └── ic_stat_onesignal_default.png │ │ │ ├── drawable-xxxhdpi │ │ │ └── ic_stat_onesignal_default.png │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── settings_aar.gradle ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── GoogleService-Info.plist │ ├── Info.plist │ └── Runner-Bridging-Header.h ├── RunnerTests │ └── RunnerTests.swift └── build │ └── Pods.build │ └── Release-iphonesimulator │ └── abseil.build │ └── dgph ├── lib ├── app │ └── src │ │ └── app.dart ├── config │ └── router │ │ ├── navigates_to.dart │ │ ├── route_guard.dart │ │ ├── routes.dart │ │ └── routes_name.dart ├── data │ ├── exceptions │ │ ├── app_exceptions.dart │ │ └── user_suspended_exception.dart │ ├── models │ │ ├── cloud_note_models │ │ │ ├── cloud_note_model.dart │ │ │ ├── cloud_note_model.g.dart │ │ │ ├── user_model.dart │ │ │ └── user_model.g.dart │ │ └── local_note_model │ │ │ ├── note_model.dart │ │ │ └── note_model.g.dart │ ├── network │ │ ├── base_api_service.dart │ │ └── network_services_api.dart │ └── repositories │ │ ├── auth_repository.dart │ │ └── cloud_note_repository.dart ├── helpers │ └── hive_manager.dart ├── main.dart ├── presentation │ ├── pages │ │ ├── cloud_notes │ │ │ ├── auth │ │ │ │ ├── forgot_password.dart │ │ │ │ ├── login_screen.dart │ │ │ │ ├── register_screen.dart │ │ │ │ ├── reset_password.dart │ │ │ │ └── verify_code.dart │ │ │ ├── cloud_create_note.dart │ │ │ ├── cloud_edit_note.dart │ │ │ ├── cloud_notes.dart │ │ │ └── cloud_read_note_screen.dart │ │ ├── home │ │ │ └── home.dart │ │ ├── local_notes │ │ │ ├── create_note_screen.dart │ │ │ ├── edit_note_screen.dart │ │ │ ├── local_notes.dart │ │ │ └── read_notes_screens.dart │ │ ├── notifications │ │ │ └── notifications_view.dart │ │ ├── settings │ │ │ └── settings_screen.dart │ │ └── trash │ │ │ └── trashed_notes.dart │ ├── views.dart │ └── widget │ │ ├── mNew_text_widget.dart │ │ └── mbutton.dart ├── services │ └── service_locator.dart ├── state │ └── cubits │ │ ├── auth_cubit │ │ ├── auth_cubit.dart │ │ └── auth_state.dart │ │ ├── cloud_note_cubit │ │ ├── cloud_note_cubit.dart │ │ └── cloud_note_state.dart │ │ ├── note_style_cubit │ │ ├── note_style_cubit.dart │ │ └── note_style_state.dart │ │ ├── play_button_cubit │ │ ├── play_button_cubit.dart │ │ └── play_button_state.dart │ │ └── theme_cubit │ │ ├── theme_cubit.dart │ │ └── theme_state.dart └── utils │ ├── colors │ └── m_colors.dart │ ├── const_values.dart │ ├── constant │ ├── api_constant.dart │ └── asset_dir.dart │ ├── greetings.dart │ ├── text_style │ └── m_text_style.dart │ ├── themes │ └── custom_theme.dart │ └── tools │ ├── hex_to_color.dart │ ├── message_dialog.dart │ ├── money_formatter.dart │ ├── sized_box_ex.dart │ └── slide_transition.dart ├── pubspec.lock ├── pubspec.yaml ├── shorebird.yaml ├── ss ├── 145535058_221013679682765_5476232847126577393_n.png └── 145944556_3545455032189582_7968282205009447548_n.png └── test ├── unit_test.dart └── widget_test.dart /.github/workflows/split_apk.yml: -------------------------------------------------------------------------------- 1 | #name: Build V Notes Android APK 2 | #on: 3 | # push: 4 | # branches: 5 | # - master 6 | # pull_request: 7 | # branches: 8 | # - master 9 | #jobs: 10 | # build: 11 | # name: Build APK's 12 | # runs-on: ubuntu-latest 13 | # steps: 14 | # - uses: actions/checkout@v2 15 | # - uses: actions/setup-java@v1 16 | # with: 17 | # java-version: '12.x' 18 | # - uses: subosito/flutter-action@v1.5.3 19 | # with: 20 | # channel: "stable" 21 | # - run: flutter pub get 22 | # - run: flutter pub outdated 23 | # - run: flutter pub upgrade 24 | # - run: flutter build apk --release 25 | # - uses: ncipollo/release-action@v1 26 | # with: 27 | # artifacts: 'build/app/outputs/flutter-apk/app-release.apk' 28 | # token: ${{ secrets.GITHUB_TOKEN }} 29 | # commit: master 30 | # tag: v1.0.${{ github.run_number }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | 42 | # Exceptions to above rules. 43 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 44 | 45 | # env 46 | .env 47 | 48 | api_credentials.yaml 49 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 17 | base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 18 | - platform: android 19 | create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 20 | base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 21 | - platform: ios 22 | create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 23 | base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 24 | - platform: linux 25 | create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 26 | base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 27 | - platform: macos 28 | create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 29 | base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 30 | - platform: web 31 | create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 32 | base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 33 | - platform: windows 34 | create_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 35 | base_revision: dec2ee5c1f98f8e84a7d5380c05eb8a3d0a81668 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## VNotes 2 | 3 |

This is an old project I was working on when learning to use Hive, will start working on it full time and will be adding new features as soon as possible. 4 | [Quick Update: I am currently working on the Project Full Time to make sure users get the best out of the app.]

5 | 6 |

You can download the app from Play Store Here

7 | 8 |

Any contribution will be accepted if it meets what is needed, Thanks.

9 | 10 | [//]: # (

If you see this error "Google Service Json Not Found"

) 11 |

Please delete pubspec.lock if you are having some issues and if you want to make a pull request, please ignore pubspec.lock Thanks

12 | 13 | ## Plugin's Used 14 | 1.

Hive (To locally store Data)

15 | 2.

Toast (To display useful message)

16 | 3.

Path Provider

17 | 4.

Upgrader (To alert users when app has a new update)

18 | 5.

Bloc (Cubit) (For State management)

19 | 6.

Shared Preferences (To Persist some values)

20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 |
31 | 32 | ## Getting Started 33 | For help getting started with Flutter, view our 34 | [online documentation](https://flutter.dev/docs), which offers tutorials, 35 | samples, guidance on mobile development, and a full API reference. 36 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | 2 | 3 | # This file configures the analyzer, which statically analyzes Dart code to 4 | # check for errors, warnings, and lints. 5 | # 6 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 7 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 8 | # invoked from the command line by running `flutter analyze`. 9 | 10 | # The following line activates a set of recommended lints for Flutter apps, 11 | # packages, and plugins designed to encourage good coding practices. 12 | include: package:flutter_lints/flutter.yaml 13 | 14 | linter: 15 | # The lint rules applied to this project can be customized in the 16 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 17 | # included above or to enable additional rules. A list of all available lints 18 | # and their documentation is published at 19 | # https://dart-lang.github.io/linter/lints/index.html. 20 | # 21 | # Instead of disabling a lint rule for the entire project in the 22 | # section below, it can also be suppressed for a single line of code 23 | # or a specific dart file by using the `// ignore: name_of_lint` and 24 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 25 | # producing the lint. 26 | rules: 27 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 28 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 29 | 30 | # Additional information about this file can be found at 31 | # https://dart.dev/guides/language/analysis-options -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | /key.properties 9 | 10 | #google json file 11 | app/google-services.json 12 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. 5 | id "dev.flutter.flutter-gradle-plugin" 6 | } 7 | 8 | def keystoreProperties = new Properties() 9 | def keystorePropertiesFile = rootProject.file('key.properties') 10 | if (keystorePropertiesFile.exists()) { 11 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 12 | } 13 | 14 | android { 15 | namespace = "com.viewus.v_notes" 16 | compileSdkVersion 35 17 | 18 | compileOptions { 19 | sourceCompatibility JavaVersion.VERSION_1_8 20 | targetCompatibility JavaVersion.VERSION_1_8 21 | } 22 | 23 | kotlinOptions { 24 | jvmTarget = '1.8' 25 | } 26 | 27 | sourceSets { 28 | main.java.srcDirs += 'src/main/kotlin' 29 | } 30 | 31 | defaultConfig { 32 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 33 | applicationId "com.viewus.v_notes" 34 | minSdkVersion 22 35 | targetSdkVersion 35 36 | multiDexEnabled true 37 | versionCode flutter.versionCode 38 | versionName flutter.versionName 39 | } 40 | 41 | signingConfigs { 42 | release { 43 | keyAlias keystoreProperties['keyAlias'] 44 | keyPassword keystoreProperties['keyPassword'] 45 | storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null 46 | storePassword keystoreProperties['storePassword'] 47 | } 48 | } 49 | 50 | buildTypes { 51 | release { 52 | // TODO: Add your own signing config for the release build. 53 | // Signing with the debug keys for now, so `flutter run --release` works. 54 | signingConfig signingConfigs.release 55 | 56 | minifyEnabled true 57 | shrinkResources true 58 | debuggable false 59 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 60 | } 61 | } 62 | } 63 | 64 | flutter { 65 | source '../..' 66 | } 67 | 68 | dependencies { 69 | def multidex_version = "2.0.1" 70 | implementation "androidx.multidex:multidex:$multidex_version" 71 | } 72 | -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | #Flutter Wrapper 2 | -keep class io.flutter.app.** { *; } 3 | -keep class io.flutter.plugin.** { *; } 4 | -keep class io.flutter.util.** { *; } 5 | -keep class io.flutter.view.** { *; } 6 | -keep class io.flutter.** { *; } 7 | -keep class io.flutter.plugins.** { *; } 8 | # -keep class com.google.firebase.** { *; } // uncomment this if you are using firebase in the project 9 | -dontwarn io.flutter.embedding.** 10 | -ignorewarnings -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 9 | 13 | 21 | 25 | 29 | 32 | 37 | 41 | 42 | 43 | 44 | 45 | 46 | 48 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/viewus/hive_note_app/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.viewus.hive_note_app 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() 6 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/viewus/v_notes/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.viewus.v_notes 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_stat_onesignal_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/android/app/src/main/res/drawable-hdpi/ic_stat_onesignal_default.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/ic_stat_onesignal_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/android/app/src/main/res/drawable-mdpi/ic_stat_onesignal_default.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/ic_stat_onesignal_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/android/app/src/main/res/drawable-xhdpi/ic_stat_onesignal_default.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/ic_stat_onesignal_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/android/app/src/main/res/drawable-xxhdpi/ic_stat_onesignal_default.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/ic_stat_onesignal_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/android/app/src/main/res/drawable-xxxhdpi/ic_stat_onesignal_default.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = "../build" 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(":app") 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError 2 | android.useAndroidX=true 3 | android.enableJetifier=true -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Mar 07 11:00:26 WAT 2022 2 | distributionBase=GRADLE_USER_HOME 3 | #distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip 5 | distributionPath=wrapper/dists 6 | zipStorePath=wrapper/dists 7 | zipStoreBase=GRADLE_USER_HOME -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 21 | id "com.android.application" version "8.3.2" apply false 22 | id "org.jetbrains.kotlin.android" version "1.9.10" apply false 23 | } 24 | 25 | include ":app" -------------------------------------------------------------------------------- /android/settings_aar.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '13.0' 3 | inhibit_all_warnings! 4 | 5 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 6 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 7 | 8 | project 'Runner', { 9 | 'Debug' => :debug, 10 | 'Profile' => :release, 11 | 'Release' => :release, 12 | } 13 | 14 | def flutter_root 15 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 16 | unless File.exist?(generated_xcode_build_settings_path) 17 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 18 | end 19 | 20 | File.foreach(generated_xcode_build_settings_path) do |line| 21 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 22 | return matches[1].strip if matches 23 | end 24 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 25 | end 26 | 27 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 28 | 29 | flutter_ios_podfile_setup 30 | 31 | target 'Runner' do 32 | use_frameworks! 33 | use_modular_headers! 34 | 35 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 36 | end 37 | 38 | post_install do |installer| 39 | installer.pods_project.targets.each do |target| 40 | flutter_additional_ios_build_settings(target) 41 | target.build_configurations.each do |config| 42 | if config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'].to_f < 13.0 43 | config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0' 44 | config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64 i386" 45 | config.build_settings['ENABLE_BITCODE'] = 'NO' 46 | config.build_settings['SWIFT_VERSION'] = '5.0' 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - flutter_tts (0.0.1): 4 | - Flutter 5 | - fluttertoast (0.0.2): 6 | - Flutter 7 | - Toast 8 | - package_info_plus (0.4.5): 9 | - Flutter 10 | - path_provider_foundation (0.0.1): 11 | - Flutter 12 | - FlutterMacOS 13 | - shared_preferences_foundation (0.0.1): 14 | - Flutter 15 | - FlutterMacOS 16 | - Toast (4.1.1) 17 | 18 | DEPENDENCIES: 19 | - Flutter (from `Flutter`) 20 | - flutter_tts (from `.symlinks/plugins/flutter_tts/ios`) 21 | - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) 22 | - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) 23 | - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) 24 | - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) 25 | 26 | SPEC REPOS: 27 | trunk: 28 | - Toast 29 | 30 | EXTERNAL SOURCES: 31 | Flutter: 32 | :path: Flutter 33 | flutter_tts: 34 | :path: ".symlinks/plugins/flutter_tts/ios" 35 | fluttertoast: 36 | :path: ".symlinks/plugins/fluttertoast/ios" 37 | package_info_plus: 38 | :path: ".symlinks/plugins/package_info_plus/ios" 39 | path_provider_foundation: 40 | :path: ".symlinks/plugins/path_provider_foundation/darwin" 41 | shared_preferences_foundation: 42 | :path: ".symlinks/plugins/shared_preferences_foundation/darwin" 43 | 44 | SPEC CHECKSUMS: 45 | Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 46 | flutter_tts: 0f492aab6accf87059b72354fcb4ba934304771d 47 | fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c 48 | package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 49 | path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 50 | shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 51 | Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e 52 | 53 | PODFILE CHECKSUM: 68b745aaca7f4f7b981818ae727abd881eccad0d 54 | 55 | COCOAPODS: 1.16.2 56 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @main 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/GoogleService-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CLIENT_ID 6 | 120784368845-a7n0rij9jepli3uvdbde6innv01h419d.apps.googleusercontent.com 7 | REVERSED_CLIENT_ID 8 | com.googleusercontent.apps.120784368845-a7n0rij9jepli3uvdbde6innv01h419d 9 | ANDROID_CLIENT_ID 10 | 120784368845-p5ids6tgl3tmkk94fkfvukoj2bgt32nj.apps.googleusercontent.com 11 | API_KEY 12 | AIzaSyDkVKVJCO26sawYxDeKUiN4uZWsyAemZkA 13 | GCM_SENDER_ID 14 | 120784368845 15 | PLIST_VERSION 16 | 1 17 | BUNDLE_ID 18 | com.example.noteApp 19 | PROJECT_ID 20 | vnotes-8af7a 21 | STORAGE_BUCKET 22 | vnotes-8af7a.appspot.com 23 | IS_ADS_ENABLED 24 | 25 | IS_ANALYTICS_ENABLED 26 | 27 | IS_APPINVITE_ENABLED 28 | 29 | IS_GCM_ENABLED 30 | 31 | IS_SIGNIN_ENABLED 32 | 33 | GOOGLE_APP_ID 34 | 1:120784368845:ios:ff9262eb2890cd431ba584 35 | 36 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | VNotes 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /ios/build/Pods.build/Release-iphonesimulator/abseil.build/dgph: -------------------------------------------------------------------------------- 1 | DGPH1.04 Jun 8 202114:00:05/Users godsendjosephAndroidStudioProjects hive_note_appiosPods -------------------------------------------------------------------------------- /lib/app/src/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flashy_flushbar/flashy_flushbar_provider.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_quill/flutter_quill.dart'; 4 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 5 | import 'package:note_app/config/router/routes.dart'; 6 | import 'package:note_app/config/router/routes_name.dart'; 7 | import 'package:note_app/state/cubits/theme_cubit/theme_cubit.dart'; 8 | import 'package:note_app/utils/themes/custom_theme.dart'; 9 | import 'package:flutter_bloc/flutter_bloc.dart'; 10 | 11 | class App extends StatefulWidget { 12 | const App({Key? key}) : super(key: key); 13 | 14 | @override 15 | _AppState createState() => _AppState(); 16 | } 17 | 18 | class _AppState extends State { 19 | @override 20 | Widget build(BuildContext context) { 21 | return ScreenUtilInit( 22 | designSize: const Size(375, 812), 23 | builder: (BuildContext context, Widget? child) { 24 | return MaterialApp( 25 | debugShowCheckedModeBanner: false, 26 | theme: context.watch().state.isDarkTheme == false 27 | ? buildLightTheme() 28 | : buildDarkTheme(), 29 | title: 'VNotes', 30 | builder: FlashyFlushbarProvider.init(), 31 | initialRoute: RoutesName.home_screen, 32 | onGenerateRoute: Routes.generateRoute, 33 | localizationsDelegates: [ 34 | FlutterQuillLocalizations.delegate, 35 | ], 36 | ); 37 | }, 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/config/router/navigates_to.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | Future navigateTo( 4 | BuildContext context, { 5 | required String destination, 6 | Map? arguments, 7 | }) { 8 | return Navigator.pushNamed( 9 | context, 10 | destination, 11 | arguments: arguments, 12 | ); 13 | } 14 | 15 | Future navigateReplaceTo( 16 | BuildContext context, { 17 | required String destination, 18 | Map? arguments, 19 | }) { 20 | return Navigator.pushReplacementNamed(context, destination, 21 | arguments: arguments); 22 | } 23 | 24 | Future navigateEndTo( 25 | BuildContext context, { 26 | required String destination, 27 | Map? arguments, 28 | }) { 29 | return Navigator.pushNamedAndRemoveUntil( 30 | context, 31 | destination, 32 | arguments: arguments, 33 | (route) => false, 34 | ); 35 | } -------------------------------------------------------------------------------- /lib/config/router/route_guard.dart: -------------------------------------------------------------------------------- 1 | import 'package:note_app/config/router/routes_name.dart'; 2 | 3 | class RoutesGuard { 4 | static final protectedRoutes = [ 5 | RoutesName.cloud_notes, 6 | RoutesName.cloud_create_notes_screen, 7 | RoutesName.cloud_edit_notes_screen, 8 | RoutesName.cloud_read_notes_screen, 9 | ]; 10 | 11 | static final requiresVerification = [ 12 | ...protectedRoutes, 13 | ]; 14 | 15 | static bool requiresAuth(String? routeName) { 16 | return protectedRoutes.contains(routeName); 17 | } 18 | 19 | static bool requiresEmailVerification(String? routeName) { 20 | return requiresVerification.contains(routeName); 21 | } 22 | } -------------------------------------------------------------------------------- /lib/config/router/routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:note_app/config/router/route_guard.dart'; 3 | import 'package:note_app/config/router/routes_name.dart'; 4 | import 'package:note_app/presentation/views.dart'; 5 | import 'package:note_app/services/service_locator.dart'; 6 | import 'package:note_app/state/cubits/auth_cubit/auth_cubit.dart'; 7 | 8 | class Routes { 9 | static Route generateRoute(RouteSettings settings) { 10 | final authCubit = getIt(); 11 | final state = authCubit.state; 12 | final args = settings.arguments as Map?; 13 | 14 | if (RoutesGuard.requiresAuth(settings.name)) { 15 | if (state is AuthEmailUnverified) { 16 | return MaterialPageRoute( 17 | builder: (_) => const VerifyCode(from: 'protected_route'), 18 | maintainState: true, // Preserve route state 19 | ); 20 | } 21 | 22 | if (state is! AuthAuthenticated) { 23 | authCubit.saveAttemptedRoute(settings.name!); 24 | return MaterialPageRoute( 25 | builder: (_) => const LoginScreen(), 26 | maintainState: true, 27 | ); 28 | } 29 | } 30 | 31 | switch (settings.name) { 32 | // 33 | case RoutesName.home_screen: 34 | return MaterialPageRoute(builder: (_) => const HomeScreen()); 35 | 36 | case RoutesName.local_notes: 37 | return MaterialPageRoute(builder: (_) => const LocalNotesScreen()); 38 | 39 | case RoutesName.cloud_notes: 40 | return MaterialPageRoute(builder: (_) => const CloudNotesScreen()); 41 | 42 | case RoutesName.settings_screen: 43 | return MaterialPageRoute(builder: (_) => const SettingsScreen()); 44 | 45 | case RoutesName.create_notes_screen: 46 | return MaterialPageRoute(builder: (_) => const CreateNoteScreen()); 47 | 48 | case RoutesName.trash_notes: 49 | return MaterialPageRoute(builder: (_) => const TrashedNotes()); 50 | // 51 | 52 | // Local Notes 53 | case RoutesName.read_notes_screen: 54 | return MaterialPageRoute( 55 | builder: (_) => ReadNotesScreen( 56 | note: args!['note'], 57 | noteKey: args['noteKey'], 58 | )); 59 | 60 | case RoutesName.edit_notes_screen: 61 | return MaterialPageRoute( 62 | builder: (_) => EditNoteScreen( 63 | notes: args!['notes'], 64 | noteKey: args['noteKey'], 65 | )); 66 | // ends here 67 | 68 | // Cloud Notes 69 | case RoutesName.cloud_create_notes_screen: 70 | return MaterialPageRoute(builder: (_) => const CloudCreateNote()); 71 | 72 | case RoutesName.cloud_read_notes_screen: 73 | return MaterialPageRoute( 74 | builder: (_) => CloudReadNote( 75 | note: args!['note'], 76 | noteKey: args['noteKey'], 77 | )); 78 | 79 | case RoutesName.cloud_edit_notes_screen: 80 | return MaterialPageRoute( 81 | builder: (_) => CloudEditNote( 82 | notes: args!['notes'], 83 | noteKey: args['noteKey'], 84 | )); 85 | // ends here 86 | 87 | //Auth 88 | case RoutesName.login_screen: 89 | return MaterialPageRoute(builder: (_) => const LoginScreen()); 90 | 91 | case RoutesName.register_screen: 92 | return MaterialPageRoute(builder: (_) => const RegisterScreen()); 93 | 94 | case RoutesName.verify_code_screen: 95 | return MaterialPageRoute( 96 | builder: (_) => VerifyCode( 97 | from: args!['from'], 98 | )); 99 | // ends here 100 | 101 | default: 102 | return MaterialPageRoute( 103 | builder: (_) { 104 | return const Scaffold( 105 | body: Center( 106 | child: Text( 107 | 'No Routes Generated', 108 | ), 109 | ), 110 | ); 111 | }, 112 | ); 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /lib/config/router/routes_name.dart: -------------------------------------------------------------------------------- 1 | class RoutesName { 2 | // initiate routes 3 | static const String home_screen = 'home_screen'; 4 | static const String local_notes = 'local_notes'; 5 | static const String cloud_notes = 'cloud_notes'; 6 | static const String settings_screen = 'settings_screen'; 7 | static const String trash_notes = 'trash_notes'; 8 | static const String wrapper = 'wrapper'; 9 | 10 | // ends here 11 | 12 | // Local Notes Route 13 | static const String create_notes_screen = 'create_notes_screen'; 14 | static const String read_notes_screen = 'read_notes_screen'; 15 | static const String edit_notes_screen = 'edit_notes_screen'; 16 | 17 | // ends here 18 | 19 | // Cloud Notes Route 20 | static const String cloud_create_notes_screen = 'cloud_create_notes_screen'; 21 | static const String cloud_edit_notes_screen = 'cloud_edit_notes_screen'; 22 | static const String cloud_read_notes_screen = 'cloud_read_notes_screen'; 23 | 24 | // ends here 25 | 26 | // Auth Route 27 | static const String login_screen = 'login_screen'; 28 | static const String register_screen = 'register_screen'; 29 | static const String verify_code_screen = 'verify_code_screen'; 30 | // ends here 31 | } 32 | -------------------------------------------------------------------------------- /lib/data/exceptions/app_exceptions.dart: -------------------------------------------------------------------------------- 1 | /// Base class for custom application exceptions. 2 | class AppExceptions implements Exception { 3 | final _message; // Message associated with the exception 4 | final _prefix; // Prefix for the exception 5 | 6 | /// Constructor for creating a [NoInternAppExceptionsetException] instance. 7 | /// 8 | /// the [message] parameter represents the message associated with the exception. 9 | /// the [prefix] parameter represents the prefix for the exception. 10 | AppExceptions([this._message, this._prefix]); 11 | 12 | @override 13 | String toString() { 14 | return _message?.toString() ?? _prefix; 15 | } 16 | } 17 | 18 | /// Exception class representing a no internet connection error. 19 | class NoInternetException extends AppExceptions { 20 | /// Constructor for creating a [NoInternetException] instance. 21 | /// 22 | /// the [message] parameter represents the error message. 23 | NoInternetException([String? message]) 24 | : super(message, 'No Internet Connection.'); 25 | } 26 | 27 | /// Exception class representing a n unauthorized access error. 28 | class UnauthorisedException extends AppExceptions { 29 | /// Constructor for creating a [UnauthorisedException] instance. 30 | /// 31 | /// the [message] parameter represents the error message. 32 | UnauthorisedException([String? message]) 33 | : super(message, 'You do not have access to this.'); 34 | } 35 | 36 | /// Exception class representing network request timing out error. 37 | class RequestTimeOutException extends AppExceptions { 38 | /// Constructor for creating a [RequestTimeOutException] instance. 39 | /// 40 | /// the [message] parameter represents the error message. 41 | RequestTimeOutException([String? message]) 42 | : super(message, 'Request Time Out.'); 43 | } 44 | 45 | /// Exception class representing a data fetching error during communication. 46 | class FetchDataException extends AppExceptions { 47 | /// Constructor for creating a [FetchDataException] instance. 48 | /// 49 | /// the [message] parameter represents the error message. 50 | FetchDataException([String? message]) : super(message, ''); 51 | } 52 | 53 | /// Exception class representing an Invalid input error. 54 | class InvalidInputException extends AppExceptions { 55 | /// Constructor for creating a [InvalidInputException] instance. 56 | /// 57 | /// the [message] parameter represents the error message. 58 | InvalidInputException([String? message]) : super(message, 'Invalid Input'); 59 | } 60 | 61 | /// Exception class representing a bad request error. 62 | class BadRequestException extends AppExceptions { 63 | /// Constructor for creating a [BadRequestException] instance. 64 | /// 65 | /// the [message] parameter represents the error message. 66 | BadRequestException([String? message]) : super(message, 'Invalid Request'); 67 | } -------------------------------------------------------------------------------- /lib/data/exceptions/user_suspended_exception.dart: -------------------------------------------------------------------------------- 1 | class UserSuspendedException implements Exception { 2 | final String message; 3 | UserSuspendedException(this.message); 4 | @override 5 | String toString() => message; 6 | } -------------------------------------------------------------------------------- /lib/data/models/cloud_note_models/cloud_note_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | 3 | part 'cloud_note_model.g.dart'; 4 | 5 | @HiveType(typeId: 1) 6 | class CloudNoteModel { 7 | @HiveField(1) 8 | final int? id; 9 | 10 | @HiveField(2) 11 | final String? uuid; 12 | 13 | @HiveField(3) 14 | final String? title; 15 | 16 | @HiveField(4) 17 | final String? notes; 18 | 19 | CloudNoteModel({ 20 | this.id, 21 | this.uuid, 22 | this.title, 23 | this.notes, 24 | }); 25 | 26 | factory CloudNoteModel.fromJson(Map response) { 27 | return CloudNoteModel( 28 | id: response['id'], 29 | uuid: response['uuid'], 30 | title: response['note_title'], 31 | notes: response['note_content'], 32 | ); 33 | } 34 | 35 | static List parseResponse(Map responseData) { 36 | if (responseData['success'] == true && responseData['data'] != null) { 37 | List noteData = responseData['data']; 38 | return noteData.map((json) => CloudNoteModel.fromJson(json)).toList(); 39 | } 40 | return []; 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /lib/data/models/cloud_note_models/cloud_note_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'cloud_note_model.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class CloudNoteModelAdapter extends TypeAdapter { 10 | @override 11 | final int typeId = 1; 12 | 13 | @override 14 | CloudNoteModel read(BinaryReader reader) { 15 | final numOfFields = reader.readByte(); 16 | final fields = { 17 | for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 18 | }; 19 | return CloudNoteModel( 20 | id: fields[1] as int?, 21 | uuid: fields[2] as String?, 22 | title: fields[3] as String?, 23 | notes: fields[4] as String?, 24 | ); 25 | } 26 | 27 | @override 28 | void write(BinaryWriter writer, CloudNoteModel obj) { 29 | writer 30 | ..writeByte(4) 31 | ..writeByte(1) 32 | ..write(obj.id) 33 | ..writeByte(2) 34 | ..write(obj.uuid) 35 | ..writeByte(3) 36 | ..write(obj.title) 37 | ..writeByte(4) 38 | ..write(obj.notes); 39 | } 40 | 41 | @override 42 | int get hashCode => typeId.hashCode; 43 | 44 | @override 45 | bool operator ==(Object other) => 46 | identical(this, other) || 47 | other is CloudNoteModelAdapter && 48 | runtimeType == other.runtimeType && 49 | typeId == other.typeId; 50 | } 51 | -------------------------------------------------------------------------------- /lib/data/models/cloud_note_models/user_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | 3 | part 'user_model.g.dart'; 4 | 5 | @HiveType(typeId: 2) 6 | class UserModel { 7 | @HiveField(1) 8 | final int? id; 9 | 10 | @HiveField(2) 11 | final String? uuid; 12 | 13 | @HiveField(3) 14 | final String? firstName; 15 | 16 | @HiveField(4) 17 | final String? lastName; 18 | 19 | @HiveField(5) 20 | final String? userName; 21 | 22 | @HiveField(6) 23 | final String? email; 24 | 25 | @HiveField(7) 26 | final String? emailVerified; 27 | 28 | @HiveField(8) 29 | final int? hasSubscription; 30 | 31 | @HiveField(9) 32 | final int? hasExceedTier; 33 | 34 | @HiveField(10) 35 | final String? planDuration; 36 | 37 | @HiveField(11) 38 | final String? planSubDate; 39 | 40 | @HiveField(12) 41 | final String? accessToken; 42 | 43 | UserModel({ 44 | this.id, 45 | this.uuid, 46 | this.firstName, 47 | this.lastName, 48 | this.userName, 49 | this.email, 50 | this.emailVerified, 51 | this.hasSubscription, 52 | this.hasExceedTier, 53 | this.planDuration, 54 | this.planSubDate, 55 | this.accessToken, 56 | }); 57 | 58 | 59 | factory UserModel.fromJsonLocalToken(responseData) { 60 | return UserModel( 61 | accessToken: responseData['data']['token'], 62 | ); 63 | } 64 | 65 | factory UserModel.fromJsonUserDetails(responseData) { 66 | return UserModel( 67 | id: responseData['data']['user']['id'], 68 | uuid: responseData['data']['user']['uuid'], 69 | firstName: responseData['data']['user']['first_name'], 70 | lastName: responseData['data']['user']['last_name'], 71 | userName: responseData['data']['user']['username'], 72 | email: responseData['data']['user']['email'], 73 | emailVerified: responseData['data']['user']['email_verified_at'], 74 | hasSubscription: responseData['data']['user']['has_subscription'], 75 | hasExceedTier: responseData['data']['user']['has_exceed_tier'], 76 | planDuration: responseData['data']['user']['plan_duration'], 77 | planSubDate: responseData['data']['user']['plan_sub_date'], 78 | ); 79 | } 80 | } -------------------------------------------------------------------------------- /lib/data/models/cloud_note_models/user_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'user_model.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class UserModelAdapter extends TypeAdapter { 10 | @override 11 | final int typeId = 2; 12 | 13 | @override 14 | UserModel read(BinaryReader reader) { 15 | final numOfFields = reader.readByte(); 16 | final fields = { 17 | for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 18 | }; 19 | return UserModel( 20 | id: fields[1] as int?, 21 | uuid: fields[2] as String?, 22 | firstName: fields[3] as String?, 23 | lastName: fields[4] as String?, 24 | userName: fields[5] as String?, 25 | email: fields[6] as String?, 26 | emailVerified: fields[7] as String?, 27 | hasSubscription: fields[8] as int?, 28 | hasExceedTier: fields[9] as int?, 29 | planDuration: fields[10] as String?, 30 | planSubDate: fields[11] as String?, 31 | accessToken: fields[12] as String?, 32 | ); 33 | } 34 | 35 | @override 36 | void write(BinaryWriter writer, UserModel obj) { 37 | writer 38 | ..writeByte(12) 39 | ..writeByte(1) 40 | ..write(obj.id) 41 | ..writeByte(2) 42 | ..write(obj.uuid) 43 | ..writeByte(3) 44 | ..write(obj.firstName) 45 | ..writeByte(4) 46 | ..write(obj.lastName) 47 | ..writeByte(5) 48 | ..write(obj.userName) 49 | ..writeByte(6) 50 | ..write(obj.email) 51 | ..writeByte(7) 52 | ..write(obj.emailVerified) 53 | ..writeByte(8) 54 | ..write(obj.hasSubscription) 55 | ..writeByte(9) 56 | ..write(obj.hasExceedTier) 57 | ..writeByte(10) 58 | ..write(obj.planDuration) 59 | ..writeByte(11) 60 | ..write(obj.planSubDate) 61 | ..writeByte(12) 62 | ..write(obj.accessToken); 63 | } 64 | 65 | @override 66 | int get hashCode => typeId.hashCode; 67 | 68 | @override 69 | bool operator ==(Object other) => 70 | identical(this, other) || 71 | other is UserModelAdapter && 72 | runtimeType == other.runtimeType && 73 | typeId == other.typeId; 74 | } 75 | -------------------------------------------------------------------------------- /lib/data/models/local_note_model/note_model.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_quill/flutter_quill.dart'; 5 | import 'package:flutter_quill/quill_delta.dart'; 6 | import 'package:hive/hive.dart'; 7 | 8 | part 'note_model.g.dart'; 9 | 10 | @HiveType(typeId: 0) 11 | class NoteModel { 12 | @HiveField(0) 13 | final String? title; 14 | 15 | @HiveField(1) 16 | final String? notes; 17 | 18 | @HiveField(2) 19 | final dynamic dateTime; 20 | 21 | NoteModel({ 22 | this.title, 23 | this.notes, 24 | this.dateTime, 25 | }); 26 | 27 | QuillController get quillController { 28 | try { 29 | // Try to parse as Quill JSON 30 | final List jsonData = jsonDecode(notes ?? ""); 31 | final delta = Delta.fromJson(jsonData); 32 | final document = Document.fromDelta(delta); 33 | 34 | return QuillController( 35 | document: document, 36 | selection: const TextSelection.collapsed(offset: 0), 37 | ); 38 | } catch (e) { 39 | // If parsing fails, it's likely a legacy note with plain text 40 | // Create a Document with the plain text content 41 | final delta = Delta() 42 | ..insert(notes ?? "") 43 | ..insert('\n'); // Ensure it ends with a newline for proper formatting 44 | 45 | final document = Document.fromDelta(delta); 46 | 47 | return QuillController( 48 | document: document, 49 | selection: const TextSelection.collapsed(offset: 0), 50 | ); 51 | } 52 | } 53 | 54 | // Extract plain text from notes (whether it's Quill JSON or plain text) 55 | String getPlainText() { 56 | try { 57 | // Try to parse as Quill JSON first 58 | final List jsonData = jsonDecode(notes ?? ""); 59 | final delta = Delta.fromJson(jsonData); 60 | final document = Document.fromDelta(delta); 61 | return document.toPlainText().trim(); 62 | } catch (e) { 63 | // If parsing fails, return the raw text 64 | return notes ?? ""; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/data/models/local_note_model/note_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'note_model.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class NoteModelAdapter extends TypeAdapter { 10 | @override 11 | final int typeId = 0; 12 | 13 | @override 14 | NoteModel read(BinaryReader reader) { 15 | final numOfFields = reader.readByte(); 16 | final fields = { 17 | for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 18 | }; 19 | return NoteModel( 20 | title: fields[0] as String?, 21 | notes: fields[1] as String?, 22 | dateTime: fields[2] as dynamic, 23 | ); 24 | } 25 | 26 | @override 27 | void write(BinaryWriter writer, NoteModel obj) { 28 | writer 29 | ..writeByte(3) 30 | ..writeByte(0) 31 | ..write(obj.title) 32 | ..writeByte(1) 33 | ..write(obj.notes) 34 | ..writeByte(2) 35 | ..write(obj.dateTime); 36 | } 37 | 38 | @override 39 | int get hashCode => typeId.hashCode; 40 | 41 | @override 42 | bool operator ==(Object other) => 43 | identical(this, other) || 44 | other is NoteModelAdapter && 45 | runtimeType == other.runtimeType && 46 | typeId == other.typeId; 47 | } 48 | -------------------------------------------------------------------------------- /lib/data/network/base_api_service.dart: -------------------------------------------------------------------------------- 1 | abstract class BaseApiService { 2 | Future getApi({ 3 | String? requestEnd, 4 | Map? queryParams, 5 | String? bearer, 6 | }); 7 | 8 | Future postApi({ 9 | String requestEnd, 10 | Map? params, 11 | String? bearer, 12 | }); 13 | 14 | Future deleteApi( 15 | String url, 16 | String? bearer, 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /lib/data/network/network_services_api.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'dart:io'; 4 | 5 | import 'package:http/http.dart' as http; 6 | import 'package:note_app/data/exceptions/app_exceptions.dart'; 7 | import 'package:note_app/data/network/base_api_service.dart'; 8 | import 'package:note_app/utils/const_values.dart'; 9 | import 'package:note_app/utils/constant/api_constant.dart'; 10 | 11 | class NetworkServicesApi implements BaseApiService { 12 | @override 13 | Future getApi({ 14 | String? requestEnd, 15 | Map? queryParams, 16 | String? bearer, 17 | }) async { 18 | dynamic jsonResponse; 19 | try { 20 | // 21 | final Uri url = Uri.parse('${ApiConstants.apiUrl}/$requestEnd'); 22 | final Uri finalUri = url.replace(queryParameters: queryParams); 23 | 24 | final response = await http.get( 25 | finalUri, 26 | headers: { 27 | 'Accept': 'application/json', 28 | 'Authorization': 'Bearer $bearer', 29 | }, 30 | ).timeout( 31 | const Duration(seconds: 30), 32 | ); 33 | jsonResponse = returnResponse(response); 34 | 35 | if (response.statusCode == 200) {} 36 | } on SocketException { 37 | throw NoInternetException(); 38 | } on TimeoutException { 39 | throw FetchDataException('Time out try again'); 40 | } 41 | 42 | return jsonResponse; 43 | } 44 | 45 | @override 46 | Future postApi({ 47 | String? requestEnd, 48 | Map? params, 49 | String? bearer, 50 | }) async { 51 | dynamic jsonResponse; 52 | try { 53 | // 54 | var url = Uri.decodeFull('${ApiConstants.apiUrl}/$requestEnd'); 55 | final response = await http.post( 56 | Uri.parse(url), 57 | body: params, 58 | headers: { 59 | 'Accept': 'application/json', 60 | 'Authorization': 'Bearer $bearer', 61 | }, 62 | ).timeout( 63 | const Duration(seconds: 30), 64 | ); 65 | jsonResponse = returnResponse(response); 66 | 67 | if (response.statusCode == 200) {} 68 | } on SocketException { 69 | throw NoInternetException(); 70 | } on TimeoutException { 71 | throw FetchDataException('Time out try again'); 72 | } 73 | 74 | return jsonResponse; 75 | } 76 | 77 | @override 78 | Future deleteApi( 79 | String url, 80 | String? bearer, 81 | ) async { 82 | dynamic jsonResponse; 83 | try { 84 | // 85 | final response = await http.delete( 86 | Uri.parse(url), 87 | headers: { 88 | 'Accept': 'application/json', 89 | 'Authorization': 'Bearer $bearer', 90 | }, 91 | ).timeout( 92 | const Duration(seconds: 30), 93 | ); 94 | jsonResponse = returnResponse(response); 95 | 96 | if (response.statusCode == 200) {} 97 | } on SocketException { 98 | throw NoInternetException(); 99 | } on TimeoutException { 100 | throw FetchDataException('Time out try again'); 101 | } 102 | 103 | return jsonResponse; 104 | } 105 | 106 | dynamic returnResponse(http.Response response) { 107 | logger.i(response.statusCode); 108 | logger.i(response.body); 109 | 110 | dynamic jsonResponse = json.decode(response.body); 111 | logger.i('Parsed Response: $jsonResponse'); 112 | 113 | switch (response.statusCode) { 114 | case 200: 115 | return jsonResponse; 116 | case 201: 117 | return jsonResponse; 118 | case 400: 119 | return jsonResponse; 120 | case 401: 121 | logger.i('401 Response: $jsonResponse'); 122 | 123 | if (jsonResponse['message']?.contains('not verified')) { 124 | logger.i('Unverified email case detected'); 125 | return jsonResponse; 126 | } 127 | 128 | throw UnauthorisedException(jsonResponse['message']); 129 | 130 | case 403: 131 | if (jsonResponse['message']?.contains('not verified')) { 132 | return jsonResponse; 133 | } 134 | throw UnauthorisedException(jsonResponse['message']); 135 | 136 | case 404: 137 | if (jsonResponse['success'] == false) { 138 | throw BadRequestException(jsonResponse['message']); 139 | } 140 | return jsonResponse; 141 | 142 | case 500: 143 | throw FetchDataException(jsonResponse['message']); 144 | 145 | default: 146 | throw UnauthorisedException(); 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /lib/data/repositories/auth_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:note_app/data/exceptions/app_exceptions.dart'; 2 | import 'package:note_app/data/models/cloud_note_models/user_model.dart'; 3 | import 'package:note_app/data/network/network_services_api.dart'; 4 | import 'package:note_app/helpers/hive_manager.dart'; 5 | import 'package:note_app/utils/const_values.dart'; 6 | 7 | class AuthRepository { 8 | final NetworkServicesApi _api; 9 | final HiveManager _hiveManager; 10 | 11 | AuthRepository({ 12 | required NetworkServicesApi api, 13 | required HiveManager hiveManager, 14 | }) : _api = api, 15 | _hiveManager = hiveManager; 16 | 17 | Future login(String email, String password) async { 18 | final response = await _api.postApi( 19 | requestEnd: 'user/auth/authenticate', 20 | params: { 21 | 'identifier': email, 22 | 'password': password, 23 | }, 24 | ); 25 | 26 | logger.i(response); 27 | 28 | if (response['success'] == false && 29 | response['message'].contains('not yet verified')) { 30 | final token = UserModel.fromJsonLocalToken(response); 31 | final user = UserModel.fromJsonUserDetails(response); 32 | await _hiveManager.userModelBox.put(tokenKey, token); 33 | await _hiveManager.userModelBox.put(userKey, user); 34 | 35 | // Check what's stored in Hive 36 | logger.i('Stored Token: ${_hiveManager.userModelBox.get(tokenKey)}'); 37 | logger.i('Stored User: ${_hiveManager.userModelBox.get(userKey)}'); 38 | logger.i('Stored User Email: ${_hiveManager.userModelBox.get(userKey)?.email}'); 39 | 40 | throw UnauthorisedException(response['message']); 41 | } 42 | 43 | if (response['success'] == true) { 44 | final token = UserModel.fromJsonLocalToken(response); 45 | final user = UserModel.fromJsonUserDetails(response); 46 | await _hiveManager.userModelBox.put(tokenKey, token); 47 | await _hiveManager.userModelBox.put(userKey, user); 48 | return user; 49 | } 50 | 51 | throw response['message']; 52 | } 53 | 54 | Future register({ 55 | required String email, 56 | required String password, 57 | required String firstName, 58 | required String lastName, 59 | required String userName, 60 | }) async { 61 | final response = await _api.postApi( 62 | requestEnd: 'user/auth/register', 63 | params: { 64 | 'email': email, 65 | 'password': password, 66 | 'first_name': firstName, 67 | 'last_name': lastName, 68 | 'username': userName, 69 | }, 70 | ); 71 | 72 | logger.i(response); 73 | 74 | if (response['success'] == true) { 75 | final token = UserModel.fromJsonLocalToken(response); 76 | final user = UserModel.fromJsonUserDetails(response); 77 | await _hiveManager.userModelBox.put(tokenKey, token); 78 | await _hiveManager.userModelBox.put(userKey, user); 79 | return user; 80 | } else { 81 | throw response['message']; 82 | } 83 | } 84 | 85 | Future verifyEmail(String code) async { 86 | final response = await _api.postApi( 87 | requestEnd: 'user/auth/verify_code', 88 | params: { 89 | 'otp_code': code, 90 | }, 91 | ); 92 | 93 | logger.i(response); 94 | 95 | if (response['success'] != true) { 96 | throw response['message']; 97 | } 98 | 99 | if (response['success'] == true) { 100 | final currentUser = _hiveManager.userModelBox.get(userKey); 101 | if (currentUser != null) { 102 | // Create new user model with verified status 103 | final updatedUser = UserModel( 104 | id: currentUser.id, 105 | uuid: currentUser.uuid, 106 | firstName: currentUser.firstName, 107 | lastName: currentUser.lastName, 108 | userName: currentUser.userName, 109 | email: currentUser.email, 110 | emailVerified: DateTime.now().toString(), // Update verification status 111 | hasSubscription: currentUser.hasSubscription, 112 | hasExceedTier: currentUser.hasExceedTier, 113 | planDuration: currentUser.planDuration, 114 | planSubDate: currentUser.planSubDate, 115 | ); 116 | await _hiveManager.userModelBox.put(userKey, updatedUser); 117 | } 118 | } 119 | } 120 | 121 | Future resendVerificationCode({String? email}) async { 122 | final user = _hiveManager.userModelBox.get(userKey); 123 | logger.i('Stored User: $user'); 124 | logger.i('User Email: ${user?.email}'); 125 | 126 | if (user?.email == null && email == null) { 127 | logger.e('User email is null'); 128 | throw 'User email not found'; 129 | } 130 | final response = await _api.postApi( 131 | requestEnd: 'user/auth/send_code', 132 | params: { 133 | 'email': email ?? user!.email, 134 | }, 135 | ); 136 | 137 | logger.i(response); 138 | 139 | if (response['success'] == false) { 140 | throw response['message']; 141 | } 142 | } 143 | 144 | Future forgotPassword(String email) async { 145 | final response = await _api.postApi( 146 | requestEnd: 'user/auth/forgot_password', 147 | params: { 148 | 'email': email, 149 | }, 150 | ); 151 | 152 | logger.i(response); 153 | 154 | if (response['success'] != true) { 155 | throw response['message']; 156 | } 157 | // return true; 158 | } 159 | 160 | Future resetPassword(String otpCode, String newPassword) async { 161 | final response = await _api.postApi( 162 | requestEnd: 'user/auth/reset_password', 163 | params: { 164 | 'otp_code': otpCode, 165 | 'new_password': newPassword, 166 | }, 167 | ); 168 | 169 | logger.i(response); 170 | 171 | if (response['success'] != true) { 172 | throw response['message']; 173 | } 174 | 175 | // return true; 176 | } 177 | 178 | // ======================================== // 179 | 180 | Future logout() async { 181 | await _hiveManager.userModelBox.delete(tokenKey); 182 | await _hiveManager.cloudNoteModelBox.clear(); 183 | } 184 | 185 | UserModel? getCurrentUser() { 186 | return _hiveManager.userModelBox.get(userKey); 187 | } 188 | 189 | UserModel? getCurrentUserToken() { 190 | return _hiveManager.userModelBox.get(tokenKey); 191 | } 192 | 193 | Future fetchUserDetails() async { 194 | try { 195 | final token = _hiveManager.userModelBox.get(tokenKey)?.accessToken; 196 | 197 | final response = await _api.getApi( 198 | requestEnd: 'user/fetch_user', 199 | bearer: token, 200 | ); 201 | 202 | if (response['success'] == false) { 203 | 204 | if (response['message'].contains('not verified') && 205 | response['data'] != null && 206 | response['data']['user'] != null) { 207 | 208 | final user = UserModel.fromJsonUserDetails(response); 209 | await _hiveManager.userModelBox.put(userKey, user); 210 | 211 | return user; 212 | } 213 | } 214 | 215 | if (response['success'] == true) { 216 | final user = UserModel.fromJsonUserDetails(response); 217 | await _hiveManager.userModelBox.put(userKey, user); 218 | await _hiveManager.userModelBox.flush(); 219 | return user; 220 | } 221 | 222 | return null; 223 | } catch (e) { 224 | logger.e('Error in fetchUserDetails: $e'); 225 | throw e.toString(); 226 | } 227 | } 228 | 229 | } -------------------------------------------------------------------------------- /lib/data/repositories/cloud_note_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:note_app/data/models/cloud_note_models/cloud_note_model.dart'; 2 | import 'package:note_app/data/network/network_services_api.dart'; 3 | import 'package:note_app/helpers/hive_manager.dart'; 4 | import 'package:note_app/utils/const_values.dart'; 5 | 6 | class CloudNoteRepository { 7 | final NetworkServicesApi _api; 8 | final HiveManager _hiveManager; 9 | 10 | CloudNoteRepository({ 11 | required NetworkServicesApi api, 12 | required HiveManager hiveManager, 13 | }) : _api = api, 14 | _hiveManager = hiveManager; 15 | 16 | String? _getToken() { 17 | return _hiveManager.userModelBox.get(tokenKey)?.accessToken; 18 | } 19 | 20 | Future> fetchNotes() async { 21 | try { 22 | final token = _getToken(); 23 | 24 | final response = await _api.getApi( 25 | requestEnd: 'user/fetch_notes', 26 | bearer: token, 27 | ); 28 | 29 | // logger.i('Fetch Notes Response: $response'); 30 | 31 | if (response['success'] == true) { 32 | List cloudNotes = CloudNoteModel.parseResponse(response); 33 | 34 | await _hiveManager.cloudNoteModelBox.clear(); 35 | await _hiveManager.cloudNoteModelBox.addAll(cloudNotes); 36 | 37 | return cloudNotes; 38 | } 39 | 40 | throw response['message'] ?? 'Failed to fetch notes'; 41 | } catch (e) { 42 | logger.e('Error in fetchNotes: $e'); 43 | throw e.toString(); 44 | } 45 | } 46 | 47 | Future createNote(String title, String content) async { 48 | try { 49 | final token = _getToken(); 50 | 51 | final response = await _api.postApi( 52 | requestEnd: 'user/create_notes', 53 | params: { 54 | 'note_title': title, 55 | 'note_content': content, 56 | }, 57 | bearer: token, 58 | ); 59 | 60 | logger.i('Create Note Response: $response'); 61 | 62 | if (response['success'] == true) { 63 | return CloudNoteModel.fromJson(response['data']); 64 | } 65 | 66 | throw response['message'] ?? 'Failed to create note'; 67 | } catch (e) { 68 | logger.e('Error in createNote: $e'); 69 | throw e.toString(); 70 | } 71 | } 72 | 73 | Future updateNote(CloudNoteModel note, int noteKey) async { 74 | try { 75 | final token = _getToken(); 76 | 77 | final response = await _api.postApi( 78 | requestEnd: 'user/edit_notes', 79 | params: { 80 | 'note_uuid': note.uuid, 81 | 'note_title': note.title, 82 | 'note_content': note.notes, 83 | }, 84 | bearer: token, 85 | ); 86 | 87 | logger.i('Update Note Response: $response'); 88 | 89 | if (response['success'] == true) { 90 | // _hiveManager.cloudNoteModelBox.put(noteKey, note); 91 | return CloudNoteModel.fromJson(response['data']); 92 | } 93 | 94 | throw response['message'] ?? 'Failed to update note'; 95 | } catch (e) { 96 | logger.e('Error in updateNote: $e'); 97 | throw e.toString(); 98 | } 99 | } 100 | 101 | Future moveToTrash(CloudNoteModel note) async { 102 | try { 103 | final token = _getToken(); 104 | 105 | final response = await _api.postApi( 106 | requestEnd: 'user/trash_note', 107 | params: { 108 | 'note_uuid': note.uuid, 109 | }, 110 | bearer: token, 111 | ); 112 | 113 | logger.i('Update Note Response: $response'); 114 | 115 | if (response['success'] == true) { 116 | return; 117 | } 118 | 119 | throw response['message'] ?? 'Failed to update note'; 120 | } catch (e) { 121 | logger.e('Error in deleteNote: $e'); 122 | throw e.toString(); 123 | } 124 | } 125 | 126 | Future moveToCloud(String title, String content, int noteKey) async { 127 | try { 128 | final token = _getToken(); 129 | 130 | final response = await _api.postApi( 131 | requestEnd: 'user/move_to_cloud', 132 | params: { 133 | 'note_title': title, 134 | 'note_content': content, 135 | }, 136 | bearer: token, 137 | ); 138 | 139 | logger.i('Create Note Response: $response'); 140 | 141 | if (response['success'] == true) { 142 | _hiveManager.noteModelBox.delete(noteKey); 143 | return CloudNoteModel.fromJson(response['data']); 144 | } 145 | 146 | throw response['message'] ?? 'Failed to create note'; 147 | } catch (e) { 148 | logger.e('Error in createNote: $e'); 149 | throw e.toString(); 150 | } 151 | } 152 | 153 | 154 | List getHiveKeys() { 155 | return _hiveManager.cloudNoteModelBox.keys.cast().toList(); 156 | } 157 | 158 | } -------------------------------------------------------------------------------- /lib/helpers/hive_manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:hive_flutter/hive_flutter.dart'; 4 | import 'package:note_app/data/models/cloud_note_models/cloud_note_model.dart'; 5 | import 'package:note_app/data/models/cloud_note_models/user_model.dart'; 6 | import 'package:note_app/data/models/local_note_model/note_model.dart'; 7 | import 'package:note_app/utils/const_values.dart'; 8 | import 'package:path_provider/path_provider.dart'; 9 | 10 | class HiveManager { 11 | static final HiveManager _instance = HiveManager._internal(); 12 | 13 | factory HiveManager() { 14 | return _instance; 15 | } 16 | 17 | HiveManager._internal(); 18 | 19 | static late Box _noteModelBox; 20 | static late Box _deleteNoteModelBox; 21 | static late Box _cloudNoteModelBox; 22 | static late Box _userModelBox; 23 | 24 | Future init() async { 25 | Directory directory = await getApplicationDocumentsDirectory(); 26 | Hive 27 | ..init(directory.path) 28 | ..registerAdapter(NoteModelAdapter()); 29 | _noteModelBox = await Hive.openBox(noteBox); 30 | _deleteNoteModelBox = await Hive.openBox(deletedNotes); 31 | 32 | Hive 33 | ..init(directory.path) 34 | ..registerAdapter(CloudNoteModelAdapter()); 35 | _cloudNoteModelBox = await Hive.openBox(cloudNoteBox); 36 | 37 | Hive 38 | ..init(directory.path) 39 | ..registerAdapter(UserModelAdapter()); 40 | _userModelBox = await Hive.openBox(userBox); 41 | 42 | } 43 | 44 | 45 | Box get noteModelBox => _noteModelBox; 46 | Box get deleteNoteModelBox => _deleteNoteModelBox; 47 | Box get cloudNoteModelBox => _cloudNoteModelBox; 48 | Box get userModelBox => _userModelBox; 49 | 50 | } -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import 'package:note_app/app/src/app.dart'; 5 | import 'package:note_app/helpers/hive_manager.dart'; 6 | import 'package:note_app/services/service_locator.dart'; 7 | import 'package:note_app/state/cubits/auth_cubit/auth_cubit.dart'; 8 | import 'package:note_app/state/cubits/cloud_note_cubit/cloud_note_cubit.dart'; 9 | import 'package:note_app/state/cubits/note_style_cubit/note_style_cubit.dart'; 10 | import 'package:note_app/state/cubits/play_button_cubit/play_button_cubit.dart'; 11 | import 'package:note_app/state/cubits/theme_cubit/theme_cubit.dart'; 12 | import 'package:note_app/utils/constant/api_constant.dart'; 13 | import 'package:shared_preferences/shared_preferences.dart'; 14 | import 'package:yaml/yaml.dart'; 15 | import 'package:provider/provider.dart'; 16 | 17 | void main() async { 18 | WidgetsFlutterBinding.ensureInitialized(); 19 | 20 | final prefs = await SharedPreferences.getInstance(); 21 | 22 | // initialize hive 23 | await HiveManager().init(); 24 | 25 | loadApiCredentials(); 26 | 27 | // Setup dependency injection 28 | await setupLocator(); 29 | 30 | await SystemChrome.setPreferredOrientations([ 31 | DeviceOrientation.portraitUp, 32 | DeviceOrientation.portraitDown, 33 | ]); 34 | 35 | runApp( 36 | MultiProvider( 37 | providers: [ 38 | BlocProvider( 39 | create: (context) => ThemeCubit(prefs), 40 | ), 41 | BlocProvider( 42 | create: (context) => PlayButtonCubit(), 43 | ), 44 | BlocProvider( 45 | create: (context) => NoteStyleCubit(), 46 | ), 47 | BlocProvider( 48 | create: (context) => getIt(), 49 | ), 50 | BlocProvider( 51 | create: (context) => getIt(), 52 | ), 53 | ], 54 | child: const App(), 55 | ), 56 | ); 57 | } 58 | 59 | Future loadApiCredentials() async { 60 | String yamlString = await rootBundle.loadString('api_credentials.yaml'); 61 | Map yamlMap = loadYaml(yamlString); 62 | Map credentialsMap = Map.from(yamlMap); 63 | ApiConstants.apiUrl = credentialsMap['api_url']; 64 | } 65 | -------------------------------------------------------------------------------- /lib/presentation/pages/cloud_notes/auth/forgot_password.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ForgotPassword extends StatefulWidget { 4 | const ForgotPassword({super.key}); 5 | 6 | @override 7 | State createState() => _ForgotPasswordState(); 8 | } 9 | 10 | class _ForgotPasswordState extends State { 11 | @override 12 | Widget build(BuildContext context) { 13 | return const Placeholder(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/presentation/pages/cloud_notes/auth/login_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 4 | import 'package:note_app/config/router/navigates_to.dart'; 5 | import 'package:note_app/config/router/routes_name.dart'; 6 | import 'package:note_app/presentation/widget/mNew_text_widget.dart'; 7 | import 'package:note_app/presentation/widget/mbutton.dart'; 8 | import 'package:note_app/state/cubits/auth_cubit/auth_cubit.dart'; 9 | import 'package:note_app/state/cubits/theme_cubit/theme_cubit.dart'; 10 | import 'package:note_app/utils/colors/m_colors.dart'; 11 | import 'package:note_app/utils/tools/message_dialog.dart'; 12 | 13 | class LoginScreen extends StatefulWidget { 14 | const LoginScreen({super.key}); 15 | 16 | @override 17 | State createState() => _LoginScreenState(); 18 | } 19 | 20 | class _LoginScreenState extends State { 21 | final formKey = GlobalKey(); 22 | 23 | bool offText = true; 24 | bool isLoading = false; 25 | bool isAuthLoading = false; 26 | 27 | final TextEditingController _identifierController = TextEditingController(); 28 | final TextEditingController _passwordController = TextEditingController(); 29 | 30 | @override 31 | void dispose() { 32 | _identifierController.dispose(); 33 | _passwordController.dispose(); 34 | super.dispose(); 35 | } 36 | 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return BlocListener( 41 | listener: (context, state) { 42 | if (state is AuthAuthenticated) { 43 | // Get saved route and navigate 44 | final attemptedRoute = 45 | context.read().getAndClearAttemptedRoute(); 46 | navigateReplaceTo(context, destination: attemptedRoute ?? RoutesName.cloud_notes); 47 | } else if(state is AuthEmailUnverified) { 48 | navigateReplaceTo(context, destination: RoutesName.verify_code_screen, arguments: { 49 | 'from': 'login', 50 | }); 51 | } else if (state is AuthError) { 52 | showError(state.message); 53 | } 54 | }, 55 | child: Scaffold( 56 | appBar: AppBar( 57 | title: const Text('Login'), 58 | ), 59 | body: SingleChildScrollView( 60 | padding: const EdgeInsets.symmetric(horizontal: 16), 61 | child: Form( 62 | key: formKey, 63 | child: Column( 64 | children: [ 65 | MNewTextField( 66 | fieldName: 'Username / Email', 67 | controller: _identifierController, 68 | ), 69 | MNewTextField( 70 | fieldName: 'Password', 71 | controller: _passwordController, 72 | isPasswordField: true, 73 | offText: offText, 74 | togglePasswordView: () { 75 | setState(() { 76 | offText = !offText; 77 | }); 78 | }, 79 | ), 80 | BlocBuilder( 81 | builder: (context, state) { 82 | return MButton( 83 | title: 'Login', 84 | isLoading: state is AuthLoading, 85 | onPressed: state is! AuthLoading 86 | ? () { 87 | var form = formKey.currentState; 88 | 89 | if (form!.validate()) { 90 | form.save(); 91 | context.read().login( 92 | _identifierController.text!, 93 | _passwordController.text!, 94 | ); 95 | } 96 | } 97 | : null, 98 | ); 99 | }, 100 | ), 101 | SizedBox( 102 | height: 20.h, 103 | ), 104 | RichText( 105 | text: TextSpan( 106 | children: [ 107 | TextSpan( 108 | text: 'Don\'t have an Account? ', 109 | style: TextStyle( 110 | color: 111 | context.watch().state.isDarkTheme == 112 | false 113 | ? AppColors.defaultBlack 114 | : AppColors.defaultWhite, 115 | ), 116 | ), 117 | WidgetSpan( 118 | child: GestureDetector( 119 | onTap: () { 120 | navigateTo(context, destination: RoutesName.register_screen); 121 | }, 122 | child: Text( 123 | 'Create One', 124 | style: TextStyle( 125 | color: context 126 | .watch() 127 | .state 128 | .isDarkTheme == 129 | false 130 | ? AppColors.defaultBlack 131 | : AppColors.defaultWhite, 132 | fontWeight: FontWeight.bold, 133 | ), 134 | ), 135 | ), 136 | ), 137 | ], 138 | ), 139 | ), 140 | ], 141 | ), 142 | ), 143 | ), 144 | ), 145 | ); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /lib/presentation/pages/cloud_notes/auth/register_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 4 | import 'package:note_app/config/router/navigates_to.dart'; 5 | import 'package:note_app/config/router/routes_name.dart'; 6 | import 'package:note_app/presentation/widget/mNew_text_widget.dart'; 7 | import 'package:note_app/presentation/widget/mbutton.dart'; 8 | import 'package:note_app/state/cubits/auth_cubit/auth_cubit.dart'; 9 | import 'package:note_app/state/cubits/theme_cubit/theme_cubit.dart'; 10 | import 'package:note_app/utils/colors/m_colors.dart'; 11 | import 'package:note_app/utils/tools/message_dialog.dart'; 12 | 13 | class RegisterScreen extends StatefulWidget { 14 | const RegisterScreen({super.key}); 15 | 16 | @override 17 | State createState() => _RegisterScreenState(); 18 | } 19 | 20 | class _RegisterScreenState extends State { 21 | final formKey = GlobalKey(); 22 | 23 | bool offText = true; 24 | 25 | final TextEditingController firstNameController = TextEditingController(); 26 | final TextEditingController lastNameController = TextEditingController(); 27 | final TextEditingController emailController = TextEditingController(); 28 | final TextEditingController usernameController = TextEditingController(); 29 | final TextEditingController passwordController = TextEditingController(); 30 | 31 | @override 32 | void dispose() { 33 | firstNameController.dispose(); 34 | lastNameController.dispose(); 35 | emailController.dispose(); 36 | usernameController.dispose(); 37 | passwordController.dispose(); 38 | super.dispose(); 39 | } 40 | 41 | @override 42 | Widget build(BuildContext context) { 43 | return BlocListener( 44 | listener: (context, state) { 45 | if (state is AuthAuthenticated) { 46 | final attemptedRoute = 47 | context.read().getAndClearAttemptedRoute(); 48 | Navigator.pop(context); 49 | navigateReplaceTo(context, 50 | destination: attemptedRoute ?? RoutesName.cloud_notes); 51 | } else if (state is AuthEmailUnverified) { 52 | navigateReplaceTo(context, 53 | destination: RoutesName.verify_code_screen, 54 | arguments: { 55 | 'from': 'login', 56 | }); 57 | } else if (state is AuthError) { 58 | showError(state.message); 59 | } 60 | }, 61 | child: Scaffold( 62 | appBar: AppBar( 63 | title: const Text('Register'), 64 | ), 65 | body: SingleChildScrollView( 66 | padding: const EdgeInsets.symmetric(horizontal: 16), 67 | child: Form( 68 | key: formKey, 69 | child: Column( 70 | children: [ 71 | Row( 72 | children: [ 73 | Expanded( 74 | child: MNewTextField( 75 | fieldName: '', 76 | sideText: 'First Name', 77 | controller: firstNameController, 78 | ), 79 | ), 80 | SizedBox( 81 | width: 10.w, 82 | ), 83 | Expanded( 84 | child: MNewTextField( 85 | fieldName: '', 86 | sideText: 'Last Name', 87 | controller: lastNameController, 88 | ), 89 | ), 90 | ], 91 | ), 92 | MNewTextField( 93 | fieldName: '', 94 | sideText: 'Email', 95 | controller: emailController, 96 | ), 97 | MNewTextField( 98 | fieldName: '', 99 | sideText: 'Username', 100 | controller: usernameController, 101 | ), 102 | MNewTextField( 103 | fieldName: '', 104 | sideText: 'Password', 105 | isPasswordField: true, 106 | offText: offText, 107 | togglePasswordView: () { 108 | setState(() { 109 | offText = !offText; 110 | }); 111 | }, 112 | controller: passwordController, 113 | ), 114 | BlocBuilder( 115 | builder: (context, state) { 116 | return MButton( 117 | title: 'Continue', 118 | isLoading: state is AuthLoading, 119 | onPressed: state is! AuthLoading 120 | ? () { 121 | var form = formKey.currentState; 122 | 123 | if (form!.validate()) { 124 | context.read().register( 125 | firstName: firstNameController.text, 126 | lastName: lastNameController.text, 127 | email: emailController.text, 128 | userName: usernameController.text, 129 | password: passwordController.text, 130 | ); 131 | } 132 | } 133 | : null, 134 | ); 135 | }, 136 | ), 137 | SizedBox( 138 | height: 15.h, 139 | ), 140 | RichText( 141 | text: TextSpan( 142 | children: [ 143 | TextSpan( 144 | text: 'Already have an Account? ', 145 | style: TextStyle( 146 | color: 147 | context.watch().state.isDarkTheme == 148 | false 149 | ? AppColors.defaultBlack 150 | : AppColors.defaultWhite, 151 | ), 152 | ), 153 | WidgetSpan( 154 | child: GestureDetector( 155 | onTap: () { 156 | // context.pop(); 157 | }, 158 | child: Text( 159 | 'Login', 160 | style: TextStyle( 161 | color: context 162 | .watch() 163 | .state 164 | .isDarkTheme == 165 | false 166 | ? AppColors.defaultBlack 167 | : AppColors.defaultWhite, 168 | fontWeight: FontWeight.bold, 169 | ), 170 | ), 171 | ), 172 | ), 173 | ], 174 | ), 175 | ), 176 | ], 177 | ), 178 | ), 179 | ), 180 | ), 181 | ); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /lib/presentation/pages/cloud_notes/auth/reset_password.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ResetPassword extends StatefulWidget { 4 | const ResetPassword({super.key}); 5 | 6 | @override 7 | State createState() => _ResetPasswordState(); 8 | } 9 | 10 | class _ResetPasswordState extends State { 11 | @override 12 | Widget build(BuildContext context) { 13 | return const Placeholder(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/presentation/pages/cloud_notes/auth/verify_code.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_bloc/flutter_bloc.dart'; 6 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 7 | import 'package:fluttertoast/fluttertoast.dart'; 8 | import 'package:note_app/config/router/navigates_to.dart'; 9 | import 'package:note_app/config/router/routes_name.dart'; 10 | import 'package:note_app/presentation/widget/mbutton.dart'; 11 | import 'package:note_app/state/cubits/auth_cubit/auth_cubit.dart'; 12 | import 'package:note_app/state/cubits/theme_cubit/theme_cubit.dart'; 13 | import 'package:note_app/utils/colors/m_colors.dart'; 14 | import 'package:note_app/utils/const_values.dart'; 15 | import 'package:note_app/utils/tools/message_dialog.dart'; 16 | import 'package:note_app/utils/tools/sized_box_ex.dart'; 17 | import 'package:otp_text_field_v2/otp_text_field_v2.dart'; 18 | 19 | class VerifyCode extends StatefulWidget { 20 | final String? email; 21 | final String? from; 22 | 23 | const VerifyCode({ 24 | super.key, 25 | this.email, 26 | this.from, 27 | }); 28 | 29 | @override 30 | State createState() => _VerifyCodeState(); 31 | } 32 | 33 | class _VerifyCodeState extends State { 34 | final formKey = GlobalKey(); 35 | OtpFieldControllerV2 otpController = OtpFieldControllerV2(); 36 | 37 | Timer? _timer; 38 | int _remainingSeconds = 0; 39 | 40 | bool offText = true; 41 | bool isLoading = false; 42 | bool isLoadingVerify = false; 43 | String? code; 44 | String? email; 45 | 46 | void startTimer() { 47 | setState(() { 48 | _remainingSeconds = 60; 49 | }); 50 | 51 | _timer = Timer.periodic(const Duration(seconds: 1), (timer) { 52 | setState(() { 53 | if (_remainingSeconds > 0) { 54 | _remainingSeconds--; 55 | } else { 56 | _timer?.cancel(); 57 | } 58 | }); 59 | }); 60 | } 61 | 62 | @override 63 | void dispose() { 64 | _timer?.cancel(); 65 | super.dispose(); 66 | } 67 | 68 | @override 69 | Widget build(BuildContext context) { 70 | return BlocBuilder( 71 | builder: (context, state) { 72 | return BlocListener( 73 | listener: (context, state) { 74 | if (state is AuthAuthenticated) { 75 | final attemptedRoute = context.read().getAndClearAttemptedRoute(); 76 | navigateReplaceTo( 77 | context, 78 | destination: attemptedRoute ?? RoutesName.cloud_notes, 79 | ); 80 | } else if (state is AuthError) { 81 | showError(state.message); 82 | } 83 | }, 84 | child: Scaffold( 85 | appBar: AppBar( 86 | title: const Text('Verify Code'), 87 | ), 88 | body: SingleChildScrollView( 89 | padding: const EdgeInsets.symmetric(horizontal: 16), 90 | child: Column( 91 | children: [ 92 | 10.toHeight, 93 | OTPTextFieldV2( 94 | controller: otpController, 95 | length: 4, 96 | width: MediaQuery.of(context).size.width, 97 | textFieldAlignment: MainAxisAlignment.spaceAround, 98 | fieldWidth: 60, 99 | fieldStyle: FieldStyle.box, 100 | otpFieldStyle: OtpFieldStyle( 101 | backgroundColor: transparent, 102 | enabledBorderColor: 103 | context.watch().state.isDarkTheme == false 104 | ? AppColors.defaultBlack 105 | : AppColors.defaultWhite, 106 | ), 107 | outlineBorderRadius: 8, 108 | style: TextStyle( 109 | fontSize: 17.sp, 110 | ), 111 | onChanged: (pin) { 112 | print("Changed: " + pin); 113 | setState(() { 114 | code = pin; 115 | }); 116 | }, 117 | onCompleted: (pin) { 118 | print("Completed: " + pin); 119 | setState(() { 120 | code = pin; 121 | }); 122 | }, 123 | ), 124 | 15.toHeight, 125 | MButton( 126 | title: 'Verify OTP', 127 | hasIcon: true, 128 | btnIcon: Icons.arrow_forward_ios, 129 | isLoading: state is AuthLoading, 130 | onPressed: state is! AuthLoading 131 | ? () { 132 | if (code == null || code!.length < 4) { 133 | Fluttertoast.showToast( 134 | msg: 'Code not complete', 135 | gravity: ToastGravity.TOP, 136 | ); 137 | return; 138 | } 139 | context.read().verifyEmail(code!); 140 | } 141 | : null, 142 | ), 143 | 15.toHeight, 144 | GestureDetector( 145 | onTap: _remainingSeconds == 0 && state is! AuthLoading 146 | ? () { 147 | context.read().resendVerificationCode(); 148 | startTimer(); 149 | } 150 | : null, 151 | child: Text( 152 | state is AuthLoading 153 | ? 'Sending code...' 154 | : _remainingSeconds == 0 155 | ? 'Send Code' 156 | : 'Resend code in $_remainingSeconds', 157 | style: TextStyle( 158 | fontSize: 16.sp, 159 | ), 160 | overflow: TextOverflow.clip, 161 | ), 162 | ), 163 | ], 164 | ), 165 | ), 166 | ), 167 | ); 168 | }, 169 | ); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /lib/presentation/pages/cloud_notes/cloud_create_note.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_bloc/flutter_bloc.dart'; 6 | import 'package:fluttertoast/fluttertoast.dart'; 7 | import 'package:note_app/config/router/navigates_to.dart'; 8 | import 'package:note_app/config/router/routes_name.dart'; 9 | import 'package:note_app/state/cubits/auth_cubit/auth_cubit.dart'; 10 | import 'package:note_app/state/cubits/cloud_note_cubit/cloud_note_cubit.dart'; 11 | import 'package:note_app/state/cubits/theme_cubit/theme_cubit.dart'; 12 | import 'package:note_app/utils/tools/message_dialog.dart'; 13 | 14 | class CloudCreateNote extends StatefulWidget { 15 | const CloudCreateNote({super.key}); 16 | 17 | @override 18 | State createState() => _CloudCreateNoteState(); 19 | } 20 | 21 | class _CloudCreateNoteState extends State { 22 | final TextEditingController _noteTitle = TextEditingController(); 23 | final TextEditingController _noteText = TextEditingController(); 24 | final scaffoldKey = GlobalKey(); 25 | 26 | TextStyle? myTextStyle; 27 | TextAlign? myTextAlign; 28 | 29 | bool? _isNotEmpty; 30 | 31 | final goToNotes = FocusNode(); 32 | 33 | Future checkIfNoteIsNotEmptyWhenGoingBack() async { 34 | if (_noteText.text.isNotEmpty || _noteTitle.text.isNotEmpty) { 35 | final String noteTitle = _noteTitle.text; 36 | final String note = _noteText.text; 37 | 38 | context.read().createNote(noteTitle, note); 39 | 40 | await Fluttertoast.showToast( 41 | msg: 'Note Saved', 42 | toastLength: Toast.LENGTH_SHORT, 43 | ); 44 | if (mounted) { 45 | navigateReplaceTo(context, destination: RoutesName.cloud_notes); 46 | } 47 | _isNotEmpty = true; 48 | } else { 49 | await Fluttertoast.showToast( 50 | msg: 'Note was empty, nothing was saved', 51 | toastLength: Toast.LENGTH_SHORT, 52 | ); 53 | if (mounted) { 54 | navigateReplaceTo(context, destination: RoutesName.cloud_notes); 55 | } 56 | _isNotEmpty = false; 57 | } 58 | return _isNotEmpty!; 59 | } 60 | 61 | void checkIfNoteIsNotEmptyAndSaveNote() { 62 | if (_noteTitle.text.isEmpty || _noteText.text.isEmpty) { 63 | Fluttertoast.showToast( 64 | msg: 'Title or note body cannot be empty', 65 | toastLength: Toast.LENGTH_SHORT, 66 | ); 67 | return; 68 | } else { 69 | final String noteTitle = _noteTitle.text; 70 | final String note = _noteText.text; 71 | 72 | // createNote(noteTitle, note); 73 | context.read().createNote(noteTitle, note); 74 | 75 | Fluttertoast.showToast( 76 | msg: 'Note Saved', 77 | toastLength: Toast.LENGTH_SHORT, 78 | ); 79 | navigateReplaceTo(context, destination: RoutesName.cloud_notes); 80 | } 81 | } 82 | 83 | @override 84 | void initState() { 85 | super.initState(); 86 | myTextStyle = const TextStyle( 87 | fontSize: 18.5, 88 | ); 89 | myTextAlign = TextAlign.left; 90 | } 91 | 92 | @override 93 | void dispose() { 94 | super.dispose(); 95 | _noteTitle.dispose(); 96 | _noteText.dispose(); 97 | } 98 | 99 | @override 100 | Widget build(BuildContext context) { 101 | var height = MediaQuery.of(context).size.height; 102 | return WillPopScope( 103 | onWillPop: checkIfNoteIsNotEmptyWhenGoingBack, 104 | child: BlocListener( 105 | listener: (context, state) { 106 | if (state is AuthEmailUnverified) { 107 | navigateReplaceTo(context, 108 | destination: RoutesName.verify_code_screen, 109 | arguments: { 110 | 'from': 'login', 111 | }); 112 | } else if (state is AuthError) { 113 | showError(state.message); 114 | } 115 | }, 116 | child: Scaffold( 117 | key: scaffoldKey, 118 | appBar: AppBar( 119 | // used a text form field for the app bar 120 | leading: Platform.isIOS 121 | ? IconButton( 122 | icon: const Icon(CupertinoIcons.back), 123 | onPressed: checkIfNoteIsNotEmptyWhenGoingBack, 124 | ) 125 | : null, 126 | title: TextFormField( 127 | autofocus: true, 128 | controller: _noteTitle, 129 | decoration: const InputDecoration( 130 | hintText: 'Create Note Title...', 131 | hintStyle: TextStyle( 132 | fontSize: 20, 133 | fontWeight: FontWeight.bold, 134 | ), 135 | border: InputBorder.none, 136 | ), 137 | style: const TextStyle( 138 | fontSize: 20, 139 | fontWeight: FontWeight.bold, 140 | ), 141 | onFieldSubmitted: (_) { 142 | FocusScope.of(context).requestFocus(goToNotes); 143 | }, 144 | keyboardType: TextInputType.text, 145 | textCapitalization: TextCapitalization.sentences, 146 | textInputAction: TextInputAction.next, 147 | ), 148 | // ends here // 149 | centerTitle: false, 150 | actions: [ 151 | TextButton.icon( 152 | onPressed: () { 153 | checkIfNoteIsNotEmptyAndSaveNote(); 154 | }, 155 | icon: Icon( 156 | Icons.done, 157 | color: context.watch().state.isDarkTheme == false 158 | ? Colors.black45 159 | : Colors.white38, 160 | ), 161 | label: Text( 162 | 'Save', 163 | style: TextStyle( 164 | color: 165 | context.watch().state.isDarkTheme == false 166 | ? Colors.black45 167 | : Colors.white38, 168 | ), 169 | ), 170 | ), 171 | ], 172 | ), 173 | body: SingleChildScrollView( 174 | child: Padding( 175 | padding: const EdgeInsets.symmetric(horizontal: 10.0), 176 | child: TextField( 177 | controller: _noteText, 178 | decoration: const InputDecoration( 179 | hintText: 'Type Note...', 180 | hintStyle: TextStyle(), 181 | border: InputBorder.none, 182 | ), 183 | textCapitalization: TextCapitalization.sentences, 184 | focusNode: goToNotes, 185 | style: myTextStyle, 186 | textAlign: myTextAlign!, 187 | maxLines: height.toInt(), 188 | ), 189 | 190 | //TODO! trying to add styling functionality, having issues 191 | //TODO! persisting the style for a saved note 192 | ), 193 | ), 194 | ), 195 | ), 196 | ); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /lib/presentation/pages/cloud_notes/cloud_edit_note.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:fluttertoast/fluttertoast.dart'; 4 | import 'package:note_app/config/router/navigates_to.dart'; 5 | import 'package:note_app/config/router/routes_name.dart'; 6 | import 'package:note_app/data/models/cloud_note_models/cloud_note_model.dart'; 7 | import 'package:note_app/helpers/hive_manager.dart'; 8 | import 'package:note_app/presentation/pages/cloud_notes/cloud_read_note_screen.dart'; 9 | import 'package:note_app/state/cubits/cloud_note_cubit/cloud_note_cubit.dart'; 10 | import 'package:note_app/state/cubits/theme_cubit/theme_cubit.dart'; 11 | import 'package:note_app/utils/const_values.dart'; 12 | import 'package:note_app/utils/tools/message_dialog.dart'; 13 | import 'package:flutter_bloc/flutter_bloc.dart'; 14 | 15 | class CloudEditNote extends StatefulWidget { 16 | final CloudNoteModel? notes; 17 | final int? noteKey; 18 | 19 | const CloudEditNote({ 20 | super.key, 21 | @required this.notes, 22 | @required this.noteKey, 23 | }); 24 | 25 | @override 26 | State createState() => _CloudEditNoteState(); 27 | } 28 | 29 | class _CloudEditNoteState extends State { 30 | final goToNotes = FocusNode(); 31 | final scaffoldKey = GlobalKey(); 32 | 33 | TextStyle? myTextStyle; 34 | TextAlign? myTextAlign; 35 | 36 | dynamic coText; 37 | var _initValue = {'notes': '', 'conText': ''}; 38 | 39 | var _isInit = true; 40 | 41 | @override 42 | void didChangeDependencies() { 43 | super.didChangeDependencies(); 44 | // _noteText = widget.notes.notes; 45 | if (_isInit) { 46 | _initValue = { 47 | 'title': widget.notes!.title.toString(), 48 | 'notes': widget.notes!.notes.toString(), 49 | 'conText': widget.notes!.notes.toString() 50 | }; 51 | } 52 | _isInit = false; 53 | } 54 | 55 | @override 56 | void initState() { 57 | super.initState(); 58 | myTextStyle = const TextStyle( 59 | fontSize: 18.5, 60 | ); 61 | myTextAlign = TextAlign.left; 62 | } 63 | 64 | bool? isEdited; 65 | 66 | @override 67 | Widget build(BuildContext context) { 68 | var height = MediaQuery.of(context).size.height; 69 | return Scaffold( 70 | key: scaffoldKey, 71 | appBar: AppBar( 72 | title: TextFormField( 73 | initialValue: _initValue['title'], 74 | autofocus: true, 75 | onChanged: (val) { 76 | _initValue['title'] = val; 77 | }, 78 | decoration: const InputDecoration( 79 | hintText: 'Create Note Title...', 80 | hintStyle: TextStyle( 81 | fontSize: 20, 82 | fontWeight: FontWeight.bold, 83 | ), 84 | border: InputBorder.none, 85 | ), 86 | style: const TextStyle( 87 | fontSize: 20, 88 | fontWeight: FontWeight.bold, 89 | ), 90 | onFieldSubmitted: (_) { 91 | FocusScope.of(context).requestFocus(goToNotes); 92 | }, 93 | keyboardType: TextInputType.text, 94 | textCapitalization: TextCapitalization.sentences, 95 | textInputAction: TextInputAction.next, 96 | ), 97 | centerTitle: false, 98 | actions: [ 99 | TextButton.icon( 100 | onPressed: () { 101 | if (_initValue['title']!.isEmpty || 102 | _initValue['notes']!.isEmpty) { 103 | Fluttertoast.showToast( 104 | msg: 'Title or note body cannot be empty', 105 | toastLength: Toast.LENGTH_SHORT, 106 | ); 107 | return; 108 | } else { 109 | var key = widget.noteKey; 110 | String? title = _initValue['title']; 111 | String? note = _initValue['notes']; 112 | 113 | CloudNoteModel noteM = CloudNoteModel( 114 | uuid: widget.notes!.uuid!, 115 | title: title!, 116 | notes: note!, 117 | ); 118 | 119 | context.read().editNote(noteM, key!); 120 | 121 | Fluttertoast.showToast( 122 | msg: 'Note Saved', 123 | toastLength: Toast.LENGTH_SHORT, 124 | ); 125 | 126 | navigateReplaceTo(context, destination: RoutesName.cloud_read_notes_screen , arguments: { 127 | 'note': noteM, 128 | 'noteKey': key 129 | }); 130 | } 131 | }, 132 | icon: Icon( 133 | Icons.done, 134 | color: context.watch().state.isDarkTheme == false 135 | ? Colors.black45 136 | : Colors.white38, 137 | ), 138 | label: Text( 139 | 'Update', 140 | style: TextStyle( 141 | color: context.watch().state.isDarkTheme == false 142 | ? Colors.black45 143 | : Colors.white38, 144 | ), 145 | ), 146 | ), 147 | ], 148 | ), 149 | body: Padding( 150 | padding: const EdgeInsets.symmetric(horizontal: 10.0), 151 | child: TextFormField( 152 | initialValue: _initValue['notes'], 153 | autofocus: true, 154 | onChanged: (value) { 155 | _initValue['notes'] = value; 156 | }, 157 | decoration: const InputDecoration( 158 | border: InputBorder.none, 159 | ), 160 | focusNode: goToNotes, 161 | style: myTextStyle, 162 | textCapitalization: TextCapitalization.sentences, 163 | textAlign: myTextAlign!, 164 | maxLines: height.toInt(), 165 | ), 166 | 167 | //TODO! trying to add styling functionality, having issues 168 | //TODO! persisting the style for a saved note 169 | ), 170 | ); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /lib/presentation/pages/cloud_notes/cloud_read_note_screen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_tts/flutter_tts.dart'; 6 | import 'package:note_app/config/router/navigates_to.dart'; 7 | import 'package:note_app/config/router/routes_name.dart'; 8 | import 'package:note_app/data/models/cloud_note_models/cloud_note_model.dart'; 9 | import 'package:note_app/state/cubits/cloud_note_cubit/cloud_note_cubit.dart'; 10 | import 'package:note_app/state/cubits/play_button_cubit/play_button_cubit.dart'; 11 | import 'package:note_app/utils/const_values.dart'; 12 | import 'package:provider/provider.dart'; 13 | 14 | class CloudReadNote extends StatefulWidget { 15 | final CloudNoteModel? note; 16 | final int? noteKey; 17 | 18 | const CloudReadNote({ 19 | super.key, 20 | @required this.note, 21 | @required this.noteKey, 22 | }); 23 | 24 | @override 25 | State createState() => _CloudReadNoteState(); 26 | } 27 | 28 | enum TtsCloudState { 29 | playing, 30 | stopped, 31 | paused, 32 | continued, 33 | } 34 | 35 | class _CloudReadNoteState extends State { 36 | FlutterTts? flutterTts; 37 | dynamic languages; 38 | String? language; 39 | double? volume = 0.5; 40 | double? pitch = 0.2; 41 | double? rate = 0.4; 42 | 43 | String? _newVoiceText; 44 | 45 | TtsCloudState ttsState = TtsCloudState.stopped; 46 | 47 | bool get isIOS => !kIsWeb && Platform.isIOS; 48 | 49 | bool get isAndroid => !kIsWeb && Platform.isAndroid; 50 | 51 | bool get isWeb => kIsWeb; 52 | 53 | @override 54 | void initState() { 55 | super.initState(); 56 | initTts(); 57 | _onChange(widget.note!.notes.toString()); 58 | } 59 | 60 | void initTts() { 61 | flutterTts = FlutterTts(); 62 | 63 | _getLanguages(); 64 | 65 | // flutterTts.setVoice("en-us-x-sfg#male_1-local"); 66 | flutterTts!.setLanguage('en-Us'); 67 | 68 | if (isAndroid) { 69 | _getEngines(); 70 | } 71 | 72 | flutterTts!.setStartHandler(() { 73 | setState(() { 74 | logger.i('Playing'); 75 | ttsState = TtsCloudState.playing; 76 | }); 77 | }); 78 | 79 | flutterTts!.setCompletionHandler(() { 80 | setState(() { 81 | logger.i('Complete'); 82 | ttsState = TtsCloudState.stopped; 83 | }); 84 | }); 85 | 86 | flutterTts!.setCancelHandler(() { 87 | setState(() { 88 | logger.i('Cancel'); 89 | ttsState = TtsCloudState.stopped; 90 | }); 91 | }); 92 | 93 | if (isWeb || isIOS) { 94 | flutterTts!.setPauseHandler(() { 95 | setState(() { 96 | logger.i('Paused'); 97 | ttsState = TtsCloudState.paused; 98 | }); 99 | }); 100 | 101 | flutterTts!.setContinueHandler(() { 102 | setState(() { 103 | logger.i('Continued'); 104 | ttsState = TtsCloudState.continued; 105 | }); 106 | }); 107 | } 108 | 109 | flutterTts!.setErrorHandler((msg) { 110 | setState(() { 111 | logger.i('error: $msg'); 112 | ttsState = TtsCloudState.stopped; 113 | }); 114 | }); 115 | } 116 | 117 | Future _getLanguages() async { 118 | languages = await flutterTts!.getLanguages; 119 | if (languages != null) setState(() => languages); 120 | } 121 | 122 | Future _getEngines() async { 123 | var engines = await flutterTts!.getEngines; 124 | if (engines != null) { 125 | for (dynamic engine in engines) { 126 | logger.i(engine); 127 | } 128 | } 129 | } 130 | 131 | Future _speak() async { 132 | await flutterTts!.setVolume(volume!); 133 | await flutterTts!.setSpeechRate(rate!); 134 | await flutterTts!.setPitch(pitch!); 135 | 136 | if (_newVoiceText != null) { 137 | if (_newVoiceText!.isNotEmpty) { 138 | await flutterTts!.awaitSpeakCompletion(true); 139 | await flutterTts!.speak(_newVoiceText!); 140 | } 141 | } 142 | } 143 | 144 | Future _stop() async { 145 | var result = await flutterTts!.stop(); 146 | if (result == 1) setState(() => ttsState = TtsCloudState.stopped); 147 | } 148 | 149 | // Android does not support pause as of this moment 150 | // Future _pause() async { 151 | // var result = 152 | // await flutterTts.setSilence(widget.note.notes.toString().length); 153 | // if (result == 1) setState(() => ttsState = TtsState.paused); 154 | // } 155 | 156 | @override 157 | void dispose() { 158 | super.dispose(); 159 | flutterTts!.stop(); 160 | } 161 | 162 | void _onChange(String text) { 163 | setState(() { 164 | _newVoiceText = text; 165 | }); 166 | } 167 | 168 | final bool isEditing = true; 169 | 170 | Widget showText() { 171 | dynamic test; 172 | setState(() { 173 | test = SelectableText( 174 | widget.note!.notes.toString(), 175 | style: const TextStyle( 176 | fontSize: 18.0, 177 | ), 178 | ); 179 | }); 180 | return test; 181 | } 182 | 183 | @override 184 | Widget build(BuildContext context) { 185 | return WillPopScope( 186 | onWillPop: () async { 187 | await context.read().fetchNotes(); 188 | return true; 189 | }, 190 | child: Scaffold( 191 | appBar: AppBar( 192 | title: const Text( 193 | 'Read Note', 194 | ), 195 | shadowColor: Colors.transparent, 196 | backgroundColor: Colors.transparent, 197 | centerTitle: true, 198 | actions: [ 199 | IconButton( 200 | icon: const Icon(Icons.mode_edit), 201 | onPressed: () { 202 | navigateReplaceTo(context, destination: RoutesName.cloud_edit_notes_screen , arguments: { 203 | 'notes': widget.note, 204 | 'noteKey': widget.noteKey 205 | }); 206 | }, 207 | ) 208 | ], 209 | ), 210 | body: SingleChildScrollView( 211 | child: Padding( 212 | padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5), 213 | child: Column( 214 | crossAxisAlignment: CrossAxisAlignment.start, 215 | children: [ 216 | SizedBox( 217 | child: Center( 218 | child: Text( 219 | '${widget.note!.title}', 220 | style: const TextStyle( 221 | fontSize: 20, 222 | fontWeight: FontWeight.bold, 223 | ), 224 | softWrap: true, 225 | textAlign: TextAlign.center, 226 | ), 227 | ), 228 | ), 229 | const SizedBox( 230 | height: 15, 231 | ), 232 | showText(), 233 | ], 234 | ), 235 | ), 236 | ), 237 | floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, 238 | floatingActionButton: context.watch().state.canPlay 239 | ? FloatingActionButton( 240 | backgroundColor: Colors.white60, 241 | onPressed: () { 242 | ttsState == TtsCloudState.stopped ? _speak() : _stop(); 243 | }, 244 | child: Icon( 245 | ttsState == TtsCloudState.stopped 246 | ? Icons.play_circle_outline 247 | : Icons.stop_circle_outlined, 248 | color: Colors.black45, 249 | ), 250 | ) 251 | : null, 252 | ), 253 | ); 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /lib/presentation/pages/home/home.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/services.dart'; 5 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 6 | import 'package:note_app/config/router/navigates_to.dart'; 7 | import 'package:note_app/config/router/routes_name.dart'; 8 | import 'package:note_app/state/cubits/theme_cubit/theme_cubit.dart'; 9 | import 'package:note_app/utils/colors/m_colors.dart'; 10 | import 'package:note_app/utils/greetings.dart'; 11 | import 'package:provider/provider.dart'; 12 | 13 | class HomeScreen extends StatefulWidget { 14 | const HomeScreen({super.key}); 15 | 16 | @override 17 | _HomeScreenState createState() => _HomeScreenState(); 18 | } 19 | 20 | class _HomeScreenState extends State { 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | } 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return Scaffold( 30 | appBar: AppBar( 31 | title: const Text('VNotes'), 32 | // TODO:* adding support for localization soon. 33 | actions: [ 34 | IconButton( 35 | onPressed: () { 36 | navigateTo(context, destination: RoutesName.settings_screen); 37 | }, 38 | icon: const Icon( 39 | Icons.settings, 40 | ), 41 | ), 42 | ], 43 | ), 44 | body: SingleChildScrollView( 45 | child: Padding( 46 | padding: const EdgeInsets.symmetric(horizontal: 20), 47 | child: Center( 48 | child: Column( 49 | mainAxisAlignment: MainAxisAlignment.center, 50 | children: [ 51 | SizedBox( 52 | height: 30.h, 53 | ), 54 | Text( 55 | 'Hi 👋🏾, ${greetingMessage()}', 56 | style: TextStyle( 57 | fontWeight: FontWeight.bold, 58 | fontSize: 20.sp, 59 | ), 60 | ), 61 | SizedBox( 62 | height: 30.h, 63 | ), 64 | Card( 65 | shape: RoundedRectangleBorder( 66 | borderRadius: BorderRadius.circular(20), 67 | ), 68 | color: context.watch().state.isDarkTheme == false 69 | ? AppColors.primaryColor.withOpacity(0.7) 70 | : AppColors.cardColor, 71 | child: InkWell( 72 | onTap: () { 73 | navigateTo(context, destination: RoutesName.local_notes); 74 | }, 75 | child: Container( 76 | height: 200.h, 77 | width: 300.w, 78 | decoration: BoxDecoration( 79 | borderRadius: BorderRadius.circular(10), 80 | ), 81 | child: Center( 82 | child: Column( 83 | mainAxisAlignment: MainAxisAlignment.center, 84 | children: [ 85 | Icon( 86 | Icons.sd_storage_outlined, 87 | size: 100.r, 88 | color: context.watch().state.isDarkTheme == false 89 | ? AppColors.defaultBlack 90 | : AppColors.defaultWhite, 91 | ), 92 | Text( 93 | 'Local Note', 94 | style: TextStyle( 95 | fontSize: 20.sp, 96 | fontWeight: FontWeight.bold, 97 | ), 98 | ), 99 | ], 100 | ), 101 | ), 102 | ), 103 | ), 104 | ), 105 | SizedBox( 106 | height: 20.h, 107 | ), 108 | Card( 109 | shape: RoundedRectangleBorder( 110 | borderRadius: BorderRadius.circular(20), 111 | ), 112 | color: context.watch().state.isDarkTheme == false 113 | ? AppColors.primaryColor.withOpacity(0.7) 114 | : AppColors.cardColor, 115 | child: InkWell( 116 | onTap: () { 117 | navigateTo(context, destination: RoutesName.cloud_notes); 118 | }, 119 | child: Container( 120 | height: 200.h, 121 | width: 300.w, 122 | decoration: BoxDecoration( 123 | borderRadius: BorderRadius.circular(10), 124 | ), 125 | child: Center( 126 | child: Column( 127 | mainAxisAlignment: MainAxisAlignment.center, 128 | children: [ 129 | Icon( 130 | Icons.cloud_queue_outlined, 131 | size: 100.r, 132 | color: context.watch().state.isDarkTheme == false 133 | ? AppColors.defaultBlack 134 | : AppColors.defaultWhite, 135 | ), 136 | Text( 137 | 'Cloud Note', 138 | style: TextStyle( 139 | fontSize: 20.sp, 140 | fontWeight: FontWeight.bold, 141 | ), 142 | ), 143 | ], 144 | ), 145 | ), 146 | ), 147 | ), 148 | ), 149 | ], 150 | ), 151 | ), 152 | ), 153 | ), 154 | ); 155 | } 156 | 157 | } 158 | -------------------------------------------------------------------------------- /lib/presentation/pages/local_notes/create_note_screen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | import 'dart:convert'; 3 | 4 | import 'package:flutter/cupertino.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:fluttertoast/fluttertoast.dart'; 7 | import 'package:note_app/data/models/local_note_model/note_model.dart'; 8 | import 'package:note_app/helpers/hive_manager.dart'; 9 | import 'package:note_app/presentation/pages/local_notes/local_notes.dart'; 10 | import 'package:note_app/state/cubits/theme_cubit/theme_cubit.dart'; 11 | import 'package:note_app/utils/tools/slide_transition.dart'; 12 | import 'package:provider/provider.dart'; 13 | import 'package:flutter_quill/flutter_quill.dart'; 14 | 15 | class CreateNoteScreen extends StatefulWidget { 16 | const CreateNoteScreen({Key? key}) : super(key: key); 17 | @override 18 | _CreateNoteScreenState createState() => _CreateNoteScreenState(); 19 | } 20 | 21 | class _CreateNoteScreenState extends State { 22 | final TextEditingController _noteTitle = TextEditingController(); 23 | 24 | // Quill controllers 25 | late QuillController _quillController; 26 | final FocusNode _quillFocusNode = FocusNode(); 27 | final ScrollController _scrollController = ScrollController(); 28 | 29 | final scaffoldKey = GlobalKey(); 30 | bool? _isNotEmpty; 31 | final goToNotes = FocusNode(); 32 | 33 | @override 34 | void initState() { 35 | super.initState(); 36 | // Initialize the Quill controller with empty document 37 | _quillController = QuillController.basic(); 38 | } 39 | 40 | @override 41 | void dispose() { 42 | _noteTitle.dispose(); 43 | _quillController.dispose(); 44 | _quillFocusNode.dispose(); 45 | _scrollController.dispose(); 46 | super.dispose(); 47 | } 48 | 49 | // Convert Quill document to JSON string for storage 50 | String _getQuillContentAsJson() { 51 | return jsonEncode(_quillController.document.toDelta().toJson()); 52 | } 53 | 54 | // Check if Quill document is empty 55 | bool _isQuillContentEmpty() { 56 | final delta = _quillController.document.toDelta(); 57 | // Check if there's only one empty line or no content 58 | return delta.length <= 1 && 59 | (delta.isEmpty || 60 | (delta.length == 1 && delta.first.data == '\n')); 61 | } 62 | 63 | Future checkIfNoteIsNotEmptyWhenGoingBack() async { 64 | final storeData = HiveManager().noteModelBox; 65 | 66 | if (!_isQuillContentEmpty() || _noteTitle.text.isNotEmpty) { 67 | final String noteTitle = _noteTitle.text; 68 | final String noteContent = _getQuillContentAsJson(); 69 | 70 | NoteModel noteM = NoteModel( 71 | title: noteTitle, 72 | notes: noteContent, 73 | // dateTime: DateTime.now().toString(), 74 | ); 75 | 76 | await storeData.add(noteM); 77 | await Fluttertoast.showToast( 78 | msg: 'Note Saved', 79 | toastLength: Toast.LENGTH_SHORT, 80 | ); 81 | 82 | if(mounted) { 83 | Navigator.of(context).pop(); 84 | await Navigator.of(context).push(MaterialPageRoute(builder: (_) { 85 | return const LocalNotesScreen(); 86 | })); 87 | } 88 | _isNotEmpty = true; 89 | } else { 90 | await Fluttertoast.showToast( 91 | msg: 'Note was empty, nothing was saved', 92 | toastLength: Toast.LENGTH_SHORT, 93 | ); 94 | 95 | if(mounted) { 96 | Navigator.of(context).pop(); 97 | await Navigator.of(context).push(MaterialPageRoute(builder: (_) { 98 | return const LocalNotesScreen(); 99 | })); 100 | } 101 | _isNotEmpty = false; 102 | } 103 | return _isNotEmpty!; 104 | } 105 | 106 | void checkIfNoteIsNotEmptyAndSaveNote() { 107 | final storeData = HiveManager().noteModelBox; 108 | 109 | if (_noteTitle.text.isEmpty || _isQuillContentEmpty()) { 110 | Fluttertoast.showToast( 111 | msg: 'Title or note body cannot be empty', 112 | toastLength: Toast.LENGTH_SHORT, 113 | ); 114 | return; 115 | } else { 116 | final String noteTitle = _noteTitle.text; 117 | final String noteContent = _getQuillContentAsJson(); 118 | 119 | NoteModel noteM = NoteModel( 120 | title: noteTitle, 121 | notes: noteContent, 122 | // dateTime: DateTime.now().toString(), 123 | ); 124 | 125 | storeData.add(noteM); 126 | Fluttertoast.showToast( 127 | msg: 'Note Saved', 128 | toastLength: Toast.LENGTH_SHORT, 129 | ); 130 | 131 | Navigator.of(context).pop(); 132 | Navigator.of(context).push(MySlide(builder: (_) { 133 | return const LocalNotesScreen(); 134 | })); 135 | } 136 | } 137 | 138 | @override 139 | Widget build(BuildContext context) { 140 | return WillPopScope( 141 | onWillPop: checkIfNoteIsNotEmptyWhenGoingBack, 142 | child: Scaffold( 143 | key: scaffoldKey, 144 | appBar: AppBar( 145 | title: TextFormField( 146 | autofocus: true, 147 | controller: _noteTitle, 148 | decoration: const InputDecoration( 149 | hintText: 'Create Note Title...', 150 | hintStyle: TextStyle( 151 | fontSize: 20, 152 | fontWeight: FontWeight.bold, 153 | ), 154 | border: InputBorder.none, 155 | ), 156 | style: const TextStyle( 157 | fontSize: 20, 158 | fontWeight: FontWeight.bold, 159 | ), 160 | onFieldSubmitted: (_) { 161 | FocusScope.of(context).requestFocus(_quillFocusNode); 162 | }, 163 | keyboardType: TextInputType.text, 164 | textCapitalization: TextCapitalization.sentences, 165 | textInputAction: TextInputAction.next, 166 | ), 167 | centerTitle: false, 168 | actions: [ 169 | TextButton.icon( 170 | onPressed: checkIfNoteIsNotEmptyAndSaveNote, 171 | icon: Icon( 172 | Icons.done, 173 | color: context.watch().state.isDarkTheme == false 174 | ? Colors.black45 175 | : Colors.white38, 176 | ), 177 | label: Text( 178 | 'Save', 179 | style: TextStyle( 180 | color: context.watch().state.isDarkTheme == false 181 | ? Colors.black45 182 | : Colors.white38, 183 | ), 184 | ), 185 | ), 186 | ], 187 | ), 188 | body: Column( 189 | children: [ 190 | // Quill Toolbar 191 | QuillSimpleToolbar( 192 | controller: _quillController, 193 | config: const QuillSimpleToolbarConfig( 194 | showFontFamily: true, 195 | showFontSize: false, 196 | showBackgroundColorButton: false, 197 | showColorButton: true, 198 | showClearFormat: true, 199 | showCodeBlock: false, 200 | showInlineCode: false, 201 | showListCheck: true, 202 | showListBullets: true, 203 | showListNumbers: true, 204 | showQuote: true, 205 | showStrikeThrough: true, 206 | showUnderLineButton: true, 207 | showDividers: false, 208 | showHeaderStyle: true, 209 | showLink: false, 210 | showSearchButton: false, 211 | showAlignmentButtons: true, 212 | ), 213 | ), 214 | // Quill Editor 215 | Expanded( 216 | child: Container( 217 | padding: const EdgeInsets.symmetric(horizontal: 10.0), 218 | child: QuillEditor( 219 | focusNode: _quillFocusNode, 220 | scrollController: _scrollController, 221 | controller: _quillController, 222 | config: const QuillEditorConfig( 223 | placeholder: 'Type Note...', 224 | scrollable: true, 225 | padding: EdgeInsets.all(0), 226 | autoFocus: false, 227 | expands: false, 228 | ), 229 | ), 230 | ), 231 | ), 232 | ], 233 | ), 234 | ), 235 | ); 236 | } 237 | } -------------------------------------------------------------------------------- /lib/presentation/pages/local_notes/edit_note_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:fluttertoast/fluttertoast.dart'; 3 | import 'package:note_app/data/models/local_note_model/note_model.dart'; 4 | import 'package:note_app/helpers/hive_manager.dart'; 5 | import 'package:note_app/state/cubits/theme_cubit/theme_cubit.dart'; 6 | import 'package:provider/provider.dart'; 7 | 8 | class EditNoteScreen extends StatefulWidget { 9 | final NoteModel? notes; 10 | final int? noteKey; 11 | 12 | const EditNoteScreen({ 13 | super.key, 14 | @required this.notes, 15 | @required this.noteKey, 16 | }); 17 | 18 | @override 19 | _EditNoteScreenState createState() => _EditNoteScreenState(); 20 | } 21 | 22 | class _EditNoteScreenState extends State { 23 | final goToNotes = FocusNode(); 24 | final scaffoldKey = GlobalKey(); 25 | 26 | TextStyle? myTextStyle; 27 | TextAlign? myTextAlign; 28 | 29 | dynamic coText; 30 | var _initValue = {'notes': '', 'conText': ''}; 31 | 32 | var _isInit = true; 33 | 34 | @override 35 | void didChangeDependencies() { 36 | super.didChangeDependencies(); 37 | // _noteText = widget.notes.notes; 38 | if (_isInit) { 39 | _initValue = { 40 | 'title': widget.notes!.title.toString(), 41 | 'notes': widget.notes!.notes.toString(), 42 | 'conText': widget.notes!.notes.toString() 43 | }; 44 | } 45 | _isInit = false; 46 | } 47 | 48 | @override 49 | void initState() { 50 | super.initState(); 51 | myTextStyle = const TextStyle( 52 | fontSize: 18.5, 53 | ); 54 | myTextAlign = TextAlign.left; 55 | } 56 | 57 | bool? isEdited; 58 | 59 | @override 60 | Widget build(BuildContext context) { 61 | var height = MediaQuery.of(context).size.height; 62 | final storeData = HiveManager().noteModelBox; 63 | return Scaffold( 64 | key: scaffoldKey, 65 | appBar: AppBar( 66 | title: TextFormField( 67 | initialValue: _initValue['title'], 68 | autofocus: true, 69 | onChanged: (val) { 70 | _initValue['title'] = val; 71 | }, 72 | decoration: const InputDecoration( 73 | hintText: 'Create Note Title...', 74 | hintStyle: TextStyle( 75 | fontSize: 20, 76 | fontWeight: FontWeight.bold, 77 | ), 78 | border: InputBorder.none, 79 | ), 80 | style: const TextStyle( 81 | fontSize: 20, 82 | fontWeight: FontWeight.bold, 83 | ), 84 | onFieldSubmitted: (_) { 85 | FocusScope.of(context).requestFocus(goToNotes); 86 | }, 87 | keyboardType: TextInputType.text, 88 | textCapitalization: TextCapitalization.sentences, 89 | textInputAction: TextInputAction.next, 90 | ), 91 | centerTitle: false, 92 | actions: [ 93 | TextButton.icon( 94 | onPressed: () { 95 | if (_initValue['title']!.isEmpty || 96 | _initValue['notes']!.isEmpty) { 97 | Fluttertoast.showToast( 98 | msg: 'Title or note body cannot be empty', 99 | toastLength: Toast.LENGTH_SHORT, 100 | ); 101 | return; 102 | } else { 103 | var key = widget.noteKey; 104 | String? title = _initValue['title']; 105 | String? note = _initValue['notes']; 106 | NoteModel noteM = NoteModel( 107 | title: title!, 108 | notes: note!, 109 | // dateTime: DateTime.now().toString(), 110 | ); 111 | storeData.put(key, noteM); 112 | Fluttertoast.showToast( 113 | msg: 'Note Saved', 114 | toastLength: Toast.LENGTH_SHORT, 115 | ); 116 | Navigator.pop(context); 117 | // navigateTo(context, 118 | // destination: ReadNotesScreen(note: noteM, noteKey: key)); 119 | } 120 | }, 121 | icon: Icon( 122 | Icons.done, 123 | color: 124 | context.watch().state.isDarkTheme == false ? Colors.black45 : Colors.white38, 125 | ), 126 | label: Text( 127 | 'Update', 128 | style: TextStyle( 129 | color: context.watch().state.isDarkTheme == false 130 | ? Colors.black45 131 | : Colors.white38, 132 | ), 133 | ), 134 | ), 135 | ], 136 | ), 137 | body: Padding( 138 | padding: const EdgeInsets.symmetric(horizontal: 10.0), 139 | child: TextFormField( 140 | initialValue: _initValue['notes'], 141 | autofocus: true, 142 | onChanged: (value) { 143 | _initValue['notes'] = value; 144 | }, 145 | decoration: const InputDecoration( 146 | border: InputBorder.none, 147 | ), 148 | focusNode: goToNotes, 149 | style: myTextStyle, 150 | textCapitalization: TextCapitalization.sentences, 151 | textAlign: myTextAlign!, 152 | maxLines: height.toInt(), 153 | ), 154 | 155 | //TODO! trying to add styling functionality, having issues 156 | //TODO! persisting the style for a saved note 157 | ), 158 | ); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /lib/presentation/pages/notifications/notifications_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | 4 | class NotificationView extends StatefulWidget { 5 | final String? title; 6 | final String? body; 7 | 8 | const NotificationView({Key? key, this.title, this.body}) : super(key: key); 9 | 10 | @override 11 | State createState() => _NotificationViewState(); 12 | } 13 | 14 | class _NotificationViewState extends State { 15 | @override 16 | Widget build(BuildContext context) { 17 | return Scaffold( 18 | appBar: AppBar(), 19 | body: SingleChildScrollView( 20 | padding: const EdgeInsets.symmetric(horizontal: 16), 21 | child: Column( 22 | crossAxisAlignment: CrossAxisAlignment.start, 23 | children: [ 24 | Center( 25 | child: Text( 26 | '${widget.title}', 27 | style: TextStyle( 28 | fontSize: 30.sp, 29 | fontWeight: FontWeight.bold, 30 | ), 31 | ), 32 | ), 33 | const SizedBox( 34 | height: 10, 35 | ), 36 | Text( 37 | '${widget.body}', 38 | style: TextStyle( 39 | fontSize: 17.sp, 40 | ), 41 | textAlign: TextAlign.justify, 42 | ) 43 | ], 44 | ), 45 | ), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/presentation/pages/settings/settings_screen.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | import 'package:note_app/config/router/navigates_to.dart'; 6 | import 'package:note_app/config/router/routes_name.dart'; 7 | import 'package:note_app/state/cubits/auth_cubit/auth_cubit.dart'; 8 | import 'package:note_app/state/cubits/theme_cubit/theme_cubit.dart'; 9 | import 'package:note_app/utils/tools/message_dialog.dart'; 10 | import 'package:note_app/utils/tools/sized_box_ex.dart'; 11 | import 'package:package_info_plus/package_info_plus.dart'; 12 | import 'package:provider/provider.dart'; 13 | 14 | class SettingsScreen extends StatefulWidget { 15 | const SettingsScreen({Key? key}) : super(key: key); 16 | 17 | @override 18 | _SettingsScreenState createState() => _SettingsScreenState(); 19 | } 20 | 21 | class _SettingsScreenState extends State { 22 | String? appName; 23 | String? packageName; 24 | String? version; 25 | String? buildNumber; 26 | 27 | bool isLoading = false; 28 | bool isLoadingRemoveKeys = false; 29 | 30 | @override 31 | void initState() { 32 | super.initState(); 33 | PackageInfo.fromPlatform().then((PackageInfo packageInfo) { 34 | setState(() { 35 | appName = packageInfo.appName; 36 | packageName = packageInfo.packageName; 37 | version = packageInfo.version; 38 | buildNumber = packageInfo.buildNumber; 39 | }); 40 | }); 41 | } 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | return Scaffold( 46 | appBar: AppBar( 47 | title: const Text('App Settings'), 48 | ), 49 | body: Padding( 50 | padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), 51 | child: Column( 52 | children: [ 53 | SingleChildScrollView( 54 | child: Column( 55 | children: [ 56 | //Body here 57 | SizedBox( 58 | child: Column( 59 | children: [ 60 | Text( 61 | 'VNotes', 62 | style: TextStyle( 63 | fontSize: 30, 64 | fontWeight: FontWeight.bold, 65 | ), 66 | ), 67 | 10.toHeight, 68 | Text( 69 | 'VNotes is a simple lite Note taking app, here to make Note taking easy.', 70 | style: TextStyle(), 71 | textAlign: TextAlign.center, 72 | ) 73 | ], 74 | ), 75 | ), 76 | 10.toHeight, 77 | const Divider(), 78 | 10.toHeight, 79 | ListTile( 80 | leading: Icon( 81 | context.watch().state.isDarkTheme == false 82 | ? Icons.brightness_3 83 | : Icons.brightness_6), 84 | title: const Text( 85 | 'Enable Dark Theme', 86 | style: TextStyle(), 87 | ), 88 | trailing: Switch( 89 | value: context.watch().state.isDarkTheme, 90 | onChanged: (val) { 91 | context.read().toggleTheme(); 92 | }, 93 | ), 94 | ), 95 | 10.toHeight, 96 | ListTile( 97 | leading: const Icon(Icons.delete), 98 | title: const Text( 99 | 'Trash', 100 | style: TextStyle(), 101 | ), 102 | subtitle: const Text( 103 | 'You can recover any note you delete and' 104 | ' also delete them permanently', 105 | style: TextStyle(), 106 | ), 107 | onTap: () { 108 | navigateTo(context, destination: RoutesName.trash_notes); 109 | }, 110 | ), 111 | 10.toHeight, 112 | BlocBuilder( 113 | builder: (context, state) { 114 | if (state is AuthAuthenticated) { 115 | return ListTile( 116 | leading: const Icon(Icons.logout), 117 | title: const Text( 118 | 'Logout', 119 | ), 120 | subtitle: const Text( 121 | 'This will log you out of you cloud account', 122 | style: TextStyle(), 123 | ), 124 | onTap: () { 125 | context.read().logout().then((value) { 126 | showSuccess('Logout successful'); 127 | }); 128 | }, 129 | ); 130 | } else { 131 | return const SizedBox.shrink(); 132 | } 133 | }, 134 | ), 135 | ], 136 | ), 137 | ), 138 | Flexible( 139 | child: Align( 140 | alignment: Alignment.bottomCenter, 141 | child: Platform.isAndroid 142 | ? Text('$packageName Version $version') 143 | : Padding( 144 | padding: const EdgeInsets.only(bottom: 20), 145 | child: Text('$packageName Version $version'), 146 | ), 147 | ), 148 | ) 149 | ], 150 | ), 151 | ), 152 | ); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /lib/presentation/views.dart: -------------------------------------------------------------------------------- 1 | export 'pages/home/home.dart'; 2 | 3 | // local notes 4 | export 'pages/local_notes/local_notes.dart'; 5 | export 'pages/local_notes/create_note_screen.dart'; 6 | export 'pages/local_notes/edit_note_screen.dart'; 7 | export 'pages/local_notes/read_notes_screens.dart'; 8 | 9 | // cloud notes 10 | export 'pages/cloud_notes/cloud_notes.dart'; 11 | export 'pages/cloud_notes/cloud_create_note.dart'; 12 | export 'pages/cloud_notes/cloud_edit_note.dart'; 13 | export 'pages/cloud_notes/cloud_read_note_screen.dart'; 14 | 15 | // cloud auth 16 | export 'pages/cloud_notes/auth/login_screen.dart'; 17 | export 'pages/cloud_notes/auth/register_screen.dart'; 18 | export 'pages/cloud_notes/auth/verify_code.dart'; 19 | 20 | // settings 21 | export 'pages/settings/settings_screen.dart'; 22 | 23 | // trash 24 | export 'pages/trash/trashed_notes.dart'; 25 | 26 | // notifications 27 | export 'pages/notifications/notifications_view.dart'; -------------------------------------------------------------------------------- /lib/presentation/widget/mbutton.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 4 | import 'package:note_app/state/cubits/theme_cubit/theme_cubit.dart'; 5 | import 'package:note_app/utils/colors/m_colors.dart'; 6 | import 'package:note_app/utils/const_values.dart'; 7 | 8 | class MButton extends StatefulWidget { 9 | final String? title; 10 | final String? secondTitle; 11 | final double? radius; 12 | final double? width; 13 | final double? height; 14 | final Color? btnColor; 15 | final Color? textColor; 16 | final double? textSize; 17 | final Color? borderColor; 18 | final IconData? btnIcon; 19 | final bool isLoading; 20 | final bool isTwoText; 21 | final bool hasIcon; 22 | final Widget? btnSVGIcon; 23 | final bool isTextBold; 24 | final VoidCallback? onPressed; 25 | 26 | const MButton({ 27 | super.key, 28 | required this.title, 29 | this.secondTitle, 30 | this.radius, 31 | this.width, 32 | this.height, 33 | this.btnColor, 34 | this.textColor, 35 | this.textSize, 36 | this.borderColor, 37 | this.btnIcon, 38 | this.btnSVGIcon, 39 | this.isLoading = false, 40 | this.isTwoText = false, 41 | this.hasIcon = false, 42 | this.isTextBold = false, 43 | required this.onPressed, 44 | }) : assert( 45 | title != null, 46 | onPressed != null, 47 | ); 48 | 49 | @override 50 | _MButtonState createState() => _MButtonState(); 51 | } 52 | 53 | class _MButtonState extends State { 54 | @override 55 | Widget build(BuildContext context) { 56 | return BlocBuilder( 57 | builder: (context, state) { 58 | return SizedBox( 59 | width: widget.width ?? 327.w, 60 | height: widget.height ?? 55.h, 61 | child: ElevatedButton( 62 | style: ElevatedButton.styleFrom( 63 | elevation: 0, 64 | backgroundColor: widget.btnColor ?? transparent, 65 | shape: RoundedRectangleBorder( 66 | borderRadius: BorderRadius.circular(widget.radius ?? 8), 67 | side: BorderSide( 68 | color: state.isDarkTheme == false 69 | ? AppColors.defaultBlack 70 | : AppColors.defaultWhite, 71 | ), 72 | ), 73 | ), 74 | onPressed: widget.onPressed, 75 | child: widget.isLoading == false 76 | ? widget.isTwoText == true 77 | ? Row( 78 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 79 | children: [ 80 | Text( 81 | '${widget.title}', 82 | style: TextStyle( 83 | fontSize: widget.textSize ?? 16.sp, 84 | fontWeight: widget.isTextBold == true 85 | ? FontWeight.bold 86 | : FontWeight.normal, 87 | color: state.isDarkTheme == false 88 | ? AppColors.defaultBlack 89 | : AppColors.defaultWhite, 90 | ), 91 | textAlign: TextAlign.center, 92 | ), 93 | Text( 94 | '${widget.secondTitle}', 95 | style: TextStyle( 96 | fontSize: widget.textSize ?? 16.sp, 97 | fontWeight: widget.isTextBold == true 98 | ? FontWeight.bold 99 | : FontWeight.normal, 100 | color: state.isDarkTheme == false 101 | ? AppColors.defaultBlack 102 | : AppColors.defaultWhite, 103 | ), 104 | textAlign: TextAlign.center, 105 | ), 106 | ], 107 | ) 108 | : widget.hasIcon == true 109 | ? Row( 110 | mainAxisAlignment: MainAxisAlignment.center, 111 | children: [ 112 | Text( 113 | '${widget.title}', 114 | style: TextStyle( 115 | fontSize: widget.textSize ?? 16.sp, 116 | fontWeight: widget.isTextBold == true 117 | ? FontWeight.bold 118 | : FontWeight.normal, 119 | color: state.isDarkTheme == false 120 | ? AppColors.defaultBlack 121 | : AppColors.defaultWhite, 122 | ), 123 | textAlign: TextAlign.center, 124 | ), 125 | SizedBox( 126 | width: 10.w, 127 | ), 128 | Icon( 129 | widget.btnIcon!, 130 | color: AppColors.defaultWhite, 131 | ), 132 | ], 133 | ) 134 | : Text( 135 | '${widget.title}', 136 | style: TextStyle( 137 | fontSize: widget.textSize ?? 13.sp, 138 | fontWeight: widget.isTextBold == true 139 | ? FontWeight.bold 140 | : FontWeight.normal, 141 | color: state.isDarkTheme == false 142 | ? AppColors.defaultBlack 143 | : AppColors.defaultWhite, 144 | ), 145 | textAlign: TextAlign.center, 146 | ) 147 | : SizedBox( 148 | width: 20.w, 149 | height: 20.w, 150 | child: CircularProgressIndicator( 151 | color: 152 | state.isDarkTheme == false ? AppColors.defaultBlack : AppColors.defaultWhite, 153 | ), 154 | ), 155 | ), 156 | ); 157 | }, 158 | ); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /lib/services/service_locator.dart: -------------------------------------------------------------------------------- 1 | import 'package:get_it/get_it.dart'; 2 | import 'package:note_app/data/network/network_services_api.dart'; 3 | import 'package:note_app/data/repositories/auth_repository.dart'; 4 | import 'package:note_app/data/repositories/cloud_note_repository.dart'; 5 | import 'package:note_app/helpers/hive_manager.dart'; 6 | import 'package:note_app/state/cubits/auth_cubit/auth_cubit.dart'; 7 | import 'package:note_app/state/cubits/cloud_note_cubit/cloud_note_cubit.dart'; 8 | 9 | final GetIt getIt = GetIt.instance; 10 | 11 | Future setupLocator() async { 12 | // Services 13 | getIt.registerLazySingleton(() => NetworkServicesApi()); 14 | getIt.registerLazySingleton(() => HiveManager()); 15 | 16 | // Repositories 17 | getIt.registerLazySingleton( 18 | () => AuthRepository( 19 | api: getIt(), 20 | hiveManager: getIt(), 21 | ), 22 | ); 23 | 24 | getIt.registerLazySingleton( 25 | () => CloudNoteRepository( 26 | api: getIt(), 27 | hiveManager: getIt(), 28 | ), 29 | ); 30 | 31 | // Cubits 32 | getIt.registerFactory( 33 | () => AuthCubit( 34 | authRepository: getIt(), 35 | ), 36 | ); 37 | 38 | getIt.registerFactory( 39 | () => CloudNoteCubit( 40 | repository: getIt(), 41 | ), 42 | ); 43 | } -------------------------------------------------------------------------------- /lib/state/cubits/auth_cubit/auth_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | import 'package:meta/meta.dart'; 4 | import 'package:note_app/data/exceptions/app_exceptions.dart'; 5 | import 'package:note_app/data/models/cloud_note_models/user_model.dart'; 6 | import 'package:note_app/data/repositories/auth_repository.dart'; 7 | import 'package:note_app/utils/const_values.dart'; 8 | import 'package:note_app/utils/tools/message_dialog.dart'; 9 | 10 | part 'auth_state.dart'; 11 | 12 | class AuthCubit extends Cubit { 13 | final AuthRepository _authRepository; 14 | String? _attemptedRoute; 15 | 16 | 17 | AuthCubit({ 18 | required AuthRepository authRepository, 19 | }) : _authRepository = authRepository, 20 | super(AuthInitial()) { 21 | _loadInitialState(); 22 | } 23 | 24 | void saveAttemptedRoute(String route) { 25 | _attemptedRoute = route; 26 | } 27 | 28 | String? getAndClearAttemptedRoute() { 29 | final route = _attemptedRoute; 30 | _attemptedRoute = null; 31 | return route; 32 | } 33 | 34 | Future _loadInitialState() async { 35 | final token = _authRepository.getCurrentUserToken(); 36 | final user = _authRepository.getCurrentUser(); 37 | 38 | if (token == null) { 39 | emit(AuthUnauthenticated()); 40 | return; 41 | } 42 | 43 | if (user != null) { 44 | logger.i('Email of user: ${user.emailVerified}'); 45 | if (user.emailVerified == null || user.emailVerified == 'null') { 46 | emit(AuthEmailUnverified(user)); 47 | } else { 48 | emit(AuthAuthenticated(user)); 49 | } 50 | } 51 | 52 | checkAuthStatus(); 53 | } 54 | 55 | Future checkAuthStatus() async { 56 | final token = _authRepository.getCurrentUserToken(); 57 | if (token != null) { 58 | try { 59 | final freshUserData = await _authRepository.fetchUserDetails(); 60 | 61 | if (freshUserData?.emailVerified == null || 62 | freshUserData?.emailVerified == 'null') { 63 | emit(AuthEmailUnverified(freshUserData!)); 64 | } else { 65 | emit(AuthAuthenticated(freshUserData!)); 66 | } 67 | } catch (e) { 68 | 69 | if (e.toString().contains('not verified')) { 70 | final user = _authRepository.getCurrentUser(); 71 | if (user != null) { 72 | emit(AuthEmailUnverified(user)); 73 | } 74 | } else if (e.toString().contains('suspended')) { 75 | emit(const AuthError('Account suspended')); 76 | await _authRepository.logout(); 77 | } else { 78 | emit(AuthError(e.toString())); 79 | } 80 | } 81 | } else { 82 | emit(AuthUnauthenticated()); 83 | } 84 | } 85 | 86 | Future login(String email, String password) async { 87 | emit(AuthLoading()); 88 | try { 89 | final user = await _authRepository.login(email, password); 90 | emit(AuthAuthenticated(user)); 91 | } catch (e) { 92 | 93 | if (e is UnauthorisedException && 94 | e.toString().contains('not yet verified')) { 95 | logger.i('Handling unverified user in cubit'); 96 | final user = _authRepository.getCurrentUser(); 97 | if (user != null) { 98 | emit(AuthError(e.toString())); 99 | emit(AuthEmailUnverified(user,)); 100 | } 101 | } else if (e is NoInternetException) { 102 | emit(const AuthError('No internet connection')); 103 | } else { 104 | emit(AuthError(e.toString())); 105 | } 106 | } 107 | } 108 | 109 | Future register({ 110 | required String email, 111 | required String password, 112 | required String firstName, 113 | required String lastName, 114 | required String userName, 115 | }) async { 116 | emit(AuthLoading()); 117 | try { 118 | final user = await _authRepository.register( 119 | email: email, 120 | password: password, 121 | firstName: firstName, 122 | lastName: lastName, 123 | userName: userName, 124 | ); 125 | emit(AuthEmailUnverified(user)); 126 | } catch (e) { 127 | if (e is NoInternetException) { 128 | emit(const AuthError('No internet connection')); 129 | } else { 130 | emit(AuthError(e.toString())); 131 | } 132 | } 133 | } 134 | 135 | Future verifyEmail(String code) async { 136 | emit(AuthLoading()); 137 | try { 138 | await _authRepository.verifyEmail(code); 139 | final user = _authRepository.getCurrentUser(); 140 | if (user != null) { 141 | showSuccess('You have been verified successfully.'); 142 | final attemptedRoute = _attemptedRoute; 143 | emit(AuthAuthenticated(user)); 144 | if (attemptedRoute != null) { 145 | saveAttemptedRoute(attemptedRoute); 146 | } 147 | } 148 | } catch (e) { 149 | if (e is NoInternetException) { 150 | emit(const AuthError('No internet connection')); 151 | } else { 152 | emit(AuthError(e.toString())); 153 | } 154 | } 155 | } 156 | 157 | Future resendVerificationCode({String? email}) async { 158 | emit(AuthLoading()); 159 | try { 160 | if(email != null) { 161 | await _authRepository.resendVerificationCode(email: email); 162 | emit(AuthInitial()); 163 | showSuccess('Code sent'); 164 | } else { 165 | await _authRepository.resendVerificationCode(); 166 | emit(AuthEmailUnverified(_authRepository.getCurrentUser()!)); 167 | showSuccess('Code sent'); 168 | } 169 | } catch (e) { 170 | if (e is NoInternetException) { 171 | emit(const AuthError('No internet connection')); 172 | } else { 173 | emit(AuthError(e.toString())); 174 | } 175 | } 176 | } 177 | 178 | Future forgotPassword(String email) async { 179 | emit(AuthLoading()); 180 | try { 181 | await _authRepository.forgotPassword(email); 182 | emit(AuthSuccess()); 183 | showSuccess('Code sent'); 184 | } catch (e) { 185 | if (e is NoInternetException) { 186 | emit(const AuthError('No internet connection')); 187 | } else { 188 | emit(AuthError(e.toString())); 189 | } 190 | } 191 | } 192 | 193 | Future resetPassword(String otpCode, String newPassword) async { 194 | emit(AuthLoading()); 195 | try { 196 | await _authRepository.resetPassword(otpCode, newPassword); 197 | emit(AuthSuccess()); 198 | showSuccess('Password reset'); 199 | } catch (e) { 200 | if (e is NoInternetException) { 201 | emit(const AuthError('No internet connection')); 202 | } else { 203 | emit(AuthError(e.toString())); 204 | } 205 | } 206 | } 207 | 208 | Future logout() async { 209 | try { 210 | await _authRepository.logout(); 211 | emit(AuthUnauthenticated()); 212 | } catch (e) { 213 | emit(AuthError(e.toString())); 214 | } 215 | } 216 | 217 | } 218 | -------------------------------------------------------------------------------- /lib/state/cubits/auth_cubit/auth_state.dart: -------------------------------------------------------------------------------- 1 | part of 'auth_cubit.dart'; 2 | 3 | abstract class AuthState extends Equatable{ 4 | const AuthState(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class AuthInitial extends AuthState {} 11 | 12 | class AuthLoading extends AuthState {} 13 | 14 | class AuthAuthenticated extends AuthState { 15 | final UserModel user; 16 | const AuthAuthenticated(this.user); 17 | 18 | @override 19 | List get props => [user]; 20 | } 21 | 22 | class AuthUnauthenticated extends AuthState {} 23 | 24 | class AuthSuccess extends AuthState {} 25 | 26 | class AuthError extends AuthState { 27 | final String message; 28 | const AuthError(this.message); 29 | 30 | @override 31 | List get props => [message]; 32 | } 33 | 34 | class AuthEmailUnverified extends AuthState { 35 | final UserModel user; 36 | const AuthEmailUnverified(this.user); 37 | 38 | @override 39 | List get props => [user]; 40 | } 41 | -------------------------------------------------------------------------------- /lib/state/cubits/cloud_note_cubit/cloud_note_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | import 'package:meta/meta.dart'; 4 | import 'package:note_app/data/models/cloud_note_models/cloud_note_model.dart'; 5 | import 'package:note_app/data/repositories/cloud_note_repository.dart'; 6 | import 'package:note_app/utils/const_values.dart'; 7 | 8 | part 'cloud_note_state.dart'; 9 | 10 | class CloudNoteCubit extends Cubit { 11 | final CloudNoteRepository _repository; 12 | 13 | CloudNoteCubit({ 14 | required CloudNoteRepository repository, 15 | }) : _repository = repository, 16 | super(CloudNoteInitial()); 17 | 18 | Future fetchNotes() async { 19 | try { 20 | emit(CloudNoteLoading()); 21 | 22 | final notes = await _repository.fetchNotes(); 23 | final hiveKeys = _repository.getHiveKeys(); 24 | emit(CloudNoteLoaded(notes, hiveKeys),); 25 | } catch (e) { 26 | logger.e('Error in fetchNotes: $e'); 27 | emit(CloudError(e.toString())); 28 | } 29 | } 30 | 31 | Future moveToCloud(String? title, String? content, int noteKey) async { 32 | try { 33 | emit(CloudNoteLoading()); 34 | 35 | await _repository.moveToCloud(title!, content!, noteKey); 36 | emit(CloudNoteSuccess()); 37 | } catch (e) { 38 | logger.e('Error in fetchNotes: $e'); 39 | emit(CloudError(e.toString())); 40 | } 41 | } 42 | 43 | Future createNote(String? title, String? content) async { 44 | try { 45 | emit(CloudNoteLoading()); 46 | 47 | final note = await _repository.createNote(title!, content!); 48 | emit(CloudNoteCreated(note)); 49 | 50 | } catch (e) { 51 | logger.e('Error in fetchNotes: $e'); 52 | emit(CloudError(e.toString())); 53 | } 54 | } 55 | 56 | Future editNote(CloudNoteModel cloudNote, int noteKey) async { 57 | try { 58 | emit(CloudNoteLoading()); 59 | 60 | final updatedNote = await _repository.updateNote(cloudNote, noteKey); 61 | 62 | emit(CloudNoteUpdated(updatedNote)); 63 | 64 | } catch (e) { 65 | logger.e('Error in fetchNotes: $e'); 66 | emit(CloudError(e.toString())); 67 | } 68 | } 69 | 70 | Future deleteNote(CloudNoteModel cloudNote) async { 71 | try { 72 | emit(CloudNoteLoading()); 73 | 74 | await _repository.moveToTrash(cloudNote); 75 | 76 | emit(CloudNoteDeleted()); 77 | 78 | } catch (e) { 79 | logger.e('Error in fetchNotes: $e'); 80 | emit(CloudError(e.toString())); 81 | } 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /lib/state/cubits/cloud_note_cubit/cloud_note_state.dart: -------------------------------------------------------------------------------- 1 | part of 'cloud_note_cubit.dart'; 2 | 3 | abstract class CloudNoteState extends Equatable { 4 | const CloudNoteState(); 5 | 6 | @override 7 | List get props => []; 8 | } 9 | 10 | class CloudNoteInitial extends CloudNoteState {} 11 | 12 | class CloudNoteLoading extends CloudNoteState {} 13 | 14 | class CloudNoteLoaded extends CloudNoteState { 15 | final List notes; 16 | final List hiveKeys; 17 | 18 | const CloudNoteLoaded(this.notes, this.hiveKeys); 19 | 20 | @override 21 | List get props => [notes, hiveKeys]; 22 | } 23 | 24 | class CloudNoteSingleLoaded extends CloudNoteState { 25 | final CloudNoteModel note; 26 | 27 | const CloudNoteSingleLoaded(this.note); 28 | 29 | @override 30 | List get props => [note]; 31 | } 32 | 33 | class CloudNoteCreated extends CloudNoteState { 34 | final CloudNoteModel note; 35 | 36 | const CloudNoteCreated(this.note); 37 | 38 | @override 39 | List get props => [note]; 40 | } 41 | 42 | class CloudNoteUpdated extends CloudNoteState { 43 | final CloudNoteModel note; 44 | 45 | const CloudNoteUpdated(this.note); 46 | 47 | @override 48 | List get props => [note]; 49 | } 50 | 51 | class CloudNoteDeleted extends CloudNoteState {} 52 | 53 | class CloudError extends CloudNoteState { 54 | final String message; 55 | 56 | const CloudError(this.message); 57 | 58 | @override 59 | List get props => [message]; 60 | } 61 | 62 | class CloudNoteMoved extends CloudNoteState { 63 | final CloudNoteModel note; 64 | 65 | const CloudNoteMoved(this.note); 66 | 67 | @override 68 | List get props => [note]; 69 | } 70 | 71 | class CloudNoteSuccess extends CloudNoteState {} 72 | -------------------------------------------------------------------------------- /lib/state/cubits/note_style_cubit/note_style_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | import 'package:meta/meta.dart'; 4 | import 'package:shared_preferences/shared_preferences.dart'; 5 | 6 | part 'note_style_state.dart'; 7 | 8 | class NoteStyleCubit extends Cubit { 9 | String key = 'changeViewStyle'; 10 | SharedPreferences? _pref; 11 | 12 | NoteStyleCubit() : super(NoteStyleState.initial()) { 13 | _loadFromPref(); 14 | } 15 | 16 | // initialize the shared Preference Instance 17 | Future _initPrefs() async { 18 | _pref ??= await SharedPreferences.getInstance(); 19 | } 20 | 21 | // Load the data from it and check the current value 22 | Future _loadFromPref() async { 23 | await _initPrefs(); 24 | final bool viewStyle = _pref!.getBool(key) ?? false; 25 | emit(state.copyWith(viewStyle: viewStyle)); 26 | } 27 | 28 | // save the new value to the key 29 | Future _saveToPref(bool value) async { 30 | await _initPrefs(); 31 | await _pref!.setBool(key, value); 32 | } 33 | 34 | // toggle between light or dark mode 35 | void toggleNoteStyle() { 36 | final newViewStyle = !state.viewStyle; 37 | _saveToPref(newViewStyle); 38 | emit(state.copyWith(viewStyle: newViewStyle)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/state/cubits/note_style_cubit/note_style_state.dart: -------------------------------------------------------------------------------- 1 | part of 'note_style_cubit.dart'; 2 | 3 | class NoteStyleState extends Equatable { 4 | final bool viewStyle; 5 | 6 | const NoteStyleState({ 7 | this.viewStyle = false, 8 | }); 9 | 10 | factory NoteStyleState.initial() { 11 | return const NoteStyleState(); 12 | } 13 | 14 | @override 15 | List get props => [viewStyle]; 16 | 17 | NoteStyleState copyWith({ 18 | bool? viewStyle, 19 | }) { 20 | return NoteStyleState( 21 | viewStyle: viewStyle ?? this.viewStyle, 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/state/cubits/play_button_cubit/play_button_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | import 'package:meta/meta.dart'; 4 | import 'package:shared_preferences/shared_preferences.dart'; 5 | 6 | part 'play_button_state.dart'; 7 | 8 | class PlayButtonCubit extends Cubit { 9 | String key = 'playButton'; 10 | SharedPreferences? _pref; 11 | 12 | 13 | PlayButtonCubit() : super(PlayButtonState.initial()) { 14 | _loadFromPref(); 15 | } 16 | 17 | // initialize the shared Preference Instance 18 | Future _initPrefs() async { 19 | _pref ??= await SharedPreferences.getInstance(); 20 | } 21 | 22 | // Load the data from it and check the current value 23 | Future _loadFromPref() async { 24 | await _initPrefs(); 25 | final bool canPlay = _pref!.getBool(key) ?? false; 26 | emit(state.copyWith(canPlay: canPlay)); 27 | } 28 | 29 | // save the new value to the key 30 | Future _saveToPref(bool value) async { 31 | await _initPrefs(); 32 | await _pref!.setBool(key, value); 33 | } 34 | 35 | // toggle between light or dark mode 36 | void togglePlayButton() { 37 | final newCanPlay = !state.canPlay; 38 | _saveToPref(newCanPlay); 39 | emit(state.copyWith(canPlay: newCanPlay)); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /lib/state/cubits/play_button_cubit/play_button_state.dart: -------------------------------------------------------------------------------- 1 | part of 'play_button_cubit.dart'; 2 | 3 | @immutable 4 | class PlayButtonState extends Equatable { 5 | final bool canPlay; 6 | 7 | const PlayButtonState({ 8 | this.canPlay = false, 9 | }); 10 | 11 | factory PlayButtonState.initial() { 12 | return const PlayButtonState(); 13 | } 14 | 15 | @override 16 | List get props => [canPlay]; 17 | 18 | PlayButtonState copyWith({ 19 | bool? canPlay, 20 | }) { 21 | return PlayButtonState( 22 | canPlay: canPlay ?? this.canPlay, 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/state/cubits/theme_cubit/theme_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:equatable/equatable.dart'; 3 | import 'package:shared_preferences/shared_preferences.dart'; 4 | 5 | part 'theme_state.dart'; 6 | 7 | class ThemeCubit extends Cubit { 8 | String key = 'appTheme'; 9 | SharedPreferences? _pref; 10 | 11 | ThemeCubit(this._pref) 12 | : super(ThemeState(isDarkTheme: _pref!.getBool('appTheme') ?? false)); 13 | 14 | // initialize the shared Preference Instance 15 | Future _initPrefs() async { 16 | _pref ??= await SharedPreferences.getInstance(); 17 | } 18 | 19 | // Load the data from it and check the current value 20 | /* 21 | This function is no longer needed but is kept for future reference 22 | */ 23 | Future _loadFromPref() async { 24 | await _initPrefs(); 25 | final bool isDarkTheme = _pref?.getBool(key) ?? false; 26 | emit(state.copyWith(isDarkTheme: isDarkTheme)); 27 | } 28 | 29 | // save the new value to the key 30 | /* 31 | This function is no longer needed but is kept for future reference 32 | */ 33 | Future _saveToPref(bool value) async { 34 | await _initPrefs(); 35 | await _pref!.setBool(key, value); 36 | } 37 | 38 | // toggle between light or dark mode 39 | Future toggleTheme() async { 40 | final newThemeValue = !state.isDarkTheme; 41 | _pref!.setBool(key, newThemeValue); 42 | emit(state.copyWith(isDarkTheme: newThemeValue)); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/state/cubits/theme_cubit/theme_state.dart: -------------------------------------------------------------------------------- 1 | part of 'theme_cubit.dart'; 2 | 3 | class ThemeState extends Equatable { 4 | final bool isDarkTheme; 5 | 6 | const ThemeState({ 7 | this.isDarkTheme = false, 8 | }); 9 | 10 | factory ThemeState.initial() { 11 | return const ThemeState(); 12 | } 13 | 14 | @override 15 | List get props => [isDarkTheme]; 16 | 17 | ThemeState copyWith({ 18 | bool? isDarkTheme, 19 | }) { 20 | return ThemeState( 21 | isDarkTheme: isDarkTheme ?? this.isDarkTheme, 22 | ); 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /lib/utils/colors/m_colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:note_app/utils/tools/hex_to_color.dart'; 3 | 4 | class AppColors { 5 | static Color darkColor = hexToColor('#333333'); 6 | static Color? cardColor = Colors.grey[850]; 7 | static Color? cardGray = Colors.grey[900]; 8 | 9 | //colors for the app 10 | static Color primaryColor = hexToColor('#E0D3AFFF'); 11 | static Color secondaryColor = hexToColor('#171B1E'); 12 | static Color subTextColor = hexToColor('#B5A006'); 13 | static Color greyTextColor = hexToColor('#798084'); 14 | static Color scaffoldLightColor = hexToColor('#FAFAFA'); 15 | 16 | static Color primaryGrey = hexToColor('#F9F9F9'); 17 | static Color secondaryGrey = hexToColor('#F5F5F5'); 18 | static Color lightGrey = hexToColor('#F1F1F1'); 19 | static Color primaryGold = hexToColor('#C9B902'); 20 | static Color transparent = Colors.transparent; 21 | static Color btnBorderColor = hexToColor('#CCE8D4'); 22 | static Color textFieldBorderColor = hexToColor('#938989'); 23 | 24 | static Color borderColor = hexToColor('#EBECED'); 25 | static Color green = hexToColor('#009A51'); 26 | static Color darkGreen = hexToColor('#006D3A'); 27 | static Color red = hexToColor('#F83446'); 28 | static Color darkRed = hexToColor('#B02532'); 29 | static Color redWarning = hexToColor('#FEEBED'); 30 | 31 | static Color dialogColor = Colors.white; 32 | static Color backColorOne = Colors.white; 33 | static Color defaultTextColor = Colors.black; 34 | 35 | 36 | static Color boxColor = const Color.fromRGBO(235, 237, 242, 1); 37 | 38 | static Color grey = hexToColor('#AFACAB'); 39 | 40 | //other colors 41 | static Color defaultWhite = const Color.fromRGBO(255, 255, 255, 1); 42 | static Color defaultBlack = const Color.fromRGBO(0, 0, 0, 1); 43 | // 44 | 45 | static Color btnSecondaryTextCode = const Color.fromRGBO(255, 255, 255, 1); 46 | static Color btnPrimaryTextCode = const Color.fromRGBO(0, 0, 0, 1); 47 | 48 | } -------------------------------------------------------------------------------- /lib/utils/const_values.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:logger/logger.dart'; 3 | import 'package:note_app/utils/constant/api_constant.dart'; 4 | import 'package:note_app/utils/tools/hex_to_color.dart'; 5 | 6 | var logger = Logger(); 7 | 8 | // String apiUrl = dotenv.env['API_URL'].toString(); 9 | // String payStackPubKey = dotenv.env['PAYSTACK_PUB_KEY'].toString(); 10 | // String payStackSecKey = dotenv.env['PAYSTACK_SEC_KEY'].toString(); 11 | 12 | String apiUrl = ApiConstants.apiUrl; 13 | 14 | const String androidID = 'com.viewus.v_notes'; 15 | const String dialogTitle = 'Update V Notes'; 16 | const String dialogText = 'There is a new update for V Notes, ' 17 | 'would you like to update to check up ' 18 | 'what we have improved about the app'; 19 | 20 | // Hive details 21 | const String noteBox = 'notebox'; 22 | const String cloudNoteBox = 'cloudNoteBox'; 23 | const String userBox = 'userBox'; 24 | const String deletedNotes = 'deletedNotes'; 25 | const String appHiveKey = 'state'; 26 | const String deleteNote = 'deleteNote'; 27 | 28 | 29 | const String userKey = 'userKey'; 30 | const String tokenKey = 'tokenKey'; 31 | 32 | Color transparent = Colors.transparent; 33 | Color grey = hexToColor('#AFACAB'); 34 | Color mGrey = grey.withOpacity(0.4); 35 | -------------------------------------------------------------------------------- /lib/utils/constant/api_constant.dart: -------------------------------------------------------------------------------- 1 | class ApiConstants { 2 | static String apiUrl = ''; 3 | } -------------------------------------------------------------------------------- /lib/utils/constant/asset_dir.dart: -------------------------------------------------------------------------------- 1 | 2 | class AssetDir { 3 | 4 | 5 | // Lufga Font Family 6 | static String PublicSansBlack = 'PublicSansBlack'; 7 | static String PublicSansBold = 'PublicSansBold'; 8 | static String PublicSansExtraBold = 'PublicSansExtraBold'; 9 | static String PublicSansExtraLight = 'PublicSansExtraLight'; 10 | static String PublicSansLight = 'PublicSansLight'; 11 | static String PublicSansMedium = 'PublicSansMedium'; 12 | static String PublicSansRegular = 'PublicSansRegular'; 13 | static String PublicSansSemiBold = 'PublicSansSemiBold'; 14 | static String PublicSansThin = 'PublicSansThin'; 15 | 16 | // SVGs 17 | // static String happyBird = '$svgAssets/undraw_happy_music_g6wc.svg'; 18 | } -------------------------------------------------------------------------------- /lib/utils/greetings.dart: -------------------------------------------------------------------------------- 1 | String greetingMessage() { 2 | var timeNow = DateTime.now().hour; 3 | 4 | if (timeNow < 12) { 5 | return 'Good Morning'; 6 | } else if ((timeNow >= 12) && (timeNow <= 16)) { 7 | return 'Good Afternoon'; 8 | } else if ((timeNow > 16) && (timeNow < 20)) { 9 | return 'Good Evening'; 10 | } else { 11 | return 'It\'s Night'; 12 | } 13 | } -------------------------------------------------------------------------------- /lib/utils/text_style/m_text_style.dart: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/lib/utils/text_style/m_text_style.dart -------------------------------------------------------------------------------- /lib/utils/themes/custom_theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:note_app/utils/colors/m_colors.dart'; 3 | import 'package:note_app/utils/const_values.dart'; 4 | 5 | ThemeData buildLightTheme() => ThemeData.light().copyWith( 6 | cardColor: Colors.white, 7 | scaffoldBackgroundColor: AppColors.primaryColor, 8 | iconTheme: IconThemeData( 9 | color: AppColors.defaultBlack, 10 | ), 11 | cardTheme: const CardTheme( 12 | color: Colors.white, 13 | ), 14 | dialogTheme: DialogTheme( 15 | backgroundColor: AppColors.primaryColor, 16 | titleTextStyle: const TextStyle( 17 | color: Colors.black, 18 | fontWeight: FontWeight.bold, 19 | ), 20 | contentTextStyle: const TextStyle( 21 | color: Colors.black, 22 | ), 23 | ), 24 | dividerColor: AppColors.defaultBlack, 25 | appBarTheme: AppBarTheme( 26 | centerTitle: true, 27 | elevation: 0.0, 28 | shadowColor: Colors.transparent, 29 | color: Colors.transparent, 30 | titleTextStyle: const TextStyle( 31 | fontSize: 20, 32 | fontWeight: FontWeight.bold, 33 | color: Colors.black, 34 | ), 35 | iconTheme: IconThemeData( 36 | color: Colors.grey[900], 37 | ), 38 | ), 39 | textButtonTheme: TextButtonThemeData( 40 | style: ButtonStyle( 41 | textStyle: MaterialStateProperty.resolveWith( 42 | (states) => TextStyle( 43 | color: AppColors.defaultBlack, 44 | ), 45 | ), 46 | ), 47 | ), 48 | dialogBackgroundColor: AppColors.primaryColor, 49 | textTheme: Typography.blackCupertino, 50 | ); 51 | 52 | ThemeData buildDarkTheme() => ThemeData.dark().copyWith( 53 | cardColor: Colors.grey[850], 54 | scaffoldBackgroundColor: AppColors.darkColor, 55 | dividerColor: AppColors.defaultWhite, 56 | iconTheme: IconThemeData( 57 | color: AppColors.defaultWhite, 58 | ), 59 | cardTheme: CardTheme( 60 | color: AppColors.cardColor, 61 | ), 62 | dialogTheme: DialogTheme( 63 | backgroundColor: Colors.grey[900], 64 | titleTextStyle: const TextStyle( 65 | color: Colors.white, 66 | fontWeight: FontWeight.bold, 67 | ), 68 | contentTextStyle: const TextStyle( 69 | color: Colors.white, 70 | ), 71 | ), 72 | switchTheme: SwitchThemeData( 73 | thumbColor: MaterialStateProperty.resolveWith( 74 | (states) => Colors.grey[400], 75 | ), 76 | trackColor: MaterialStateProperty.resolveWith( 77 | (states) => Colors.white, 78 | ), 79 | ), 80 | appBarTheme: AppBarTheme( 81 | centerTitle: true, 82 | elevation: 0.0, 83 | titleTextStyle: const TextStyle( 84 | fontSize: 20, 85 | fontWeight: FontWeight.bold, 86 | color: Colors.white, 87 | ), 88 | color: Colors.grey[900], 89 | iconTheme: IconThemeData( 90 | color: Colors.grey[400], 91 | ), 92 | ), 93 | textButtonTheme: TextButtonThemeData( 94 | style: ButtonStyle( 95 | textStyle: MaterialStateProperty.resolveWith( 96 | (states) => TextStyle( 97 | color: AppColors.defaultBlack, 98 | ), 99 | ), 100 | ), 101 | ), 102 | textTheme: Typography.whiteCupertino, 103 | ); 104 | -------------------------------------------------------------------------------- /lib/utils/tools/hex_to_color.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | Color hexToColor(String code) { 4 | return Color(int.parse(code.substring(1, 7), radix: 16) + 0xFF000000); 5 | } -------------------------------------------------------------------------------- /lib/utils/tools/message_dialog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flashy_flushbar/flashy_flushbar.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | void showError(String errorMsg) { 5 | FlashyFlushbar( 6 | leadingWidget: const Icon( 7 | Icons.error_outline, 8 | color: Colors.orange, 9 | size: 24, 10 | ), 11 | message: errorMsg, 12 | duration: const Duration(seconds: 5), 13 | trailingWidget: IconButton( 14 | icon: const Icon( 15 | Icons.close, 16 | color: Colors.black, 17 | size: 24, 18 | ), 19 | onPressed: () { 20 | FlashyFlushbar.cancel(); 21 | }, 22 | ), 23 | isDismissible: true, 24 | ).show(); 25 | } 26 | 27 | void showSuccess(String msg) { 28 | FlashyFlushbar( 29 | leadingWidget: const Icon( 30 | Icons.check_circle_outline_outlined, 31 | color: Colors.green, 32 | size: 24, 33 | ), 34 | message: msg, 35 | duration: const Duration(seconds: 5), 36 | trailingWidget: IconButton( 37 | icon: const Icon( 38 | Icons.close, 39 | color: Colors.black, 40 | size: 24, 41 | ), 42 | onPressed: () { 43 | FlashyFlushbar.cancel(); 44 | }, 45 | ), 46 | isDismissible: true, 47 | ).show(); 48 | } -------------------------------------------------------------------------------- /lib/utils/tools/money_formatter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | 3 | class ThousandsSeparatorInputFormatter extends TextInputFormatter { 4 | static const separator = ','; // Change this to '.' for other locales 5 | 6 | @override 7 | TextEditingValue formatEditUpdate( 8 | TextEditingValue oldValue, TextEditingValue newValue) { 9 | // Short-circuit if the new value is empty 10 | if (newValue.text.isEmpty) { 11 | return newValue.copyWith(text: ''); 12 | } 13 | 14 | // Handle "deletion" of separator character 15 | String oldValueText = oldValue.text.replaceAll(separator, ''); 16 | String newValueText = newValue.text.replaceAll(separator, ''); 17 | 18 | if (oldValue.text.endsWith(separator) && 19 | oldValue.text.length == newValue.text.length + 1) { 20 | newValueText = newValueText.substring(0, newValueText.length - 1); 21 | } 22 | 23 | // Only process if the old value and new value are different 24 | if (oldValueText != newValueText) { 25 | int selectionIndex = 26 | newValue.text.length - newValue.selection.extentOffset; 27 | final chars = newValueText.split(''); 28 | 29 | String newString = ''; 30 | for (int i = chars.length - 1; i >= 0; i--) { 31 | if ((chars.length - 1 - i) % 3 == 0 && i != chars.length - 1) { 32 | newString = separator + newString; 33 | } 34 | newString = chars[i] + newString; 35 | } 36 | 37 | return TextEditingValue( 38 | text: newString.toString(), 39 | selection: TextSelection.collapsed( 40 | offset: newString.length - selectionIndex, 41 | ), 42 | ); 43 | } 44 | 45 | // If the new value and old value are the same, just return as-is 46 | return newValue; 47 | } 48 | } -------------------------------------------------------------------------------- /lib/utils/tools/sized_box_ex.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 | 4 | extension SizedBoxExtension on int { 5 | Widget get toHeight { 6 | return SizedBox( 7 | height: toDouble().h, 8 | ); 9 | } 10 | 11 | Widget get toWidth { 12 | return SizedBox( 13 | width: toDouble().w, 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/utils/tools/slide_transition.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class MySlide extends MaterialPageRoute { 4 | MySlide({ 5 | WidgetBuilder? builder, 6 | RouteSettings? settings, 7 | }) : super(builder: builder!, settings: settings); 8 | 9 | @override 10 | Widget buildTransitions(BuildContext context, Animation animation, 11 | Animation secondaryAnimation, Widget child) { 12 | Animation custom = 13 | Tween(begin: Offset(1.0, 0.0), end: Offset(0.0, 0.0)) 14 | .animate(animation); 15 | return SlideTransition( 16 | position: custom, 17 | child: child, 18 | ); 19 | // return super.buildTransitions(context, animation, secondaryAnimation, child); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: note_app 2 | description: A new Flutter project. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: "none" # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 2.0.17+24 19 | 20 | environment: 21 | sdk: ^3.5.4 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | cupertino_icons: ^1.0.3 27 | flutter_staggered_grid_view: ^0.7.0 28 | flutter_tts: ^4.0.2 29 | hive: ^2.0.4 30 | hive_flutter: ^1.1.0 31 | path_provider: ^2.0.2 32 | provider: ^6.0.2 33 | shared_preferences: ^2.2.3 34 | fluttertoast: ^8.0.7 35 | logger: ^2.0.2 36 | intl: ^0.19.0 37 | flutter_screenutil: ^5.5.3+2 38 | package_info_plus: ^8.0.0 39 | flashy_flushbar: 40 | git: 41 | url: https://github.com/quiet-programmer/flashy_flushbar.git 42 | ref: 0cda07e 43 | font_awesome_flutter: ^10.7.0 44 | otp_text_field_v2: ^1.0.3 45 | yaml: ^3.1.2 46 | flutter_bloc: ^9.1.0 47 | equatable: ^2.0.5 48 | get_it: ^8.0.3 49 | flutter_quill: ^11.2.0 50 | 51 | dependency_overrides: 52 | flutter_inappwebview: ^6.1.3 53 | flutter_keyboard_visibility: ^6.0.0 54 | 55 | dev_dependencies: 56 | flutter_test: 57 | sdk: flutter 58 | build_runner: 59 | change_app_package_name: ^1.1.0 60 | hive_generator: ^2.0.0 61 | flutter_lints: ^5.0.0 62 | 63 | # For information on the generic Dart part of this file, see the 64 | # following page: https://dart.dev/tools/pub/pubspec 65 | # The following section is specific to Flutter. 66 | flutter: 67 | # The following line ensures that the Material Icons font is 68 | # included with your application, so that you can use the icons in 69 | # the material Icons class. 70 | assets: 71 | - shorebird.yaml 72 | - api_credentials.yaml 73 | uses-material-design: true 74 | # To add assets to your application, add an assets section, like this: 75 | # assets: 76 | # - .env 77 | # - images/a_dot_ham.jpeg 78 | # An image asset can refer to one or more resolution-specific "variants", see 79 | # https://flutter.dev/assets-and-images/#resolution-aware. 80 | # For details regarding adding assets from package dependencies, see 81 | # https://flutter.dev/assets-and-images/#from-packages 82 | # To add custom fonts to your application, add a fonts section here, 83 | # in this "flutter" section. Each entry in this list should have a 84 | # "family" key with the font family name, and a "fonts" key with a 85 | # list giving the asset and other descriptors for the font. For 86 | # example: 87 | # fonts: 88 | # - family: Schyler 89 | # fonts: 90 | # - asset: fonts/Schyler-Regular.ttf 91 | # - asset: fonts/Schyler-Italic.ttf 92 | # style: italic 93 | # - family: Trajan Pro 94 | # fonts: 95 | # - asset: fonts/TrajanPro.ttf 96 | # - asset: fonts/TrajanPro_Bold.ttf 97 | # weight: 700 98 | # 99 | # For details regarding fonts from package dependencies, 100 | # see https://flutter.dev/custom-fonts/#from-packages 101 | -------------------------------------------------------------------------------- /shorebird.yaml: -------------------------------------------------------------------------------- 1 | # This file is used to configure the Shorebird updater used by your app. 2 | # Learn more at https://docs.shorebird.dev 3 | # This file should be checked into version control. 4 | 5 | # This is the unique identifier assigned to your app. 6 | # Your app_id is not a secret and is just used to identify your app 7 | # when requesting patches from Shorebird's servers. 8 | app_id: 02f3cd98-f488-4ec4-bbe3-f722d9131537 9 | 10 | # auto_update controls if Shorebird should automatically update in the background on launch. 11 | # If auto_update: false, you will need to use package:shorebird_code_push to trigger updates. 12 | # https://pub.dev/packages/shorebird_code_push 13 | # Uncomment the following line to disable automatic updates. 14 | # auto_update: false 15 | -------------------------------------------------------------------------------- /ss/145535058_221013679682765_5476232847126577393_n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/ss/145535058_221013679682765_5476232847126577393_n.png -------------------------------------------------------------------------------- /ss/145944556_3545455032189582_7968282205009447548_n.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sudo-which-qp/hive_note_app/7cdad3dbd23c38ac8532109b6014a5e4e9d7fd55/ss/145944556_3545455032189582_7968282205009447548_n.png -------------------------------------------------------------------------------- /test/unit_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | void main() { 4 | test('String should not be empty', () async {}); 5 | } 6 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | import 'package:note_app/app/src/app.dart'; 11 | 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(App()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | --------------------------------------------------------------------------------