├── .cph └── .A_Minimal_Coprime.cpp_54e86288cedbc49628b71caf69c90717.prob ├── .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 │ │ │ │ └── example │ │ │ │ └── revo │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-hdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable-mdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable-night-hdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable-night-mdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable-night-v21 │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── drawable-night-xhdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable-night-xxhdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable-night-xxxhdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable-night │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── drawable-v21 │ │ │ ├── background.png │ │ │ └── launch_background.xml │ │ │ ├── drawable-xhdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable-xxhdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable-xxxhdpi │ │ │ ├── android12splash.png │ │ │ └── splash.png │ │ │ ├── drawable │ │ │ ├── background.png │ │ │ └── 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-v31 │ │ │ └── styles.xml │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ ├── values-v31 │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── branding.png ├── dialpad │ ├── 0.mp3 │ ├── 1.mp3 │ ├── 2.mp3 │ ├── 3.mp3 │ ├── 4.mp3 │ ├── 5.mp3 │ ├── 6.mp3 │ ├── 7.mp3 │ ├── 8.mp3 │ ├── 9.mp3 │ ├── hash.mp3 │ └── star.mp3 ├── icon-bg.png ├── icon.png ├── splash.png └── static-bg.jpg ├── devtools_options.yaml ├── images ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png ├── 6.png ├── full-1.png └── full-2.png ├── lib ├── constants │ ├── pref.dart │ └── routes.dart ├── extentions │ ├── datetime.dart │ ├── theme.dart │ └── title.dart ├── main.dart ├── model │ ├── call_log.dart │ ├── call_type.dart │ ├── contact.dart │ └── sim_card.dart ├── services │ ├── activity_service.dart │ ├── backgroundservice.dart2 │ ├── cubit │ │ ├── call_log_service.dart │ │ ├── contact_service.dart │ │ └── mobile_service.dart │ └── prefservice.dart ├── ui │ ├── popups │ │ ├── number_choose_popup.dart │ │ ├── qr_popup.dart │ │ ├── sim_choose_popup.dart │ │ └── welcome_changelog.dart │ ├── theme │ │ └── handler.dart │ └── views │ │ ├── call_screen.dart │ │ ├── common │ │ ├── constants.dart │ │ ├── contact_tile.dart │ │ └── matched_view.dart │ │ ├── contactinfo_view.dart │ │ ├── dialpad_view.dart │ │ ├── dialpad_view │ │ ├── action_btn.dart │ │ └── dial_btn.dart │ │ ├── history_view.dart │ │ ├── home_view.dart │ │ ├── home_view │ │ ├── appbar_view.dart │ │ ├── contacts_view.dart │ │ ├── fav_view.dart │ │ ├── navigation_view.dart │ │ └── recents_view.dart │ │ ├── qr_scanner_view.dart │ │ ├── search_view.dart │ │ ├── settings_view.dart │ │ └── settings_view │ │ ├── about.dart │ │ ├── call.dart │ │ ├── sound.dart │ │ └── user_interface.dart └── utils │ ├── center_text.dart │ ├── circle_profile.dart │ ├── menu_tile.dart │ ├── rounded_icon_btn.dart │ ├── share.dart │ ├── switch_tile.dart │ └── utils.dart ├── privacy-policy.md ├── pubspec.lock └── pubspec.yaml /.cph/.A_Minimal_Coprime.cpp_54e86288cedbc49628b71caf69c90717.prob: -------------------------------------------------------------------------------- 1 | {"name":"A. Minimal Coprime","group":"Codeforces - Codeforces Round 1000 (Div. 2)","url":"https://codeforces.com/contest/2063/problem/A","interactive":false,"memoryLimit":256,"timeLimit":1000,"tests":[{"id":1737555498318,"input":"6\n1 2\n1 10\n49 49\n69 420\n1 1\n9982 44353\n","output":"1\n9\n0\n351\n1\n34371\n"}],"testType":"single","input":{"type":"stdin"},"output":{"type":"stdout"},"languages":{"java":{"mainClass":"Main","taskClass":"AMinimalCoprime"}},"batch":{"id":"2fbebdc0-7915-41eb-82e5-c6d7a6cd7e4b","size":1},"srcPath":"d:\\GitHub Projects\\Rivo\\A_Minimal_Coprime.cpp"} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .build/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | .swiftpm/ 13 | migrate_working_dir/ 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | # The .vscode folder contains launch configuration and tasks you configure in 22 | # VS Code which you may wish to be included in version control, so this line 23 | # is commented out by default. 24 | #.vscode/ 25 | 26 | # Flutter/Dart/Pub related 27 | **/doc/api/ 28 | **/ios/Flutter/.last_build_id 29 | .dart_tool/ 30 | .flutter-plugins 31 | .flutter-plugins-dependencies 32 | .pub-cache/ 33 | .pub/ 34 | /build/ 35 | 36 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | 42 | # Android Studio will place build artifacts here 43 | /android/app/debug 44 | /android/app/profile 45 | /android/app/release 46 | -------------------------------------------------------------------------------- /.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: "17025dd88227cd9532c33fa78f5250d548d87e9a" 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: 17025dd88227cd9532c33fa78f5250d548d87e9a 17 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 18 | - platform: android 19 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 20 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 21 | - platform: ios 22 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 23 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 24 | - platform: linux 25 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 26 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 27 | - platform: macos 28 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 29 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 30 | - platform: web 31 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 32 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 33 | - platform: windows 34 | create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 35 | base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a 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 | # Rivo 2 | 3 | **Rivo** is a modern, feature-rich dialer app built using Flutter. Designed for seamless communication, Rivo provides a sleek and user-friendly interface for managing calls and contacts. 4 | 5 | ## Features 6 | - **Fast Dialing:** Quickly dial numbers with an intuitive interface. 7 | - **Contact Management:** Easily manage and organize your contacts. 8 | - **Call History:** Access and manage your call logs effortlessly. 9 | - **Customizable UI:** Enjoy a visually appealing and customizable design. 10 | 11 | ## Tech Stack 12 | - **Framework:** Flutter 13 | - **Language:** Dart 14 | 15 | ## Preview 16 | 17 | ### Screenshots 18 |
19 | Show Images 20 | Preview 1 21 | Preview 2 22 | Preview 3 23 | Preview 4 24 | Preview 5 25 | Preview 5 26 |
27 | 28 | ## Getting Started 29 | 1. Clone the repository: 30 | ```bash 31 | git clone https://github.com/user-grinch/Rivo.git 32 | ``` 33 | 2. Navigate to the project directory: 34 | ```bash 35 | cd Rivo 36 | ``` 37 | 3. Install dependencies: 38 | ```bash 39 | flutter pub get 40 | ``` 41 | 4. Run the app in debug mode: 42 | ```bash 43 | flutter run 44 | ``` 45 | 5. Build the app for release: 46 | ```bash 47 | flutter build apk 48 | ``` 49 | 50 | ## Contribution Guidelines 51 | We welcome contributions to improve Rivo! Follow these steps to contribute: 52 | 1. Fork the repository. 53 | 2. Create a new branch for your feature or bug fix: 54 | ```bash 55 | git checkout -b feature-name 56 | ``` 57 | 3. Make your changes and commit them with a descriptive message: 58 | ```bash 59 | git commit -m "Add a brief description of your changes" 60 | ``` 61 | 4. Push your branch to your forked repository: 62 | ```bash 63 | git push origin feature-name 64 | ``` 65 | 5. Create a pull request on the main repository, describing your changes. 66 | 67 | ## License 68 | This project is licensed under the GNU General Public License v3.0. See the [LICENSE](LICENSE) file for details. 69 | 70 | --- 71 | 72 | Made with ❤️ using Flutter. 73 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/guides/language/analysis-options 29 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/to/reference-keystore 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /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 | android { 9 | namespace = "com.grinch.rivo" 10 | compileSdk = flutter.compileSdkVersion 11 | ndkVersion = flutter.ndkVersion 12 | 13 | compileOptions { 14 | sourceCompatibility = JavaVersion.VERSION_17 15 | targetCompatibility = JavaVersion.VERSION_17 16 | } 17 | 18 | kotlinOptions { 19 | jvmTarget = JavaVersion.VERSION_17 20 | } 21 | 22 | defaultConfig { 23 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 24 | applicationId = "com.grinch.rivo" 25 | // You can update the following values to match your application needs. 26 | // For more information, see: https://flutter.dev/to/review-gradle-config. 27 | minSdk = 24 28 | targetSdk = flutter.targetSdkVersion 29 | versionCode = flutter.versionCode 30 | versionName = flutter.versionName 31 | } 32 | 33 | buildTypes { 34 | release { 35 | // TODO: Add your own signing config for the release build. 36 | // Signing with the debug keys for now, so `flutter run --release` works. 37 | signingConfig = signingConfigs.debug 38 | } 39 | } 40 | } 41 | 42 | flutter { 43 | source = "../.." 44 | } 45 | -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -dontwarn java.beans.ConstructorProperties 2 | -dontwarn java.beans.Transient 3 | -dontwarn org.conscrypt.Conscrypt 4 | -dontwarn org.conscrypt.OpenSSLProvider 5 | -dontwarn org.w3c.dom.bootstrap.DOMImplementationRegistry -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 23 | 31 | 32 | 33 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/revo/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.grinch.rivo 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() 6 | 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/android/app/src/main/res/drawable-hdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/android/app/src/main/res/drawable-hdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/android/app/src/main/res/drawable-mdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/android/app/src/main/res/drawable-mdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-hdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/android/app/src/main/res/drawable-night-hdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/android/app/src/main/res/drawable-night-hdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-mdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/android/app/src/main/res/drawable-night-mdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/android/app/src/main/res/drawable-night-mdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-v21/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/android/app/src/main/res/drawable-night-v21/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/android/app/src/main/res/drawable-night-xhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/android/app/src/main/res/drawable-night-xhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/android/app/src/main/res/drawable-night-xxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/android/app/src/main/res/drawable-night-xxxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/android/app/src/main/res/drawable-night/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-night/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/android/app/src/main/res/drawable-v21/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/android/app/src/main/res/drawable-xhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/android/app/src/main/res/drawable-xhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/android/app/src/main/res/drawable-xxhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/android/app/src/main/res/drawable-xxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/android12splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/android/app/src/main/res/drawable-xxxhdpi/android12splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/android/app/src/main/res/drawable-xxxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/android/app/src/main/res/drawable/background.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night-v31/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-v31/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 18 | 21 | 22 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 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 | } 19 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip 6 | -------------------------------------------------------------------------------- /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.1.0" apply false 22 | id "org.jetbrains.kotlin.android" version "1.8.22" apply false 23 | } 24 | 25 | include ":app" 26 | -------------------------------------------------------------------------------- /assets/branding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/assets/branding.png -------------------------------------------------------------------------------- /assets/dialpad/0.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/assets/dialpad/0.mp3 -------------------------------------------------------------------------------- /assets/dialpad/1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/assets/dialpad/1.mp3 -------------------------------------------------------------------------------- /assets/dialpad/2.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/assets/dialpad/2.mp3 -------------------------------------------------------------------------------- /assets/dialpad/3.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/assets/dialpad/3.mp3 -------------------------------------------------------------------------------- /assets/dialpad/4.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/assets/dialpad/4.mp3 -------------------------------------------------------------------------------- /assets/dialpad/5.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/assets/dialpad/5.mp3 -------------------------------------------------------------------------------- /assets/dialpad/6.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/assets/dialpad/6.mp3 -------------------------------------------------------------------------------- /assets/dialpad/7.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/assets/dialpad/7.mp3 -------------------------------------------------------------------------------- /assets/dialpad/8.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/assets/dialpad/8.mp3 -------------------------------------------------------------------------------- /assets/dialpad/9.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/assets/dialpad/9.mp3 -------------------------------------------------------------------------------- /assets/dialpad/hash.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/assets/dialpad/hash.mp3 -------------------------------------------------------------------------------- /assets/dialpad/star.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/assets/dialpad/star.mp3 -------------------------------------------------------------------------------- /assets/icon-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/assets/icon-bg.png -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/assets/icon.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/assets/splash.png -------------------------------------------------------------------------------- /assets/static-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/assets/static-bg.jpg -------------------------------------------------------------------------------- /devtools_options.yaml: -------------------------------------------------------------------------------- 1 | description: This file stores settings for Dart & Flutter DevTools. 2 | documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states 3 | extensions: 4 | -------------------------------------------------------------------------------- /images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/images/1.png -------------------------------------------------------------------------------- /images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/images/2.png -------------------------------------------------------------------------------- /images/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/images/3.png -------------------------------------------------------------------------------- /images/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/images/4.png -------------------------------------------------------------------------------- /images/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/images/5.png -------------------------------------------------------------------------------- /images/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/images/6.png -------------------------------------------------------------------------------- /images/full-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/images/full-1.png -------------------------------------------------------------------------------- /images/full-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/user-grinch/RivoPhoneApp/d9a8e0354b595afda28b408aee0cf73c186d2b6a/images/full-2.png -------------------------------------------------------------------------------- /lib/constants/pref.dart: -------------------------------------------------------------------------------- 1 | const String PREF_DTMF_TONE = "PREF_DTMF_TONE"; 2 | const String PREF_DIALPAD_VIBRATION = "PREF_DIALPAD_VIBRATION"; 3 | const String PREF_DIALPAD_LETTERS = "PREF_DIALPAD_LETTERS"; 4 | 5 | const String PREF_MATERIAL_THEMING = "PREF_MATERIAL_THEMING"; 6 | const String PREF_AMOLED_DARK_MODE = "PREF_AMOLED_DARK_MODE"; 7 | const String PREF_SHOW_FIRST_LETTER = "PREF_SHOW_FIRST_LETTER"; 8 | const String PREF_SHOW_PICTURE_IN_AVARTAR = "PREF_SHOW_PICTURE_IN_AVARTAR"; 9 | const String PREF_ICON_ONLY_BOTTOMSHEET = "PREF_ICON_ONLY_BOTTOMSHEET"; 10 | const String PREF_ALWAYS_SHOW_SELECTED_IN_BOTTOMSHEET = 11 | "PREF_ALWAYS_SHOW_SELECTED_IN_BOTTOMSHEET"; 12 | -------------------------------------------------------------------------------- /lib/constants/routes.dart: -------------------------------------------------------------------------------- 1 | const String settingsRoute = '/settings'; 2 | const String searchRoute = '/search'; 3 | const String homeRoute = '/'; 4 | const String dialpadRoute = '/dialpad'; 5 | const String contactInfoRoute = '/contact-info'; 6 | const String qrScanRoute = '/qr-scan'; 7 | const String callHistoryRoute = '/call-history'; 8 | const String callScreenRoute = '/call-screen'; 9 | -------------------------------------------------------------------------------- /lib/extentions/datetime.dart: -------------------------------------------------------------------------------- 1 | import 'package:intl/intl.dart'; 2 | 3 | extension ContextAware on DateTime { 4 | String getContextAwareDate() { 5 | final now = DateTime.now(); 6 | final today = DateTime(now.year, now.month, now.day); 7 | final yesterday = today.subtract(Duration(days: 1)); 8 | final aWeekAgo = today.subtract(Duration(days: 7)); 9 | 10 | if (isAfter(today)) { 11 | return 'Today'; 12 | } else if (isAfter(yesterday)) { 13 | return 'Yesterday'; 14 | } else if (isAfter(aWeekAgo)) { 15 | return DateFormat.EEEE().format(this); 16 | } else { 17 | return DateFormat('d MMM, y').format(this); 18 | } 19 | } 20 | 21 | String getContextAwareDateTime() { 22 | final now = DateTime.now(); 23 | final today = DateTime(now.year, now.month, now.day); 24 | final yesterday = today.subtract(Duration(days: 1)); 25 | final aWeekAgo = today.subtract(Duration(days: 7)); 26 | 27 | if (isAfter(today)) { 28 | return '${DateFormat.jm().format(this)}, Today'; 29 | } else if (isAfter(yesterday)) { 30 | return '${DateFormat.jm().format(this)}, Yesterday'; 31 | } else if (isAfter(aWeekAgo)) { 32 | return '${DateFormat.EEEE().format(this)}, ${DateFormat.jm().format(this)}'; 33 | } else { 34 | return '${DateFormat('d MMM, y').format(this)}, ${DateFormat.jm().format(this)}'; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/extentions/theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | extension ColorSchemeExtension on BuildContext { 4 | ColorScheme get colorScheme => Theme.of(this).colorScheme; 5 | } 6 | 7 | extension ThemeExtension on BuildContext { 8 | TextTheme get textTheme => Theme.of(this).textTheme; 9 | } 10 | -------------------------------------------------------------------------------- /lib/extentions/title.dart: -------------------------------------------------------------------------------- 1 | extension StringExtensions on String { 2 | String toTitleCase() { 3 | if (isEmpty) return this; 4 | 5 | return split(' ').map((word) { 6 | if (word.isEmpty) return word; 7 | return word[0].toUpperCase() + word.substring(1).toLowerCase(); 8 | }).join(' '); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:dynamic_color/dynamic_color.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | import 'package:provider/provider.dart'; 6 | import 'package:revo/constants/routes.dart'; 7 | import 'package:revo/model/contact.dart'; 8 | import 'package:revo/services/cubit/call_log_service.dart'; 9 | import 'package:revo/services/cubit/contact_service.dart'; 10 | import 'package:revo/services/cubit/mobile_service.dart'; 11 | import 'package:revo/ui/theme/handler.dart'; 12 | import 'package:revo/ui/views/call_screen.dart'; 13 | import 'package:revo/ui/views/contactinfo_view.dart'; 14 | import 'package:revo/ui/views/dialpad_view.dart'; 15 | import 'package:revo/ui/views/history_view.dart'; 16 | import 'package:revo/ui/views/home_view.dart'; 17 | import 'package:revo/ui/views/search_view.dart'; 18 | import 'package:revo/ui/views/settings_view.dart'; 19 | 20 | void main() { 21 | WidgetsFlutterBinding.ensureInitialized(); 22 | SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); 23 | SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( 24 | statusBarColor: Colors.transparent, 25 | systemNavigationBarColor: Colors.transparent)); 26 | 27 | runApp(ChangeNotifierProvider( 28 | create: (context) => ThemeProvider(), 29 | child: DynamicColorBuilder(builder: ( 30 | ColorScheme? lightDynamic, 31 | ColorScheme? darkDynamic, 32 | ) { 33 | return MultiProvider( 34 | providers: [ 35 | BlocProvider(create: (context) => CallLogService(), lazy: false), 36 | BlocProvider(create: (context) => ContactService(), lazy: false), 37 | BlocProvider(create: (context) => MobileService(), lazy: false), 38 | ], 39 | child: Consumer( 40 | builder: (context, themeProvider, child) { 41 | return MaterialApp( 42 | debugShowCheckedModeBanner: false, 43 | theme: getTheme(lightDynamic, themeProvider, false), 44 | darkTheme: getTheme(darkDynamic, themeProvider, true), 45 | themeMode: ThemeMode.system, 46 | initialRoute: homeRoute, 47 | routes: { 48 | homeRoute: (context) => HomeView(), 49 | settingsRoute: (context) => SettingsView(), 50 | searchRoute: (context) => SearchView(), 51 | dialpadRoute: (context) => DialPadView(), 52 | callScreenRoute: (context) => CallScreenView(), 53 | contactInfoRoute: (context) => ContactInfoView( 54 | ModalRoute.of(context)!.settings.arguments as Contact), 55 | callHistoryRoute: (context) => HistoryView( 56 | numbers: ModalRoute.of(context)!.settings.arguments 57 | as List), 58 | }, 59 | ); 60 | }, 61 | ), 62 | ); 63 | }), 64 | )); 65 | } 66 | -------------------------------------------------------------------------------- /lib/model/call_log.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import 'package:call_e_log/call_log.dart' as lib; 3 | import 'package:revo/model/call_type.dart'; 4 | 5 | class CallLog { 6 | final Uint8List? profile; 7 | final String name; 8 | final String number; 9 | final String simDisplayName; 10 | final DateTime date; 11 | final String duration; 12 | final CallType type; 13 | final String accountId; 14 | 15 | CallLog( 16 | this.profile, { 17 | required this.name, 18 | required this.number, 19 | required this.simDisplayName, 20 | required this.date, 21 | required this.duration, 22 | required this.type, 23 | required this.accountId, 24 | }); 25 | 26 | factory CallLog.fromEntry({ 27 | required lib.CallLogEntry entry, 28 | Uint8List? profile, 29 | }) { 30 | return CallLog( 31 | profile, 32 | name: entry.name ?? '', 33 | number: entry.number ?? '', 34 | simDisplayName: entry.simDisplayName ?? 'Unknown', 35 | date: DateTime.fromMillisecondsSinceEpoch(entry.timestamp ?? 0), 36 | duration: entry.duration.toString(), 37 | type: _convertFromInternalType(entry.callType ?? lib.CallType.unknown), 38 | accountId: entry.phoneAccountId ?? '', 39 | ); 40 | } 41 | 42 | static CallType _convertFromInternalType(lib.CallType type) { 43 | return type == lib.CallType.incoming 44 | ? CallType.incoming 45 | : type == lib.CallType.outgoing 46 | ? CallType.outgoing 47 | : type == lib.CallType.rejected 48 | ? CallType.rejected 49 | : type == lib.CallType.blocked 50 | ? CallType.blocked 51 | : type == lib.CallType.missed 52 | ? CallType.missed 53 | : CallType.unknown; 54 | } 55 | 56 | String get displayName { 57 | if (name.isNotEmpty) { 58 | return name; 59 | } else if (number.isNotEmpty) { 60 | return number; 61 | } else { 62 | return 'Unknown'; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/model/call_type.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hugeicons/hugeicons.dart'; 3 | 4 | enum CallType { 5 | incoming, 6 | outgoing, 7 | missed, 8 | rejected, 9 | blocked, 10 | unknown, 11 | } 12 | 13 | extension CallTypeHelper on CallType { 14 | IconData getIcon() { 15 | switch (this) { 16 | case CallType.incoming: 17 | return HugeIcons.strokeRoundedCallReceived; 18 | case CallType.outgoing: 19 | return HugeIcons.strokeRoundedCallOutgoing01; 20 | case CallType.rejected: 21 | return HugeIcons.strokeRoundedCallDisabled02; 22 | case CallType.blocked: 23 | return HugeIcons.strokeRoundedCallBlocked; 24 | default: 25 | return HugeIcons.strokeRoundedCallMissed01; 26 | } 27 | } 28 | 29 | String getText() { 30 | switch (this) { 31 | case CallType.incoming: 32 | return 'Incoming'; 33 | case CallType.outgoing: 34 | return 'Outgoing'; 35 | case CallType.rejected: 36 | return 'Rejected'; 37 | case CallType.missed: 38 | return 'Missed'; 39 | case CallType.blocked: 40 | return 'Blocked'; 41 | default: 42 | return ''; 43 | } 44 | } 45 | 46 | Color getColor() { 47 | switch (this) { 48 | case CallType.incoming: 49 | return Colors.blue.withAlpha(200); 50 | case CallType.outgoing: 51 | return Colors.green.withAlpha(200); 52 | case CallType.rejected: 53 | return Colors.red.withAlpha(200); 54 | case CallType.missed: 55 | return Colors.red.withAlpha(200); 56 | case CallType.blocked: 57 | return Colors.grey.withAlpha(200); 58 | default: 59 | return Colors.white.withAlpha(200); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/model/contact.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import 'package:flutter_contacts/flutter_contacts.dart' as lib; 3 | import 'package:revo/utils/utils.dart'; 4 | 5 | class Contact { 6 | String id; 7 | String displayName; 8 | Uint8List? thumbnail; 9 | Uint8List? photo; 10 | bool isStarred; 11 | String fullName; 12 | List phones; 13 | List emails; 14 | List addresses; 15 | List organizations; 16 | List websites; 17 | List socialMedias; 18 | List events; 19 | List notes; 20 | List accounts; 21 | List groups; 22 | 23 | Contact({ 24 | required this.id, 25 | required this.displayName, 26 | this.thumbnail, 27 | this.photo, 28 | this.isStarred = false, 29 | required this.fullName, 30 | this.phones = const [], 31 | this.emails = const [], 32 | this.addresses = const [], 33 | this.organizations = const [], 34 | this.websites = const [], 35 | this.socialMedias = const [], 36 | this.events = const [], 37 | this.notes = const [], 38 | this.accounts = const [], 39 | this.groups = const [], 40 | }); 41 | 42 | // TODO: Needs more work 43 | factory Contact.fromInternal(lib.Contact contact) { 44 | return Contact( 45 | id: contact.id, 46 | displayName: contact.displayName, 47 | thumbnail: contact.thumbnail, 48 | photo: contact.photo, 49 | isStarred: contact.isStarred, 50 | fullName: 51 | '${contact.name.first} ${contact.name.middle} ${contact.name.last}', 52 | phones: contact.phones.map((phone) => (phone.number)).toList(), 53 | emails: contact.emails, 54 | addresses: contact.addresses, 55 | organizations: contact.organizations, 56 | websites: contact.websites, 57 | socialMedias: contact.socialMedias, 58 | events: contact.events, 59 | notes: contact.notes, 60 | accounts: contact.accounts, 61 | groups: contact.groups, 62 | ); 63 | } 64 | 65 | lib.Contact toInternal() { 66 | return lib.Contact( 67 | id: id, 68 | displayName: displayName, 69 | thumbnail: thumbnail, 70 | photo: photo, 71 | name: lib.Name( 72 | first: fullName.split(' ')[0], 73 | middle: fullName.split(' ').length > 2 ? fullName.split(' ')[1] : '', 74 | last: fullName.split(' ').last, 75 | ), 76 | phones: phones.map((p) => lib.Phone(p)).toList(), 77 | emails: emails, 78 | addresses: addresses, 79 | organizations: organizations, 80 | websites: websites, 81 | socialMedias: socialMedias, 82 | events: events, 83 | notes: notes, 84 | accounts: accounts, 85 | groups: groups, 86 | isStarred: isStarred, 87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/model/sim_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_sim_data/sim_data_model.dart' as lib; 2 | 3 | class SimCard { 4 | final String carrierName; 5 | final bool isESIM; 6 | final int subscriptionId; 7 | final int simSlotIndex; 8 | final int cardId; 9 | final String phoneNumber; 10 | final String displayName; 11 | final String countryCode; 12 | 13 | SimCard({ 14 | required this.carrierName, 15 | required this.isESIM, 16 | required this.subscriptionId, 17 | required this.simSlotIndex, 18 | required this.cardId, 19 | required this.phoneNumber, 20 | required this.displayName, 21 | required this.countryCode, 22 | }); 23 | 24 | factory SimCard.fromInternal(lib.SimDataModel data) { 25 | return SimCard( 26 | carrierName: data.carrierName, 27 | isESIM: data.isESIM, 28 | subscriptionId: data.subscriptionId, 29 | simSlotIndex: data.simSlotIndex, 30 | cardId: data.cardId, 31 | phoneNumber: data.phoneNumber, 32 | displayName: data.displayName, 33 | countryCode: data.countryCode, 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/services/activity_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:direct_caller_sim_choice/direct_caller_sim_choice.dart'; 4 | import 'package:permission_handler/permission_handler.dart'; 5 | import 'package:url_launcher/url_launcher.dart'; 6 | 7 | class ActivityService { 8 | ActivityService._internal(); 9 | static final ActivityService _instance = ActivityService._internal(); 10 | factory ActivityService() { 11 | return _instance; 12 | } 13 | 14 | Completer? _permissionsCompleter; 15 | 16 | Future requestPermissions() async { 17 | if (_permissionsCompleter == null) { 18 | _permissionsCompleter = Completer(); 19 | try { 20 | await [ 21 | Permission.sms, 22 | Permission.phone, 23 | Permission.contacts, 24 | Permission.camera, 25 | ].request(); 26 | _permissionsCompleter!.complete(); 27 | } catch (e) { 28 | _permissionsCompleter!.completeError(e); 29 | } finally { 30 | _permissionsCompleter = null; 31 | } 32 | } else { 33 | await _permissionsCompleter!.future; 34 | } 35 | } 36 | 37 | Future makePhoneCall(String phoneNumber, int simSlot) async { 38 | if (await Permission.phone.status.isGranted) { 39 | final DirectCaller directCaller = DirectCaller(); 40 | directCaller.makePhoneCall(phoneNumber, simSlot: simSlot + 1); 41 | } 42 | } 43 | 44 | Future sendSMS(String phoneNumber) async { 45 | if (await Permission.sms.status.isGranted) { 46 | final Uri smsUri = Uri(scheme: 'sms', path: phoneNumber); 47 | if (await canLaunchUrl(smsUri)) { 48 | await launchUrl(smsUri); 49 | } else { 50 | throw 'Could not send SMS.'; 51 | } 52 | } 53 | } 54 | 55 | Future makeVideoCall(String phoneNumber) async { 56 | if (await Permission.phone.status.isGranted) { 57 | final Uri videoCallUri = Uri(scheme: 'tel', path: phoneNumber); 58 | if (await canLaunchUrl(videoCallUri)) { 59 | await launchUrl(videoCallUri); 60 | } else { 61 | throw 'Could not start the video call.'; 62 | } 63 | } 64 | } 65 | 66 | void openWhatsApp(String phoneNumber) async { 67 | final url = 'https://wa.me/$phoneNumber'; 68 | if (await canLaunchUrl(Uri.parse(url))) { 69 | await launchUrl(Uri.parse(url)); 70 | } else { 71 | throw 'Could not launch $url'; 72 | } 73 | } 74 | 75 | void openTelegram(String phoneNumber) async { 76 | final url = 'https://t.me/$phoneNumber'; 77 | if (await canLaunchUrl(Uri.parse(url))) { 78 | await launchUrl(Uri.parse(url)); 79 | } else { 80 | throw 'Could not launch $url'; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/services/backgroundservice.dart2: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:convert'; 3 | import 'dart:ui'; 4 | 5 | import 'package:contest_flow/modal/contestdata.dart'; 6 | import 'package:contest_flow/services/notificationservice.dart'; 7 | import 'package:contest_flow/services/prefservice.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_background_service/flutter_background_service.dart'; 10 | import 'package:intl/intl.dart'; 11 | 12 | class BackgroundService { 13 | static Future init() async { 14 | final service = FlutterBackgroundService(); 15 | 16 | await service.configure( 17 | iosConfiguration: IosConfiguration( 18 | autoStart: true, 19 | onForeground: onStart, 20 | onBackground: onIosBackground, 21 | ), 22 | androidConfiguration: AndroidConfiguration( 23 | autoStart: true, 24 | onStart: onStart, 25 | isForegroundMode: false, 26 | autoStartOnBoot: true, 27 | ), 28 | ); 29 | } 30 | } 31 | 32 | @pragma('vm:entry-point') 33 | Future onIosBackground(ServiceInstance service) async { 34 | WidgetsFlutterBinding.ensureInitialized(); 35 | DartPluginRegistrant.ensureInitialized(); 36 | 37 | return true; 38 | } 39 | 40 | void scheduleDailyNotification() { 41 | DateTime now = DateTime.now(); 42 | DateTime next8AM = DateTime(now.year, now.month, now.day, 8, 0, 0); 43 | 44 | if (now.isAfter(next8AM)) { 45 | next8AM = next8AM.add(const Duration(days: 1)); 46 | } 47 | 48 | Timer.periodic(const Duration(days: 1), (timer) { 49 | if (SharedPrefService().getBool('daily_update')) { 50 | String? contestsJson = SharedPrefService().getString('contests'); 51 | if (contestsJson != null) { 52 | List contestsData = jsonDecode(contestsJson); 53 | List contests = 54 | contestsData.map((e) => ContestData.fromJson(e)).toList(); 55 | 56 | String str = ""; 57 | for (var e in contests) { 58 | final DateTime startTime = 59 | DateTime.fromMillisecondsSinceEpoch(e.startTimeSeconds * 1000); 60 | final DateTime endTime = 61 | startTime.add(Duration(seconds: e.durationSeconds)); 62 | 63 | final String formattedDate = 64 | DateFormat('EE, dd MMM').format(startTime); 65 | final String formattedTime = 66 | "${DateFormat('h:mm a ').format(startTime)} - ${DateFormat('h:mm a').format(endTime)}"; 67 | str += "$formattedDate ($formattedTime)\n"; 68 | } 69 | NotificationService.showSimpleNotification( 70 | id: 2000, 71 | title: "Upcoming Contests", 72 | body: str, 73 | payload: "payload", 74 | ); 75 | } 76 | } 77 | }); 78 | } 79 | 80 | void scheduleContestReminders() { 81 | Timer.periodic(const Duration(minutes: 30), (timer) async { 82 | if (SharedPrefService().getBool('contest_reminder')) { 83 | String? contestsJson = SharedPrefService().getString('contests'); 84 | if (contestsJson != null) { 85 | List contestsData = jsonDecode(contestsJson); 86 | List contests = 87 | contestsData.map((e) => ContestData.fromJson(e)).toList(); 88 | 89 | DateTime now = DateTime.now(); 90 | 91 | for (var e in contests) { 92 | final DateTime startTime = DateTime.fromMillisecondsSinceEpoch( 93 | e.startTimeSeconds * 1000 - 45 * 60 * 1000); // before 45 mins 94 | final DateTime endTime = 95 | startTime.add(Duration(seconds: e.durationSeconds)); 96 | final String formattedTime = 97 | "${DateFormat('h:mm a ').format(startTime)} - ${DateFormat('h:mm a').format(endTime)}"; 98 | 99 | if (now.isAfter(startTime) && now.isBefore(endTime)) { 100 | NotificationService.showFullScreenNotification( 101 | title: "Contest Reminder", 102 | body: 103 | "A contest is going to start soon.\n ${e.name}\n$formattedTime", 104 | payload: "payload", 105 | ); 106 | } 107 | } 108 | } 109 | } 110 | }); 111 | } 112 | 113 | @pragma('vm:entry-point') 114 | void onStart(ServiceInstance service) async { 115 | service.on("stop").listen((event) { 116 | service.stopSelf(); 117 | }); 118 | 119 | await SharedPrefService().init(); 120 | scheduleDailyNotification(); 121 | scheduleContestReminders(); 122 | } 123 | -------------------------------------------------------------------------------- /lib/services/cubit/call_log_service.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import 'package:call_e_log/call_log.dart' as lib; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | import 'package:permission_handler/permission_handler.dart'; 6 | import 'package:revo/model/call_log.dart'; 7 | import 'package:revo/services/activity_service.dart'; 8 | import 'package:revo/services/cubit/contact_service.dart'; 9 | import 'package:revo/utils/utils.dart'; 10 | import 'package:flutter_contacts/flutter_contacts.dart' as fc; 11 | 12 | class CallLogService extends Cubit> { 13 | CallLogService() : super([]) { 14 | _initialize(); 15 | } 16 | 17 | Future _initialize() async { 18 | await ActivityService().requestPermissions(); 19 | if (await Permission.phone.status.isGranted) { 20 | var fContact = (await fc.FlutterContacts.getContacts( 21 | withProperties: true, 22 | withAccounts: true, 23 | withThumbnail: true, 24 | )) 25 | .toList(); 26 | 27 | List logs = (await lib.CallLog.get()).toList(); 28 | 29 | var list = logs.map((e) { 30 | Uint8List? photo; 31 | 32 | try { 33 | String logNumber = normalizePhoneNumber(e.number!); 34 | fc.Contact contact = fContact.firstWhere((f) { 35 | return f.phones.any((g) { 36 | String contactNumber = normalizePhoneNumber(g.normalizedNumber); 37 | return logNumber.endsWith(contactNumber) || 38 | logNumber == contactNumber; 39 | }); 40 | }); 41 | 42 | if ((e.name ?? '').isEmpty) { 43 | e.name = 44 | '${contact.name.first} ${contact.name.middle} ${contact.name.last}' 45 | .trim(); 46 | } 47 | photo = contact.thumbnail; 48 | } catch (_) { 49 | photo = null; 50 | } 51 | 52 | return CallLog.fromEntry(entry: e, profile: photo); 53 | }).toList(); 54 | 55 | emit(list); 56 | } 57 | } 58 | 59 | Future fetchData(BuildContext context) async { 60 | if (state.isNotEmpty && await Permission.phone.status.isGranted) { 61 | var list = state.map((e) { 62 | var contactList = context.read(); 63 | var contact = contactList.findByNumber(e.number); 64 | e = CallLog( 65 | contact.photo, 66 | name: e.name, 67 | number: e.number, 68 | simDisplayName: e.simDisplayName, 69 | date: e.date, 70 | duration: e.duration, 71 | type: e.type, 72 | accountId: e.accountId, 73 | ); 74 | }).toList(); 75 | emit(state); 76 | } 77 | } 78 | 79 | List filterByNumber(List numbers) { 80 | final filteredLogs = state 81 | .where( 82 | (element) => numbers.any( 83 | (e) { 84 | String p1 = normalizePhoneNumber(e); 85 | String p2 = normalizePhoneNumber(element.number); 86 | return p1 == p2 || p1.endsWith(p2) || p2.endsWith(p1); 87 | }, 88 | ), 89 | ) 90 | .toList(); 91 | return filteredLogs; 92 | } 93 | 94 | CallLogService refresh() { 95 | _initialize(); 96 | return this; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/services/cubit/contact_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:flutter_contacts/flutter_contacts.dart' as fc; 4 | import 'package:permission_handler/permission_handler.dart'; 5 | import 'package:revo/model/contact.dart'; 6 | import 'package:revo/services/activity_service.dart'; 7 | import 'package:revo/utils/utils.dart'; 8 | import 'package:flutter_contacts/flutter_contacts.dart' as lib; 9 | 10 | class ContactService extends Cubit> { 11 | ContactService() : super([]) { 12 | _initialize(); 13 | } 14 | 15 | Future _initialize() async { 16 | await ActivityService().requestPermissions(); 17 | if (await Permission.contacts.status.isGranted) { 18 | var contact = (await fc.FlutterContacts.getContacts( 19 | withProperties: true, 20 | withAccounts: true, 21 | withGroups: true, 22 | withPhoto: true, 23 | withThumbnail: true, 24 | )) 25 | .toList(); 26 | 27 | contact = contact.where((e) => e.phones.isNotEmpty).toList(); 28 | emit(contact.map((e) => Contact.fromInternal(e)).toList()); 29 | } 30 | } 31 | 32 | List filterByStars() { 33 | return state.where((e) => e.isStarred).toList(); 34 | } 35 | 36 | ContactService refresh() { 37 | _initialize(); 38 | return this; 39 | } 40 | 41 | Contact findByNumber(String number) { 42 | String target = normalizePhoneNumber(number); 43 | try { 44 | return state.firstWhere((f) { 45 | return f.phones.any((g) { 46 | String contactNumber = normalizePhoneNumber(g); 47 | return contactNumber.endsWith(target) || 48 | // target.endsWith(contactNumber) || 49 | target == contactNumber; 50 | }); 51 | }); 52 | } catch (_) { 53 | return Contact( 54 | id: '"Unknown"', 55 | displayName: "Unknown", 56 | fullName: "Unknown", 57 | phones: [number]); 58 | } 59 | } 60 | 61 | Contact findByName(String name) { 62 | try { 63 | return state.firstWhere((f) { 64 | return name.isNotEmpty && 65 | f.phones.isNotEmpty && 66 | f.fullName.toLowerCase().contains(name.toLowerCase()); 67 | }); 68 | } catch (_) { 69 | return Contact(id: '"Unknown"', displayName: "Unknown", fullName: name); 70 | } 71 | } 72 | 73 | List findAllByNameOrNumber(String name, String number) { 74 | String target = normalizePhoneNumber(number); 75 | try { 76 | return state.where((f) { 77 | bool nameMatches = name.isNotEmpty && 78 | f.fullName.toLowerCase().contains(name.toLowerCase()); 79 | 80 | bool isNumber = number.isNotEmpty && num.tryParse(number) != null; 81 | bool numberMatches = isNumber && 82 | f.phones.any((g) { 83 | String contactNumber = normalizePhoneNumber(g); 84 | return contactNumber.contains(target) || target == contactNumber; 85 | }); 86 | return nameMatches || numberMatches; 87 | }).toList(); 88 | } catch (_) { 89 | return []; 90 | } 91 | } 92 | 93 | Future createNewContact({String? number}) async { 94 | if (await Permission.contacts.status.isGranted) { 95 | if (number == null) { 96 | await fc.FlutterContacts.openExternalInsert(); 97 | } else { 98 | await fc.FlutterContacts.openExternalInsert( 99 | fc.Contact(phones: [fc.Phone(number)])); 100 | } 101 | } 102 | } 103 | 104 | Future insertContact(Contact contact) async { 105 | if (await Permission.contacts.status.isGranted) { 106 | await fc.FlutterContacts.insertContact(contact.toInternal()); 107 | } 108 | } 109 | 110 | Future insertContactFromVCard(String data) async { 111 | if (await Permission.contacts.status.isGranted) { 112 | try { 113 | await fc.FlutterContacts.insertContact(lib.Contact.fromVCard(data)); 114 | print('Contact added successfully!'); 115 | } catch (e) { 116 | print('Error adding contact: $e'); 117 | } 118 | } else { 119 | print('Permission to access contacts denied!'); 120 | } 121 | } 122 | 123 | Future editContact(Contact contact) async { 124 | if (await Permission.contacts.status.isGranted) { 125 | await fc.FlutterContacts.openExternalEdit(contact.id); 126 | } else { 127 | print("Permission denied to access contacts"); 128 | } 129 | } 130 | 131 | void updateContact({ 132 | required Contact contact, 133 | bool withGroups = false, 134 | }) async { 135 | if (await Permission.contacts.status.isGranted) { 136 | fc.FlutterContacts.updateContact( 137 | contact.toInternal(), 138 | withGroups: withGroups, 139 | ); 140 | } else { 141 | print("Permission denied to access contacts"); 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /lib/services/cubit/mobile_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_bloc/flutter_bloc.dart'; 2 | import 'package:flutter_sim_data/sim_data.dart'; 3 | import 'package:permission_handler/permission_handler.dart'; 4 | import 'package:revo/model/sim_card.dart'; 5 | import 'package:revo/services/activity_service.dart'; 6 | 7 | class MobileService extends Cubit> { 8 | List? _simCards; 9 | MobileService() : super([]) { 10 | _initialize(); 11 | } 12 | 13 | Future _initialize() async { 14 | await ActivityService().requestPermissions(); 15 | if (await Permission.phone.status.isGranted) { 16 | try { 17 | var data = await SimData().getSimData(); 18 | _simCards = data.map((e) => SimCard.fromInternal(e)).toList(); 19 | } catch (e) { 20 | // TODO: Show error dialog 21 | } 22 | } 23 | } 24 | 25 | List get getSimInfo { 26 | return _simCards ?? []; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/services/prefservice.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:shared_preferences/shared_preferences.dart'; 3 | 4 | class SharedPrefService { 5 | static final SharedPrefService _instance = SharedPrefService._internal(); 6 | 7 | factory SharedPrefService() { 8 | return _instance; 9 | } 10 | 11 | SharedPrefService._internal(); 12 | 13 | late SharedPreferences _prefs; 14 | final StreamController _prefChangesController = 15 | StreamController.broadcast(); 16 | 17 | Stream get onPreferenceChanged => _prefChangesController.stream; 18 | 19 | Future init() async { 20 | _prefs = await SharedPreferences.getInstance(); 21 | } 22 | 23 | Future saveString(String key, String value) async { 24 | await _prefs.setString(key, value); 25 | _prefChangesController.add(key); 26 | } 27 | 28 | String? getString(String key) { 29 | return _prefs.getString(key); 30 | } 31 | 32 | Future saveBool(String key, bool value) async { 33 | await _prefs.setBool(key, value); 34 | _prefChangesController.add(key); 35 | } 36 | 37 | bool getBool(String key, {bool def = false}) { 38 | return _prefs.getBool(key) ?? def; 39 | } 40 | 41 | Future saveInt(String key, int value) async { 42 | await _prefs.setInt(key, value); 43 | _prefChangesController.add(key); 44 | } 45 | 46 | int? getInt(String key) { 47 | return _prefs.getInt(key); 48 | } 49 | 50 | Future saveDouble(String key, double value) async { 51 | await _prefs.setDouble(key, value); 52 | _prefChangesController.add(key); 53 | } 54 | 55 | double? getDouble(String key) { 56 | return _prefs.getDouble(key); 57 | } 58 | 59 | Future saveStringList(String key, List value) async { 60 | await _prefs.setStringList(key, value); 61 | _prefChangesController.add(key); 62 | } 63 | 64 | List? getStringList(String key) { 65 | return _prefs.getStringList(key); 66 | } 67 | 68 | Future remove(String key) async { 69 | await _prefs.remove(key); 70 | _prefChangesController.add(key); 71 | } 72 | 73 | Future clear() async { 74 | await _prefs.clear(); 75 | _prefChangesController.add("all"); 76 | } 77 | 78 | /// Dispose the stream when not needed 79 | void dispose() { 80 | _prefChangesController.close(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/ui/popups/number_choose_popup.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hugeicons/hugeicons.dart'; 3 | import 'package:revo/extentions/theme.dart'; 4 | import 'package:revo/utils/center_text.dart'; 5 | 6 | Widget numberChooserDialog( 7 | BuildContext context, 8 | List numbers, 9 | void Function(String)? onTap, 10 | ) { 11 | return Dialog( 12 | backgroundColor: context.colorScheme.surfaceContainer, 13 | shape: RoundedRectangleBorder( 14 | borderRadius: BorderRadius.circular(24), 15 | ), 16 | alignment: Alignment.bottomCenter, 17 | child: Padding( 18 | padding: const EdgeInsets.all(24.0), 19 | child: Column( 20 | mainAxisSize: MainAxisSize.min, 21 | crossAxisAlignment: CrossAxisAlignment.start, 22 | children: [ 23 | CenterText( 24 | text: "Choose a number", 25 | size: 24, 26 | ), 27 | const SizedBox(height: 8), 28 | Column( 29 | children: numbers.map((number) { 30 | return _buildNumberOption(context, number, onTap); 31 | }).toList(), 32 | ), 33 | ], 34 | ), 35 | ), 36 | ); 37 | } 38 | 39 | Widget _buildNumberOption( 40 | BuildContext context, 41 | String number, 42 | void Function(String)? onTap, 43 | ) { 44 | return Card( 45 | elevation: 0, 46 | margin: const EdgeInsets.symmetric(vertical: 4), 47 | shape: RoundedRectangleBorder( 48 | borderRadius: BorderRadius.circular(24), 49 | ), 50 | color: context.colorScheme.secondaryContainer, 51 | child: InkWell( 52 | onTap: () { 53 | if (onTap != null) { 54 | onTap(number); 55 | } 56 | }, 57 | borderRadius: BorderRadius.circular(20), 58 | child: Padding( 59 | padding: const EdgeInsets.all(6.0), 60 | child: Row( 61 | children: [ 62 | CircleAvatar( 63 | radius: 18, 64 | backgroundColor: Theme.of(context).colorScheme.primary, 65 | child: Icon( 66 | HugeIcons.strokeRoundedSmartPhone01, 67 | color: context.colorScheme.onPrimary, 68 | size: 18, 69 | ), 70 | ), 71 | const SizedBox(width: 16), 72 | CenterText( 73 | text: number, 74 | size: 18, 75 | ), 76 | ], 77 | ), 78 | ), 79 | ), 80 | ); 81 | } 82 | -------------------------------------------------------------------------------- /lib/ui/popups/qr_popup.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_fonts/google_fonts.dart'; 3 | import 'package:qr_flutter/qr_flutter.dart'; 4 | import 'package:revo/extentions/theme.dart'; 5 | 6 | Widget qrCodePopup( 7 | BuildContext context, 8 | String data, 9 | ) { 10 | return Dialog( 11 | backgroundColor: context.colorScheme.surfaceContainer, 12 | child: Padding( 13 | padding: const EdgeInsets.all(16.0), 14 | child: Column( 15 | mainAxisSize: MainAxisSize.min, 16 | children: [ 17 | Text( 18 | 'Scan to add contact', 19 | style: GoogleFonts.raleway( 20 | color: context.colorScheme.onSurface, 21 | fontSize: 20, 22 | ), 23 | ), 24 | SizedBox(height: 20), 25 | QrImageView( 26 | data: data, 27 | size: 280, 28 | backgroundColor: Colors.white, 29 | ), 30 | SizedBox(height: 20), 31 | ], 32 | ), 33 | ), 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /lib/ui/popups/sim_choose_popup.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:google_fonts/google_fonts.dart'; 4 | import 'package:revo/extentions/theme.dart'; 5 | import 'package:revo/model/sim_card.dart'; 6 | import 'package:revo/services/activity_service.dart'; 7 | import 'package:revo/services/cubit/mobile_service.dart'; 8 | import 'package:revo/utils/center_text.dart'; 9 | 10 | Widget simChooserDialog(BuildContext context, String number) { 11 | return BlocBuilder>( 12 | builder: (context, state) { 13 | return Dialog( 14 | backgroundColor: context.colorScheme.surfaceContainer, 15 | shape: RoundedRectangleBorder( 16 | borderRadius: BorderRadius.circular(24), 17 | ), 18 | alignment: Alignment.bottomCenter, 19 | child: Padding( 20 | padding: const EdgeInsets.all(24), 21 | child: Column( 22 | mainAxisSize: MainAxisSize.min, 23 | crossAxisAlignment: CrossAxisAlignment.start, 24 | children: [ 25 | CenterText( 26 | text: "Choose SIM for call", 27 | size: 24, 28 | ), 29 | const SizedBox(height: 8), 30 | Column( 31 | children: context.read().getSimInfo.map((sim) { 32 | return _buildSimCard(context, sim, number); 33 | }).toList(), 34 | ), 35 | ], 36 | ), 37 | ), 38 | ); 39 | }, 40 | ); 41 | } 42 | 43 | Widget _buildSimCard(BuildContext context, SimCard sim, String number) { 44 | return Card( 45 | elevation: 0, 46 | margin: const EdgeInsets.symmetric(vertical: 4), 47 | shape: RoundedRectangleBorder( 48 | borderRadius: BorderRadius.circular(24), 49 | ), 50 | color: context.colorScheme.primaryContainer, 51 | child: InkWell( 52 | onTap: () async { 53 | ActivityService().makePhoneCall(number, sim.simSlotIndex); 54 | Navigator.of(context).pop(); 55 | }, 56 | borderRadius: BorderRadius.circular(20), 57 | child: Padding( 58 | padding: const EdgeInsets.all(12.0), 59 | child: Row( 60 | children: [ 61 | CircleAvatar( 62 | backgroundColor: Theme.of(context).colorScheme.primary, 63 | child: Text( 64 | "${sim.simSlotIndex + 1}", 65 | style: GoogleFonts.raleway( 66 | color: context.colorScheme.onSecondary, 67 | fontSize: 22, 68 | ), 69 | ), 70 | ), 71 | const SizedBox(width: 16), 72 | Column( 73 | crossAxisAlignment: CrossAxisAlignment.start, 74 | children: [ 75 | CenterText( 76 | text: "${sim.carrierName} (${sim.countryCode.toUpperCase()})", 77 | size: 18, 78 | ), 79 | const SizedBox(height: 2), 80 | CenterText( 81 | text: sim.phoneNumber, 82 | size: 12, 83 | ), 84 | ], 85 | ), 86 | ], 87 | ), 88 | ), 89 | ), 90 | ); 91 | } 92 | -------------------------------------------------------------------------------- /lib/ui/popups/welcome_changelog.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_fonts/google_fonts.dart'; 3 | import 'package:revo/extentions/theme.dart'; 4 | 5 | Widget welcomePopup( 6 | BuildContext context, 7 | String version, 8 | String changelog, 9 | ) { 10 | return Dialog( 11 | backgroundColor: context.colorScheme.surfaceContainer, 12 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), 13 | child: Padding( 14 | padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 24.0), 15 | child: Padding( 16 | padding: const EdgeInsets.symmetric(vertical: 12.0), 17 | child: Column( 18 | mainAxisSize: MainAxisSize.min, 19 | crossAxisAlignment: CrossAxisAlignment.center, 20 | children: [ 21 | Image.asset( 22 | 'assets/icon.png', 23 | width: 70, 24 | height: 70, 25 | fit: BoxFit.cover, 26 | ), 27 | SizedBox(height: 16), 28 | Text( 29 | 'Rivo', 30 | style: GoogleFonts.raleway( 31 | color: context.colorScheme.onSurface, 32 | fontSize: 30, 33 | fontWeight: FontWeight.bold, 34 | ), 35 | ), 36 | Text( 37 | 'Version: $version', 38 | style: GoogleFonts.raleway( 39 | color: context.colorScheme.onSurfaceVariant, 40 | fontSize: 16, 41 | ), 42 | ), 43 | SizedBox(height: 50), 44 | Align( 45 | alignment: Alignment.centerLeft, 46 | child: Text( 47 | 'Changelog:', 48 | style: GoogleFonts.raleway( 49 | color: context.colorScheme.onSurface, 50 | fontSize: 16, 51 | fontWeight: FontWeight.w600, 52 | ), 53 | ), 54 | ), 55 | SizedBox(height: 8), 56 | Text( 57 | changelog, 58 | style: GoogleFonts.raleway( 59 | color: context.colorScheme.onSurfaceVariant, 60 | fontSize: 16, 61 | height: 1.3, 62 | ), 63 | ), 64 | ], 65 | ), 66 | ), 67 | ), 68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /lib/ui/theme/handler.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_fonts/google_fonts.dart'; 3 | import 'package:revo/constants/pref.dart'; 4 | import 'package:revo/services/prefservice.dart'; 5 | 6 | ThemeData getTheme( 7 | ColorScheme? dynamicCol, 8 | ThemeProvider provider, 9 | bool isDark, 10 | ) { 11 | ColorScheme defScheme = ColorScheme.fromSeed( 12 | seedColor: Colors.blueAccent.shade100, 13 | brightness: isDark ? Brightness.dark : Brightness.light, 14 | ); 15 | if (provider.isAmoled) { 16 | return _getAmoledTheme(); 17 | } 18 | 19 | return ThemeData( 20 | colorScheme: provider.isDynamic ? dynamicCol ?? defScheme : defScheme, 21 | useMaterial3: true, 22 | textTheme: GoogleFonts.cabinTextTheme(), 23 | ); 24 | } 25 | 26 | ThemeData _getAmoledTheme() { 27 | return ThemeData( 28 | brightness: Brightness.dark, 29 | scaffoldBackgroundColor: Colors.black, 30 | primaryColor: Colors.black, 31 | cardColor: Colors.black, 32 | dialogBackgroundColor: Colors.black, 33 | appBarTheme: AppBarTheme( 34 | backgroundColor: Colors.black, 35 | foregroundColor: Colors.white, 36 | ), 37 | colorScheme: ColorScheme.dark( 38 | primary: Color(0xFF000000), 39 | onPrimary: Color(0xFFFFFFFF), 40 | primaryContainer: Color(0xFF121212), 41 | secondary: Color(0xFF1C1C1C), 42 | onSecondary: Color(0xFFD3D3D3), 43 | secondaryContainer: Color(0xFF2B2B2B), 44 | onSecondaryContainer: Color(0xFFFFFFFF), 45 | background: Color(0xFF000000), 46 | surface: Color(0xFF121212), 47 | onSurface: Color(0xFFE0E0E0), 48 | surfaceContainer: Color(0xFF1A1A1A), 49 | ), 50 | textTheme: GoogleFonts.cabinTextTheme(), 51 | ); 52 | } 53 | 54 | class ThemeProvider extends ChangeNotifier { 55 | bool _isAmoled = false; 56 | bool _isDynamic = false; 57 | 58 | bool get isAmoled => _isAmoled; 59 | bool get isDynamic => _isDynamic; 60 | 61 | Future initTheme() async { 62 | await SharedPrefService().init(); 63 | _isAmoled = SharedPrefService().getBool(PREF_AMOLED_DARK_MODE, def: false); 64 | _isDynamic = SharedPrefService().getBool(PREF_MATERIAL_THEMING, def: false); 65 | notifyListeners(); 66 | } 67 | 68 | void toggleDynamicColors() { 69 | _isDynamic = !_isDynamic; 70 | SharedPrefService().saveBool(PREF_MATERIAL_THEMING, _isDynamic); 71 | notifyListeners(); 72 | } 73 | 74 | void toggleAmoledColors() { 75 | _isAmoled = !_isAmoled; 76 | SharedPrefService().saveBool(PREF_AMOLED_DARK_MODE, _isAmoled); 77 | notifyListeners(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/ui/views/call_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_fonts/google_fonts.dart'; 3 | import 'package:revo/extentions/theme.dart'; 4 | 5 | class CallScreenView extends StatelessWidget { 6 | const CallScreenView({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Scaffold( 11 | backgroundColor: context.colorScheme.surface, 12 | body: SafeArea( 13 | child: Stack( 14 | children: [ 15 | Align( 16 | alignment: Alignment.topCenter, 17 | child: Padding( 18 | padding: 19 | const EdgeInsets.symmetric(vertical: 40, horizontal: 16), 20 | child: Column( 21 | children: [ 22 | CircleAvatar( 23 | radius: 90, 24 | backgroundColor: context.colorScheme.secondaryContainer, 25 | child: Icon( 26 | Icons.person, 27 | size: 100, 28 | color: context.colorScheme.onSecondaryContainer, 29 | ), 30 | ), 31 | const SizedBox(height: 20), 32 | Text( 33 | "John Doe", 34 | style: GoogleFonts.raleway( 35 | fontSize: 30, 36 | fontWeight: FontWeight.bold, 37 | color: context.colorScheme.onSurface, 38 | ), 39 | ), 40 | Text( 41 | "+1 (123) 456-7890", 42 | style: GoogleFonts.raleway( 43 | fontSize: 15, 44 | color: context.colorScheme.onSurface.withAlpha(150), 45 | ), 46 | ), 47 | const SizedBox(height: 16), 48 | Text( 49 | "01:25", 50 | style: GoogleFonts.raleway( 51 | fontSize: 20, 52 | fontWeight: FontWeight.bold, 53 | color: context.colorScheme.primary.withAlpha(150), 54 | ), 55 | ), 56 | ], 57 | ), 58 | ), 59 | ), 60 | Align( 61 | alignment: Alignment.bottomCenter, 62 | child: Container( 63 | padding: 64 | const EdgeInsets.symmetric(horizontal: 20, vertical: 30), 65 | decoration: BoxDecoration( 66 | color: context.colorScheme.surface, 67 | borderRadius: 68 | const BorderRadius.vertical(top: Radius.circular(30)), 69 | ), 70 | child: Column( 71 | mainAxisSize: MainAxisSize.min, 72 | children: [ 73 | Row( 74 | mainAxisAlignment: MainAxisAlignment.spaceAround, 75 | children: [ 76 | _CallActionButton( 77 | icon: Icons.record_voice_over, 78 | label: "Record", 79 | color: context.colorScheme.secondaryContainer, 80 | textColor: context.colorScheme.onSecondaryContainer, 81 | size: 65, 82 | ), 83 | _CallActionButton( 84 | icon: Icons.mic_off, 85 | label: "Mute", 86 | color: context.colorScheme.secondaryContainer, 87 | textColor: context.colorScheme.onSecondaryContainer, 88 | size: 65, 89 | ), 90 | _CallActionButton( 91 | icon: Icons.pause, 92 | label: "Hold", 93 | color: context.colorScheme.secondaryContainer, 94 | textColor: context.colorScheme.onSecondaryContainer, 95 | size: 65, 96 | ), 97 | ], 98 | ), 99 | const SizedBox(height: 20), 100 | Row( 101 | mainAxisAlignment: MainAxisAlignment.spaceAround, 102 | children: [ 103 | _CallActionButton( 104 | icon: Icons.add_call, 105 | label: "Add Call", 106 | color: context.colorScheme.secondaryContainer, 107 | textColor: context.colorScheme.onSecondaryContainer, 108 | size: 65, 109 | ), 110 | _CallActionButton( 111 | icon: Icons.volume_up, 112 | label: "Speaker", 113 | color: context.colorScheme.secondaryContainer, 114 | textColor: context.colorScheme.onSecondaryContainer, 115 | size: 65, 116 | ), 117 | _CallActionButton( 118 | icon: Icons.dialpad, 119 | label: "Dialpad", 120 | color: context.colorScheme.secondaryContainer, 121 | textColor: context.colorScheme.onSecondaryContainer, 122 | size: 65, 123 | ), 124 | ], 125 | ), 126 | const SizedBox(height: 50), 127 | Row( 128 | mainAxisAlignment: MainAxisAlignment.center, 129 | children: [ 130 | _CallActionButton( 131 | icon: Icons.call_end, 132 | label: "End", 133 | color: Colors.redAccent, 134 | textColor: context.colorScheme.onError, 135 | size: 70, 136 | onPressed: () { 137 | // Add your end call logic here 138 | }, 139 | showLabel: false, 140 | ), 141 | ], 142 | ), 143 | ], 144 | ), 145 | ), 146 | ), 147 | ], 148 | ), 149 | ), 150 | ); 151 | } 152 | } 153 | 154 | class _CallActionButton extends StatelessWidget { 155 | final IconData icon; 156 | final String label; 157 | final Color color; 158 | final Color textColor; 159 | final double size; 160 | final VoidCallback? onPressed; 161 | final bool showLabel; 162 | 163 | const _CallActionButton({ 164 | required this.icon, 165 | required this.label, 166 | required this.color, 167 | required this.textColor, 168 | this.size = 60, 169 | this.onPressed, 170 | super.key, 171 | this.showLabel = true, 172 | }); 173 | 174 | @override 175 | Widget build(BuildContext context) { 176 | return Column( 177 | children: [ 178 | GestureDetector( 179 | onTap: onPressed, 180 | child: Container( 181 | width: size, 182 | height: size, 183 | decoration: BoxDecoration( 184 | color: color, 185 | shape: BoxShape.circle, 186 | ), 187 | child: Icon( 188 | icon, 189 | size: size * 0.35, 190 | color: Colors.white, 191 | ), 192 | ), 193 | ), 194 | const SizedBox(height: 10), 195 | if (showLabel) 196 | Text( 197 | label, 198 | style: TextStyle( 199 | fontSize: 12, 200 | fontWeight: FontWeight.w500, 201 | color: textColor, 202 | ), 203 | ), 204 | ], 205 | ); 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /lib/ui/views/common/constants.dart: -------------------------------------------------------------------------------- 1 | const String version = "1.1"; 2 | const String changelog = 3 | "1. Tweaked the interface\n2. Added settings page\n3. Fixed data fetch issues"; 4 | -------------------------------------------------------------------------------- /lib/ui/views/common/contact_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_fonts/google_fonts.dart'; 3 | import 'package:hugeicons/hugeicons.dart'; 4 | import 'package:revo/constants/routes.dart'; 5 | import 'package:revo/extentions/theme.dart'; 6 | import 'package:revo/model/contact.dart'; 7 | import 'package:revo/ui/popups/sim_choose_popup.dart'; 8 | import 'package:revo/utils/circle_profile.dart'; 9 | import 'package:revo/utils/rounded_icon_btn.dart'; 10 | 11 | class ContactTile extends StatelessWidget { 12 | final Contact contact; 13 | 14 | const ContactTile({ 15 | super.key, 16 | required this.contact, 17 | }); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return ListTile( 22 | onTap: () async { 23 | if (contact.phones.isNotEmpty) { 24 | showDialog( 25 | context: context, 26 | builder: (context) => simChooserDialog(context, contact.phones[0]), 27 | ); 28 | } 29 | }, 30 | shape: RoundedRectangleBorder( 31 | borderRadius: BorderRadius.circular(20), 32 | ), 33 | leading: CircleProfile( 34 | name: contact.fullName, 35 | profile: contact.photo, 36 | size: 30, 37 | ), 38 | title: Text( 39 | contact.displayName, 40 | style: GoogleFonts.raleway( 41 | fontSize: 16, 42 | color: context.colorScheme.onSurface.withAlpha(200), 43 | ), 44 | ), 45 | subtitle: Text( 46 | contact.phones 47 | .toString() 48 | .substring(1, contact.phones.toString().length - 1), 49 | style: GoogleFonts.raleway( 50 | fontSize: 12, 51 | color: context.colorScheme.onSurface.withAlpha(200), 52 | ), 53 | ), 54 | trailing: RoundedIconButton( 55 | context, 56 | icon: HugeIcons.strokeRoundedArrowRight01, 57 | size: 30, 58 | onTap: () async { 59 | await Navigator.of(context).pushNamed( 60 | contactInfoRoute, 61 | arguments: contact, 62 | ); 63 | }, 64 | ), 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/ui/views/common/matched_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:revo/model/contact.dart'; 4 | import 'package:revo/services/cubit/contact_service.dart'; 5 | import 'package:revo/ui/views/common/contact_tile.dart'; 6 | 7 | class MatchedView extends StatelessWidget { 8 | final ScrollController scrollController; 9 | final String number; 10 | 11 | const MatchedView({ 12 | super.key, 13 | required this.scrollController, 14 | required this.number, 15 | }); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return BlocBuilder>( 20 | builder: (context, state) { 21 | List contacts; 22 | if (number.isEmpty) { 23 | contacts = context.read().state; 24 | } else { 25 | contacts = context 26 | .read() 27 | .findAllByNameOrNumber(number, number); 28 | } 29 | return Scrollbar( 30 | controller: scrollController, 31 | child: ListView.builder( 32 | padding: const EdgeInsets.only(top: 20.0), 33 | shrinkWrap: true, 34 | itemCount: contacts.length, 35 | controller: scrollController, 36 | itemBuilder: (context, i) { 37 | return ContactTile(contact: contacts[i]); 38 | }, 39 | ), 40 | ); 41 | }, 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/ui/views/contactinfo_view.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import 'package:google_fonts/google_fonts.dart'; 5 | import 'package:hugeicons/hugeicons.dart'; 6 | import 'package:revo/constants/routes.dart'; 7 | import 'package:revo/extentions/theme.dart'; 8 | import 'package:revo/model/contact.dart'; 9 | import 'package:revo/services/activity_service.dart'; 10 | import 'package:revo/services/cubit/contact_service.dart'; 11 | import 'package:revo/ui/popups/number_choose_popup.dart'; 12 | import 'package:revo/ui/popups/qr_popup.dart'; 13 | import 'package:revo/ui/popups/sim_choose_popup.dart'; 14 | import 'package:revo/utils/rounded_icon_btn.dart'; 15 | import 'package:revo/utils/share.dart'; 16 | import 'package:share_plus/share_plus.dart'; 17 | 18 | class ContactInfoView extends StatefulWidget { 19 | final Contact contact; 20 | const ContactInfoView(this.contact, {super.key}); 21 | 22 | @override 23 | State createState() => _ContactInfoViewState(); 24 | } 25 | 26 | class _ContactInfoViewState extends State { 27 | @override 28 | Widget build(BuildContext context) { 29 | return Scaffold( 30 | appBar: AppBar( 31 | leading: IconButton( 32 | icon: Icon(HugeIcons.strokeRoundedArrowLeft01), 33 | onPressed: () => Navigator.of(context).pop(), 34 | ), 35 | elevation: 0, 36 | ), 37 | floatingActionButton: FloatingActionButton.extended( 38 | elevation: 1, 39 | onPressed: () async { 40 | await context.read().editContact(widget.contact); 41 | }, 42 | backgroundColor: context.colorScheme.secondaryContainer, 43 | label: Text( 44 | "Edit", 45 | style: TextStyle(color: context.colorScheme.onSecondaryContainer), 46 | ), 47 | icon: Icon(HugeIcons.strokeRoundedEdit02, 48 | color: context.colorScheme.onSecondaryContainer), 49 | ), 50 | body: SingleChildScrollView( 51 | padding: EdgeInsets.only( 52 | top: MediaQuery.of(context).padding.top, left: 16, right: 16), 53 | child: Column( 54 | crossAxisAlignment: CrossAxisAlignment.center, 55 | children: [ 56 | _buildProfilePicture(context), 57 | const SizedBox(height: 16), 58 | Text( 59 | widget.contact.fullName, 60 | style: GoogleFonts.raleway( 61 | fontSize: 28, 62 | color: context.colorScheme.onSurface, 63 | ), 64 | textAlign: TextAlign.center, 65 | ), 66 | 67 | Padding( 68 | padding: const EdgeInsets.all(16), 69 | child: Wrap( 70 | alignment: WrapAlignment.center, 71 | spacing: 20, 72 | children: [ 73 | RoundedIconButton( 74 | context, 75 | size: 45, 76 | icon: HugeIcons.strokeRoundedQrCode, 77 | text: 'QR Code', 78 | onTap: () { 79 | showDialog( 80 | context: context, 81 | builder: (context) => qrCodePopup( 82 | context, 83 | generateVCardString(widget.contact), 84 | ), 85 | ); 86 | }, 87 | ), 88 | RoundedIconButton( 89 | context, 90 | icon: HugeIcons.strokeRoundedShare08, 91 | size: 45, 92 | text: 'Share', 93 | onTap: () { 94 | Share.shareXFiles([ 95 | XFile.fromData( 96 | utf8.encode(generateVCardString(widget.contact)), 97 | mimeType: 'text/plain') 98 | ], fileNameOverrides: [ 99 | 'contact.vcf' 100 | ]); 101 | }, 102 | ), 103 | RoundedIconButton( 104 | context, 105 | icon: HugeIcons.strokeRoundedClock04, 106 | size: 45, 107 | text: 'Call History', 108 | onTap: () { 109 | Navigator.of(context).pushNamed( 110 | callHistoryRoute, 111 | arguments: widget.contact.phones, 112 | ); 113 | }, 114 | ), 115 | RoundedIconButton( 116 | context, 117 | icon: widget.contact.isStarred 118 | ? HugeIcons.strokeRoundedHeartCheck 119 | : HugeIcons.strokeRoundedHeartAdd, 120 | size: 45, 121 | text: 'Favourite', 122 | onTap: () { 123 | setState(() { 124 | widget.contact.isStarred = !widget.contact.isStarred; 125 | }); 126 | context 127 | .read() 128 | .updateContact(contact: widget.contact); 129 | }, 130 | ), 131 | ], 132 | ), 133 | ), 134 | 135 | _buildContactInfoSection(context), 136 | const SizedBox(height: 16), 137 | 138 | // External Apps Section 139 | Card( 140 | elevation: 0, 141 | margin: const EdgeInsets.symmetric(vertical: 16), 142 | shape: RoundedRectangleBorder( 143 | borderRadius: BorderRadius.circular(16), 144 | ), 145 | color: context.colorScheme.secondaryContainer.withAlpha(100), 146 | child: Padding( 147 | padding: const EdgeInsets.all(16), 148 | child: Column( 149 | crossAxisAlignment: CrossAxisAlignment.start, 150 | children: [ 151 | Text( 152 | "External Apps", 153 | style: GoogleFonts.raleway( 154 | fontSize: 20, 155 | color: context.colorScheme.onSurface, 156 | ), 157 | ), 158 | const SizedBox(height: 16), 159 | Column( 160 | children: [ 161 | _buildListTile(context, HugeIcons.strokeRoundedTelegram, 162 | 'Telegram', () { 163 | showDialog( 164 | context: context, 165 | builder: (context) => numberChooserDialog( 166 | context, widget.contact.phones, 167 | (String num) async { 168 | ActivityService().openTelegram(num); 169 | }), 170 | ); 171 | }), 172 | _buildListTile(context, HugeIcons.strokeRoundedVideo01, 173 | 'Video Call', () { 174 | showDialog( 175 | context: context, 176 | builder: (context) => numberChooserDialog( 177 | context, widget.contact.phones, 178 | (String num) async { 179 | ActivityService().makeVideoCall(num); 180 | }), 181 | ); 182 | }), 183 | _buildListTile(context, HugeIcons.strokeRoundedWhatsapp, 184 | 'WhatsApp', () { 185 | showDialog( 186 | context: context, 187 | builder: (context) => numberChooserDialog( 188 | context, widget.contact.phones, 189 | (String num) async { 190 | ActivityService().openWhatsApp(num); 191 | }), 192 | ); 193 | }), 194 | ], 195 | ), 196 | ], 197 | ), 198 | ), 199 | ), 200 | ], 201 | ), 202 | ), 203 | ); 204 | } 205 | 206 | Widget _buildListTile( 207 | BuildContext context, IconData icon, String label, VoidCallback onTap) { 208 | return ListTile( 209 | leading: Container( 210 | width: 35, 211 | height: 35, 212 | decoration: BoxDecoration( 213 | color: context.colorScheme.secondaryContainer, 214 | shape: BoxShape.circle, 215 | ), 216 | child: Icon(icon, color: context.colorScheme.onSecondaryContainer), 217 | ), 218 | title: Text( 219 | label, 220 | style: context.textTheme.bodyLarge?.copyWith( 221 | color: context.colorScheme.onSurface, 222 | ), 223 | ), 224 | onTap: onTap, 225 | ); 226 | } 227 | 228 | Widget _buildContactInfoSection(BuildContext context) { 229 | return Card( 230 | elevation: 0, 231 | margin: const EdgeInsets.symmetric(vertical: 16), 232 | shape: RoundedRectangleBorder( 233 | borderRadius: BorderRadius.circular(16), 234 | ), 235 | color: context.colorScheme.secondaryContainer.withAlpha(100), 236 | child: Padding( 237 | padding: const EdgeInsets.all(16), 238 | child: Column( 239 | crossAxisAlignment: CrossAxisAlignment.start, 240 | children: [ 241 | Text( 242 | "Phone Numbers", 243 | style: GoogleFonts.raleway( 244 | fontSize: 20, 245 | color: context.colorScheme.onSurface, 246 | ), 247 | ), 248 | const SizedBox(height: 16), 249 | if (widget.contact.phones.isNotEmpty) 250 | ...widget.contact.phones 251 | .map((phone) => _buildPhoneWithActionIcons(context, phone)), 252 | ], 253 | ), 254 | ), 255 | ); 256 | } 257 | 258 | Widget _buildPhoneWithActionIcons(BuildContext context, var phone) { 259 | return Padding( 260 | padding: const EdgeInsets.only(bottom: 16), 261 | child: Row( 262 | children: [ 263 | Expanded( 264 | child: Text( 265 | phone, 266 | style: GoogleFonts.raleway( 267 | textStyle: context.textTheme.bodyLarge, 268 | color: context.colorScheme.onSurface, 269 | ), 270 | ), 271 | ), 272 | Wrap( 273 | spacing: 12, 274 | children: [ 275 | RoundedIconButton( 276 | context, 277 | icon: HugeIcons.strokeRoundedCall02, 278 | onTap: () { 279 | showDialog( 280 | context: context, 281 | builder: (context) => simChooserDialog(context, phone), 282 | ); 283 | }, 284 | size: 36, 285 | ), 286 | RoundedIconButton( 287 | context, 288 | icon: HugeIcons.strokeRoundedMessage01, 289 | onTap: () { 290 | ActivityService().sendSMS(phone); 291 | }, 292 | size: 36, 293 | ), 294 | ], 295 | ), 296 | ], 297 | ), 298 | ); 299 | } 300 | 301 | Widget _buildProfilePicture(BuildContext context) { 302 | return CircleAvatar( 303 | backgroundColor: context.colorScheme.secondaryContainer, 304 | radius: 70, 305 | backgroundImage: widget.contact.photo != null 306 | ? MemoryImage(widget.contact.photo!) 307 | : null, 308 | child: widget.contact.photo == null 309 | ? Icon( 310 | HugeIcons.strokeRoundedUser, 311 | size: 100, 312 | color: context.colorScheme.onSecondaryContainer, 313 | ) 314 | : null, 315 | ); 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /lib/ui/views/dialpad_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import 'package:google_fonts/google_fonts.dart'; 5 | import 'package:hugeicons/hugeicons.dart'; 6 | import 'package:revo/constants/pref.dart'; 7 | import 'package:revo/extentions/theme.dart'; 8 | import 'package:revo/services/cubit/contact_service.dart'; 9 | import 'package:revo/services/prefservice.dart'; 10 | import 'package:revo/ui/popups/sim_choose_popup.dart'; 11 | import 'package:revo/ui/views/common/matched_view.dart'; 12 | import 'package:revo/ui/views/dialpad_view/action_btn.dart'; 13 | import 'package:revo/ui/views/dialpad_view/dial_btn.dart'; 14 | import 'package:revo/utils/rounded_icon_btn.dart'; 15 | import 'package:revo/utils/utils.dart'; 16 | 17 | class DialPadView extends StatefulWidget { 18 | const DialPadView({super.key}); 19 | 20 | @override 21 | State createState() => _DialPadViewState(); 22 | } 23 | 24 | class _DialPadViewState extends State { 25 | String _number = ''; 26 | 27 | late final ScrollController _scrollController; 28 | late final FocusNode _focusNode; 29 | 30 | @override 31 | void initState() { 32 | _scrollController = ScrollController(); 33 | _focusNode = FocusNode(); 34 | WidgetsBinding.instance.addPostFrameCallback((_) { 35 | _focusNode.requestFocus(); 36 | }); 37 | SharedPrefService().onPreferenceChanged.listen((key) { 38 | if (key == PREF_DIALPAD_LETTERS) { 39 | setState(() {}); 40 | } 41 | }); 42 | super.initState(); 43 | } 44 | 45 | @override 46 | void dispose() { 47 | _scrollController.dispose(); 48 | _focusNode.dispose(); 49 | super.dispose(); 50 | } 51 | 52 | final List keys = [ 53 | '1', 54 | '2', 55 | '3', 56 | '4', 57 | '5', 58 | '6', 59 | '7', 60 | '8', 61 | '9', 62 | '*', 63 | '0', 64 | '#', 65 | ]; 66 | 67 | final Map subKeys = { 68 | '2': 'ABC', 69 | '3': 'DEF', 70 | '4': 'GHI', 71 | '5': 'JKL', 72 | '6': 'MNO', 73 | '7': 'PQRS', 74 | '8': 'TUV', 75 | '9': 'WXYZ', 76 | '0': '+', 77 | }; 78 | 79 | @override 80 | Widget build(BuildContext context) { 81 | return Scaffold( 82 | body: SafeArea( 83 | child: Column( 84 | mainAxisAlignment: MainAxisAlignment.end, 85 | children: [ 86 | Expanded( 87 | child: Padding( 88 | padding: const EdgeInsets.all(8.0), 89 | child: MatchedView( 90 | scrollController: _scrollController, 91 | number: _number, 92 | ), 93 | ), 94 | ), 95 | Container( 96 | color: context.colorScheme.secondaryContainer.withAlpha(50), 97 | child: Column( 98 | mainAxisSize: MainAxisSize.min, 99 | children: [ 100 | _number.isNotEmpty 101 | ? Padding( 102 | padding: const EdgeInsets.symmetric( 103 | horizontal: 20, 104 | vertical: 15, 105 | ), 106 | child: Text( 107 | _number, 108 | style: GoogleFonts.raleway( 109 | fontSize: 30, 110 | color: context.colorScheme.onSurface, 111 | ), 112 | ), 113 | ) 114 | : SizedBox(height: 30), 115 | GridView.builder( 116 | shrinkWrap: true, 117 | physics: NeverScrollableScrollPhysics(), 118 | itemCount: keys.length, 119 | gridDelegate: 120 | const SliverGridDelegateWithFixedCrossAxisCount( 121 | crossAxisCount: 3, 122 | mainAxisSpacing: 8, 123 | crossAxisSpacing: 8, 124 | childAspectRatio: 1.75, 125 | ), 126 | padding: const EdgeInsets.symmetric(horizontal: 20), 127 | itemBuilder: (context, index) { 128 | String key = keys[index]; 129 | return DialPadButton( 130 | mainText: key, 131 | subText: SharedPrefService() 132 | .getBool(PREF_DIALPAD_LETTERS, def: true) 133 | ? subKeys[key] 134 | : null, 135 | onUpdate: (String str) { 136 | setState(() { 137 | _number += str; 138 | }); 139 | }, 140 | ); 141 | }, 142 | ), 143 | SizedBox(height: 20), 144 | Padding( 145 | padding: const EdgeInsets.symmetric(horizontal: 25), 146 | child: Row( 147 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 148 | children: [ 149 | if (_number.isNotEmpty) 150 | RoundedIconButton( 151 | context, 152 | icon: HugeIcons.strokeRoundedUserAdd01, 153 | size: 40, 154 | onTap: () { 155 | hapticVibration(); 156 | context 157 | .read() 158 | .createNewContact(number: _number); 159 | }, 160 | ), 161 | Spacer(), 162 | DialActionButton( 163 | icon: HugeIcons.strokeRoundedSimcard01, 164 | label: 'Call', 165 | func: () { 166 | hapticVibration(); 167 | showDialog( 168 | context: context, 169 | builder: (context) => 170 | simChooserDialog(context, _number), 171 | ); 172 | }, 173 | ), 174 | Spacer(), 175 | if (_number.isNotEmpty) 176 | RoundedIconButton( 177 | context, 178 | icon: HugeIcons.strokeRoundedArrowLeft01, 179 | size: 40, 180 | onTap: () { 181 | hapticVibration(); 182 | setState(() { 183 | if (_number.isNotEmpty) { 184 | _number = _number.substring( 185 | 0, 186 | _number.length - 1, 187 | ); 188 | } 189 | }); 190 | }, 191 | onLongPress: () { 192 | HapticFeedback.vibrate(); 193 | setState(() { 194 | if (_number.isNotEmpty) { 195 | _number = ''; 196 | } 197 | }); 198 | }, 199 | ), 200 | ], 201 | ), 202 | ), 203 | SizedBox(height: 30), 204 | ], 205 | ), 206 | ), 207 | ], 208 | ), 209 | ), 210 | ); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /lib/ui/views/dialpad_view/action_btn.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:revo/extentions/theme.dart'; 3 | 4 | class DialActionButton extends StatelessWidget { 5 | final IconData icon; 6 | final String label; 7 | final Function()? func; 8 | 9 | const DialActionButton( 10 | {required this.icon, required this.label, this.func, super.key}); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return TextButton( 15 | style: TextButton.styleFrom( 16 | shape: RoundedRectangleBorder( 17 | borderRadius: BorderRadius.circular(50), 18 | ), 19 | backgroundColor: context.colorScheme.secondaryContainer.withAlpha(150), 20 | elevation: 0, 21 | padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 10), 22 | ), 23 | onPressed: func, 24 | child: Row( 25 | children: [ 26 | Icon( 27 | icon, 28 | color: context.colorScheme.onSurface, 29 | ), 30 | SizedBox( 31 | width: 2, 32 | ), 33 | Text( 34 | label, 35 | style: 36 | TextStyle(fontSize: 18, color: context.colorScheme.onSurface), 37 | ), 38 | ], 39 | ), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/ui/views/dialpad_view/dial_btn.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_fonts/google_fonts.dart'; 3 | import 'package:revo/constants/pref.dart'; 4 | import 'package:revo/extentions/theme.dart'; 5 | import 'package:flutter_dtmf/dtmf.dart'; 6 | import 'package:flutter/services.dart'; 7 | import 'package:revo/services/prefservice.dart'; 8 | import 'package:revo/utils/utils.dart'; 9 | 10 | class DialPadButton extends StatefulWidget { 11 | final String mainText; 12 | final String? subText; 13 | final Function(String) onUpdate; 14 | 15 | const DialPadButton({ 16 | required this.mainText, 17 | this.subText, 18 | required this.onUpdate, 19 | super.key, 20 | }); 21 | 22 | @override 23 | _DialPadButtonState createState() => _DialPadButtonState(); 24 | } 25 | 26 | class _DialPadButtonState extends State { 27 | @override 28 | Widget build(BuildContext context) { 29 | bool letters = SharedPrefService().getBool(PREF_DIALPAD_LETTERS, def: true); 30 | double textSz = widget.mainText == "*" ? 45 : 20; 31 | 32 | if (!letters) { 33 | textSz += 10; 34 | } 35 | return TextButton( 36 | style: TextButton.styleFrom( 37 | elevation: 0, 38 | shape: const RoundedRectangleBorder( 39 | borderRadius: BorderRadius.all(Radius.circular(50)), 40 | ), 41 | backgroundColor: context.colorScheme.secondaryContainer.withAlpha(150), 42 | overlayColor: context.colorScheme.onSurface, 43 | ), 44 | onPressed: () async { 45 | if (SharedPrefService().getBool(PREF_DTMF_TONE, def: true)) { 46 | await Dtmf.playTone(digits: widget.mainText, volume: 3); 47 | } 48 | hapticVibration(); 49 | widget.onUpdate(widget.mainText); 50 | }, 51 | child: Column( 52 | mainAxisAlignment: MainAxisAlignment.center, 53 | children: [ 54 | Text( 55 | widget.mainText, 56 | style: GoogleFonts.raleway( 57 | fontSize: textSz, 58 | fontWeight: FontWeight.normal, 59 | color: context.colorScheme.onSurface, 60 | ), 61 | ), 62 | if (widget.subText != null) 63 | Text( 64 | widget.subText!, 65 | style: GoogleFonts.raleway( 66 | fontSize: 12, 67 | color: context.colorScheme.onSurface, 68 | ), 69 | ), 70 | ], 71 | ), 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/ui/views/history_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:google_fonts/google_fonts.dart'; 4 | import 'package:hugeicons/hugeicons.dart'; 5 | import 'package:revo/extentions/datetime.dart'; 6 | import 'package:revo/extentions/theme.dart'; 7 | import 'package:revo/model/call_log.dart'; 8 | import 'package:revo/model/call_type.dart'; 9 | import 'package:revo/services/cubit/call_log_service.dart'; 10 | import 'package:revo/utils/center_text.dart'; 11 | import 'package:revo/utils/utils.dart'; 12 | 13 | class HistoryView extends StatefulWidget { 14 | List numbers; 15 | HistoryView({super.key, required this.numbers}); 16 | 17 | @override 18 | State createState() => _HistoryViewState(); 19 | } 20 | 21 | class _HistoryViewState extends State { 22 | late ScrollController _controller; 23 | 24 | @override 25 | void initState() { 26 | _controller = ScrollController(); 27 | super.initState(); 28 | } 29 | 30 | @override 31 | void dispose() { 32 | _controller.dispose(); 33 | super.dispose(); 34 | } 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | return Scaffold( 39 | appBar: AppBar( 40 | leading: IconButton( 41 | icon: Icon(HugeIcons.strokeRoundedArrowLeft01), 42 | onPressed: () => Navigator.of(context).pop(), 43 | ), 44 | title: const Text('Call History'), 45 | ), 46 | body: BlocBuilder>( 47 | builder: (BuildContext context, List state) { 48 | var logs = 49 | context.read().filterByNumber(widget.numbers); 50 | if (logs.isEmpty) { 51 | return CenterText( 52 | text: 'No call logs found.', 53 | ); 54 | } 55 | return ListView.builder( 56 | itemCount: logs.length, 57 | itemBuilder: (context, i) => _displayHistory(context, logs[i]), 58 | ); 59 | }, 60 | ), 61 | ); 62 | } 63 | 64 | Widget _displayHistory(BuildContext context, CallLog history) { 65 | String underlineText = 66 | '${history.type.getText()} ${convertSecondsToHMS(int.parse(history.duration))}'; 67 | 68 | return Padding( 69 | padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 16), 70 | child: Container( 71 | decoration: BoxDecoration( 72 | color: context.colorScheme.secondaryContainer.withAlpha(100), 73 | shape: BoxShape.rectangle, 74 | borderRadius: BorderRadius.circular(25), 75 | ), 76 | child: ListTile( 77 | leading: Container( 78 | width: 50, 79 | height: 50, 80 | decoration: BoxDecoration( 81 | color: context.colorScheme.primary.withAlpha(25), 82 | shape: BoxShape.circle, 83 | ), 84 | child: Icon(history.type.getIcon(), 85 | color: history.type.getColor(), size: 28), 86 | ), 87 | title: Text( 88 | history.date.getContextAwareDateTime(), 89 | style: GoogleFonts.raleway(fontSize: 16), 90 | ), 91 | subtitle: Column( 92 | crossAxisAlignment: CrossAxisAlignment.start, 93 | children: [ 94 | Text( 95 | history.simDisplayName, 96 | style: const TextStyle(color: Colors.grey), 97 | ), 98 | Text( 99 | underlineText, 100 | style: const TextStyle(color: Colors.grey), 101 | ), 102 | Text( 103 | history.number, 104 | style: const TextStyle(color: Colors.grey), 105 | ), 106 | ], 107 | ), 108 | ), 109 | ), 110 | ); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /lib/ui/views/home_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:hugeicons/hugeicons.dart'; 4 | import 'package:revo/constants/routes.dart'; 5 | import 'package:revo/extentions/theme.dart'; 6 | import 'package:revo/services/cubit/contact_service.dart'; 7 | import 'package:revo/services/prefservice.dart'; 8 | import 'package:revo/ui/popups/welcome_changelog.dart'; 9 | import 'package:revo/ui/theme/handler.dart'; 10 | import 'package:revo/ui/views/common/constants.dart'; 11 | import 'package:revo/ui/views/home_view/appbar_view.dart'; 12 | import 'package:revo/ui/views/home_view/contacts_view.dart'; 13 | import 'package:revo/ui/views/home_view/fav_view.dart'; 14 | import 'package:revo/ui/views/home_view/navigation_view.dart'; 15 | import 'package:revo/ui/views/home_view/recents_view.dart'; 16 | 17 | class HomeView extends StatefulWidget { 18 | const HomeView({super.key}); 19 | 20 | @override 21 | State createState() => _HomeViewState(); 22 | } 23 | 24 | class _HomeViewState extends State { 25 | late final PageController _pageController; 26 | int _currentPage = 0; 27 | 28 | @override 29 | void initState() { 30 | _pageController = PageController(); 31 | _pageController.addListener(() { 32 | final pageIndex = _pageController.page?.round() ?? 0; 33 | if (pageIndex != _currentPage) { 34 | setState(() { 35 | _currentPage = pageIndex; 36 | }); 37 | } 38 | }); 39 | super.initState(); 40 | 41 | WidgetsBinding.instance.addPostFrameCallback((_) { 42 | Future.delayed(Duration(milliseconds: 100), () async { 43 | await context.read().initTheme(); 44 | await SharedPrefService().init(); 45 | bool flag = SharedPrefService().getBool("WelcomeShown$version"); 46 | if (!flag) { 47 | showDialog( 48 | context: context, 49 | builder: (context) => welcomePopup(context, version, changelog), 50 | ); 51 | SharedPrefService().saveBool("WelcomeShown$version", true); 52 | } 53 | }); 54 | }); 55 | } 56 | 57 | @override 58 | void dispose() { 59 | _pageController.dispose(); 60 | super.dispose(); 61 | } 62 | 63 | @override 64 | Widget build(BuildContext context) { 65 | return Scaffold( 66 | appBar: AppBarView(), 67 | body: Padding( 68 | padding: const EdgeInsets.symmetric(horizontal: 8.0), 69 | child: PageView( 70 | controller: _pageController, 71 | children: const [ 72 | RecentsView(), 73 | ContactsView(), 74 | FavView(), 75 | ], 76 | ), 77 | ), 78 | bottomNavigationBar: NavigationView(pageController: _pageController), 79 | floatingActionButton: FloatingActionButton( 80 | backgroundColor: context.colorScheme.secondaryContainer, 81 | onPressed: () { 82 | if (_pageController.page == 1.0) { 83 | context.read().createNewContact(); 84 | } else { 85 | Navigator.of(context).pushNamed(dialpadRoute); 86 | } 87 | }, 88 | elevation: 1, 89 | child: Icon(_currentPage == 1.0 90 | ? HugeIcons.strokeRoundedUserAdd01 91 | : HugeIcons.strokeRoundedDialpadCircle02), 92 | ), 93 | ); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /lib/ui/views/home_view/appbar_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:google_fonts/google_fonts.dart'; 4 | import 'package:hugeicons/hugeicons.dart'; 5 | import 'package:revo/constants/routes.dart'; 6 | import 'package:revo/extentions/theme.dart'; 7 | import 'package:revo/services/cubit/contact_service.dart'; 8 | import 'package:simple_barcode_scanner/simple_barcode_scanner.dart'; 9 | 10 | class AppBarView extends StatelessWidget implements PreferredSizeWidget { 11 | @override 12 | final Size preferredSize; 13 | 14 | const AppBarView({super.key}) 15 | : preferredSize = const Size.fromHeight(kToolbarHeight); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return AppBar( 20 | title: InkWell( 21 | onTap: () { 22 | Navigator.pushNamed(context, searchRoute); 23 | }, 24 | borderRadius: BorderRadius.circular(50), 25 | splashColor: context.colorScheme.secondaryContainer, 26 | child: Container( 27 | decoration: BoxDecoration( 28 | color: context.colorScheme.secondaryContainer.withAlpha(200), 29 | borderRadius: BorderRadius.circular(50), 30 | ), 31 | child: Row( 32 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 33 | children: [ 34 | IconButton( 35 | onPressed: () async { 36 | // Navigator.pushNamed(context, qrScanRoute); 37 | String? res = await SimpleBarcodeScanner.scanBarcode( 38 | context, 39 | barcodeAppBar: const BarcodeAppBar( 40 | appBarTitle: 'Scan QR to add contact', 41 | centerTitle: false, 42 | enableBackButton: true, 43 | backButtonIcon: 44 | Icon(HugeIcons.strokeRoundedArrowLeft01), 45 | ), 46 | scanType: ScanType.qr, 47 | isShowFlashIcon: true, 48 | delayMillis: 1000, 49 | ) ?? 50 | ""; 51 | 52 | if (res.startsWith("BEGIN:VCARD") && 53 | res.endsWith("END:VCARD")) { 54 | await context 55 | .read() 56 | .insertContactFromVCard(res); 57 | ScaffoldMessenger.of(context).showSnackBar( 58 | const SnackBar( 59 | content: Text('Contact added successfully!')), 60 | ); 61 | } else { 62 | ScaffoldMessenger.of(context).showSnackBar( 63 | const SnackBar(content: Text('Invalid vCard format!')), 64 | ); 65 | } 66 | }, 67 | icon: Icon( 68 | HugeIcons.strokeRoundedQrCode, 69 | ), 70 | ), 71 | Text( 72 | 'Search in Rivo', 73 | style: GoogleFonts.raleway( 74 | fontSize: 20, 75 | color: context.colorScheme.onSurface, 76 | ), 77 | ), 78 | IconButton( 79 | onPressed: () { 80 | Navigator.pushNamed(context, settingsRoute); 81 | }, 82 | icon: Icon( 83 | HugeIcons.strokeRoundedSettings03, 84 | ), 85 | ), 86 | ], 87 | ), 88 | ), 89 | ), 90 | centerTitle: true, 91 | ); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /lib/ui/views/home_view/contacts_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:google_fonts/google_fonts.dart'; 4 | import 'package:revo/constants/routes.dart'; 5 | import 'package:revo/extentions/theme.dart'; 6 | import 'package:revo/model/contact.dart'; 7 | import 'package:revo/services/cubit/contact_service.dart'; 8 | import 'package:revo/utils/center_text.dart'; 9 | import 'package:revo/utils/circle_profile.dart'; 10 | 11 | class ContactsView extends StatefulWidget { 12 | const ContactsView({super.key}); 13 | 14 | @override 15 | State createState() => _ContactsViewState(); 16 | } 17 | 18 | class _ContactsViewState extends State { 19 | late final ScrollController _controller; 20 | 21 | @override 22 | void initState() { 23 | _controller = ScrollController(); 24 | super.initState(); 25 | } 26 | 27 | @override 28 | void dispose() { 29 | _controller.dispose(); 30 | super.dispose(); 31 | } 32 | 33 | Future _refreshContacts(BuildContext context) async { 34 | context.read().refresh(); 35 | } 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | return BlocBuilder>( 40 | builder: (context, state) { 41 | if (state.isEmpty) { 42 | return RefreshIndicator( 43 | onRefresh: () => _refreshContacts(context), 44 | child: ListView( 45 | physics: AlwaysScrollableScrollPhysics(), 46 | children: [ 47 | CenterText(text: 'No contacts found'), 48 | ], 49 | ), 50 | ); 51 | } 52 | 53 | return RefreshIndicator( 54 | onRefresh: () => _refreshContacts(context), 55 | child: Scrollbar( 56 | trackVisibility: true, 57 | thickness: 2.5, 58 | interactive: true, 59 | radius: Radius.circular(30), 60 | controller: _controller, 61 | child: ListView.builder( 62 | itemCount: state.length, 63 | controller: _controller, 64 | physics: AlwaysScrollableScrollPhysics(), 65 | itemBuilder: (context, i) => _displayContact(context, state, i), 66 | ), 67 | ), 68 | ); 69 | }, 70 | ); 71 | } 72 | 73 | bool _shouldShowHeader(List contacts, int i) { 74 | return i == 0 || 75 | contacts[i].fullName.isNotEmpty && 76 | contacts[i].fullName[0] != contacts[i - 1].fullName[0]; 77 | } 78 | 79 | Widget _displayContact( 80 | BuildContext context, 81 | List contacts, 82 | int i, 83 | ) { 84 | return Column( 85 | crossAxisAlignment: CrossAxisAlignment.start, 86 | children: [ 87 | if (_shouldShowHeader(contacts, i)) 88 | Padding( 89 | padding: EdgeInsets.fromLTRB(30, i == 0 ? 10 : 50, 0, 0), 90 | child: Text( 91 | contacts[i].fullName[0], 92 | style: GoogleFonts.raleway( 93 | fontSize: 20, 94 | color: context.colorScheme.onSurface.withAlpha(200), 95 | ), 96 | ), 97 | ), 98 | ListTile( 99 | shape: RoundedRectangleBorder( 100 | borderRadius: BorderRadius.circular(20), 101 | ), 102 | title: Row( 103 | children: [ 104 | const SizedBox(width: 10), 105 | CircleProfile( 106 | name: contacts[i].displayName, 107 | profile: contacts[i].photo, 108 | size: 30, 109 | ), 110 | const SizedBox(width: 10), 111 | Text( 112 | contacts[i].displayName, 113 | style: GoogleFonts.raleway( 114 | fontSize: 16, 115 | color: context.colorScheme.onSurface, 116 | ), 117 | ), 118 | ], 119 | ), 120 | onTap: () async { 121 | await Navigator.of(context).pushNamed( 122 | contactInfoRoute, 123 | arguments: contacts[i], 124 | ); 125 | }, 126 | ), 127 | ], 128 | ); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /lib/ui/views/home_view/fav_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:google_fonts/google_fonts.dart'; 4 | import 'package:revo/extentions/theme.dart'; 5 | import 'package:revo/model/contact.dart'; 6 | import 'package:revo/services/cubit/contact_service.dart'; 7 | import 'package:revo/ui/views/contactinfo_view.dart'; 8 | import 'package:revo/utils/center_text.dart'; 9 | import 'package:revo/utils/circle_profile.dart'; 10 | 11 | class FavView extends StatelessWidget { 12 | const FavView({super.key}); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return BlocBuilder>( 17 | builder: (context, state) { 18 | var stars = context.read().filterByStars(); 19 | if (stars.isEmpty) { 20 | return CenterText(text: 'No favorite contacts found'); 21 | } 22 | return Padding( 23 | padding: const EdgeInsets.only(top: 30.0), 24 | child: GridView.builder( 25 | padding: const EdgeInsets.all(8.0), 26 | shrinkWrap: true, 27 | gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( 28 | crossAxisCount: 3, 29 | crossAxisSpacing: 16.0, 30 | mainAxisSpacing: 16.0, 31 | childAspectRatio: 0.8, 32 | ), 33 | itemCount: stars.length, 34 | itemBuilder: (context, i) { 35 | return _buildFavs(context, stars[i]); 36 | }, 37 | ), 38 | ); 39 | }, 40 | ); 41 | } 42 | 43 | Widget _buildFavs(BuildContext context, Contact contact) { 44 | return InkWell( 45 | borderRadius: BorderRadius.all(Radius.circular(15)), 46 | onTap: () { 47 | Navigator.of(context) 48 | .push(MaterialPageRoute(builder: (_) => ContactInfoView(contact))); 49 | }, 50 | child: Column( 51 | children: [ 52 | CircleProfile( 53 | name: contact.displayName, 54 | profile: contact.photo, 55 | size: 45, 56 | ), 57 | const SizedBox(height: 10), 58 | Center( 59 | child: Text( 60 | contact.displayName, 61 | textAlign: TextAlign.center, 62 | style: GoogleFonts.raleway( 63 | color: context.colorScheme.onSurface, 64 | fontSize: 14, 65 | ), 66 | ), 67 | ), 68 | ], 69 | ), 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/ui/views/home_view/navigation_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hugeicons/hugeicons.dart'; 3 | import 'package:revo/constants/pref.dart'; 4 | import 'package:revo/extentions/theme.dart'; 5 | import 'package:revo/services/prefservice.dart'; 6 | 7 | class NavigationView extends StatefulWidget { 8 | final PageController pageController; 9 | 10 | const NavigationView({super.key, required this.pageController}); 11 | 12 | @override 13 | State createState() => _NavigationViewState(); 14 | } 15 | 16 | class _NavigationViewState extends State { 17 | int _selectedIndex = 0; 18 | bool _prevFlag = false; 19 | 20 | @override 21 | void initState() { 22 | widget.pageController.addListener(() { 23 | setState(() { 24 | _selectedIndex = widget.pageController.page?.round() ?? 0; 25 | }); 26 | }); 27 | SharedPrefService().onPreferenceChanged.listen((key) { 28 | if (key == PREF_ICON_ONLY_BOTTOMSHEET || 29 | key == PREF_ALWAYS_SHOW_SELECTED_IN_BOTTOMSHEET) { 30 | setState(() {}); 31 | } 32 | }); 33 | super.initState(); 34 | } 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | bool onlyIcons = SharedPrefService().getBool(PREF_ICON_ONLY_BOTTOMSHEET); 39 | bool alwaysSelectedIcon = 40 | SharedPrefService().getBool(PREF_ALWAYS_SHOW_SELECTED_IN_BOTTOMSHEET); 41 | return NavigationBar( 42 | backgroundColor: context.colorScheme.surface, 43 | elevation: 3, 44 | indicatorColor: context.colorScheme.secondaryContainer, 45 | surfaceTintColor: context.colorScheme.surfaceTint, 46 | labelBehavior: onlyIcons 47 | ? alwaysSelectedIcon 48 | ? NavigationDestinationLabelBehavior.onlyShowSelected 49 | : NavigationDestinationLabelBehavior.alwaysHide 50 | : NavigationDestinationLabelBehavior.alwaysShow, 51 | destinations: [ 52 | NavigationDestination( 53 | icon: Icon(HugeIcons.strokeRoundedClock01), 54 | label: 'Recents', 55 | selectedIcon: Icon(HugeIcons.strokeRoundedClock01), 56 | ), 57 | NavigationDestination( 58 | icon: Icon(HugeIcons.strokeRoundedUser), 59 | label: 'Contacts', 60 | selectedIcon: Icon(HugeIcons.strokeRoundedUser), 61 | ), 62 | NavigationDestination( 63 | icon: Icon(HugeIcons.strokeRoundedFavourite), 64 | label: 'Favorites', 65 | selectedIcon: Icon(HugeIcons.strokeRoundedFavourite), 66 | ), 67 | ], 68 | onDestinationSelected: (index) { 69 | setState(() { 70 | _selectedIndex = index; 71 | }); 72 | widget.pageController.animateToPage( 73 | index, 74 | duration: Duration(milliseconds: 250), 75 | curve: Curves.easeInOut, 76 | ); 77 | }, 78 | selectedIndex: _selectedIndex, 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/ui/views/home_view/recents_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_fonts/google_fonts.dart'; 3 | import 'package:hugeicons/hugeicons.dart'; 4 | import 'package:revo/constants/routes.dart'; 5 | import 'package:revo/extentions/datetime.dart'; 6 | import 'package:revo/extentions/theme.dart'; 7 | import 'package:revo/model/call_log.dart'; 8 | import 'package:revo/model/call_type.dart'; 9 | import 'package:revo/model/contact.dart'; 10 | import 'package:revo/services/cubit/call_log_service.dart'; 11 | import 'package:revo/services/cubit/contact_service.dart'; 12 | import 'package:revo/ui/popups/sim_choose_popup.dart'; 13 | import 'package:revo/utils/circle_profile.dart'; 14 | import 'package:flutter_bloc/flutter_bloc.dart'; 15 | import 'package:revo/utils/rounded_icon_btn.dart'; 16 | import 'package:revo/utils/utils.dart'; 17 | 18 | class RecentsView extends StatefulWidget { 19 | const RecentsView({super.key}); 20 | 21 | @override 22 | State createState() => _RecentsViewState(); 23 | } 24 | 25 | class _RecentsViewState extends State { 26 | late final ScrollController _controller; 27 | 28 | @override 29 | void initState() { 30 | _controller = ScrollController(); 31 | super.initState(); 32 | } 33 | 34 | @override 35 | void dispose() { 36 | _controller.dispose(); 37 | super.dispose(); 38 | } 39 | 40 | Future _refreshLogs(BuildContext context) async { 41 | // Call a method in your CallLogService to refresh the call logs. 42 | await context.read().refresh(); // Example method 43 | } 44 | 45 | @override 46 | Widget build(BuildContext context) { 47 | return Scrollbar( 48 | trackVisibility: true, 49 | thickness: 2.5, 50 | interactive: true, 51 | radius: Radius.circular(30), 52 | controller: _controller, 53 | child: RefreshIndicator( 54 | onRefresh: () => _refreshLogs(context), 55 | child: BlocBuilder>( 56 | builder: (BuildContext context, List state) { 57 | if (state.isEmpty) { 58 | return ListView( 59 | physics: AlwaysScrollableScrollPhysics(), 60 | children: const [ 61 | Center( 62 | child: Padding( 63 | padding: EdgeInsets.all(20.0), 64 | child: Text('No call logs found.'), 65 | ), 66 | ), 67 | ], 68 | ); 69 | } 70 | 71 | return ListView.builder( 72 | itemCount: state.length, 73 | controller: _controller, 74 | physics: AlwaysScrollableScrollPhysics(), 75 | itemBuilder: (context, i) { 76 | return _buildLog( 77 | context, 78 | state[i], 79 | _shouldShowHeader(state, i), 80 | ); 81 | }, 82 | ); 83 | }, 84 | ), 85 | ), 86 | ); 87 | } 88 | 89 | bool _shouldShowHeader(List logs, int i) { 90 | return i == 0 || logs[i].date.weekday != logs[i - 1].date.weekday; 91 | } 92 | 93 | Widget _buildLog(BuildContext context, CallLog log, bool showDateHeader) { 94 | return Column( 95 | mainAxisAlignment: MainAxisAlignment.start, 96 | crossAxisAlignment: CrossAxisAlignment.start, 97 | children: [ 98 | if (showDateHeader) 99 | Padding( 100 | padding: const EdgeInsets.fromLTRB(20, 50, 0, 0), 101 | child: Text( 102 | log.date.getContextAwareDate(), 103 | style: GoogleFonts.raleway( 104 | fontSize: 20, 105 | color: context.colorScheme.onSurface, 106 | ), 107 | ), 108 | ), 109 | ListTile( 110 | onTap: () async { 111 | showDialog( 112 | context: context, 113 | builder: (context) => simChooserDialog(context, log.number), 114 | ); 115 | }, 116 | shape: RoundedRectangleBorder( 117 | borderRadius: BorderRadius.circular(20), 118 | ), 119 | leading: CircleProfile( 120 | name: log.name, 121 | profile: log.profile, 122 | size: 30, 123 | ), 124 | title: Text( 125 | log.displayName, 126 | style: GoogleFonts.raleway( 127 | fontSize: 16, 128 | color: context.colorScheme.onSurface, 129 | ), 130 | ), 131 | trailing: RoundedIconButton( 132 | context, 133 | icon: HugeIcons.strokeRoundedArrowRight01, 134 | size: 30, 135 | onTap: () async { 136 | Contact contact = 137 | context.read().findByName(log.name); 138 | if (contact.phones.isEmpty) { 139 | contact = 140 | context.read().findByNumber(log.number); 141 | } 142 | await Navigator.of(context) 143 | .pushNamed(contactInfoRoute, arguments: contact); 144 | }, 145 | ), 146 | subtitle: Column( 147 | mainAxisAlignment: MainAxisAlignment.start, 148 | crossAxisAlignment: CrossAxisAlignment.start, 149 | children: [ 150 | Row( 151 | children: [ 152 | Icon( 153 | log.type.getIcon(), 154 | color: log.type.getColor(), 155 | size: 16, 156 | ), 157 | SizedBox(width: 5), 158 | Text( 159 | log.date.getContextAwareDateTime(), 160 | style: GoogleFonts.raleway( 161 | fontSize: 12, 162 | color: log.type.getColor(), 163 | ), 164 | ), 165 | ], 166 | ), 167 | Text( 168 | convertSecondsToHMS(int.parse(log.duration)), 169 | style: GoogleFonts.raleway( 170 | fontSize: 12, 171 | color: context.colorScheme.onSurface.withAlpha(200), 172 | ), 173 | ), 174 | ], 175 | ), 176 | ), 177 | ], 178 | ); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /lib/ui/views/qr_scanner_view.dart: -------------------------------------------------------------------------------- 1 | // class SimpleBarcodeScannerView extends StatelessWidget { 2 | // @override 3 | // Widget build(BuildContext context) { 4 | // bool isProcessingScan = false; 5 | 6 | // return Scaffold( 7 | // appBar: AppBar( 8 | // leading: IconButton( 9 | // icon: Icon(HugeIcons.strokeRoundedArrowLeft01), 10 | // onPressed: () => Navigator.of(context).pop(), // Close scanner screen 11 | // ), 12 | // title: const Text('Scan QR to add contact'), 13 | // ), 14 | // body: SimpleBarcodeScanner.streamBarcode( 15 | // context, 16 | // barcodeAppBar: const BarcodeAppBar( 17 | // appBarTitle: 'Scan QR to add contact', 18 | // centerTitle: false, 19 | // enableBackButton: true, 20 | // backButtonIcon: Icon(HugeIcons.strokeRoundedArrowLeft01), 21 | // ), 22 | // scanType: ScanType.qr, 23 | // isShowFlashIcon: true, 24 | // delayMillis: 500, 25 | // ).listen((event) async { 26 | // if (isProcessingScan) return; 27 | // isProcessingScan = true; 28 | 29 | // if (event.startsWith("BEGIN:VCARD") && event.endsWith("END:VCARD")) { 30 | // await context.read().insertContactFromVCard(event); 31 | // ScaffoldMessenger.of(context).showSnackBar( 32 | // const SnackBar(content: Text('Contact added successfully!')), 33 | // ); 34 | // Navigator.of(context).pop(); // Close scanner window after success 35 | // } else { 36 | // ScaffoldMessenger.of(context).showSnackBar( 37 | // const SnackBar(content: Text('Invalid vCard format!')), 38 | // ); 39 | // } 40 | // isProcessingScan = false; 41 | // }), 42 | // ); 43 | // } 44 | // } 45 | -------------------------------------------------------------------------------- /lib/ui/views/search_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_fonts/google_fonts.dart'; 3 | import 'package:hugeicons/hugeicons.dart'; 4 | import 'package:revo/extentions/theme.dart'; 5 | import 'package:revo/ui/views/common/matched_view.dart'; 6 | import 'package:revo/utils/center_text.dart'; 7 | 8 | class SearchView extends StatefulWidget { 9 | const SearchView({super.key}); 10 | 11 | @override 12 | _SearchViewState createState() => _SearchViewState(); 13 | } 14 | 15 | class _SearchViewState extends State { 16 | late final TextEditingController _controller; 17 | String _searchQuery = ''; 18 | 19 | late final ScrollController _scrollController; 20 | late final FocusNode _focusNode; 21 | 22 | @override 23 | void initState() { 24 | _controller = TextEditingController(); 25 | _scrollController = ScrollController(); 26 | _focusNode = FocusNode(); 27 | WidgetsBinding.instance.addPostFrameCallback((_) { 28 | _focusNode.requestFocus(); 29 | }); 30 | super.initState(); 31 | } 32 | 33 | @override 34 | void dispose() { 35 | _scrollController.dispose(); 36 | _controller.dispose(); 37 | _focusNode.dispose(); 38 | super.dispose(); 39 | } 40 | 41 | @override 42 | Widget build(BuildContext context) { 43 | return Scaffold( 44 | appBar: AppBar( 45 | leading: IconButton( 46 | icon: Icon(HugeIcons.strokeRoundedArrowLeft01), 47 | onPressed: () => Navigator.of(context).pop(), 48 | ), 49 | title: _buildSearchBox(), 50 | backgroundColor: context.colorScheme.surfaceTint.withAlpha(25), 51 | elevation: 0, 52 | ), 53 | body: SafeArea( 54 | child: Padding( 55 | padding: EdgeInsets.only(left: 16), 56 | child: MatchedView( 57 | scrollController: _scrollController, 58 | number: _searchQuery, 59 | ), 60 | ), 61 | ), 62 | ); 63 | } 64 | 65 | Widget _buildSearchBox() { 66 | return Container( 67 | decoration: BoxDecoration( 68 | borderRadius: BorderRadius.circular(50), 69 | ), 70 | child: TextField( 71 | focusNode: _focusNode, 72 | controller: _controller, 73 | style: GoogleFonts.raleway( 74 | color: context.colorScheme.onSurface, 75 | ), 76 | decoration: InputDecoration( 77 | hintText: 'Search name/ number...', 78 | hintStyle: GoogleFonts.raleway( 79 | color: Colors.grey, 80 | ), 81 | border: InputBorder.none, 82 | ), 83 | onChanged: (query) { 84 | setState(() { 85 | _searchQuery = query; 86 | }); 87 | }, 88 | ), 89 | ); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /lib/ui/views/settings_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_fonts/google_fonts.dart'; 3 | import 'package:hugeicons/hugeicons.dart'; 4 | import 'package:revo/extentions/theme.dart'; 5 | import 'package:revo/ui/views/settings_view/about.dart'; 6 | import 'package:revo/ui/views/settings_view/call.dart'; 7 | import 'package:revo/ui/views/settings_view/sound.dart'; 8 | import 'package:revo/ui/views/settings_view/user_interface.dart'; 9 | import 'package:revo/utils/center_text.dart'; 10 | import 'package:revo/utils/menu_tile.dart'; 11 | import 'package:revo/utils/utils.dart'; 12 | 13 | class SettingsView extends StatelessWidget { 14 | const SettingsView({super.key}); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | return Scaffold( 19 | appBar: AppBar( 20 | leading: IconButton( 21 | icon: Icon(HugeIcons.strokeRoundedArrowLeft01), 22 | onPressed: () => Navigator.of(context).pop(), 23 | ), 24 | title: Text( 25 | 'Settings', 26 | style: GoogleFonts.raleway( 27 | fontSize: 20, 28 | fontWeight: FontWeight.w600, 29 | color: context.colorScheme.onSurface, 30 | ), 31 | ), 32 | ), 33 | body: ListView( 34 | padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), 35 | children: [ 36 | Container( 37 | decoration: BoxDecoration( 38 | color: Theme.of(context).colorScheme.primaryContainer, 39 | borderRadius: BorderRadius.all(Radius.circular(15)), 40 | ), 41 | child: ListTile( 42 | onTap: () async { 43 | await launchURL('https://www.patreon.com/grinch_'); 44 | }, 45 | shape: RoundedRectangleBorder( 46 | borderRadius: BorderRadius.all(Radius.circular(15)), 47 | ), 48 | leading: Icon( 49 | HugeIcons.strokeRoundedFavourite, 50 | size: 40, 51 | ), 52 | title: Column( 53 | children: [ 54 | CenterText( 55 | text: "Help keep Rivo free for everyone.", 56 | ), 57 | CenterText(text: "Consider Donating!"), 58 | ], 59 | ), 60 | ), 61 | ), 62 | const SizedBox( 63 | height: 30, 64 | ), 65 | CenterText(text: "This section is work in progress"), 66 | MenuTile( 67 | title: 'User Interface', 68 | subtitle: 'Customize looks & behaviors', 69 | icon: HugeIcons.strokeRoundedImage02, 70 | onTap: () { 71 | Navigator.of(context).push( 72 | MaterialPageRoute(builder: (context) => UserInterfaceView()), 73 | ); 74 | }, 75 | isFirst: true, 76 | ), 77 | MenuTile( 78 | title: 'Sound & Vibration', 79 | subtitle: 'Manage ringtones & volume', 80 | icon: HugeIcons.strokeRoundedVolumeHigh, 81 | onTap: () { 82 | Navigator.of(context).push( 83 | MaterialPageRoute(builder: (context) => SoundView()), 84 | ); 85 | }, 86 | isLast: true, 87 | ), 88 | const SizedBox(height: 10.0), 89 | MenuTile( 90 | title: 'Blocklist', 91 | subtitle: 'Block calls from people', 92 | icon: HugeIcons.strokeRoundedCallBlocked02, 93 | onTap: () {}, 94 | isFirst: true, 95 | ), 96 | MenuTile( 97 | title: 'Call Settings', 98 | subtitle: 'Incoming call settings', 99 | icon: HugeIcons.strokeRoundedCallIncoming03, 100 | onTap: () { 101 | // Navigator.of(context).push( 102 | // MaterialPageRoute(builder: (context) => CallView()), 103 | // ); 104 | }, 105 | isLast: true, 106 | ), 107 | const SizedBox(height: 10.0), 108 | MenuTile( 109 | title: 'About', 110 | subtitle: 'Information about the dialer app', 111 | icon: HugeIcons.strokeRoundedInformationCircle, 112 | onTap: () { 113 | Navigator.of(context).push( 114 | MaterialPageRoute(builder: (context) => AboutView()), 115 | ); 116 | }, 117 | isFirst: true, 118 | isLast: true, 119 | ), 120 | const SizedBox(height: 12.0), 121 | Center( 122 | child: Text( 123 | '© Copyright Grinch_ 2025', 124 | style: TextStyle( 125 | fontSize: 14, 126 | color: Colors.grey, 127 | ), 128 | ), 129 | ), 130 | ], 131 | ), 132 | ); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /lib/ui/views/settings_view/about.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_fonts/google_fonts.dart'; 3 | import 'package:hugeicons/hugeicons.dart'; 4 | import 'package:revo/extentions/theme.dart'; 5 | import 'package:revo/ui/views/common/constants.dart'; 6 | import 'package:revo/utils/menu_tile.dart'; 7 | import 'package:revo/utils/utils.dart'; 8 | 9 | class AboutView extends StatelessWidget { 10 | const AboutView({super.key}); 11 | 12 | final String githubUrl = "https://github.com/user-grinch/Rivo"; 13 | final String patreonUrl = "https://www.patreon.com/grinch_"; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Scaffold( 18 | appBar: AppBar( 19 | leading: IconButton( 20 | icon: Icon(HugeIcons.strokeRoundedArrowLeft01), 21 | onPressed: () => Navigator.of(context).pop(), 22 | ), 23 | title: Text( 24 | 'About', 25 | style: GoogleFonts.raleway( 26 | fontSize: 20, 27 | fontWeight: FontWeight.w600, 28 | color: context.colorScheme.onSurface, 29 | ), 30 | ), 31 | ), 32 | body: Padding( 33 | padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), 34 | child: Column( 35 | crossAxisAlignment: CrossAxisAlignment.center, 36 | children: [ 37 | Image.asset( 38 | 'assets/icon.png', 39 | width: 100, 40 | height: 100, 41 | fit: BoxFit.cover, 42 | ), 43 | const SizedBox(height: 12), 44 | Text( 45 | 'Rivo', 46 | style: GoogleFonts.raleway( 47 | fontSize: 26, 48 | fontWeight: FontWeight.w700, 49 | color: context.colorScheme.onBackground, 50 | ), 51 | ), 52 | const SizedBox(height: 20), 53 | Container( 54 | decoration: BoxDecoration( 55 | color: Theme.of(context) 56 | .colorScheme 57 | .secondaryContainer 58 | .withAlpha(110), 59 | borderRadius: BorderRadius.all( 60 | Radius.circular(15), 61 | ), 62 | ), 63 | child: Padding( 64 | padding: const EdgeInsets.all(16.0), 65 | child: Column( 66 | crossAxisAlignment: CrossAxisAlignment.start, 67 | children: [ 68 | Text( 69 | 'About the App', 70 | style: GoogleFonts.raleway( 71 | fontSize: 18, 72 | fontWeight: FontWeight.w600, 73 | color: context.colorScheme.onSurface, 74 | ), 75 | ), 76 | const SizedBox(height: 8), 77 | Text( 78 | 'Rivo is a modern dialer app that brings simplicity and elegance to calling. ' 79 | 'Designed with Material You, it adapts seamlessly to your theme while ensuring a smooth and intuitive experience.', 80 | style: GoogleFonts.raleway( 81 | fontSize: 15, 82 | color: context.colorScheme.onSurfaceVariant, 83 | ), 84 | ), 85 | ], 86 | ), 87 | ), 88 | ), 89 | const SizedBox(height: 10), 90 | MenuTile( 91 | title: 'Author', 92 | subtitle: 'Grinch_', 93 | icon: HugeIcons.strokeRoundedUser, 94 | onTap: () {}, 95 | isFirst: true, 96 | ), 97 | MenuTile( 98 | title: 'Version', 99 | subtitle: version, 100 | icon: HugeIcons.strokeRoundedInformationCircle, 101 | onTap: () {}, 102 | isLast: true, 103 | ), 104 | const SizedBox(height: 10), 105 | MenuTile( 106 | title: 'Source Code', 107 | subtitle: 'View the source code on GitHub', 108 | icon: HugeIcons.strokeRoundedGithub01, 109 | onTap: () async => 110 | await launchURL('https://github.com/user-grinch/Rivo'), 111 | isFirst: true, 112 | ), 113 | MenuTile( 114 | title: 'Support Us on Patreon', 115 | subtitle: 'Contribute to our development', 116 | icon: HugeIcons.strokeRoundedFavourite, 117 | onTap: () async => 118 | await launchURL('https://www.patreon.com/grinch_'), 119 | isLast: true, 120 | ), 121 | ], 122 | ), 123 | ), 124 | ); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /lib/ui/views/settings_view/call.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_fonts/google_fonts.dart'; 3 | import 'package:hugeicons/hugeicons.dart'; 4 | import 'package:revo/extentions/theme.dart'; 5 | import 'package:revo/utils/menu_tile.dart'; 6 | import 'package:revo/utils/switch_tile.dart'; 7 | 8 | class CallView extends StatefulWidget { 9 | const CallView({super.key}); 10 | 11 | @override 12 | State createState() => _CallViewState(); 13 | } 14 | 15 | class _CallViewState extends State { 16 | bool disableMaterialYou = false; 17 | bool hideAvatarInitials = false; 18 | bool showAvatarPictures = true; 19 | bool iconOnlyBottomNav = false; 20 | bool enableCustomCallScreen = false; 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return Scaffold( 25 | appBar: AppBar( 26 | leading: IconButton( 27 | icon: Icon(HugeIcons.strokeRoundedArrowLeft01), 28 | onPressed: () => Navigator.of(context).pop(), 29 | ), 30 | title: Text( 31 | 'Call Settings', 32 | style: GoogleFonts.raleway( 33 | fontSize: 20, 34 | fontWeight: FontWeight.w600, 35 | color: context.colorScheme.onSurface, 36 | ), 37 | ), 38 | ), 39 | body: ListView( 40 | padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), 41 | children: [ 42 | SwitchTileWidget( 43 | title: "Speed dial", 44 | subtitle: "Directly call someone by holding a dialpad key", 45 | value: disableMaterialYou, 46 | onChanged: (value) { 47 | setState(() { 48 | disableMaterialYou = value; 49 | }); 50 | }, 51 | isFirst: true), 52 | MenuTile( 53 | title: 'Speed dial Settings', 54 | subtitle: '', 55 | icon: HugeIcons.strokeRoundedDialpadSquare02, 56 | onTap: () {}, 57 | isLast: true, 58 | ), 59 | const SizedBox( 60 | height: 10, 61 | ), 62 | SwitchTileWidget( 63 | title: "T9 Dialing", 64 | subtitle: "Predicts words from numeric keypad inputs", 65 | value: enableCustomCallScreen, 66 | onChanged: (value) { 67 | setState(() { 68 | enableCustomCallScreen = value; 69 | }); 70 | }, 71 | isFirst: true, 72 | isLast: true), 73 | ], 74 | ), 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/ui/views/settings_view/sound.dart: -------------------------------------------------------------------------------- 1 | import 'package:android_intent_plus/android_intent.dart'; 2 | import 'package:android_intent_plus/flag.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:google_fonts/google_fonts.dart'; 5 | import 'package:hugeicons/hugeicons.dart'; 6 | import 'package:revo/constants/pref.dart'; 7 | import 'package:revo/extentions/theme.dart'; 8 | import 'package:revo/services/prefservice.dart'; 9 | import 'package:revo/utils/menu_tile.dart'; 10 | import 'package:revo/utils/switch_tile.dart'; 11 | 12 | class SoundView extends StatefulWidget { 13 | const SoundView({super.key}); 14 | 15 | @override 16 | State createState() => _SoundViewState(); 17 | } 18 | 19 | class _SoundViewState extends State { 20 | @override 21 | Widget build(BuildContext context) { 22 | return Scaffold( 23 | appBar: AppBar( 24 | leading: IconButton( 25 | icon: Icon(HugeIcons.strokeRoundedArrowLeft01), 26 | onPressed: () => Navigator.of(context).pop(), 27 | ), 28 | title: Text( 29 | 'Sound & Vibration', 30 | style: GoogleFonts.raleway( 31 | fontSize: 20, 32 | fontWeight: FontWeight.w600, 33 | color: context.colorScheme.onSurface, 34 | ), 35 | ), 36 | ), 37 | body: ListView( 38 | padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), 39 | children: [ 40 | SwitchTileWidget( 41 | title: "DTMF tone", 42 | subtitle: "Dialpad tone that plays during keypress", 43 | value: SharedPrefService().getBool(PREF_DTMF_TONE, def: true), 44 | onChanged: (value) { 45 | SharedPrefService().saveBool(PREF_DTMF_TONE, value); 46 | setState(() {}); 47 | }, 48 | isFirst: true), 49 | SwitchTileWidget( 50 | title: "Dialpad vibration", 51 | subtitle: "Dialpad vibration that plays during keypress", 52 | value: 53 | SharedPrefService().getBool(PREF_DIALPAD_VIBRATION, def: true), 54 | onChanged: (value) { 55 | SharedPrefService().saveBool(PREF_DIALPAD_VIBRATION, value); 56 | setState(() {}); 57 | }, 58 | isLast: true, 59 | ), 60 | const SizedBox( 61 | height: 10, 62 | ), 63 | MenuTile( 64 | title: 'Ringtone Settings', 65 | subtitle: '', 66 | icon: HugeIcons.strokeRoundedMusicNote02, 67 | onTap: () { 68 | final intent = AndroidIntent( 69 | action: 'android.settings.SOUND_SETTINGS', 70 | flags: [Flag.FLAG_ACTIVITY_NEW_TASK], 71 | ); 72 | intent.launch(); 73 | }, 74 | isFirst: true, 75 | isLast: true, 76 | ), 77 | ], 78 | ), 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /lib/ui/views/settings_view/user_interface.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | import 'package:google_fonts/google_fonts.dart'; 4 | import 'package:hugeicons/hugeicons.dart'; 5 | import 'package:revo/constants/pref.dart'; 6 | import 'package:revo/extentions/theme.dart'; 7 | import 'package:revo/services/prefservice.dart'; 8 | import 'package:revo/ui/theme/handler.dart'; 9 | import 'package:revo/utils/switch_tile.dart'; 10 | 11 | class UserInterfaceView extends StatefulWidget { 12 | const UserInterfaceView({super.key}); 13 | 14 | @override 15 | State createState() => _UserInterfaceViewState(); 16 | } 17 | 18 | class _UserInterfaceViewState extends State { 19 | @override 20 | Widget build(BuildContext context) { 21 | return Scaffold( 22 | appBar: AppBar( 23 | leading: IconButton( 24 | icon: Icon(HugeIcons.strokeRoundedArrowLeft01), 25 | onPressed: () => Navigator.of(context).pop(), 26 | ), 27 | title: Text( 28 | 'User Interface', 29 | style: GoogleFonts.raleway( 30 | fontSize: 20, 31 | fontWeight: FontWeight.w600, 32 | color: context.colorScheme.onSurface, 33 | ), 34 | ), 35 | ), 36 | body: ListView( 37 | padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0), 38 | children: [ 39 | SwitchTileWidget( 40 | title: "Material You theming", 41 | subtitle: 42 | "Wallpaper based app color theming. Restart is required.", 43 | value: context.read().isDynamic, 44 | onChanged: (value) { 45 | setState(() { 46 | context.read().toggleDynamicColors(); 47 | setState(() {}); 48 | }); 49 | }, 50 | isFirst: true), 51 | SwitchTileWidget( 52 | title: "Amoled dark mode", 53 | subtitle: 54 | "Uses pitch black for UI elements. This may save some battery life on OLED screens.", 55 | value: context.read().isAmoled, 56 | onChanged: (value) { 57 | context.read().toggleAmoledColors(); 58 | setState(() {}); 59 | }, 60 | isLast: true), 61 | const SizedBox( 62 | height: 10, 63 | ), 64 | SwitchTileWidget( 65 | title: "Show first letter in avartar", 66 | subtitle: 67 | "Displays the first letter of the contact name when a profile picture isn't available", 68 | value: SharedPrefService().getBool(PREF_SHOW_FIRST_LETTER), 69 | onChanged: (value) { 70 | SharedPrefService().saveBool(PREF_SHOW_FIRST_LETTER, value); 71 | setState(() {}); 72 | }, 73 | isFirst: true), 74 | SwitchTileWidget( 75 | title: "Show picture in avatar", 76 | subtitle: "Shows the contact picture if available", 77 | value: SharedPrefService().getBool(PREF_SHOW_PICTURE_IN_AVARTAR), 78 | onChanged: (value) { 79 | SharedPrefService() 80 | .saveBool(PREF_SHOW_PICTURE_IN_AVARTAR, value); 81 | setState(() {}); 82 | }, 83 | isLast: true), 84 | const SizedBox( 85 | height: 10, 86 | ), 87 | SwitchTileWidget( 88 | title: "Icon-only bottom sheet", 89 | subtitle: 90 | "Only shows navigation icons in the bottom navigation bar", 91 | value: SharedPrefService().getBool(PREF_ICON_ONLY_BOTTOMSHEET), 92 | onChanged: (value) { 93 | SharedPrefService().saveBool(PREF_ICON_ONLY_BOTTOMSHEET, value); 94 | setState(() {}); 95 | }, 96 | isFirst: true, 97 | ), 98 | SwitchTileWidget( 99 | title: "Selected icon in bottom sheet", 100 | subtitle: "Always shows the icon for the selected tab", 101 | value: SharedPrefService() 102 | .getBool(PREF_ALWAYS_SHOW_SELECTED_IN_BOTTOMSHEET), 103 | onChanged: (value) { 104 | SharedPrefService() 105 | .saveBool(PREF_ALWAYS_SHOW_SELECTED_IN_BOTTOMSHEET, value); 106 | setState(() {}); 107 | }, 108 | isLast: true, 109 | ), 110 | const SizedBox( 111 | height: 10, 112 | ), 113 | SwitchTileWidget( 114 | title: "Dialpad letters", 115 | subtitle: "Show letters on the dialpad buttons", 116 | value: SharedPrefService().getBool(PREF_DIALPAD_LETTERS), 117 | onChanged: (value) { 118 | SharedPrefService().saveBool(PREF_DIALPAD_LETTERS, value); 119 | setState(() {}); 120 | }, 121 | isFirst: true, 122 | isLast: true, 123 | ), 124 | ], 125 | ), 126 | ); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /lib/utils/center_text.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_fonts/google_fonts.dart'; 3 | import 'package:revo/extentions/theme.dart'; 4 | 5 | class CenterText extends StatelessWidget { 6 | final String text; 7 | final double size; 8 | const CenterText({super.key, required this.text, this.size = 16.0}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Center( 13 | child: Text( 14 | text, 15 | style: GoogleFonts.raleway( 16 | fontSize: size, 17 | color: context.colorScheme.onSurface, 18 | ), 19 | ), 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/utils/circle_profile.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:google_fonts/google_fonts.dart'; 5 | import 'package:hugeicons/hugeicons.dart'; 6 | import 'package:revo/constants/pref.dart'; 7 | import 'package:revo/extentions/theme.dart'; 8 | import 'package:revo/services/prefservice.dart'; 9 | 10 | class CircleProfile extends StatefulWidget { 11 | final Uint8List? profile; 12 | final String name; 13 | final double size; 14 | const CircleProfile( 15 | {super.key, required this.name, this.profile, required this.size}); 16 | 17 | @override 18 | State createState() => _CircleProfileState(); 19 | } 20 | 21 | class _CircleProfileState extends State { 22 | @override 23 | void initState() { 24 | SharedPrefService().onPreferenceChanged.listen((key) { 25 | if (key == PREF_SHOW_FIRST_LETTER || 26 | key == PREF_SHOW_PICTURE_IN_AVARTAR) { 27 | setState(() {}); 28 | } 29 | }); 30 | super.initState(); 31 | } 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | bool showPic = widget.profile != null && 36 | SharedPrefService().getBool(PREF_SHOW_PICTURE_IN_AVARTAR, def: true); 37 | 38 | bool showFirstLetter = widget.name.isNotEmpty && 39 | SharedPrefService().getBool(PREF_SHOW_FIRST_LETTER, def: true); 40 | return CircleAvatar( 41 | radius: widget.size, 42 | backgroundColor: context.colorScheme.secondaryContainer, 43 | backgroundImage: showPic ? MemoryImage(widget.profile!) : null, 44 | child: !showPic 45 | ? showFirstLetter 46 | ? Text( 47 | widget.name[0].toUpperCase(), 48 | style: GoogleFonts.raleway( 49 | fontSize: widget.size, 50 | fontWeight: FontWeight.w300, 51 | color: context.colorScheme.onSurface, 52 | ), 53 | ) 54 | : Icon( 55 | HugeIcons.strokeRoundedUser, 56 | size: widget.size, 57 | color: context.colorScheme.onSurface, 58 | ) 59 | : null, 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/utils/menu_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class MenuTile extends StatelessWidget { 4 | final String title; 5 | final String subtitle; 6 | final IconData icon; 7 | final VoidCallback onTap; 8 | final bool isFirst; 9 | final bool isLast; 10 | 11 | const MenuTile({ 12 | Key? key, 13 | required this.title, 14 | required this.subtitle, 15 | required this.icon, 16 | required this.onTap, 17 | this.isFirst = false, 18 | this.isLast = false, 19 | }) : super(key: key); 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return Padding( 24 | padding: const EdgeInsets.all(2.0), 25 | child: Container( 26 | decoration: BoxDecoration( 27 | color: 28 | Theme.of(context).colorScheme.secondaryContainer.withAlpha(110), 29 | borderRadius: BorderRadius.vertical( 30 | top: isFirst ? const Radius.circular(15) : Radius.zero, 31 | bottom: isLast ? const Radius.circular(15) : Radius.zero, 32 | ), 33 | ), 34 | child: ListTile( 35 | shape: RoundedRectangleBorder( 36 | borderRadius: BorderRadius.only( 37 | topLeft: isFirst ? const Radius.circular(15) : Radius.zero, 38 | topRight: isFirst ? const Radius.circular(15) : Radius.zero, 39 | bottomLeft: isLast ? const Radius.circular(15) : Radius.zero, 40 | bottomRight: isLast ? const Radius.circular(15) : Radius.zero, 41 | ), 42 | ), 43 | contentPadding: const EdgeInsets.symmetric(horizontal: 20.0), 44 | leading: Icon( 45 | icon, 46 | color: Theme.of(context).colorScheme.onSecondaryContainer, 47 | size: 30, 48 | ), 49 | title: Text( 50 | title, 51 | style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500), 52 | ), 53 | subtitle: Text( 54 | subtitle, 55 | style: TextStyle( 56 | fontSize: 12, 57 | color: Theme.of(context) 58 | .colorScheme 59 | .onSecondaryContainer 60 | .withAlpha(128), 61 | ), 62 | ), 63 | onTap: onTap, 64 | ), 65 | ), 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/utils/rounded_icon_btn.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:google_fonts/google_fonts.dart'; 3 | import 'package:revo/extentions/theme.dart'; 4 | 5 | class RoundedIconButton extends StatelessWidget { 6 | final VoidCallback? onTap; 7 | final VoidCallback? onLongPress; 8 | final IconData icon; 9 | final double size; 10 | final String text; 11 | 12 | const RoundedIconButton( 13 | BuildContext context, { 14 | super.key, 15 | required this.icon, 16 | required this.size, 17 | this.text = '', 18 | this.onTap, 19 | this.onLongPress, 20 | }); 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return Column( 25 | children: [ 26 | GestureDetector( 27 | onTap: onTap, 28 | onLongPress: onLongPress, 29 | child: Container( 30 | decoration: BoxDecoration( 31 | color: context.colorScheme.secondaryContainer, 32 | shape: BoxShape.circle, 33 | ), 34 | width: size, 35 | height: size, 36 | child: Icon( 37 | icon, 38 | color: context.colorScheme.onSecondaryContainer, 39 | size: size / 1.75, 40 | ), 41 | ), 42 | ), 43 | const SizedBox(height: 8), 44 | if (text.isNotEmpty) 45 | Text( 46 | text, 47 | style: GoogleFonts.raleway( 48 | textStyle: context.textTheme.bodyLarge, 49 | color: context.colorScheme.onSurface, 50 | fontSize: 12, 51 | ), 52 | ), 53 | ], 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/utils/share.dart: -------------------------------------------------------------------------------- 1 | import 'package:revo/model/contact.dart'; 2 | 3 | String generateVCardString(Contact contact) { 4 | String str = ''' 5 | BEGIN:VCARD 6 | VERSION:3.0 7 | FN:${contact.fullName}'''; 8 | 9 | for (var phone in contact.phones) { 10 | str += 'TEL:$phone\n'; 11 | } 12 | str += 'END:VCARD'; 13 | return str; 14 | } 15 | -------------------------------------------------------------------------------- /lib/utils/switch_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SwitchTileWidget extends StatelessWidget { 4 | final String title; 5 | final String subtitle; 6 | final bool value; 7 | final ValueChanged onChanged; 8 | final bool isFirst; 9 | final bool isLast; 10 | 11 | const SwitchTileWidget({ 12 | super.key, 13 | required this.title, 14 | required this.subtitle, 15 | required this.value, 16 | required this.onChanged, 17 | this.isFirst = false, 18 | this.isLast = false, 19 | }); 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return Padding( 24 | padding: const EdgeInsets.all(2.0), 25 | child: Container( 26 | decoration: BoxDecoration( 27 | color: 28 | Theme.of(context).colorScheme.secondaryContainer.withAlpha(110), 29 | borderRadius: BorderRadius.vertical( 30 | top: isFirst ? const Radius.circular(15) : Radius.zero, 31 | bottom: isLast ? const Radius.circular(15) : Radius.zero, 32 | ), 33 | ), 34 | child: SwitchListTile( 35 | shape: RoundedRectangleBorder( 36 | borderRadius: BorderRadius.only( 37 | topLeft: isFirst ? const Radius.circular(15) : Radius.zero, 38 | topRight: isFirst ? const Radius.circular(15) : Radius.zero, 39 | bottomLeft: isLast ? const Radius.circular(15) : Radius.zero, 40 | bottomRight: isLast ? const Radius.circular(15) : Radius.zero, 41 | ), 42 | ), 43 | contentPadding: 44 | const EdgeInsets.symmetric(horizontal: 20.0, vertical: 2), 45 | title: Text( 46 | title, 47 | style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500), 48 | ), 49 | subtitle: Text( 50 | subtitle, 51 | style: TextStyle( 52 | fontSize: 12, 53 | fontWeight: FontWeight.w500, 54 | color: Theme.of(context) 55 | .colorScheme 56 | .onSecondaryContainer 57 | .withAlpha(180), 58 | ), 59 | ), 60 | value: value, 61 | onChanged: onChanged, 62 | ), 63 | ), 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/utils/utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:revo/constants/pref.dart'; 4 | import 'package:revo/services/prefservice.dart'; 5 | import 'package:url_launcher/url_launcher.dart'; 6 | 7 | String normalizePhoneNumber(String phoneNumber) { 8 | return phoneNumber.replaceAll(RegExp(r'[^0-9+]'), ''); 9 | } 10 | 11 | String convertSecondsToHMS(int totalSeconds) { 12 | int hours = totalSeconds ~/ 3600; 13 | int minutes = (totalSeconds % 3600) ~/ 60; 14 | int seconds = totalSeconds % 60; 15 | 16 | String result = ''; 17 | if (hours > 0) { 18 | result += '$hours hour${hours > 1 ? 's' : ''} '; 19 | } 20 | if (minutes > 0) { 21 | result += '$minutes min${minutes > 1 ? 's' : ''} '; 22 | } 23 | if (seconds > 0 || result.isEmpty) { 24 | result += '$seconds sec${seconds > 1 ? 's' : ''}'; 25 | } 26 | 27 | return result.trim(); 28 | } 29 | 30 | Future launchURL(String url) async { 31 | try { 32 | final Uri uri = Uri.parse(url); 33 | if (await canLaunchUrl(uri)) { 34 | await launchUrl(uri, mode: LaunchMode.externalApplication); 35 | } else { 36 | debugPrint('Cannot launch URL: $url'); 37 | throw Exception('Could not launch $url'); 38 | } 39 | } catch (e) { 40 | debugPrint('Error occurred: $e'); 41 | } 42 | } 43 | 44 | void hapticVibration() { 45 | if (SharedPrefService().getBool(PREF_DIALPAD_VIBRATION, def: true)) { 46 | HapticFeedback.lightImpact(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /privacy-policy.md: -------------------------------------------------------------------------------- 1 | Privacy Policy for Rivo Dialer App 2 | 3 | We value your privacy and are committed to protecting the personal information you share with us. This Privacy Policy explains how we handle and protect any data you provide while using our app. 4 | 5 | 1. Information We Collect 6 | 7 | Rivo does not collect any personal information from users. The app does not track or store data such as your phone number, contacts, or call logs externally. 8 | 9 | 2. Data Storage 10 | 11 | All data, including call logs and user preferences, is stored locally on the user’s device. We do not store any user data on remote servers, and everything is kept on your device for your privacy and security. 12 | 13 | 3. Data Sharing 14 | 15 | Rivo does not share any data with third parties. All information stored within the app remains private and is not transmitted or shared externally in any form. 16 | 17 | 4. Open Source 18 | 19 | Rivo is an open-source application. This means that the app's source code is publicly available for review. Users are encouraged to inspect the code, and anyone can contribute to the development of the app. 20 | 21 | 5. Privacy Policy Changes 22 | 23 | We may update this Privacy Policy from time to time. If any changes are made, users will be notified through the app. Please check for notifications regularly to stay informed about our privacy practices. 24 | 25 | 6. User Rights 26 | 27 | Since no personal data is collected, users are not required to manage or delete any stored data from our end. All data remains solely on your device, and you have full control over your data at all times. 28 | 29 | 7. Data Security 30 | 31 | As we do not store data remotely, security concerns regarding your personal information are minimized. However, we encourage users to protect their devices with proper security measures to safeguard their privacy. 32 | 33 | 8. Contact Information 34 | 35 | If you have any questions about this Privacy Policy or the app's privacy practices, please feel free to contact us at: 36 | Email: user.grinch@gmail.com 37 | 38 | By using Rivo, you agree to this Privacy Policy. If you do not agree with the terms outlined here, we recommend not using the app. 39 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | android_intent_plus: 5 | dependency: "direct main" 6 | description: 7 | name: android_intent_plus 8 | sha256: e1c62bb41c90e15083b7fb84dc327fe90396cc9c1445b55ff1082144fabfb4d9 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "4.0.3" 12 | ansicolor: 13 | dependency: transitive 14 | description: 15 | name: ansicolor 16 | sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.0.3" 20 | archive: 21 | dependency: transitive 22 | description: 23 | name: archive 24 | sha256: "6199c74e3db4fbfbd04f66d739e72fe11c8a8957d5f219f1f4482dbde6420b5a" 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "4.0.2" 28 | args: 29 | dependency: transitive 30 | description: 31 | name: args 32 | sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "2.6.0" 36 | async: 37 | dependency: transitive 38 | description: 39 | name: async 40 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "2.11.0" 44 | bloc: 45 | dependency: "direct main" 46 | description: 47 | name: bloc 48 | sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "8.1.4" 52 | boolean_selector: 53 | dependency: transitive 54 | description: 55 | name: boolean_selector 56 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 57 | url: "https://pub.dev" 58 | source: hosted 59 | version: "2.1.1" 60 | call_e_log: 61 | dependency: "direct main" 62 | description: 63 | name: call_e_log 64 | sha256: "4e8ef87330e0b1208fe4e3ca586f45053180c8451d5c5359dc0e6a34951886c4" 65 | url: "https://pub.dev" 66 | source: hosted 67 | version: "0.0.4" 68 | characters: 69 | dependency: transitive 70 | description: 71 | name: characters 72 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 73 | url: "https://pub.dev" 74 | source: hosted 75 | version: "1.3.0" 76 | clock: 77 | dependency: transitive 78 | description: 79 | name: clock 80 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 81 | url: "https://pub.dev" 82 | source: hosted 83 | version: "1.1.1" 84 | collection: 85 | dependency: transitive 86 | description: 87 | name: collection 88 | sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf 89 | url: "https://pub.dev" 90 | source: hosted 91 | version: "1.19.0" 92 | cross_file: 93 | dependency: transitive 94 | description: 95 | name: cross_file 96 | sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" 97 | url: "https://pub.dev" 98 | source: hosted 99 | version: "0.3.4+2" 100 | crypto: 101 | dependency: transitive 102 | description: 103 | name: crypto 104 | sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" 105 | url: "https://pub.dev" 106 | source: hosted 107 | version: "3.0.6" 108 | csslib: 109 | dependency: transitive 110 | description: 111 | name: csslib 112 | sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" 113 | url: "https://pub.dev" 114 | source: hosted 115 | version: "1.0.2" 116 | direct_caller_sim_choice: 117 | dependency: "direct main" 118 | description: 119 | name: direct_caller_sim_choice 120 | sha256: "96086f9c5d5d25c52120218bab4db400b2a814d5dd84ce55c8df09889a2ded89" 121 | url: "https://pub.dev" 122 | source: hosted 123 | version: "1.0.5" 124 | dynamic_color: 125 | dependency: "direct main" 126 | description: 127 | name: dynamic_color 128 | sha256: eae98052fa6e2826bdac3dd2e921c6ce2903be15c6b7f8b6d8a5d49b5086298d 129 | url: "https://pub.dev" 130 | source: hosted 131 | version: "1.7.0" 132 | fake_async: 133 | dependency: transitive 134 | description: 135 | name: fake_async 136 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 137 | url: "https://pub.dev" 138 | source: hosted 139 | version: "1.3.1" 140 | ffi: 141 | dependency: transitive 142 | description: 143 | name: ffi 144 | sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" 145 | url: "https://pub.dev" 146 | source: hosted 147 | version: "2.1.3" 148 | file: 149 | dependency: transitive 150 | description: 151 | name: file 152 | sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 153 | url: "https://pub.dev" 154 | source: hosted 155 | version: "7.0.1" 156 | fixnum: 157 | dependency: transitive 158 | description: 159 | name: fixnum 160 | sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be 161 | url: "https://pub.dev" 162 | source: hosted 163 | version: "1.1.1" 164 | flutter: 165 | dependency: "direct main" 166 | description: flutter 167 | source: sdk 168 | version: "0.0.0" 169 | flutter_bloc: 170 | dependency: "direct main" 171 | description: 172 | name: flutter_bloc 173 | sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a 174 | url: "https://pub.dev" 175 | source: hosted 176 | version: "8.1.6" 177 | flutter_callkit_incoming: 178 | dependency: "direct main" 179 | description: 180 | name: flutter_callkit_incoming 181 | sha256: c1e90252b45d84ec5b0792f77aaf3c07222ceade3698307d1a890e514690f7fc 182 | url: "https://pub.dev" 183 | source: hosted 184 | version: "2.5.0" 185 | flutter_contacts: 186 | dependency: "direct main" 187 | description: 188 | name: flutter_contacts 189 | sha256: "388d32cd33f16640ee169570128c933b45f3259bddbfae7a100bb49e5ffea9ae" 190 | url: "https://pub.dev" 191 | source: hosted 192 | version: "1.1.9+2" 193 | flutter_dtmf: 194 | dependency: "direct main" 195 | description: 196 | path: "." 197 | ref: master 198 | resolved-ref: "26779739c7c263d09a8cd452cdec67c6cbd2879b" 199 | url: "https://github.com/eopeter/flutter_dtmf.git" 200 | source: git 201 | version: "3.1.0" 202 | flutter_lints: 203 | dependency: "direct dev" 204 | description: 205 | name: flutter_lints 206 | sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" 207 | url: "https://pub.dev" 208 | source: hosted 209 | version: "5.0.0" 210 | flutter_native_splash: 211 | dependency: "direct main" 212 | description: 213 | name: flutter_native_splash 214 | sha256: "7062602e0dbd29141fb8eb19220b5871ca650be5197ab9c1f193a28b17537bc7" 215 | url: "https://pub.dev" 216 | source: hosted 217 | version: "2.4.4" 218 | flutter_plugin_android_lifecycle: 219 | dependency: transitive 220 | description: 221 | name: flutter_plugin_android_lifecycle 222 | sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e" 223 | url: "https://pub.dev" 224 | source: hosted 225 | version: "2.0.24" 226 | flutter_sim_data: 227 | dependency: "direct main" 228 | description: 229 | name: flutter_sim_data 230 | sha256: "41706faabc297b7254542ea3be6b5c597c795b852ad2cb7be89f244a8d2744dd" 231 | url: "https://pub.dev" 232 | source: hosted 233 | version: "1.0.5" 234 | flutter_test: 235 | dependency: "direct dev" 236 | description: flutter 237 | source: sdk 238 | version: "0.0.0" 239 | flutter_web_plugins: 240 | dependency: transitive 241 | description: flutter 242 | source: sdk 243 | version: "0.0.0" 244 | font_awesome_flutter: 245 | dependency: "direct main" 246 | description: 247 | name: font_awesome_flutter 248 | sha256: d3a89184101baec7f4600d58840a764d2ef760fe1c5a20ef9e6b0e9b24a07a3a 249 | url: "https://pub.dev" 250 | source: hosted 251 | version: "10.8.0" 252 | google_fonts: 253 | dependency: "direct main" 254 | description: 255 | name: google_fonts 256 | sha256: b1ac0fe2832c9cc95e5e88b57d627c5e68c223b9657f4b96e1487aa9098c7b82 257 | url: "https://pub.dev" 258 | source: hosted 259 | version: "6.2.1" 260 | html: 261 | dependency: transitive 262 | description: 263 | name: html 264 | sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec" 265 | url: "https://pub.dev" 266 | source: hosted 267 | version: "0.15.5" 268 | http: 269 | dependency: transitive 270 | description: 271 | name: http 272 | sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 273 | url: "https://pub.dev" 274 | source: hosted 275 | version: "1.2.2" 276 | http_parser: 277 | dependency: transitive 278 | description: 279 | name: http_parser 280 | sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360" 281 | url: "https://pub.dev" 282 | source: hosted 283 | version: "4.1.1" 284 | hugeicons: 285 | dependency: "direct main" 286 | description: 287 | name: hugeicons 288 | sha256: b5cf08183a75b7774ca6d601a2ef025ed26550e4fca9d956673ffda91e132411 289 | url: "https://pub.dev" 290 | source: hosted 291 | version: "0.0.7" 292 | image: 293 | dependency: transitive 294 | description: 295 | name: image 296 | sha256: "8346ad4b5173924b5ddddab782fc7d8a6300178c8b1dc427775405a01701c4a6" 297 | url: "https://pub.dev" 298 | source: hosted 299 | version: "4.5.2" 300 | intl: 301 | dependency: "direct main" 302 | description: 303 | name: intl 304 | sha256: "00f33b908655e606b86d2ade4710a231b802eec6f11e87e4ea3783fd72077a50" 305 | url: "https://pub.dev" 306 | source: hosted 307 | version: "0.20.1" 308 | json_annotation: 309 | dependency: transitive 310 | description: 311 | name: json_annotation 312 | sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" 313 | url: "https://pub.dev" 314 | source: hosted 315 | version: "4.9.0" 316 | leak_tracker: 317 | dependency: transitive 318 | description: 319 | name: leak_tracker 320 | sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" 321 | url: "https://pub.dev" 322 | source: hosted 323 | version: "10.0.7" 324 | leak_tracker_flutter_testing: 325 | dependency: transitive 326 | description: 327 | name: leak_tracker_flutter_testing 328 | sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" 329 | url: "https://pub.dev" 330 | source: hosted 331 | version: "3.0.8" 332 | leak_tracker_testing: 333 | dependency: transitive 334 | description: 335 | name: leak_tracker_testing 336 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" 337 | url: "https://pub.dev" 338 | source: hosted 339 | version: "3.0.1" 340 | lints: 341 | dependency: transitive 342 | description: 343 | name: lints 344 | sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 345 | url: "https://pub.dev" 346 | source: hosted 347 | version: "5.1.1" 348 | matcher: 349 | dependency: transitive 350 | description: 351 | name: matcher 352 | sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb 353 | url: "https://pub.dev" 354 | source: hosted 355 | version: "0.12.16+1" 356 | material_color_utilities: 357 | dependency: transitive 358 | description: 359 | name: material_color_utilities 360 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec 361 | url: "https://pub.dev" 362 | source: hosted 363 | version: "0.11.1" 364 | meta: 365 | dependency: transitive 366 | description: 367 | name: meta 368 | sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 369 | url: "https://pub.dev" 370 | source: hosted 371 | version: "1.15.0" 372 | mime: 373 | dependency: transitive 374 | description: 375 | name: mime 376 | sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" 377 | url: "https://pub.dev" 378 | source: hosted 379 | version: "2.0.0" 380 | nested: 381 | dependency: transitive 382 | description: 383 | name: nested 384 | sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" 385 | url: "https://pub.dev" 386 | source: hosted 387 | version: "1.0.0" 388 | path: 389 | dependency: transitive 390 | description: 391 | name: path 392 | sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" 393 | url: "https://pub.dev" 394 | source: hosted 395 | version: "1.9.0" 396 | path_provider: 397 | dependency: transitive 398 | description: 399 | name: path_provider 400 | sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" 401 | url: "https://pub.dev" 402 | source: hosted 403 | version: "2.1.5" 404 | path_provider_android: 405 | dependency: transitive 406 | description: 407 | name: path_provider_android 408 | sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" 409 | url: "https://pub.dev" 410 | source: hosted 411 | version: "2.2.15" 412 | path_provider_foundation: 413 | dependency: transitive 414 | description: 415 | name: path_provider_foundation 416 | sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" 417 | url: "https://pub.dev" 418 | source: hosted 419 | version: "2.4.1" 420 | path_provider_linux: 421 | dependency: transitive 422 | description: 423 | name: path_provider_linux 424 | sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 425 | url: "https://pub.dev" 426 | source: hosted 427 | version: "2.2.1" 428 | path_provider_platform_interface: 429 | dependency: transitive 430 | description: 431 | name: path_provider_platform_interface 432 | sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" 433 | url: "https://pub.dev" 434 | source: hosted 435 | version: "2.1.2" 436 | path_provider_windows: 437 | dependency: transitive 438 | description: 439 | name: path_provider_windows 440 | sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 441 | url: "https://pub.dev" 442 | source: hosted 443 | version: "2.3.0" 444 | permission_handler: 445 | dependency: "direct main" 446 | description: 447 | name: permission_handler 448 | sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb" 449 | url: "https://pub.dev" 450 | source: hosted 451 | version: "11.3.1" 452 | permission_handler_android: 453 | dependency: transitive 454 | description: 455 | name: permission_handler_android 456 | sha256: "71bbecfee799e65aff7c744761a57e817e73b738fedf62ab7afd5593da21f9f1" 457 | url: "https://pub.dev" 458 | source: hosted 459 | version: "12.0.13" 460 | permission_handler_apple: 461 | dependency: transitive 462 | description: 463 | name: permission_handler_apple 464 | sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0 465 | url: "https://pub.dev" 466 | source: hosted 467 | version: "9.4.5" 468 | permission_handler_html: 469 | dependency: transitive 470 | description: 471 | name: permission_handler_html 472 | sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" 473 | url: "https://pub.dev" 474 | source: hosted 475 | version: "0.1.3+5" 476 | permission_handler_platform_interface: 477 | dependency: transitive 478 | description: 479 | name: permission_handler_platform_interface 480 | sha256: e9c8eadee926c4532d0305dff94b85bf961f16759c3af791486613152af4b4f9 481 | url: "https://pub.dev" 482 | source: hosted 483 | version: "4.2.3" 484 | permission_handler_windows: 485 | dependency: transitive 486 | description: 487 | name: permission_handler_windows 488 | sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" 489 | url: "https://pub.dev" 490 | source: hosted 491 | version: "0.2.1" 492 | petitparser: 493 | dependency: transitive 494 | description: 495 | name: petitparser 496 | sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 497 | url: "https://pub.dev" 498 | source: hosted 499 | version: "6.0.2" 500 | phone_state: 501 | dependency: "direct main" 502 | description: 503 | name: phone_state 504 | sha256: "291f7444e68f9a504afa8c7ceee42a5bbfe7131f059439e4e8d23d9d347f1157" 505 | url: "https://pub.dev" 506 | source: hosted 507 | version: "2.1.1" 508 | phosphor_flutter: 509 | dependency: "direct main" 510 | description: 511 | name: phosphor_flutter 512 | sha256: "8a14f238f28a0b54842c5a4dc20676598dd4811fcba284ed828bd5a262c11fde" 513 | url: "https://pub.dev" 514 | source: hosted 515 | version: "2.1.0" 516 | platform: 517 | dependency: transitive 518 | description: 519 | name: platform 520 | sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" 521 | url: "https://pub.dev" 522 | source: hosted 523 | version: "3.1.6" 524 | plugin_platform_interface: 525 | dependency: transitive 526 | description: 527 | name: plugin_platform_interface 528 | sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" 529 | url: "https://pub.dev" 530 | source: hosted 531 | version: "2.1.8" 532 | posix: 533 | dependency: transitive 534 | description: 535 | name: posix 536 | sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a 537 | url: "https://pub.dev" 538 | source: hosted 539 | version: "6.0.1" 540 | provider: 541 | dependency: "direct main" 542 | description: 543 | name: provider 544 | sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c 545 | url: "https://pub.dev" 546 | source: hosted 547 | version: "6.1.2" 548 | qr: 549 | dependency: transitive 550 | description: 551 | name: qr 552 | sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445" 553 | url: "https://pub.dev" 554 | source: hosted 555 | version: "3.0.2" 556 | qr_flutter: 557 | dependency: "direct main" 558 | description: 559 | name: qr_flutter 560 | sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097" 561 | url: "https://pub.dev" 562 | source: hosted 563 | version: "4.1.0" 564 | share_plus: 565 | dependency: "direct main" 566 | description: 567 | name: share_plus 568 | sha256: "6327c3f233729374d0abaafd61f6846115b2a481b4feddd8534211dc10659400" 569 | url: "https://pub.dev" 570 | source: hosted 571 | version: "10.1.3" 572 | share_plus_platform_interface: 573 | dependency: transitive 574 | description: 575 | name: share_plus_platform_interface 576 | sha256: cc012a23fc2d479854e6c80150696c4a5f5bb62cb89af4de1c505cf78d0a5d0b 577 | url: "https://pub.dev" 578 | source: hosted 579 | version: "5.0.2" 580 | shared_preferences: 581 | dependency: "direct main" 582 | description: 583 | name: shared_preferences 584 | sha256: "688ee90fbfb6989c980254a56cb26ebe9bb30a3a2dff439a78894211f73de67a" 585 | url: "https://pub.dev" 586 | source: hosted 587 | version: "2.5.1" 588 | shared_preferences_android: 589 | dependency: transitive 590 | description: 591 | name: shared_preferences_android 592 | sha256: "650584dcc0a39856f369782874e562efd002a9c94aec032412c9eb81419cce1f" 593 | url: "https://pub.dev" 594 | source: hosted 595 | version: "2.4.4" 596 | shared_preferences_foundation: 597 | dependency: transitive 598 | description: 599 | name: shared_preferences_foundation 600 | sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" 601 | url: "https://pub.dev" 602 | source: hosted 603 | version: "2.5.4" 604 | shared_preferences_linux: 605 | dependency: transitive 606 | description: 607 | name: shared_preferences_linux 608 | sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" 609 | url: "https://pub.dev" 610 | source: hosted 611 | version: "2.4.1" 612 | shared_preferences_platform_interface: 613 | dependency: transitive 614 | description: 615 | name: shared_preferences_platform_interface 616 | sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" 617 | url: "https://pub.dev" 618 | source: hosted 619 | version: "2.4.1" 620 | shared_preferences_web: 621 | dependency: transitive 622 | description: 623 | name: shared_preferences_web 624 | sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e 625 | url: "https://pub.dev" 626 | source: hosted 627 | version: "2.4.2" 628 | shared_preferences_windows: 629 | dependency: transitive 630 | description: 631 | name: shared_preferences_windows 632 | sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" 633 | url: "https://pub.dev" 634 | source: hosted 635 | version: "2.4.1" 636 | simple_barcode_scanner: 637 | dependency: "direct main" 638 | description: 639 | name: simple_barcode_scanner 640 | sha256: "2b6ec05e10fbf4f07687f3687c5cf46d3dcf873492e0a5758211bd957c854113" 641 | url: "https://pub.dev" 642 | source: hosted 643 | version: "0.3.0" 644 | sky_engine: 645 | dependency: transitive 646 | description: flutter 647 | source: sdk 648 | version: "0.0.0" 649 | source_span: 650 | dependency: transitive 651 | description: 652 | name: source_span 653 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 654 | url: "https://pub.dev" 655 | source: hosted 656 | version: "1.10.0" 657 | sprintf: 658 | dependency: transitive 659 | description: 660 | name: sprintf 661 | sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" 662 | url: "https://pub.dev" 663 | source: hosted 664 | version: "7.0.0" 665 | stack_trace: 666 | dependency: transitive 667 | description: 668 | name: stack_trace 669 | sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" 670 | url: "https://pub.dev" 671 | source: hosted 672 | version: "1.12.0" 673 | stream_channel: 674 | dependency: transitive 675 | description: 676 | name: stream_channel 677 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 678 | url: "https://pub.dev" 679 | source: hosted 680 | version: "2.1.2" 681 | string_scanner: 682 | dependency: transitive 683 | description: 684 | name: string_scanner 685 | sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" 686 | url: "https://pub.dev" 687 | source: hosted 688 | version: "1.3.0" 689 | term_glyph: 690 | dependency: transitive 691 | description: 692 | name: term_glyph 693 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 694 | url: "https://pub.dev" 695 | source: hosted 696 | version: "1.2.1" 697 | test_api: 698 | dependency: transitive 699 | description: 700 | name: test_api 701 | sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" 702 | url: "https://pub.dev" 703 | source: hosted 704 | version: "0.7.3" 705 | typed_data: 706 | dependency: transitive 707 | description: 708 | name: typed_data 709 | sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 710 | url: "https://pub.dev" 711 | source: hosted 712 | version: "1.4.0" 713 | universal_io: 714 | dependency: transitive 715 | description: 716 | name: universal_io 717 | sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" 718 | url: "https://pub.dev" 719 | source: hosted 720 | version: "2.2.2" 721 | url_launcher: 722 | dependency: "direct main" 723 | description: 724 | name: url_launcher 725 | sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" 726 | url: "https://pub.dev" 727 | source: hosted 728 | version: "6.3.1" 729 | url_launcher_android: 730 | dependency: transitive 731 | description: 732 | name: url_launcher_android 733 | sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" 734 | url: "https://pub.dev" 735 | source: hosted 736 | version: "6.3.14" 737 | url_launcher_ios: 738 | dependency: transitive 739 | description: 740 | name: url_launcher_ios 741 | sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" 742 | url: "https://pub.dev" 743 | source: hosted 744 | version: "6.3.2" 745 | url_launcher_linux: 746 | dependency: transitive 747 | description: 748 | name: url_launcher_linux 749 | sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" 750 | url: "https://pub.dev" 751 | source: hosted 752 | version: "3.2.1" 753 | url_launcher_macos: 754 | dependency: transitive 755 | description: 756 | name: url_launcher_macos 757 | sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" 758 | url: "https://pub.dev" 759 | source: hosted 760 | version: "3.2.2" 761 | url_launcher_platform_interface: 762 | dependency: transitive 763 | description: 764 | name: url_launcher_platform_interface 765 | sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" 766 | url: "https://pub.dev" 767 | source: hosted 768 | version: "2.3.2" 769 | url_launcher_web: 770 | dependency: transitive 771 | description: 772 | name: url_launcher_web 773 | sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" 774 | url: "https://pub.dev" 775 | source: hosted 776 | version: "2.3.3" 777 | url_launcher_windows: 778 | dependency: transitive 779 | description: 780 | name: url_launcher_windows 781 | sha256: "44cf3aabcedde30f2dba119a9dea3b0f2672fbe6fa96e85536251d678216b3c4" 782 | url: "https://pub.dev" 783 | source: hosted 784 | version: "3.1.3" 785 | uuid: 786 | dependency: transitive 787 | description: 788 | name: uuid 789 | sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff 790 | url: "https://pub.dev" 791 | source: hosted 792 | version: "4.5.1" 793 | vector_math: 794 | dependency: transitive 795 | description: 796 | name: vector_math 797 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 798 | url: "https://pub.dev" 799 | source: hosted 800 | version: "2.1.4" 801 | vm_service: 802 | dependency: transitive 803 | description: 804 | name: vm_service 805 | sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b 806 | url: "https://pub.dev" 807 | source: hosted 808 | version: "14.3.0" 809 | web: 810 | dependency: transitive 811 | description: 812 | name: web 813 | sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb 814 | url: "https://pub.dev" 815 | source: hosted 816 | version: "1.1.0" 817 | webview_windows: 818 | dependency: transitive 819 | description: 820 | name: webview_windows 821 | sha256: "47fcad5875a45db29dbb5c9e6709bf5c88dcc429049872701343f91ed7255730" 822 | url: "https://pub.dev" 823 | source: hosted 824 | version: "0.4.0" 825 | win32: 826 | dependency: transitive 827 | description: 828 | name: win32 829 | sha256: "154360849a56b7b67331c21f09a386562d88903f90a1099c5987afc1912e1f29" 830 | url: "https://pub.dev" 831 | source: hosted 832 | version: "5.10.0" 833 | xdg_directories: 834 | dependency: transitive 835 | description: 836 | name: xdg_directories 837 | sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" 838 | url: "https://pub.dev" 839 | source: hosted 840 | version: "1.1.0" 841 | xml: 842 | dependency: transitive 843 | description: 844 | name: xml 845 | sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 846 | url: "https://pub.dev" 847 | source: hosted 848 | version: "6.5.0" 849 | yaml: 850 | dependency: transitive 851 | description: 852 | name: yaml 853 | sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce 854 | url: "https://pub.dev" 855 | source: hosted 856 | version: "3.1.3" 857 | sdks: 858 | dart: ">=3.6.0 <4.0.0" 859 | flutter: ">=3.24.0" 860 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: revo 2 | description: "A new Flutter project." 3 | # The following line prevents the package from being accidentally published to 4 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 5 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 6 | 7 | # The following defines the version and build number for your application. 8 | # A version number is three numbers separated by dots, like 1.2.43 9 | # followed by an optional build number separated by a +. 10 | # Both the version and the builder number may be overridden in flutter 11 | # build by specifying --build-name and --build-number, respectively. 12 | # In Android, build-name is used as versionName while build-number used as versionCode. 13 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 14 | # In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. 15 | # Read more about iOS versioning at 16 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 17 | # In Windows, build-name is used as the major, minor, and patch parts 18 | # of the product and file versions while build-number is used as the build suffix. 19 | version: 1.0.0+1 20 | 21 | environment: 22 | sdk: ^3.6.0 23 | 24 | # Dependencies specify other packages that your package needs in order to work. 25 | # To automatically upgrade your package dependencies to the latest versions 26 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 27 | # dependencies can be manually updated by changing the version numbers below to 28 | # the latest version available on pub.dev. To see which dependencies have newer 29 | # versions available, run `flutter pub outdated`. 30 | dependencies: 31 | flutter: 32 | sdk: flutter 33 | 34 | # The following adds the Cupertino Icons font to your application. 35 | # Use with the CupertinoIcons class for iOS style icons. 36 | dynamic_color: ^1.7.0 37 | google_fonts: ^6.2.1 38 | flutter_contacts: ^1.1.9+2 39 | permission_handler: ^11.3.1 40 | call_e_log: ^0.0.4 41 | intl: ^0.20.1 42 | share_plus: ^10.1.3 43 | qr_flutter: ^4.1.0 44 | bloc: ^8.1.4 45 | flutter_bloc: ^8.1.6 46 | provider: ^6.1.2 47 | flutter_sim_data: ^1.0.5 48 | url_launcher: ^6.3.1 49 | direct_caller_sim_choice: ^1.0.5 50 | phone_state: ^2.1.1 51 | flutter_native_splash: ^2.4.4 52 | hugeicons: ^0.0.7 53 | shared_preferences: ^2.5.1 54 | font_awesome_flutter: ^10.8.0 55 | phosphor_flutter: ^2.1.0 56 | flutter_callkit_incoming: ^2.5.0 57 | simple_barcode_scanner: ^0.3.0 58 | flutter_dtmf: 59 | git: 60 | url: https://github.com/eopeter/flutter_dtmf.git 61 | ref: master 62 | android_intent_plus: ^4.0.3 63 | 64 | flutter_native_splash: 65 | image: 'assets/splash.png' 66 | color: "#FFFFFF" 67 | image_dark: 'assets/splash.png' 68 | color_dark: "#000000" 69 | # branding: 'assets/branding.png' 70 | 71 | android_12: 72 | image: 'assets/splash.png' 73 | color: "#FFFFFF" 74 | image_dark: 'assets/splash.png' 75 | color_dark: "#000000" 76 | # branding: 'assets/branding.png' 77 | 78 | dev_dependencies: 79 | flutter_test: 80 | sdk: flutter 81 | 82 | # The "flutter_lints" package below contains a set of recommended lints to 83 | # encourage good coding practices. The lint set provided by the package is 84 | # activated in the `analysis_options.yaml` file located at the root of your 85 | # package. See that file for information about deactivating specific lint 86 | # rules and activating additional ones. 87 | flutter_lints: ^5.0.0 88 | 89 | # For information on the generic Dart part of this file, see the 90 | # following page: https://dart.dev/tools/pub/pubspec 91 | 92 | # The following section is specific to Flutter packages. 93 | flutter: 94 | 95 | # The following line ensures that the Material Icons font is 96 | # included with your application, so that you can use the icons in 97 | # the material Icons class. 98 | uses-material-design: true 99 | 100 | # To add assets to your application, add an assets section, like this: 101 | assets: 102 | - assets/ 103 | - assets/dialpad/ 104 | - assets/icon.png 105 | - assets/branding.png 106 | - assets/static-bg.jpg 107 | - assets/dialpad/1.mp3 108 | - assets/dialpad/2.mp3 109 | - assets/dialpad/3.mp3 110 | - assets/dialpad/4.mp3 111 | - assets/dialpad/5.mp3 112 | - assets/dialpad/6.mp3 113 | - assets/dialpad/7.mp3 114 | - assets/dialpad/8.mp3 115 | - assets/dialpad/9.mp3 116 | - assets/dialpad/0.mp3 117 | - assets/dialpad/star.mp3 118 | - assets/dialpad/hash.mp3 119 | 120 | # An image asset can refer to one or more resolution-specific "variants", see 121 | # https://flutter.dev/to/resolution-aware-images 122 | 123 | # For details regarding adding assets from package dependencies, see 124 | # https://flutter.dev/to/asset-from-package 125 | 126 | # To add custom fonts to your application, add a fonts section here, 127 | # in this "flutter" section. Each entry in this list should have a 128 | # "family" key with the font family name, and a "fonts" key with a 129 | # list giving the asset and other descriptors for the font. For 130 | # example: 131 | # fonts: 132 | # - family: Schyler 133 | # fonts: 134 | # - asset: fonts/Schyler-Regular.ttf 135 | # - asset: fonts/Schyler-Italic.ttf 136 | # style: italic 137 | # - family: Trajan Pro 138 | # fonts: 139 | # - asset: fonts/TrajanPro.ttf 140 | # - asset: fonts/TrajanPro_Bold.ttf 141 | # weight: 700 142 | # 143 | # For details regarding fonts from package dependencies, 144 | # see https://flutter.dev/to/font-from-package 145 | --------------------------------------------------------------------------------