├── .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 |
21 |
22 |
23 |
24 |
25 |
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 |
--------------------------------------------------------------------------------