├── .gitignore
├── .idea
├── appInsightsSettings.xml
├── compiler.xml
├── deploymentTargetDropDown.xml
├── gradle.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── kotlinc.xml
├── migrations.xml
├── misc.xml
└── vcs.xml
├── README.md
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
├── release
│ ├── app-release.apk
│ └── output-metadata.json
└── src
│ ├── androidTest
│ └── java
│ │ └── dev
│ │ └── prince
│ │ └── securify
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-playstore.png
│ ├── java
│ │ └── dev
│ │ │ └── prince
│ │ │ └── securify
│ │ │ ├── SecurifyApplication.kt
│ │ │ ├── database
│ │ │ ├── AccountDao.kt
│ │ │ ├── AccountEntity.kt
│ │ │ ├── CardDao.kt
│ │ │ ├── CardEntity.kt
│ │ │ └── SecurifyDatabase.kt
│ │ │ ├── di
│ │ │ └── AppModule.kt
│ │ │ ├── encryption
│ │ │ └── EncryptionManager.kt
│ │ │ ├── local
│ │ │ └── SharedPrefHelper.kt
│ │ │ ├── signin
│ │ │ ├── GoogleAuthUiClient.kt
│ │ │ ├── SignInResult.kt
│ │ │ ├── SignInScreen.kt
│ │ │ └── SignInState.kt
│ │ │ ├── ui
│ │ │ ├── MainActivity.kt
│ │ │ ├── auth
│ │ │ │ ├── AuthViewModel.kt
│ │ │ │ ├── MasterKeyScreen.kt
│ │ │ │ └── UnlockScreen.kt
│ │ │ ├── bottomnav
│ │ │ │ ├── BottomBar.kt
│ │ │ │ └── BottomNavDestinations.kt
│ │ │ ├── card
│ │ │ │ ├── CardScreen.kt
│ │ │ │ ├── CardUi.kt
│ │ │ │ └── CardViewModel.kt
│ │ │ ├── components
│ │ │ │ ├── AlertDialog.kt
│ │ │ │ ├── BottomSheet.kt
│ │ │ │ ├── CreateMasterKeySheetContent.kt
│ │ │ │ ├── SheetSurface.kt
│ │ │ │ ├── UpdateMasterKeySheetContent.kt
│ │ │ │ └── fab
│ │ │ │ │ ├── ExpandedFabButton.kt
│ │ │ │ │ ├── FabButtonItem.kt
│ │ │ │ │ ├── FabButtonMain.kt
│ │ │ │ │ ├── FabButtonState.kt
│ │ │ │ │ └── FabButtonSub.kt
│ │ │ ├── generate
│ │ │ │ ├── CustomSlider.kt
│ │ │ │ ├── GenerateScreen.kt
│ │ │ │ └── GenerateViewModel.kt
│ │ │ ├── home
│ │ │ │ ├── HomeScreen.kt
│ │ │ │ └── HomeViewModel.kt
│ │ │ ├── intro
│ │ │ │ ├── IntroScreen.kt
│ │ │ │ └── IntroViewModel.kt
│ │ │ ├── password
│ │ │ │ ├── PasswordScreen.kt
│ │ │ │ └── PasswordViewModel.kt
│ │ │ ├── settings
│ │ │ │ ├── SettingsScreen.kt
│ │ │ │ └── SettingsViewModel.kt
│ │ │ └── theme
│ │ │ │ ├── Color.kt
│ │ │ │ ├── Theme.kt
│ │ │ │ └── Type.kt
│ │ │ └── util
│ │ │ ├── AccountOrCard.kt
│ │ │ ├── CardUtils.kt
│ │ │ ├── Constants.kt
│ │ │ ├── CryptoUtils.kt
│ │ │ └── Util.kt
│ └── res
│ │ ├── drawable
│ │ ├── ic_launcher_background.xml
│ │ ├── ic_launcher_foreground.xml
│ │ ├── icon_add.xml
│ │ ├── icon_amazon_prime_video.png
│ │ ├── icon_american_express.xml
│ │ ├── icon_arrow_drop_down.xml
│ │ ├── icon_arrow_left.xml
│ │ ├── icon_behance.png
│ │ ├── icon_call.xml
│ │ ├── icon_card.xml
│ │ ├── icon_card_number.xml
│ │ ├── icon_copy.png
│ │ ├── icon_cvv.xml
│ │ ├── icon_date.xml
│ │ ├── icon_delete.xml
│ │ ├── icon_diners_club.xml
│ │ ├── icon_discord.png
│ │ ├── icon_dribbble.png
│ │ ├── icon_edit.xml
│ │ ├── icon_email.xml
│ │ ├── icon_facebook.png
│ │ ├── icon_filter.xml
│ │ ├── icon_fingerprint.xml
│ │ ├── icon_github.png
│ │ ├── icon_gmail.png
│ │ ├── icon_home.xml
│ │ ├── icon_info.xml
│ │ ├── icon_instagram.png
│ │ ├── icon_key.xml
│ │ ├── icon_linkedin.png
│ │ ├── icon_lock.xml
│ │ ├── icon_lock_open.xml
│ │ ├── icon_logout.xml
│ │ ├── icon_mastercard.xml
│ │ ├── icon_medium.png
│ │ ├── icon_messenger.png
│ │ ├── icon_more.xml
│ │ ├── icon_netflix.png
│ │ ├── icon_note.xml
│ │ ├── icon_others.png
│ │ ├── icon_pass.xml
│ │ ├── icon_pinterest.png
│ │ ├── icon_quora.png
│ │ ├── icon_reddit.png
│ │ ├── icon_regenerate.xml
│ │ ├── icon_rupay.png
│ │ ├── icon_search.xml
│ │ ├── icon_selected.xml
│ │ ├── icon_settings.xml
│ │ ├── icon_share.xml
│ │ ├── icon_snapchat.png
│ │ ├── icon_spotify.png
│ │ ├── icon_stackoverflow.png
│ │ ├── icon_tumblr.png
│ │ ├── icon_twitterx.png
│ │ ├── icon_unselected.xml
│ │ ├── icon_username.xml
│ │ ├── icon_visa.xml
│ │ ├── icon_visibility.xml
│ │ ├── icon_visibility_off.xml
│ │ ├── icon_whatsapp.png
│ │ ├── icon_wordpress.png
│ │ ├── icon_youtube.png
│ │ ├── icons_snapchat.png
│ │ ├── img_empty_box.png
│ │ ├── key.png
│ │ ├── lock.png
│ │ └── surviellance.jpg
│ │ ├── font
│ │ ├── poppins_light.ttf
│ │ ├── poppins_medium.ttf
│ │ ├── poppins_regular.ttf
│ │ └── poppins_semibold.ttf
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_foreground.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_foreground.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_foreground.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_foreground.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_foreground.webp
│ │ └── ic_launcher_round.webp
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ │ └── xml
│ │ ├── backup_rules.xml
│ │ └── data_extraction_rules.xml
│ └── test
│ └── java
│ └── dev
│ └── prince
│ └── securify
│ └── ExampleUnitTest.kt
├── build.gradle.kts
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── privacy-policy
└── PrivacyPolicy
└── settings.gradle.kts
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 | /app/google-services.json
17 |
--------------------------------------------------------------------------------
/.idea/appInsightsSettings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
27 |
28 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/deploymentTargetDropDown.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
18 |
19 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/migrations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Securify
2 | Securify is a password and credit/debit card manager app, with advanced encryption. Features include Fingerprint unlock, one-tap sign-in and generate password
3 |
4 | ## ⬇️ Download from Playstore
5 |
6 |
7 |
8 |
9 |
10 | ## 🎥 Demo Video
11 | https://github.com/devprincefahad/Securify/assets/94643962/6f3e5ce8-8f33-4aa0-940a-300ceae96aa7
12 |
13 | ## 📷 Screen Shots
14 |
15 |
16 |
17 |
18 | ## 🔥 Features
19 | - One Tap Sign-In
20 | - Fingerprint Unlock
21 | - Save Bank Cards
22 | - Save social or any account password
23 | - Generate Strong Password
24 | - Offline mode Support
25 | - Safe and Secure
26 |
27 | ## 🛠 Tech Used
28 | - Jetpack Compose
29 | - Kotlin
30 | - Room Database
31 | - Flow
32 | - Coil (Image Loading Lib)
33 | - Coroutines
34 | - MVVM (Model-View-Viewmodel)
35 | - Compose Navigation Destinations
36 | - SplashScreen Compat
37 | - Material 3
38 | - Dependency Injection using Hilt
39 | - Firebase one tap sign-in
40 | - Firebase Crashlytics
41 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
2 | plugins {
3 | alias(libs.plugins.androidApplication)
4 | alias(libs.plugins.kotlinAndroid)
5 | id("dagger.hilt.android.plugin")
6 | id("com.google.devtools.ksp")
7 | // Add the Crashlytics Gradle plugin
8 | id("com.google.firebase.crashlytics")
9 | // Add the Google services Gradle plugin
10 | id("com.google.gms.google-services")
11 | }
12 |
13 | android {
14 | namespace = "dev.prince.securify"
15 |
16 | hilt {
17 | enableAggregatingTask = true
18 | }
19 |
20 | compileSdk = 34
21 |
22 | defaultConfig {
23 | applicationId = "dev.prince.securify"
24 | minSdk = 24
25 | targetSdk = 34
26 | versionCode = 3
27 | versionName = "1.0.2"
28 |
29 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
30 | vectorDrawables {
31 | useSupportLibrary = true
32 | }
33 | signingConfig = signingConfigs.getByName("debug")
34 | }
35 |
36 | buildTypes {
37 | release {
38 | isMinifyEnabled = false
39 | proguardFiles(
40 | getDefaultProguardFile("proguard-android-optimize.txt"),
41 | "proguard-rules.pro"
42 | )
43 | signingConfig = signingConfigs.getByName("debug")
44 | }
45 | }
46 | compileOptions {
47 | sourceCompatibility = JavaVersion.VERSION_1_8
48 | targetCompatibility = JavaVersion.VERSION_1_8
49 | }
50 | kotlinOptions {
51 | jvmTarget = "1.8"
52 | }
53 | buildFeatures {
54 | compose = true
55 | }
56 | composeOptions {
57 | kotlinCompilerExtensionVersion = "1.5.1"
58 | }
59 | packaging {
60 | resources {
61 | excludes += "/META-INF/{AL2.0,LGPL2.1}"
62 | }
63 | }
64 | }
65 |
66 | dependencies {
67 |
68 | implementation(libs.core.ktx)
69 | implementation(libs.lifecycle.runtime.ktx)
70 | implementation(libs.activity.compose)
71 | implementation(platform(libs.compose.bom))
72 | implementation(libs.ui)
73 | implementation(libs.ui.graphics)
74 | implementation(libs.ui.tooling.preview)
75 | implementation(libs.material3)
76 |
77 | // Dagger - Hilt
78 | implementation(libs.hilt.android)
79 | implementation(libs.firebase.auth)
80 | ksp(libs.hilt.android.compiler)
81 | implementation(libs.androidx.hilt.navigation.compose)
82 |
83 | // Crypto security
84 | implementation(libs.androidx.security.crypto)
85 |
86 | // Compose Nav Destinations
87 | implementation("io.github.raamcosta.compose-destinations:core:1.9.52")
88 | ksp("io.github.raamcosta.compose-destinations:ksp:1.9.52")
89 |
90 | // Room
91 | implementation("androidx.room:room-runtime:2.5.2")
92 | annotationProcessor("androidx.room:room-compiler:2.5.2")
93 | ksp("androidx.room:room-compiler:2.5.2")
94 |
95 | // Kotlin Extensions and Coroutines support for Room
96 | implementation("androidx.room:room-ktx:2.5.2")
97 |
98 | // Biometric
99 | implementation(libs.androidx.biometric)
100 |
101 | // Firebase Crashlytics
102 | // Import the BoM for the Firebase platform
103 | implementation(platform("com.google.firebase:firebase-bom:32.4.0"))
104 |
105 | // Add the dependencies for the Crashlytics and Analytics libraries
106 | // When using the BoM, you don't specify versions in Firebase library dependencies
107 | implementation("com.google.firebase:firebase-crashlytics-ktx")
108 | implementation("com.google.firebase:firebase-analytics-ktx")
109 |
110 | // Coil
111 | implementation(libs.coil.compose)
112 |
113 | // Auth and google play services
114 | implementation(libs.firebase.auth.ktx)
115 | implementation(libs.play.services.auth)
116 |
117 | // SplashScreen
118 | implementation("androidx.core:core-splashscreen:1.0.0")
119 |
120 | testImplementation(libs.junit)
121 | androidTestImplementation(libs.androidx.test.ext.junit)
122 | androidTestImplementation(libs.espresso.core)
123 | androidTestImplementation(platform(libs.compose.bom))
124 | androidTestImplementation(libs.ui.test.junit4)
125 | debugImplementation(libs.ui.tooling)
126 | debugImplementation(libs.ui.test.manifest)
127 |
128 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/release/app-release.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/release/app-release.apk
--------------------------------------------------------------------------------
/app/release/output-metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 3,
3 | "artifactType": {
4 | "type": "APK",
5 | "kind": "Directory"
6 | },
7 | "applicationId": "dev.prince.securify",
8 | "variantName": "release",
9 | "elements": [
10 | {
11 | "type": "SINGLE",
12 | "filters": [],
13 | "attributes": [],
14 | "versionCode": 3,
15 | "versionName": "1.0.2",
16 | "outputFile": "app-release.apk"
17 | }
18 | ],
19 | "elementType": "File"
20 | }
--------------------------------------------------------------------------------
/app/src/androidTest/java/dev/prince/securify/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("dev.prince.securify", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
16 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/SecurifyApplication.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify
2 |
3 | import android.app.Application
4 | import dagger.hilt.android.HiltAndroidApp
5 |
6 | @HiltAndroidApp
7 | class SecurifyApplication : Application()
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/database/AccountDao.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.database
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Delete
5 | import androidx.room.Insert
6 | import androidx.room.OnConflictStrategy
7 | import androidx.room.Query
8 | import androidx.room.Update
9 | import kotlinx.coroutines.flow.Flow
10 |
11 | @Dao
12 | interface AccountDao {
13 |
14 | @Insert(onConflict = OnConflictStrategy.REPLACE)
15 | suspend fun insertAccount(accountEntity: AccountEntity)
16 |
17 | @Update
18 | suspend fun updateAccount(accountEntity: AccountEntity)
19 |
20 | @Delete
21 | suspend fun deleteAccount(accountEntity: AccountEntity)
22 |
23 | @Query("SELECT * FROM account ORDER BY id ASC")
24 | fun getAllAccounts(): Flow>
25 |
26 | @Query("SELECT * FROM `account` WHERE id = :id")
27 | fun getAccountById(id: Int): Flow
28 |
29 | @Query("DELETE FROM account")
30 | fun deleteAllAccounts()
31 |
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/database/AccountEntity.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.database
2 |
3 | import androidx.room.Entity
4 | import androidx.room.PrimaryKey
5 |
6 | @Entity(tableName = "account")
7 | data class AccountEntity(
8 | @PrimaryKey(autoGenerate = true)
9 | val id: Int,
10 | val accountName: String,
11 | val userName: String,
12 | val email: String,
13 | val mobileNumber: String,
14 | val password: String,
15 | val note: String,
16 | val createdAt: Long
17 | )
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/database/CardDao.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.database
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Delete
5 | import androidx.room.Insert
6 | import androidx.room.OnConflictStrategy
7 | import androidx.room.Query
8 | import androidx.room.Update
9 | import kotlinx.coroutines.flow.Flow
10 |
11 | @Dao
12 | interface CardDao {
13 |
14 | @Insert(onConflict = OnConflictStrategy.REPLACE)
15 | suspend fun insertCard(cardEntity: CardEntity)
16 |
17 | @Update
18 | suspend fun updateCard(cardEntity: CardEntity)
19 |
20 | @Delete
21 | suspend fun deleteCard(cardEntity: CardEntity)
22 |
23 | @Query("SELECT * FROM card ORDER BY id ASC")
24 | fun getAllCards(): Flow>
25 |
26 | @Query("SELECT * FROM `card` WHERE id = :id")
27 | fun getCardsById(id: Int): Flow
28 |
29 | @Query("DELETE FROM card")
30 | fun deleteAllCards()
31 |
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/database/CardEntity.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.database
2 |
3 | import androidx.room.Entity
4 | import androidx.room.PrimaryKey
5 |
6 | @Entity(tableName = "card")
7 | data class CardEntity(
8 | @PrimaryKey(autoGenerate = true)
9 | val id: Int,
10 | val cardHolderName: String,
11 | val cardNumber: String,
12 | val cardExpiryDate: String,
13 | val cardCvv: String,
14 | val cardProvider: String,
15 | val createdAt: Long
16 | )
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/database/SecurifyDatabase.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.database
2 |
3 | import androidx.room.Database
4 | import androidx.room.RoomDatabase
5 |
6 | @Database(
7 | entities = [AccountEntity::class, CardEntity::class],
8 | version = 1
9 | )
10 | abstract class SecurifyDatabase : RoomDatabase() {
11 | abstract fun accountDao(): AccountDao
12 | abstract fun cardDao(): CardDao
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/di/AppModule.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.di
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 | import androidx.room.Room
6 | import androidx.security.crypto.EncryptedSharedPreferences
7 | import androidx.security.crypto.MasterKeys
8 | import dagger.Module
9 | import dagger.Provides
10 | import dagger.hilt.InstallIn
11 | import dagger.hilt.android.qualifiers.ApplicationContext
12 | import dagger.hilt.components.SingletonComponent
13 | import dev.prince.securify.database.SecurifyDatabase
14 | import dev.prince.securify.util.ENCRYPTED_SHARED_PREFS_NAME
15 | import javax.inject.Singleton
16 |
17 | @Module
18 | @InstallIn(SingletonComponent::class)
19 | object AppModule {
20 |
21 | @Provides
22 | @Singleton
23 | fun provideRoomInstance(@ApplicationContext context: Context) = Room.databaseBuilder(
24 | context,
25 | SecurifyDatabase::class.java,
26 | "securify_database"
27 | ).build()
28 |
29 | @Singleton
30 | @Provides
31 | fun provideAccountDao(db: SecurifyDatabase) = db.accountDao()
32 |
33 | @Singleton
34 | @Provides
35 | fun provideCardDao(db: SecurifyDatabase) = db.cardDao()
36 |
37 | @Provides
38 | @Singleton
39 | fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences {
40 | val keyGenParameterSpec = MasterKeys.AES256_GCM_SPEC
41 | val masterKeyAlias = MasterKeys.getOrCreate(keyGenParameterSpec)
42 |
43 | return EncryptedSharedPreferences.create(
44 | ENCRYPTED_SHARED_PREFS_NAME,
45 | masterKeyAlias,
46 | context,
47 | EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
48 | EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
49 | )
50 | }
51 |
52 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/encryption/EncryptionManager.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.encryption
2 |
3 | import android.content.Context
4 | import android.util.Base64
5 | import com.google.android.gms.auth.api.identity.Identity
6 | import dagger.hilt.android.qualifiers.ApplicationContext
7 | import dev.prince.securify.local.SharedPrefHelper
8 | import dev.prince.securify.signin.GoogleAuthUiClient
9 | import dev.prince.securify.util.BYTE_SIZE
10 | import dev.prince.securify.util.TRANSFORMATION
11 | import dev.prince.securify.util.generateKey
12 | import javax.crypto.Cipher
13 | import javax.crypto.spec.IvParameterSpec
14 | import javax.inject.Inject
15 |
16 | class EncryptionManager @Inject constructor(
17 | @ApplicationContext private val context: Context
18 | ) {
19 |
20 | private val googleAuthUiClient by lazy {
21 | GoogleAuthUiClient(
22 | context = context,
23 | oneTapClient = Identity.getSignInClient(context)
24 | )
25 | }
26 |
27 | private val user = googleAuthUiClient.getSignedInUser()
28 |
29 | fun encrypt(input: String): String {
30 | if (input.isBlank()) {
31 | return input
32 | }
33 | return runCatching {
34 | val key = user?.email?.let { generateKey(user.userId, it) }
35 | val cipher = Cipher.getInstance(TRANSFORMATION)
36 | val ivBytes = ByteArray(BYTE_SIZE)
37 | val ivSpec = IvParameterSpec(ivBytes)
38 | cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec)
39 | val encryptedBytes = cipher.doFinal(input.toByteArray(Charsets.UTF_8))
40 | return Base64.encodeToString(encryptedBytes, Base64.DEFAULT)
41 | }.getOrElse {
42 | it.printStackTrace()
43 | ""
44 | }
45 | }
46 |
47 | fun decrypt(input: String): String {
48 | if (input.isBlank()) {
49 | return input
50 | }
51 | return runCatching {
52 | val key = user?.email?.let { generateKey(user.userId, it) }
53 | val cipher = Cipher.getInstance(TRANSFORMATION)
54 | val ivBytes = ByteArray(BYTE_SIZE)
55 | val ivSpec = IvParameterSpec(ivBytes)
56 | cipher.init(Cipher.DECRYPT_MODE, key, ivSpec)
57 | val encryptedBytes = Base64.decode(input, Base64.DEFAULT)
58 | val decryptedBytes = cipher.doFinal(encryptedBytes)
59 | return String(decryptedBytes, Charsets.UTF_8)
60 | }.getOrElse {
61 | it.printStackTrace()
62 | ""
63 | }
64 | }
65 |
66 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/local/SharedPrefHelper.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.local
2 |
3 | import android.content.SharedPreferences
4 | import androidx.core.content.edit
5 | import javax.inject.Inject
6 |
7 | class SharedPrefHelper @Inject constructor(
8 | private val pref: SharedPreferences
9 | ) {
10 |
11 | private val KEY_AUTH = "auth_key"
12 | private val KEY_SWITCH_STATE = "switchState"
13 |
14 | var masterKey: String
15 | get() = getString(KEY_AUTH)
16 | set(value) {
17 | setString(value)
18 | }
19 |
20 | private fun getString(key: String): String {
21 | return pref.getString(key, "") ?: ""
22 | }
23 |
24 | private fun setString(value: String) {
25 | pref.edit {
26 | putString(KEY_AUTH, value)
27 | }
28 | }
29 |
30 | fun resetMasterKeyAndSwitch() {
31 | pref.edit {
32 | remove(KEY_AUTH)
33 | remove(KEY_SWITCH_STATE)
34 | }
35 | }
36 |
37 | fun getSwitchState(): Boolean {
38 | return pref.getBoolean(KEY_SWITCH_STATE, false)
39 | }
40 |
41 | fun setSwitchState(state: Boolean) {
42 | pref.edit { putBoolean(KEY_SWITCH_STATE, state) }
43 | }
44 |
45 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/signin/GoogleAuthUiClient.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.signin
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.content.IntentSender
6 | import com.google.android.gms.auth.api.identity.BeginSignInRequest
7 | import com.google.android.gms.auth.api.identity.BeginSignInRequest.GoogleIdTokenRequestOptions
8 | import com.google.android.gms.auth.api.identity.SignInClient
9 | import com.google.firebase.auth.GoogleAuthProvider
10 | import com.google.firebase.auth.ktx.auth
11 | import com.google.firebase.ktx.Firebase
12 | import dev.prince.securify.R
13 | import kotlinx.coroutines.CancellationException
14 | import kotlinx.coroutines.tasks.await
15 |
16 | class GoogleAuthUiClient(
17 | private val context: Context,
18 | private val oneTapClient: SignInClient
19 | ) {
20 | private val auth = Firebase.auth
21 |
22 | suspend fun signIn(): IntentSender? {
23 | val result = try {
24 | oneTapClient.beginSignIn(
25 | buildSignInRequest()
26 | ).await()
27 | } catch(e: Exception) {
28 | e.printStackTrace()
29 | if(e is CancellationException) throw e
30 | null
31 | }
32 | return result?.pendingIntent?.intentSender
33 | }
34 |
35 | suspend fun signInWithIntent(intent: Intent): SignInResult {
36 | val credential = oneTapClient.getSignInCredentialFromIntent(intent)
37 | val googleIdToken = credential.googleIdToken
38 | val googleCredentials = GoogleAuthProvider.getCredential(googleIdToken, null)
39 | return try {
40 | val user = auth.signInWithCredential(googleCredentials).await().user
41 | SignInResult(
42 | data = user?.run {
43 | UserData(
44 | userId = uid,
45 | username = displayName,
46 | email = email,
47 | profilePictureUrl = photoUrl?.toString()
48 | )
49 | },
50 | errorMessage = null
51 | )
52 | } catch(e: Exception) {
53 | e.printStackTrace()
54 | if(e is CancellationException) throw e
55 | SignInResult(
56 | data = null,
57 | errorMessage = e.message
58 | )
59 | }
60 | }
61 |
62 | suspend fun signOut() {
63 | try {
64 | //google sign out
65 | oneTapClient.signOut().await()
66 | //firebase sign out
67 | auth.signOut()
68 | } catch(e: Exception) {
69 | e.printStackTrace()
70 | if(e is CancellationException) throw e
71 | }
72 | }
73 |
74 | fun getSignedInUser(): UserData? = auth.currentUser?.run {
75 | UserData(
76 | userId = uid,
77 | username = displayName,
78 | email = email,
79 | profilePictureUrl = photoUrl?.toString()
80 | )
81 | }
82 |
83 | private fun buildSignInRequest(): BeginSignInRequest {
84 | return BeginSignInRequest.Builder()
85 | .setGoogleIdTokenRequestOptions(
86 | GoogleIdTokenRequestOptions.builder()
87 | .setSupported(true)
88 | // .setFilterByAuthorizedAccounts(false) = if this is set to true than it will check if we are already signed in with
89 | // a specific account or we have signed in with a specific account already
90 | // so it only show that account as an option, if set to false it will give full
91 | // list of accounts user can login with
92 | .setFilterByAuthorizedAccounts(false)
93 | .setServerClientId(context.getString(R.string.web_client_id))
94 | .build()
95 | )
96 | // .setAutoSelectEnabled(true) = if user has single google account it will automatically
97 | // sign in instead of user choosing that single account with tap
98 | .setAutoSelectEnabled(true)
99 | .build()
100 | }
101 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/signin/SignInResult.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.signin
2 |
3 | data class SignInResult(
4 | val data: UserData?,
5 | val errorMessage: String?
6 | )
7 |
8 | data class UserData(
9 | val userId: String,
10 | val username: String?,
11 | val email: String?,
12 | val profilePictureUrl: String?
13 | )
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/signin/SignInScreen.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.signin
2 |
3 | import android.widget.Toast
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.fillMaxSize
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.material3.Button
8 | import androidx.compose.material3.Text
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.runtime.LaunchedEffect
11 | import androidx.compose.ui.Alignment
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.platform.LocalContext
14 | import androidx.compose.ui.unit.dp
15 |
16 | @Composable
17 | fun SignInScreen(
18 | state: SignInState,
19 | onSignInClick: () -> Unit
20 | ) {
21 | val context = LocalContext.current
22 | LaunchedEffect(key1 = state.signInError) {
23 | state.signInError?.let { error ->
24 | Toast.makeText(
25 | context,
26 | error,
27 | Toast.LENGTH_LONG
28 | ).show()
29 | }
30 | }
31 |
32 | Box(
33 | modifier = Modifier
34 | .fillMaxSize()
35 | .padding(16.dp),
36 | contentAlignment = Alignment.Center
37 | ) {
38 | Button(onClick = onSignInClick) {
39 | Text(text = "Sign in")
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/signin/SignInState.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.signin
2 |
3 | data class SignInState(
4 | val isSignInSuccessful: Boolean = false,
5 | val signInError: String? = null
6 | )
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/ui/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.ui
2 |
3 | import android.os.Bundle
4 | import androidx.activity.compose.setContent
5 | import androidx.compose.foundation.layout.fillMaxSize
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.material3.Scaffold
8 | import androidx.compose.material3.ScaffoldDefaults
9 | import androidx.compose.material3.SnackbarHost
10 | import androidx.compose.material3.SnackbarHostState
11 | import androidx.compose.runtime.CompositionLocalProvider
12 | import androidx.compose.runtime.remember
13 | import androidx.compose.runtime.rememberCoroutineScope
14 | import androidx.compose.ui.Modifier
15 | import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
16 | import androidx.fragment.app.FragmentActivity
17 | import com.ramcosta.composedestinations.DestinationsNavHost
18 | import com.ramcosta.composedestinations.rememberNavHostEngine
19 | import dagger.hilt.android.AndroidEntryPoint
20 | import dev.prince.securify.ui.bottomnav.BottomBar
21 | import dev.prince.securify.ui.theme.SecurifyTheme
22 | import dev.prince.securify.util.LocalSnackbar
23 | import dev.prince.securify.util.shouldShowBottomBar
24 | import kotlinx.coroutines.launch
25 |
26 | @AndroidEntryPoint
27 | class MainActivity : FragmentActivity() {
28 | override fun onCreate(savedInstanceState: Bundle?) {
29 | installSplashScreen() // Complying with Android 12 Splash Screen guidelines
30 | super.onCreate(savedInstanceState)
31 |
32 | setContent {
33 | SecurifyTheme {
34 | ScaffoldDefaults.contentWindowInsets
35 | val engine = rememberNavHostEngine()
36 | val navController = engine.rememberNavController()
37 | val destination = navController.appCurrentDestinationAsState().value
38 | ?: NavGraphs.root.startRoute.startAppDestination
39 |
40 | val snackbarHostState = remember { SnackbarHostState() }
41 | val scope = rememberCoroutineScope()
42 |
43 | val onSnackbarMessageReceived = fun(message: String) {
44 | scope.launch {
45 | snackbarHostState.showSnackbar(message)
46 | }
47 | }
48 |
49 | Scaffold(
50 | modifier = Modifier.fillMaxSize(),
51 | bottomBar = {
52 | if (destination.shouldShowBottomBar()) {
53 | BottomBar(navController)
54 | }
55 | },
56 | snackbarHost = {
57 | SnackbarHost(snackbarHostState)
58 | }
59 | ) { contentPadding ->
60 | CompositionLocalProvider(
61 | LocalSnackbar provides onSnackbarMessageReceived
62 | ) {
63 | DestinationsNavHost(
64 | modifier = Modifier
65 | .padding(contentPadding),
66 | navGraph = NavGraphs.root,
67 | navController = navController,
68 | engine = engine
69 | )
70 | }
71 | }
72 | }
73 | }
74 | }
75 | }
76 |
77 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/ui/auth/AuthViewModel.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.ui.auth
2 |
3 | import android.content.Context
4 | import android.os.Build
5 | import androidx.annotation.RequiresApi
6 | import androidx.biometric.BiometricManager
7 | import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
8 | import androidx.biometric.BiometricPrompt
9 | import androidx.compose.runtime.getValue
10 | import androidx.compose.runtime.mutableStateOf
11 | import androidx.compose.runtime.remember
12 | import androidx.compose.runtime.setValue
13 | import androidx.core.content.ContextCompat
14 | import androidx.fragment.app.Fragment
15 | import androidx.fragment.app.FragmentActivity
16 | import androidx.lifecycle.ViewModel
17 | import androidx.lifecycle.viewModelScope
18 | import dagger.hilt.android.lifecycle.HiltViewModel
19 | import dagger.hilt.android.qualifiers.ApplicationContext
20 | import dev.prince.securify.local.SharedPrefHelper
21 | import dev.prince.securify.util.oneShotFlow
22 | import kotlinx.coroutines.delay
23 | import kotlinx.coroutines.launch
24 | import javax.inject.Inject
25 |
26 | @HiltViewModel
27 | class AuthViewModel @Inject constructor(
28 | private val prefs: SharedPrefHelper,
29 | @ApplicationContext private val context: Context
30 | ) : ViewModel() {
31 |
32 | // For SharedPrefs
33 | private val loginKey = prefs.masterKey
34 |
35 | val isUserLoggedIn = prefs.masterKey.isNotEmpty()
36 |
37 | private fun saveUserLoginInfo(value: String) {
38 | prefs.masterKey = value
39 | }
40 |
41 | // For Setup Key Screen
42 | var key by mutableStateOf("")
43 | var keyVisible by mutableStateOf(false)
44 |
45 | var confirmKey by mutableStateOf("")
46 | var confirmKeyVisible by mutableStateOf(false)
47 |
48 | var isLoading by mutableStateOf(false)
49 |
50 | val messages = oneShotFlow()
51 |
52 | val navigateToHome = oneShotFlow()
53 |
54 | fun validateAndSaveMasterKey() {
55 |
56 | if (key.isEmpty() and confirmKey.isEmpty()) {
57 | messages.tryEmit("Please enter a Master Key")
58 | return
59 | }
60 | if (key != confirmKey) {
61 | messages.tryEmit("Please enter correct Master Key")
62 | return
63 | }
64 | if (key.length < 6) {
65 | messages.tryEmit("A Master Key should at least have 6 characters")
66 | return
67 | }
68 |
69 | val isNotEmpty = key.isNotEmpty() and confirmKey.isNotEmpty()
70 | val isKeySame = key == confirmKey
71 |
72 | if (isNotEmpty and isKeySame) {
73 | saveUserLoginInfo(confirmKey)
74 | viewModelScope.launch {
75 | isLoading = true
76 | delay(2000)
77 | isLoading = false
78 | navigateToHome.tryEmit(Unit)
79 | }
80 | }
81 | }
82 |
83 | //For Unlock Screen
84 | var unlockKey by mutableStateOf("")
85 | var unlockKeyVisible by mutableStateOf(false)
86 | var isLoadingForUnlock by (mutableStateOf(false))
87 |
88 | fun validateAndOpen() {
89 | if (unlockKey.isEmpty()) {
90 | messages.tryEmit("Please enter a Master Key")
91 | } else if (unlockKey == loginKey) {
92 | viewModelScope.launch {
93 | isLoadingForUnlock = true
94 | delay(2000)
95 | isLoadingForUnlock = false
96 | navigateToHome.tryEmit(Unit)
97 | }
98 | } else {
99 | messages.tryEmit(
100 | "Incorrect Master Key," +
101 | " Please check again"
102 | )
103 | }
104 | }
105 |
106 |
107 | //For Update key
108 | var oldKey by mutableStateOf("")
109 | var oldKeyVisible by mutableStateOf(false)
110 |
111 | var newKey by mutableStateOf("")
112 | var newKeyVisible by mutableStateOf(false)
113 |
114 | var confirmNewKey by mutableStateOf("")
115 | var confirmNewKeyVisible by mutableStateOf(false)
116 |
117 |
118 | fun validateAndUpdateMasterKey() {
119 |
120 | if (oldKey.isEmpty()) {
121 | messages.tryEmit("Please enter Old Key")
122 | return
123 | }
124 | if (newKey.isEmpty()) {
125 | messages.tryEmit("Please enter New Master Key")
126 | return
127 | }
128 | if (confirmNewKey.isEmpty()) {
129 | messages.tryEmit("Please enter Confirm Master Key")
130 | return
131 | }
132 | if (newKey != confirmNewKey) {
133 | messages.tryEmit("Please enter correct Master Key")
134 | return
135 | }
136 | if (oldKey != loginKey) {
137 | messages.tryEmit("Please enter correct old key")
138 | return
139 | }
140 | if (oldKey == newKey) {
141 | messages.tryEmit("New Key and Old Key cannot be the same")
142 | return
143 | }
144 |
145 | val isNotEmpty = oldKey.isNotEmpty() and newKey.isNotEmpty() and confirmNewKey.isNotEmpty()
146 | val isOldKeySame = oldKey == loginKey
147 | val isKeyNotSame = oldKey != newKey
148 |
149 | if (isNotEmpty and isOldKeySame and isKeyNotSame) {
150 | saveUserLoginInfo(newKey)
151 | viewModelScope.launch {
152 | isLoading = true
153 | delay(2000)
154 | isLoading = false
155 | navigateToHome.tryEmit(Unit)
156 | }
157 | }
158 | }
159 |
160 | // For Biometric
161 |
162 | val promptInfo = BiometricPrompt.PromptInfo.Builder()
163 | .setAllowedAuthenticators(BIOMETRIC_STRONG)
164 | .setTitle("Fingerprint Unlock")
165 | .setSubtitle("Use your fingerprint to unlock Securify")
166 | .setNegativeButtonText("Cancel")
167 | .setDeviceCredentialAllowed(true)
168 | .build()
169 |
170 | fun showSnackBarMsg(msg: String) {
171 | messages.tryEmit(msg)
172 | }
173 |
174 | val getState: Boolean
175 | get() = prefs.getSwitchState()
176 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/ui/auth/MasterKeyScreen.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.ui.auth
2 |
3 | import androidx.activity.ComponentActivity
4 | import androidx.activity.compose.BackHandler
5 | import androidx.compose.foundation.background
6 | import androidx.compose.foundation.layout.Box
7 | import androidx.compose.foundation.layout.Column
8 | import androidx.compose.foundation.layout.Spacer
9 | import androidx.compose.foundation.layout.fillMaxHeight
10 | import androidx.compose.foundation.layout.fillMaxSize
11 | import androidx.compose.foundation.layout.fillMaxWidth
12 | import androidx.compose.foundation.layout.height
13 | import androidx.compose.foundation.layout.padding
14 | import androidx.compose.foundation.rememberScrollState
15 | import androidx.compose.foundation.verticalScroll
16 | import androidx.compose.material3.Surface
17 | import androidx.compose.material3.Text
18 | import androidx.compose.runtime.Composable
19 | import androidx.compose.runtime.LaunchedEffect
20 | import androidx.compose.ui.Alignment
21 | import androidx.compose.ui.Modifier
22 | import androidx.compose.ui.graphics.Color
23 | import androidx.compose.ui.platform.LocalContext
24 | import androidx.compose.ui.res.stringResource
25 | import androidx.compose.ui.text.TextStyle
26 | import androidx.compose.ui.text.font.FontWeight
27 | import androidx.compose.ui.text.style.TextAlign
28 | import androidx.compose.ui.tooling.preview.Preview
29 | import androidx.compose.ui.unit.dp
30 | import androidx.compose.ui.unit.sp
31 | import androidx.hilt.navigation.compose.hiltViewModel
32 | import com.ramcosta.composedestinations.annotation.Destination
33 | import com.ramcosta.composedestinations.navigation.DestinationsNavigator
34 | import com.ramcosta.composedestinations.navigation.popUpTo
35 | import dev.prince.securify.R
36 | import dev.prince.securify.ui.components.CreateMasterKeySheetContent
37 | import dev.prince.securify.ui.components.UpdateMasterKeySheetContent
38 | import dev.prince.securify.ui.destinations.HomeScreenDestination
39 | import dev.prince.securify.ui.theme.BgBlack
40 | import dev.prince.securify.ui.theme.poppinsFamily
41 | import dev.prince.securify.util.LocalSnackbar
42 |
43 | enum class NavigationSource {
44 | INTRO,
45 | SETTINGS
46 | }
47 |
48 | @Destination
49 | @Composable
50 | fun MasterKeyScreen(
51 | navigator: DestinationsNavigator,
52 | navigationSource: NavigationSource,
53 | viewModel: AuthViewModel = hiltViewModel()
54 | ) {
55 |
56 | val context = LocalContext.current
57 |
58 | val snackbar = LocalSnackbar.current
59 |
60 | LaunchedEffect(Unit) {
61 | viewModel.messages.collect {
62 | snackbar(it)
63 | }
64 | }
65 |
66 | LaunchedEffect(Unit) {
67 | viewModel.navigateToHome.collect {
68 | navigator.navigate(HomeScreenDestination) {
69 | popUpTo(HomeScreenDestination) {
70 | inclusive = true
71 | }
72 | }
73 | }
74 | }
75 |
76 | Column(
77 | modifier = Modifier
78 | .background(color = BgBlack)
79 | ) {
80 | Spacer(modifier = Modifier.height(32.dp))
81 | Text(
82 | modifier = Modifier
83 | .padding(start = 16.dp, end = 32.dp),
84 | text = stringResource(R.string.setup_key_tagline_1),
85 | textAlign = TextAlign.Left,
86 | color = Color.White,
87 | style = TextStyle(
88 | fontSize = 46.sp,
89 | fontWeight = FontWeight.Bold,
90 | fontFamily = poppinsFamily,
91 | lineHeight = 60.sp
92 | )
93 | )
94 |
95 | Spacer(modifier = Modifier.height(32.dp))
96 |
97 | if (navigationSource == NavigationSource.SETTINGS) {
98 | UpdateMasterKeySheetContent()
99 | } else {
100 | CreateMasterKeySheetContent()
101 | }
102 |
103 | BackHandler {
104 | if (navigationSource == NavigationSource.INTRO) {
105 | (context as ComponentActivity).finish()
106 | } else {
107 | navigator.popBackStack()
108 | }
109 | }
110 | }
111 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/ui/bottomnav/BottomBar.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.ui.bottomnav
2 |
3 | import android.annotation.SuppressLint
4 | import androidx.compose.material3.Icon
5 | import androidx.compose.material3.NavigationBar
6 | import androidx.compose.material3.NavigationBarItem
7 | import androidx.compose.material3.NavigationBarItemDefaults
8 | import androidx.compose.material3.Text
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.graphics.Color
11 | import androidx.compose.ui.res.painterResource
12 | import androidx.compose.ui.res.stringResource
13 | import androidx.navigation.NavBackStackEntry
14 | import androidx.navigation.NavController
15 | import androidx.navigation.NavOptionsBuilder
16 | import com.ramcosta.composedestinations.navigation.navigate
17 | import dev.prince.securify.ui.NavGraphs
18 | import dev.prince.securify.ui.appCurrentDestinationAsState
19 | import dev.prince.securify.ui.destinations.Destination
20 | import dev.prince.securify.ui.startAppDestination
21 | import dev.prince.securify.ui.theme.Blue
22 |
23 | @SuppressLint("RestrictedApi")
24 | @Composable
25 | fun BottomBar(
26 | navController: NavController
27 | ) {
28 | val currentDestination: Destination = navController.appCurrentDestinationAsState().value
29 | ?: NavGraphs.root.startAppDestination
30 |
31 | NavigationBar(
32 | containerColor = Color.White,
33 | contentColor = Color.Black
34 | ) {
35 | BottomBarDestination.entries.forEach { destination ->
36 | NavigationBarItem(
37 | selected = currentDestination == destination.direction,
38 | onClick = {
39 | // remove all navigation items from the stack
40 | // so only the currently selected screen remains in the stack
41 | // Avoid multiple copies of the same destination when
42 | // reselecting the same item
43 | navController
44 | // Restore state when reselecting a previously selected item
45 | .navigate(destination.direction, fun NavOptionsBuilder.() {
46 | launchSingleTop = true
47 | /*val navigationRoutes = BottomBarDestination.entries.toTypedArray()
48 |
49 | val firstBottomBarDestination = navController.currentBackStack.value
50 | .firstOrNull { navBackStackEntry ->
51 | checkForDestinations(
52 | navigationRoutes,
53 | navBackStackEntry
54 | )
55 | }
56 | ?.destination
57 | // remove all navigation items from the stack
58 | // so only the currently selected screen remains in the stack
59 | if (firstBottomBarDestination != null) {
60 | popUpTo(firstBottomBarDestination.id) {
61 | inclusive = true
62 | saveState = true
63 | }
64 | }
65 | // Avoid multiple copies of the same destination when
66 | // reselecting the same item
67 | launchSingleTop = true
68 | // Restore state when reselecting a previously selected item
69 | restoreState = true*/
70 | })
71 | },
72 | icon = {
73 | Icon(
74 | painter = painterResource(destination.icon),
75 | contentDescription = stringResource(destination.label),
76 | tint = if (currentDestination == destination.direction) Color.White else Color.Black // Icon color when selected
77 | )
78 | },
79 | label = { Text(stringResource(destination.label)) },
80 | colors = NavigationBarItemDefaults.colors(
81 | selectedIconColor = Blue,
82 | indicatorColor = Blue
83 | )
84 | )
85 | }
86 | }
87 | }
88 |
89 | fun checkForDestinations(
90 | navigationRoutes: Array,
91 | navBackStackEntry: NavBackStackEntry
92 | ): Boolean {
93 | navigationRoutes.forEach {
94 | if (it.direction.route == navBackStackEntry.destination.route) {
95 | return true
96 | }
97 | }
98 | return false
99 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/ui/bottomnav/BottomNavDestinations.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.ui.bottomnav
2 |
3 | import androidx.annotation.StringRes
4 | import com.ramcosta.composedestinations.spec.DirectionDestinationSpec
5 | import dev.prince.securify.R
6 | import dev.prince.securify.ui.destinations.GenerateScreenDestination
7 | import dev.prince.securify.ui.destinations.HomeScreenDestination
8 | import dev.prince.securify.ui.destinations.SettingsScreenDestination
9 |
10 | enum class BottomBarDestination(
11 | val direction: DirectionDestinationSpec,
12 | val icon: Int,
13 | @StringRes val label: Int
14 | ) {
15 | Home(HomeScreenDestination, R.drawable.icon_home, R.string.home),
16 | Generate(
17 | GenerateScreenDestination,
18 | R.drawable.icon_key,
19 | R.string.generate
20 | ),
21 | Settings(SettingsScreenDestination, R.drawable.icon_settings, R.string.settings)
22 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/ui/card/CardUi.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.ui.card
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.Column
7 | import androidx.compose.foundation.layout.Row
8 | import androidx.compose.foundation.layout.fillMaxSize
9 | import androidx.compose.foundation.layout.fillMaxWidth
10 | import androidx.compose.foundation.layout.height
11 | import androidx.compose.foundation.layout.padding
12 | import androidx.compose.foundation.layout.size
13 | import androidx.compose.foundation.layout.width
14 | import androidx.compose.foundation.shape.RoundedCornerShape
15 | import androidx.compose.material3.Surface
16 | import androidx.compose.material3.Text
17 | import androidx.compose.runtime.Composable
18 | import androidx.compose.ui.Alignment
19 | import androidx.compose.ui.Modifier
20 | import androidx.compose.ui.graphics.Brush
21 | import androidx.compose.ui.graphics.Color
22 | import androidx.compose.ui.res.painterResource
23 | import androidx.compose.ui.text.TextStyle
24 | import androidx.compose.ui.text.font.FontWeight
25 | import androidx.compose.ui.text.style.TextAlign
26 | import androidx.compose.ui.unit.dp
27 | import androidx.compose.ui.unit.sp
28 | import dev.prince.securify.ui.theme.LightGray
29 | import dev.prince.securify.ui.theme.poppinsFamily
30 | import dev.prince.securify.util.formatCardNumber
31 | import dev.prince.securify.util.formatExpiryDate
32 | import dev.prince.securify.util.randomGradient
33 |
34 | @Composable
35 | fun CardUi(
36 | cardHolderName: String,
37 | cardNumber: String,
38 | cardExpiryDate: String,
39 | cardCVV: String,
40 | cardIcon: Int
41 | ) {
42 |
43 | val formattedCardNumber = formatCardNumber(cardNumber)
44 | val formattedExpiryDate = formatExpiryDate(cardExpiryDate)
45 |
46 | Box(
47 | modifier = Modifier
48 | .fillMaxWidth(),
49 | contentAlignment = Alignment.Center
50 | ) {
51 | Surface(
52 | modifier = Modifier
53 | .height(190.dp)
54 | .width(320.dp),
55 | shape = RoundedCornerShape(10.dp),
56 | contentColor = Color.White,
57 | shadowElevation = 26.dp
58 | ) {
59 | Box(
60 | modifier = Modifier
61 | .fillMaxSize()
62 | .background(
63 | Brush.verticalGradient(randomGradient)
64 | )
65 | ) {
66 |
67 | Text(
68 | modifier = Modifier
69 | .align(Alignment.TopStart)
70 | .padding(all = 16.dp),
71 | text = cardHolderName,
72 | style = TextStyle(
73 | fontSize = 18.sp,
74 | fontWeight = FontWeight.Medium,
75 | fontFamily = poppinsFamily
76 | )
77 | )
78 |
79 | Image(
80 | modifier = Modifier
81 | .align(Alignment.BottomEnd)
82 | .size(80.dp)
83 | .padding(16.dp),
84 | painter = painterResource(cardIcon),
85 | contentDescription = null,
86 | )
87 |
88 | Text(
89 | modifier = Modifier
90 | .align(Alignment.CenterStart)
91 | .padding(horizontal = 16.dp),
92 | text = formattedCardNumber,
93 | textAlign = TextAlign.Center,
94 | style = TextStyle(
95 | fontSize = 22.sp,
96 | fontWeight = FontWeight.Medium,
97 | fontFamily = poppinsFamily
98 | )
99 | )
100 |
101 | Row(
102 | modifier = Modifier
103 | .padding(all = 16.dp)
104 | .align(Alignment.BottomStart)
105 | ) {
106 | Column(
107 | modifier = Modifier
108 | .padding(end = 16.dp)
109 | ) {
110 | Text(
111 | text = "VALID",
112 | style = TextStyle(
113 | fontSize = 14.sp,
114 | fontWeight = FontWeight.Medium,
115 | fontFamily = poppinsFamily,
116 | color = LightGray
117 | )
118 | )
119 | Text(
120 | text = formattedExpiryDate,
121 | style = TextStyle(
122 | fontSize = 14.sp,
123 | fontWeight = FontWeight.Medium,
124 | fontFamily = poppinsFamily,
125 | color = Color.White
126 | )
127 | )
128 | }
129 |
130 | Column(
131 | modifier = Modifier
132 | .padding(end = 16.dp)
133 | ) {
134 | Text(
135 | text = "CVV",
136 | style = TextStyle(
137 | fontSize = 14.sp,
138 | fontWeight = FontWeight.Medium,
139 | fontFamily = poppinsFamily,
140 | color = LightGray
141 | )
142 | )
143 | Text(
144 | text = cardCVV,
145 | style = TextStyle(
146 | fontSize = 14.sp,
147 | fontWeight = FontWeight.Medium,
148 | fontFamily = poppinsFamily,
149 | color = Color.White
150 | )
151 | )
152 | }
153 | }
154 | }
155 | }
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/ui/card/CardViewModel.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.ui.card
2 |
3 | import android.util.Log
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableIntStateOf
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.setValue
8 | import androidx.core.text.isDigitsOnly
9 | import androidx.lifecycle.ViewModel
10 | import androidx.lifecycle.viewModelScope
11 | import dagger.hilt.android.lifecycle.HiltViewModel
12 | import dev.prince.securify.database.CardDao
13 | import dev.prince.securify.database.CardEntity
14 | import dev.prince.securify.encryption.EncryptionManager
15 | import dev.prince.securify.util.cardSuggestions
16 | import dev.prince.securify.util.oneShotFlow
17 | import kotlinx.coroutines.launch
18 | import java.util.Calendar
19 | import java.util.Date
20 | import javax.inject.Inject
21 |
22 | @HiltViewModel
23 | class CardViewModel @Inject constructor(
24 | private val db: CardDao,
25 | private val encryptionManager: EncryptionManager
26 | ) : ViewModel() {
27 |
28 | var isEditScreen by mutableStateOf(false)
29 |
30 | val messages = oneShotFlow()
31 |
32 | var cardNumber by mutableStateOf("")
33 |
34 | var cardHolderName by mutableStateOf("")
35 |
36 | var cardExpiryDate by mutableStateOf("")
37 |
38 | var cardCVV by mutableStateOf("")
39 |
40 | var expanded by mutableStateOf(false)
41 |
42 | var cardProviderName by mutableStateOf("Select Card Provider")
43 |
44 | var expandedProviderField by mutableStateOf(false)
45 | var hideKeyboard by mutableStateOf(false)
46 | var selectedCardImage by mutableIntStateOf(cardSuggestions.first().second)
47 |
48 | val success = mutableStateOf(false)
49 |
50 | fun getCardById(cardId: Int) {
51 | viewModelScope.launch {
52 | db.getCardsById(cardId).collect {
53 | cardNumber = encryptionManager.decrypt(it.cardNumber)
54 | cardHolderName = encryptionManager.decrypt(it.cardHolderName)
55 | cardProviderName = it.cardProvider
56 | cardCVV = encryptionManager.decrypt(it.cardCvv)
57 | cardExpiryDate = encryptionManager.decrypt(it.cardExpiryDate)
58 | it.createdAt
59 | }
60 | }
61 | }
62 |
63 | fun validateAndUpdate(id: Int) {
64 | if (validateFields()) {
65 |
66 | val currentTimeInMillis = System.currentTimeMillis()
67 |
68 | val card = CardEntity(
69 | id = id,
70 | cardHolderName = encryptionManager.encrypt(cardHolderName.trim()),
71 | cardNumber = encryptionManager.encrypt(cardNumber),
72 | cardExpiryDate = encryptionManager.encrypt(cardExpiryDate),
73 | cardCvv = encryptionManager.encrypt(cardCVV),
74 | cardProvider = cardProviderName,
75 | createdAt = currentTimeInMillis
76 | )
77 |
78 | viewModelScope.launch {
79 | db.updateCard(card)
80 | messages.tryEmit("Credentials Updated!")
81 | success.value = true
82 | }
83 |
84 | }
85 | }
86 |
87 | private fun validateFields(): Boolean {
88 | if (cardProviderName == "Select Card Provider") {
89 | messages.tryEmit("Please choose a Card Provider")
90 | return false
91 | }
92 | if (cardNumber.isEmpty()) {
93 | messages.tryEmit("Please provide Card Number")
94 | return false
95 | }
96 | if (cardNumber.length < 16) {
97 | messages.tryEmit("Card number must be 16 digits long")
98 | return false
99 | }
100 | if (!cardNumber.isDigitsOnly()) {
101 | messages.tryEmit("Invalid Card Number")
102 | return false
103 | }
104 | if (cardHolderName.isEmpty()) {
105 | messages.tryEmit("Please provide Card holder's name")
106 | return false
107 | }
108 | if (cardExpiryDate.isEmpty()) {
109 | messages.tryEmit("Please provide Card Expiry Date")
110 | return false
111 | }
112 | if (!validateExpiryDate(cardExpiryDate)) {
113 | return false
114 | }
115 | if (cardCVV.isEmpty()) {
116 | messages.tryEmit("Please provide Card CVV")
117 | return false
118 | }
119 | if (!cardCVV.isDigitsOnly()) {
120 | messages.tryEmit("Invalid Card CVV")
121 | return false
122 | }
123 | if (cardCVV.length < 3) {
124 | messages.tryEmit("Card cvv must be 3 digits")
125 | return false
126 | }
127 | return true
128 | }
129 |
130 | fun validateAndInsert() {
131 | if (validateFields()) {
132 |
133 | val currentTimeInMillis = System.currentTimeMillis()
134 |
135 | val card = CardEntity(
136 | id = 0,
137 | cardHolderName = encryptionManager.encrypt(cardHolderName.trim()),
138 | cardNumber = encryptionManager.encrypt(cardNumber),
139 | cardExpiryDate = encryptionManager.encrypt(cardExpiryDate),
140 | cardCvv = encryptionManager.encrypt(cardCVV),
141 | cardProvider = cardProviderName,
142 | createdAt = currentTimeInMillis
143 | )
144 |
145 | viewModelScope.launch {
146 | db.insertCard(card)
147 | messages.tryEmit("Credentials Added!")
148 | success.value = true
149 | }
150 |
151 | }
152 | }
153 |
154 | private fun validateExpiryDate(cardExpiryDate: String): Boolean {
155 |
156 | val currentDate = Date()
157 | val calender = Calendar.getInstance()
158 |
159 | val userMonth = cardExpiryDate.substring(0, 2).toInt()
160 | val userYear = cardExpiryDate.substring(2, 4).toInt()
161 | val currentYear = calender.get(Calendar.YEAR) % 100
162 |
163 | if (userMonth < 1 || userMonth > 12) {
164 | messages.tryEmit("Invalid month. Please enter a valid month between 01 and 12.")
165 | return false
166 | }
167 |
168 | if (userYear < currentYear) {
169 | messages.tryEmit("Expiry year is in the past. Please enter a valid future year.")
170 | return false
171 | }
172 |
173 | if (userYear == currentYear && userMonth < currentDate.month + 1) {
174 | messages.tryEmit("Expiry date is in the past. Please enter a valid future date.")
175 | return false
176 | }
177 |
178 | return true
179 | }
180 |
181 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/ui/components/AlertDialog.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.ui.components
2 |
3 | import androidx.compose.foundation.clickable
4 | import androidx.compose.foundation.layout.padding
5 | import androidx.compose.foundation.shape.RoundedCornerShape
6 | import androidx.compose.material3.AlertDialog
7 | import androidx.compose.material3.Button
8 | import androidx.compose.material3.ButtonDefaults
9 | import androidx.compose.material3.Text
10 | import androidx.compose.runtime.Composable
11 | import androidx.compose.ui.Modifier
12 | import androidx.compose.ui.graphics.Color
13 | import androidx.compose.ui.text.TextStyle
14 | import androidx.compose.ui.text.font.FontWeight
15 | import androidx.compose.ui.unit.dp
16 | import androidx.compose.ui.unit.sp
17 | import dev.prince.securify.ui.theme.Blue
18 | import dev.prince.securify.ui.theme.Gray
19 | import dev.prince.securify.ui.theme.poppinsFamily
20 |
21 | @Composable
22 | fun AlertDialogContent(
23 | onDismissRequest: () -> Unit,
24 | onConfirmation: () -> Unit,
25 | dialogTitle: String,
26 | dialogText: String,
27 | confirmTitle: String
28 | ) {
29 | AlertDialog(
30 | title = {
31 | Text(
32 | text = dialogTitle,
33 | style = TextStyle(
34 | fontSize = 18.sp,
35 | fontFamily = poppinsFamily,
36 | fontWeight = FontWeight.Medium
37 | )
38 | )
39 | },
40 | text = {
41 | Text(
42 | text = dialogText,
43 | style = TextStyle(
44 | fontSize = 14.sp,
45 | fontFamily = poppinsFamily,
46 | fontWeight = FontWeight.Normal
47 | )
48 | )
49 | },
50 | containerColor = Color.White,
51 | onDismissRequest = {
52 | onDismissRequest()
53 | },
54 | confirmButton = {
55 | Button(
56 | onClick = {
57 | onConfirmation()
58 | },
59 | shape = RoundedCornerShape(8.dp),
60 | colors = ButtonDefaults.buttonColors(
61 | containerColor = Blue,
62 | contentColor = Color.White
63 | )
64 | ) {
65 | Text(
66 | modifier = Modifier
67 | .padding(
68 | vertical = 4.dp,
69 | horizontal = 8.dp
70 | ),
71 | text = confirmTitle,
72 | color = Color.White,
73 | style = TextStyle(
74 | fontSize = 14.sp,
75 | fontFamily = poppinsFamily,
76 | fontWeight = FontWeight.Normal
77 | )
78 | )
79 | }
80 | },
81 | dismissButton = {
82 | Text(
83 | modifier = Modifier
84 | .padding(all = 16.dp)
85 | .clickable {
86 | onDismissRequest()
87 | },
88 | text = "Cancel",
89 | color = Gray,
90 | style = TextStyle(
91 | fontSize = 14.sp,
92 | fontFamily = poppinsFamily,
93 | fontWeight = FontWeight.Normal
94 | )
95 | )
96 | }
97 | )
98 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/ui/components/BottomSheet.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.ui.components
2 |
3 | import androidx.activity.compose.BackHandler
4 | import androidx.compose.foundation.layout.WindowInsets
5 | import androidx.compose.material3.BottomSheetDefaults
6 | import androidx.compose.material3.ExperimentalMaterial3Api
7 | import androidx.compose.material3.ModalBottomSheet
8 | import androidx.compose.material3.rememberModalBottomSheetState
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.graphics.Color
11 |
12 | @OptIn(ExperimentalMaterial3Api::class)
13 | @Composable
14 | fun BottomSheet(
15 | onDismiss: () -> Unit,
16 | content: @Composable () -> Unit
17 | ) {
18 |
19 | ModalBottomSheet(
20 | containerColor = Color(0xFFFFFFFF),
21 | sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
22 | onDismissRequest = {
23 | onDismiss()
24 | },
25 | dragHandle = {
26 | BottomSheetDefaults.DragHandle()
27 | },
28 | content = {
29 | content()
30 | },
31 | windowInsets = WindowInsets(0, 0, 0, 0)
32 | )
33 |
34 | BackHandler(
35 | onBack = {
36 | onDismiss()
37 | }
38 | )
39 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/ui/components/SheetSurface.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.ui.components
2 |
3 | import androidx.compose.foundation.layout.fillMaxWidth
4 | import androidx.compose.foundation.layout.wrapContentHeight
5 | import androidx.compose.foundation.rememberScrollState
6 | import androidx.compose.foundation.shape.RoundedCornerShape
7 | import androidx.compose.foundation.verticalScroll
8 | import androidx.compose.material3.Surface
9 | import androidx.compose.runtime.Composable
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.graphics.Color
12 | import androidx.compose.ui.unit.dp
13 |
14 | @Composable
15 | fun SheetSurface(
16 | modifier: Modifier = Modifier,
17 | content: @Composable () -> Unit
18 | ) {
19 | Surface(
20 | modifier = modifier
21 | .fillMaxWidth(),
22 | shape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp),
23 | color = Color.White,
24 | content = content
25 | )
26 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/ui/components/fab/ExpandedFabButton.kt:
--------------------------------------------------------------------------------
1 | import androidx.compose.animation.AnimatedVisibility
2 | import androidx.compose.animation.core.animateFloatAsState
3 | import androidx.compose.animation.expandVertically
4 | import androidx.compose.animation.fadeIn
5 | import androidx.compose.animation.fadeOut
6 | import androidx.compose.animation.shrinkVertically
7 | import androidx.compose.foundation.background
8 | import androidx.compose.foundation.layout.Arrangement
9 | import androidx.compose.foundation.layout.Column
10 | import androidx.compose.foundation.layout.Row
11 | import androidx.compose.foundation.layout.padding
12 | import androidx.compose.foundation.layout.size
13 | import androidx.compose.foundation.layout.wrapContentSize
14 | import androidx.compose.foundation.lazy.LazyColumn
15 | import androidx.compose.foundation.shape.RoundedCornerShape
16 | import androidx.compose.material3.FloatingActionButton
17 | import androidx.compose.material3.Icon
18 | import androidx.compose.material3.MaterialTheme.typography
19 | import androidx.compose.material3.Text
20 | import androidx.compose.runtime.Composable
21 | import androidx.compose.runtime.MutableState
22 | import androidx.compose.runtime.getValue
23 | import androidx.compose.ui.Alignment
24 | import androidx.compose.ui.Modifier
25 | import androidx.compose.ui.draw.clip
26 | import androidx.compose.ui.draw.rotate
27 | import androidx.compose.ui.graphics.Color
28 | import androidx.compose.ui.res.painterResource
29 | import androidx.compose.ui.res.stringResource
30 | import androidx.compose.ui.text.TextStyle
31 | import androidx.compose.ui.text.font.FontWeight
32 | import androidx.compose.ui.tooling.preview.Preview
33 | import androidx.compose.ui.unit.dp
34 | import androidx.compose.ui.unit.sp
35 | import dev.prince.securify.R
36 | import dev.prince.securify.ui.components.fab.FabButtonItem
37 | import dev.prince.securify.ui.components.fab.FabButtonMain
38 | import dev.prince.securify.ui.components.fab.FabButtonState
39 | import dev.prince.securify.ui.components.fab.FabButtonSub
40 | import dev.prince.securify.ui.components.fab.rememberMultiFabState
41 | import dev.prince.securify.ui.theme.poppinsFamily
42 |
43 | /**
44 | * Composable function to display a Multi-Floating Action Button (Multi-FAB) that can be expanded to reveal sub-items.
45 | *
46 | * @param modifier The optional [Modifier] for customizing the layout of the Multi-FAB.
47 | * @param items The list of [FabButtonItem] representing the sub-items to be displayed when the Multi-FAB is expanded.
48 | * @param fabState The [MutableState] representing the current state of the Multi-FAB, whether it is expanded or collapsed.
49 | * @param fabIcon The [FabButtonMain] representing the main FAB with an icon and optional rotation.
50 | * @param fabOption The [FabButtonSub] representing the customization options for the sub-items.
51 | * @param onFabItemClicked The callback function to handle click events on the sub-items.
52 | * @param stateChanged The optional callback function to notify when the state of the Multi-FAB changes (expanded or collapsed).
53 | */
54 | @Composable
55 | fun MultiFloatingActionButton(
56 | items: List,
57 | fabState: MutableState = rememberMultiFabState(),
58 | fabIcon: FabButtonMain,
59 | fabOption: FabButtonSub = FabButtonSub(),
60 | onFabItemClicked: (fabItem: FabButtonItem) -> Unit,
61 | stateChanged: (fabState: FabButtonState) -> Unit = {}
62 | ) {
63 | // Animation for rotating the main FAB icon based on its state (expanded or collapsed)
64 | val rotation by animateFloatAsState(
65 | if (fabState.value == FabButtonState.Expand) {
66 | fabIcon.iconRotate ?: 0f
67 | } else {
68 | 0f
69 | }, label = "main_fab_rotation"
70 | )
71 |
72 | Column(
73 | modifier = Modifier.wrapContentSize(),
74 | horizontalAlignment = Alignment.End
75 | ) {
76 | // AnimatedVisibility to show or hide the sub-items when the Multi-FAB is expanded or collapsed
77 | AnimatedVisibility(
78 | visible = fabState.value.isExpanded(),
79 | enter = fadeIn() + expandVertically(),
80 | exit = fadeOut() + shrinkVertically()
81 | ) {
82 | // LazyColumn to display the sub-items in a vertical list
83 | LazyColumn(
84 | modifier = Modifier.wrapContentSize(),
85 | horizontalAlignment = Alignment.End,
86 | verticalArrangement = Arrangement.spacedBy(15.dp)
87 | ) {
88 | items(items.size) { index ->
89 | // Composable to display each individual sub-item
90 | MiniFabItem(
91 | item = items[index],
92 | fabOption = fabOption,
93 | onFabItemClicked = onFabItemClicked
94 | )
95 | }
96 | item {} // Empty item to provide spacing at the end of the list
97 | }
98 | }
99 |
100 | // Main FloatingActionButton representing the Multi-FAB
101 | FloatingActionButton(
102 | modifier = Modifier.size(66.dp),
103 | onClick = {
104 | fabState.value = fabState.value.toggleValue()
105 | stateChanged(fabState.value)
106 | },
107 | containerColor = fabOption.backgroundTint,
108 | contentColor = fabOption.iconTint
109 | ) {
110 | // Icon for the main FAB with optional rotation based on its state (expanded or collapsed)
111 | Icon(
112 | painter = painterResource(fabIcon.iconRes),
113 | contentDescription = null,
114 | modifier = Modifier.size(32.dp)
115 | .rotate(rotation),
116 | tint = fabOption.iconTint
117 | )
118 | }
119 | }
120 | }
121 |
122 | /**
123 | * Composable function to display an individual sub-item of the Multi-Floating Action Button (Multi-FAB).
124 | *
125 | * @param item The [FabButtonItem] representing the sub-item with an icon and label.
126 | * @param fabOption The [FabButtonSub] representing the customization options for the sub-items.
127 | * @param onFabItemClicked The callback function to handle click events on the sub-items.
128 | */
129 | @Composable
130 | fun MiniFabItem(
131 | item: FabButtonItem,
132 | fabOption: FabButtonSub,
133 | onFabItemClicked: (item: FabButtonItem) -> Unit
134 | ) {
135 | Row(
136 | modifier = Modifier
137 | .wrapContentSize()
138 | .padding(end = 10.dp),
139 | horizontalArrangement = Arrangement.spacedBy(10.dp),
140 | verticalAlignment = Alignment.CenterVertically
141 | ) {
142 | // Text label for the sub-item displayed in a rounded-corner background
143 | Text(
144 | text = item.label,
145 | style = TextStyle(
146 | fontFamily = poppinsFamily,
147 | fontSize = 12.sp,
148 | fontWeight = FontWeight.Medium
149 | ),
150 | color = Color.White,
151 | modifier = Modifier
152 | .clip(RoundedCornerShape(size = 8.dp))
153 | .background(Color(0xFF000000).copy(alpha = 0.5f))
154 | .padding(all = 8.dp)
155 | )
156 |
157 | // FloatingActionButton representing the sub-item
158 | FloatingActionButton(
159 | onClick = { onFabItemClicked(item) },
160 | modifier = Modifier.size(40.dp),
161 | containerColor = fabOption.backgroundTint,
162 | contentColor = fabOption.iconTint
163 | ) {
164 | // Icon for the sub-item with customized tint
165 | Icon(
166 | painter = painterResource(item.iconRes),
167 | contentDescription = null,
168 | tint = fabOption.iconTint
169 | )
170 | }
171 | }
172 | }
173 |
174 | @Composable
175 | @Preview(showBackground = true, showSystemUi = true)
176 | fun MultiFloatingActionButtonPreview(){
177 | val items = listOf(
178 | FabButtonItem(R.drawable.icon_lock, "lock"),
179 | FabButtonItem(R.drawable.icon_note, "note")
180 | )
181 |
182 | val fabIcon = FabButtonMain(R.drawable.icon_add)
183 |
184 | MultiFloatingActionButton(
185 | items = items,
186 | fabIcon = fabIcon,
187 | onFabItemClicked = { /* Define a click handler for the items */ }
188 | )
189 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/ui/components/fab/FabButtonItem.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.ui.components.fab
2 |
3 | /**
4 | * Represents an item for a Floating Action Button (FAB) with an icon and label.
5 | * */
6 |
7 | data class FabButtonItem(val iconRes: Int, val label: String)
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/ui/components/fab/FabButtonMain.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.ui.components.fab
2 |
3 | import dev.prince.securify.R
4 |
5 | /**
6 | * Represents the main floating action button (FAB) with an icon and optional rotation.
7 | * The main FAB is the primary action button that can be expanded to reveal sub-items.
8 | */
9 | interface FabButtonMain {
10 | val iconRes: Int
11 | val iconRotate: Float?
12 | }
13 |
14 | /**
15 | * Implementation of [FabButtonMain] interface.
16 | *
17 | * @property iconRes The [ImageVector] representing the icon to be displayed on the main FAB.
18 | * @property iconRotate The optional rotation angle for the main FAB icon. If null, the icon will not be rotated.
19 | */
20 | private class FabButtonMainImpl(
21 | override val iconRes: Int,
22 | override val iconRotate: Float?
23 | ) : FabButtonMain
24 |
25 | /**
26 | * Creates a new instance of [FabButtonMain] with the provided icon and optional rotation.
27 | *
28 | * @param iconRes The [ImageVector] representing the icon to be displayed on the main FAB.
29 | * @param iconRotate The optional rotation angle for the main FAB icon. If null, the icon will not be rotated.
30 | * @return A new instance of [FabButtonMain] with the specified icon and rotation.
31 | */
32 | fun FabButtonMain(iconRes: Int = R.drawable.icon_add, iconRotate: Float = 45f): FabButtonMain =
33 | FabButtonMainImpl(iconRes, iconRotate)
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/ui/components/fab/FabButtonState.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.ui.components.fab
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.mutableStateOf
5 | import androidx.compose.runtime.remember
6 |
7 | /**
8 | * Represents the state of a Floating Action Button (FAB), which can be either Collapsed or Expanded.
9 | * The FAB state is used to determine its visibility and behavior, such as showing or hiding sub-items.
10 | */
11 | sealed class FabButtonState {
12 | object Collapsed : FabButtonState()
13 | object Expand : FabButtonState()
14 |
15 | fun isExpanded() = this == Expand
16 |
17 | fun toggleValue() = if (isExpanded()) {
18 | Collapsed
19 | } else {
20 | Expand
21 | }
22 | }
23 |
24 | /**
25 | * Remembers the state of a Multi-Floating Action Button (FAB) using [remember] and [mutableStateOf].
26 | *
27 | * @return A [MutableState] that holds the current state of the Multi-FAB.
28 | */
29 | @Composable
30 | fun rememberMultiFabState() =
31 | remember { mutableStateOf(FabButtonState.Collapsed) }
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/ui/components/fab/FabButtonSub.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.ui.components.fab
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import dev.prince.securify.ui.theme.Blue
5 |
6 | interface FabButtonSub {
7 | val iconTint: Color
8 | val backgroundTint: Color
9 | }
10 |
11 | /**
12 | * Implementation of [FabButtonSub] interface.
13 | *
14 | * @property iconTint The [Color] used to tint the icon of the sub-item.
15 | * @property backgroundTint The [Color] used to tint the background of the sub-item.
16 | */
17 | private class FabButtonSubImpl(
18 | override val iconTint: Color,
19 | override val backgroundTint: Color
20 | ) : FabButtonSub
21 |
22 | /**
23 | * Creates a new instance of [FabButtonSub] with the provided icon and background tints.
24 | *
25 | * @param backgroundTint The [Color] used to tint the background of the sub-item.
26 | * @param iconTint The [Color] used to tint the icon of the sub-item.
27 | * @return A new instance of [FabButtonSub] with the specified icon and background tints.
28 | */
29 | fun FabButtonSub(
30 | backgroundTint: Color = Blue,
31 | iconTint: Color = Color.White
32 | ): FabButtonSub = FabButtonSubImpl(iconTint, backgroundTint)
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/ui/generate/CustomSlider.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.ui.generate
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.interaction.MutableInteractionSource
5 | import androidx.compose.foundation.layout.Box
6 | import androidx.compose.foundation.layout.fillMaxWidth
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.foundation.layout.size
9 | import androidx.compose.foundation.shape.CircleShape
10 | import androidx.compose.material3.ExperimentalMaterial3Api
11 | import androidx.compose.material3.Slider
12 | import androidx.compose.material3.SliderDefaults
13 | import androidx.compose.material3.Text
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.graphics.Color
18 | import androidx.compose.ui.text.font.FontWeight
19 | import androidx.compose.ui.unit.dp
20 | import androidx.compose.ui.unit.sp
21 | import dev.prince.securify.ui.theme.Blue
22 |
23 | @OptIn(ExperimentalMaterial3Api::class)
24 | @Composable
25 | fun CustomSlider(
26 | value: Float,
27 | onValueChange: (Float) -> Unit,
28 | valueRange: ClosedFloatingPointRange,
29 | activeTrackColor: Color = Blue,
30 | inactiveTrackColor: Color = Color.DarkGray,
31 | thumbTextColor: Color = Color.Black, // Changed to black
32 | interactionSource: MutableInteractionSource
33 | ) {
34 | Slider(
35 | value = value,
36 | onValueChange = onValueChange,
37 | valueRange = valueRange,
38 | colors = SliderDefaults.colors(
39 | activeTickColor = activeTrackColor,
40 | activeTrackColor = activeTrackColor,
41 | inactiveTrackColor = inactiveTrackColor,
42 | thumbColor = Color.Transparent // Set thumb color to transparent
43 | ),
44 | thumb = {
45 | Box(
46 | modifier = Modifier
47 | .size(40.dp)
48 | .background(color = Blue, shape = CircleShape)
49 | .padding(6.dp), // Add padding to create the border
50 | contentAlignment = Alignment.Center,
51 | content = {
52 | Box(
53 | modifier = Modifier
54 | .size(32.dp) // Adjust the size of the inner white circle
55 | .background(color = Color.White, shape = CircleShape),
56 | contentAlignment = Alignment.Center,
57 | content = {
58 | Text(
59 | text = value.toInt().toString(),
60 | color = thumbTextColor,
61 | fontSize = 12.sp,
62 | fontWeight = FontWeight.SemiBold
63 | )
64 | }
65 | )
66 | }
67 | )
68 | },
69 | modifier = Modifier
70 | .fillMaxWidth()
71 | .padding(start = 16.dp),
72 | interactionSource = interactionSource,
73 | onValueChangeFinished = null
74 | )
75 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/ui/generate/GenerateViewModel.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.ui.generate
2 |
3 | import androidx.compose.runtime.getValue
4 | import androidx.compose.runtime.mutableStateOf
5 | import androidx.compose.runtime.setValue
6 | import androidx.lifecycle.ViewModel
7 | import dagger.hilt.android.lifecycle.HiltViewModel
8 | import dev.prince.securify.util.generatePassword
9 | import dev.prince.securify.util.oneShotFlow
10 | import javax.inject.Inject
11 |
12 | @HiltViewModel
13 | class GenerateViewModel @Inject constructor() : ViewModel() {
14 |
15 | val messages = oneShotFlow()
16 |
17 | var passwordLength by mutableStateOf(12)
18 | var lowerCase by mutableStateOf(true)
19 | var upperCase by mutableStateOf(true)
20 | var digits by mutableStateOf(true)
21 | var specialCharacters by mutableStateOf(true)
22 |
23 |
24 | var password by mutableStateOf(
25 | generatePassword(
26 | passwordLength,
27 | lowerCase,
28 | upperCase,
29 | digits,
30 | specialCharacters
31 | )
32 | )
33 |
34 | fun showCopyMsg() {
35 | messages.tryEmit("Password copied to clipboard.")
36 | }
37 |
38 | fun checkToggleAndSave() {
39 | if (!(lowerCase || upperCase || digits || specialCharacters)) {
40 | messages.tryEmit("Please toggle at least one option.")
41 | } else {
42 | password = generatePassword(
43 | passwordLength,
44 | lowerCase,
45 | upperCase,
46 | digits,
47 | specialCharacters
48 | )
49 | }
50 | }
51 |
52 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/ui/home/HomeViewModel.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.ui.home
2 |
3 | import androidx.compose.runtime.getValue
4 | import androidx.compose.runtime.mutableStateListOf
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.setValue
7 | import androidx.lifecycle.ViewModel
8 | import androidx.lifecycle.viewModelScope
9 | import dagger.hilt.android.lifecycle.HiltViewModel
10 | import dev.prince.securify.database.AccountDao
11 | import dev.prince.securify.database.AccountEntity
12 | import dev.prince.securify.database.CardDao
13 | import dev.prince.securify.database.CardEntity
14 | import dev.prince.securify.encryption.EncryptionManager
15 | import dev.prince.securify.util.AccountOrCard
16 | import dev.prince.securify.util.oneShotFlow
17 | import kotlinx.coroutines.flow.Flow
18 | import kotlinx.coroutines.flow.MutableStateFlow
19 | import kotlinx.coroutines.flow.StateFlow
20 | import kotlinx.coroutines.flow.combine
21 | import kotlinx.coroutines.flow.map
22 | import kotlinx.coroutines.launch
23 | import javax.inject.Inject
24 |
25 | @HiltViewModel
26 | class HomeViewModel @Inject constructor(
27 | private val dbAccount: AccountDao,
28 | private val dbCard: CardDao,
29 | private val encryptionManager: EncryptionManager
30 | ) : ViewModel() {
31 |
32 | val messages = oneShotFlow()
33 |
34 | var selectedOption by mutableStateOf("All")
35 |
36 | val filterOptions = listOf(
37 | "All",
38 | "Passwords",
39 | "Cards"
40 | )
41 |
42 | var showAccountDeleteDialog by mutableStateOf(false)
43 |
44 | var showCardDeleteDialog by mutableStateOf(false)
45 |
46 | var accountToDelete by mutableStateOf(AccountEntity(-1, "", "", "", "", "", "", 0L))
47 |
48 | var cardToDelete by mutableStateOf(CardEntity(-1, "", "", "", "", "", 0L))
49 |
50 | private val _searchQuery = MutableStateFlow("")
51 | private val searchQuery: StateFlow get() = _searchQuery
52 |
53 | val combinedData: Flow> = combine(
54 | dbAccount.getAllAccounts(),
55 | dbCard.getAllCards(),
56 | searchQuery
57 | ) { accounts, cards, query ->
58 |
59 | val itemsWithTimestamp = mutableStateListOf>()
60 |
61 | accounts.forEach { item -> itemsWithTimestamp.add(AccountOrCard.AccountItem(item) to item.createdAt) }
62 | cards.forEach { item -> itemsWithTimestamp.add(AccountOrCard.CardItem(item) to item.createdAt) }
63 |
64 | val sortedItems = itemsWithTimestamp.sortedByDescending { it.second }
65 |
66 | val filteredItems = if (query.isNotBlank()) {
67 | sortedItems.filter { (item, _) ->
68 | when (selectedOption) {
69 | "All" -> true
70 | "Passwords" -> item is AccountOrCard.AccountItem
71 | "Cards" -> item is AccountOrCard.CardItem
72 | else -> true
73 | } &&
74 | when (item) {
75 | is AccountOrCard.AccountItem -> {
76 | val account = item.account
77 | account.accountName.contains(query, ignoreCase = true) ||
78 | decryptInput(account.userName).contains(query, ignoreCase = true) ||
79 | decryptInput(account.email).contains(query, ignoreCase = true) ||
80 | decryptInput(account.mobileNumber).contains(query, ignoreCase = true)
81 | }
82 |
83 | is AccountOrCard.CardItem -> {
84 | val card = item.card
85 | decryptInput(card.cardHolderName).contains(query, ignoreCase = true) ||
86 | decryptInput(card.cardNumber).contains(query, ignoreCase = true) ||
87 | card.cardProvider.contains(query, ignoreCase = true)
88 | }
89 | }
90 | }
91 | } else {
92 | sortedItems
93 | }
94 | filteredItems.map { it.first }
95 | }
96 |
97 | fun setSearchQuery(query: String) {
98 | _searchQuery.value = query
99 | }
100 |
101 | fun onUserAccountDeleteClick(accountEntity: AccountEntity) {
102 | accountToDelete = accountEntity
103 | showAccountDeleteDialog = true
104 | }
105 |
106 | fun deleteAccount() {
107 | viewModelScope.launch {
108 | dbAccount.deleteAccount(accountToDelete)
109 | showAccountDeleteDialog = false
110 | }
111 | }
112 |
113 | fun onUserCardDeleteClick(cardEntity: CardEntity) {
114 | cardToDelete = cardEntity
115 | showCardDeleteDialog = true
116 | }
117 |
118 | fun deleteCard() {
119 | viewModelScope.launch {
120 | dbCard.deleteCard(cardToDelete)
121 | showCardDeleteDialog = false
122 | }
123 | }
124 |
125 | fun decryptInput(input: String): String {
126 | return encryptionManager.decrypt(input)
127 | }
128 |
129 | fun showCopyMsg(stringType: String) {
130 | messages.tryEmit("$stringType copied to clipboard.")
131 | }
132 |
133 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/ui/intro/IntroScreen.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.ui.intro
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.Activity.RESULT_OK
5 | import android.content.Context
6 | import android.net.ConnectivityManager
7 | import androidx.activity.ComponentActivity
8 | import androidx.activity.compose.BackHandler
9 | import androidx.activity.compose.ManagedActivityResultLauncher
10 | import androidx.activity.compose.rememberLauncherForActivityResult
11 | import androidx.activity.result.ActivityResult
12 | import androidx.activity.result.IntentSenderRequest
13 | import androidx.activity.result.contract.ActivityResultContracts
14 | import androidx.compose.foundation.Image
15 | import androidx.compose.foundation.layout.Box
16 | import androidx.compose.foundation.layout.Column
17 | import androidx.compose.foundation.layout.Spacer
18 | import androidx.compose.foundation.layout.fillMaxSize
19 | import androidx.compose.foundation.layout.fillMaxWidth
20 | import androidx.compose.foundation.layout.height
21 | import androidx.compose.foundation.layout.padding
22 | import androidx.compose.foundation.shape.RoundedCornerShape
23 | import androidx.compose.material3.Button
24 | import androidx.compose.material3.ButtonDefaults
25 | import androidx.compose.material3.Text
26 | import androidx.compose.runtime.Composable
27 | import androidx.compose.runtime.LaunchedEffect
28 | import androidx.compose.runtime.collectAsState
29 | import androidx.compose.runtime.getValue
30 | import androidx.compose.runtime.rememberCoroutineScope
31 | import androidx.compose.ui.Alignment
32 | import androidx.compose.ui.Modifier
33 | import androidx.compose.ui.graphics.Color
34 | import androidx.compose.ui.layout.ContentScale
35 | import androidx.compose.ui.platform.LocalContext
36 | import androidx.compose.ui.res.painterResource
37 | import androidx.compose.ui.res.stringResource
38 | import androidx.compose.ui.text.TextStyle
39 | import androidx.compose.ui.text.font.FontWeight
40 | import androidx.compose.ui.text.style.TextAlign
41 | import androidx.compose.ui.unit.dp
42 | import androidx.compose.ui.unit.sp
43 | import androidx.hilt.navigation.compose.hiltViewModel
44 | import com.google.android.gms.auth.api.identity.Identity
45 | import com.ramcosta.composedestinations.annotation.Destination
46 | import com.ramcosta.composedestinations.navigation.DestinationsNavigator
47 | import dev.prince.securify.R
48 | import dev.prince.securify.signin.GoogleAuthUiClient
49 | import dev.prince.securify.ui.auth.NavigationSource
50 | import dev.prince.securify.ui.destinations.MasterKeyScreenDestination
51 | import dev.prince.securify.ui.theme.Blue
52 | import dev.prince.securify.ui.theme.poppinsFamily
53 | import dev.prince.securify.util.LocalSnackbar
54 | import kotlinx.coroutines.launch
55 |
56 | @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
57 | @Destination
58 | @Composable
59 | fun IntroScreen(
60 | viewModel: IntroViewModel = hiltViewModel(),
61 | navigator: DestinationsNavigator
62 | ) {
63 |
64 | val snackbar = LocalSnackbar.current
65 |
66 | LaunchedEffect(Unit) {
67 | viewModel.messages.collect {
68 | snackbar(it)
69 | }
70 | }
71 |
72 | val context = LocalContext.current
73 |
74 | val googleAuthUiClient by lazy {
75 | GoogleAuthUiClient(
76 | context = context,
77 | oneTapClient = Identity.getSignInClient(context)
78 | )
79 | }
80 |
81 | val state by viewModel.state.collectAsState()
82 | LaunchedEffect(key1 = Unit) {
83 | if (googleAuthUiClient.getSignedInUser() != null) {
84 | navigator.navigate(
85 | MasterKeyScreenDestination(NavigationSource.INTRO)
86 | )
87 | }
88 | }
89 | val scope = rememberCoroutineScope()
90 | val launcher = rememberLauncherForActivityResult(
91 | contract = ActivityResultContracts.StartIntentSenderForResult(),
92 | onResult = { result ->
93 | if (result.resultCode == RESULT_OK) {
94 | scope.launch {
95 | val signInResult = googleAuthUiClient.signInWithIntent(
96 | intent = result.data ?: return@launch
97 | )
98 | viewModel.onSignInResult(signInResult)
99 | }
100 | }
101 | }
102 | )
103 |
104 | LaunchedEffect(key1 = state.isSignInSuccessful) {
105 | if (state.isSignInSuccessful) {
106 | viewModel.showSnackBar("Sign in successful")
107 |
108 | navigator.navigate(
109 | MasterKeyScreenDestination(NavigationSource.INTRO)
110 | )
111 | viewModel.resetState()
112 | }
113 | }
114 |
115 | IntroScreenContent(
116 | navigator = navigator,
117 | googleAuthUiClient = googleAuthUiClient,
118 | launcher = launcher
119 | )
120 |
121 | BackHandler {
122 | (context as ComponentActivity).finish()
123 | }
124 | }
125 |
126 | @Composable
127 | fun IntroScreenContent(
128 | viewModel: IntroViewModel = hiltViewModel(),
129 | navigator: DestinationsNavigator,
130 | googleAuthUiClient: GoogleAuthUiClient,
131 | launcher: ManagedActivityResultLauncher
132 | ) {
133 |
134 | val scope = rememberCoroutineScope()
135 | val context = LocalContext.current
136 |
137 | val connectivityManager =
138 | context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
139 | Box(
140 | modifier = Modifier.fillMaxSize()
141 | ) {
142 | Image(
143 | painterResource(id = R.drawable.surviellance),
144 | contentDescription = "Background Image",
145 | contentScale = ContentScale.Crop
146 | )
147 |
148 | Column(
149 | modifier = Modifier
150 | .fillMaxSize(),
151 | horizontalAlignment = Alignment.CenterHorizontally
152 | ) {
153 |
154 | Spacer(modifier = Modifier.weight(1f))
155 |
156 | Text(
157 | modifier = Modifier
158 | .padding(horizontal = 16.dp)
159 | .fillMaxWidth(),
160 | text = stringResource(R.string.intro_tagline_1),
161 | textAlign = TextAlign.Start,
162 | fontWeight = FontWeight.Bold,
163 | color = Color.White,
164 | style = TextStyle(
165 | fontSize = 42.sp,
166 | fontFamily = poppinsFamily,
167 | lineHeight = 42.sp
168 | )
169 | )
170 |
171 | Text(
172 | modifier = Modifier
173 | .padding(horizontal = 16.dp)
174 | .fillMaxWidth(),
175 | text = stringResource(R.string.intro_tagline_2),
176 | textAlign = TextAlign.Start,
177 | fontSize = 20.sp,
178 | fontFamily = poppinsFamily,
179 | fontWeight = FontWeight.Normal,
180 | color = Color.LightGray
181 | )
182 |
183 | Spacer(modifier = Modifier.height(46.dp))
184 |
185 | Button(
186 | modifier = Modifier
187 | .padding(horizontal = 16.dp)
188 | .fillMaxWidth(),
189 | onClick = {
190 | if (viewModel.isNetworkConnected(connectivityManager)) {
191 | scope.launch {
192 | val signInIntentSender = googleAuthUiClient.signIn()
193 | launcher.launch(
194 | IntentSenderRequest.Builder(
195 | signInIntentSender ?: return@launch
196 | ).build()
197 | )
198 | }
199 | } else {
200 | viewModel.showSnackBar("Not connected to the internet")
201 | }
202 | /*navigator.navigate(
203 | MasterKeyScreenDestination(NavigationSource.INTRO)
204 | )*/
205 | },
206 | shape = RoundedCornerShape(16.dp),
207 | colors = ButtonDefaults.buttonColors(
208 | containerColor = Blue,
209 | contentColor = Color.White
210 | )
211 | ) {
212 | Text(
213 | text = stringResource(R.string.get_started),
214 | fontWeight = FontWeight.Bold,
215 | style = TextStyle(
216 | fontSize = 22.sp,
217 | fontFamily = poppinsFamily
218 | )
219 | )
220 | }
221 |
222 | Spacer(modifier = Modifier.height(60.dp))
223 | }
224 | }
225 | }
226 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/ui/intro/IntroViewModel.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.ui.intro
2 |
3 | import android.net.ConnectivityManager
4 | import android.net.NetworkCapabilities
5 | import androidx.lifecycle.ViewModel
6 | import dagger.hilt.android.lifecycle.HiltViewModel
7 | import dev.prince.securify.signin.SignInResult
8 | import dev.prince.securify.signin.SignInState
9 | import dev.prince.securify.util.oneShotFlow
10 | import kotlinx.coroutines.flow.MutableStateFlow
11 | import kotlinx.coroutines.flow.asStateFlow
12 | import kotlinx.coroutines.flow.update
13 | import javax.inject.Inject
14 |
15 | @HiltViewModel
16 | class IntroViewModel @Inject constructor() : ViewModel() {
17 |
18 | private val _state = MutableStateFlow(SignInState())
19 | val state = _state.asStateFlow()
20 |
21 | val messages = oneShotFlow()
22 |
23 | fun onSignInResult(result: SignInResult) {
24 | _state.update {
25 | it.copy(
26 | isSignInSuccessful = result.data != null,
27 | signInError = result.errorMessage
28 | )
29 | }
30 | }
31 |
32 | fun resetState() {
33 | _state.update { SignInState() }
34 | }
35 |
36 | fun showSnackBar(text: String) {
37 | messages.tryEmit(text)
38 | }
39 |
40 | fun isNetworkConnected(connectivityManager: ConnectivityManager): Boolean {
41 | val network = connectivityManager.activeNetwork
42 | val capabilities = connectivityManager.getNetworkCapabilities(network)
43 | return capabilities?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) == true
44 | }
45 |
46 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/ui/password/PasswordViewModel.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.ui.password
2 |
3 | import android.util.Log
4 | import android.util.Patterns
5 | import androidx.compose.runtime.getValue
6 | import androidx.compose.runtime.mutableStateOf
7 | import androidx.compose.runtime.setValue
8 | import androidx.compose.runtime.snapshots.SnapshotStateList
9 | import androidx.core.text.isDigitsOnly
10 | import androidx.lifecycle.ViewModel
11 | import androidx.lifecycle.viewModelScope
12 | import dagger.hilt.android.lifecycle.HiltViewModel
13 | import dev.prince.securify.database.AccountDao
14 | import dev.prince.securify.database.AccountEntity
15 | import dev.prince.securify.encryption.EncryptionManager
16 | import dev.prince.securify.util.accountSuggestions
17 | import dev.prince.securify.util.generatePassword
18 | import dev.prince.securify.util.getRandomNumber
19 | import dev.prince.securify.util.oneShotFlow
20 | import kotlinx.coroutines.launch
21 | import javax.inject.Inject
22 |
23 | @HiltViewModel
24 | class PasswordViewModel @Inject constructor(
25 | private val db: AccountDao,
26 | private val encryptionManager: EncryptionManager
27 | ) : ViewModel() {
28 |
29 | var isEditScreen by mutableStateOf(false)
30 |
31 | val messages = oneShotFlow()
32 |
33 | var expanded by mutableStateOf(false)
34 | var accountName by mutableStateOf("")
35 | var suggestions = SnapshotStateList()
36 |
37 | var username by mutableStateOf("")
38 |
39 | var email by mutableStateOf("")
40 |
41 | var note by mutableStateOf("")
42 |
43 | var mobileNumber by mutableStateOf("")
44 | var keyVisible by mutableStateOf(false)
45 |
46 | var password by mutableStateOf("")
47 |
48 | val success = mutableStateOf(false)
49 |
50 | fun getAccountById(accountId: Int) {
51 | viewModelScope.launch {
52 | db.getAccountById(accountId).collect {
53 | accountName = it.accountName
54 | username = encryptionManager.decrypt(it.userName)
55 | email = encryptionManager.decrypt(it.email)
56 | mobileNumber = encryptionManager.decrypt(it.mobileNumber)
57 | password = encryptionManager.decrypt(it.password)
58 | }
59 | }
60 | }
61 |
62 | private fun validateFields(): Boolean {
63 | if (accountName.isBlank()) {
64 | messages.tryEmit("Please provide an account name")
65 | return false
66 | }
67 | if (username.isEmpty() && email.isBlank() && mobileNumber.isBlank()) {
68 | messages.tryEmit("Please provide a username, email, or mobile number")
69 | return false
70 | }
71 | if (password.isBlank()) {
72 | messages.tryEmit("Password cannot be empty")
73 | return false
74 | }
75 | if (password.trim().isEmpty() || password.contains("\\s+".toRegex())) {
76 | messages.tryEmit("Password cannot contain whitespace")
77 | return false
78 | }
79 | if (email.isNotBlank() && !Patterns.EMAIL_ADDRESS.matcher(email).matches()) {
80 | messages.tryEmit("Invalid email address")
81 | return false
82 | }
83 | if (!mobileNumber.isDigitsOnly()) {
84 | messages.tryEmit("Invalid mobile number")
85 | return false
86 | }
87 | return true
88 | }
89 |
90 | fun validateAndInsert() {
91 |
92 | if (validateFields()) {
93 | val currentTimeInMillis = System.currentTimeMillis()
94 | val account = AccountEntity(
95 | id = 0,
96 | accountName = accountName.trim(),
97 | userName = encryptionManager.encrypt(username).trim(),
98 | email = encryptionManager.encrypt(email).trim(),
99 | mobileNumber = encryptionManager.encrypt(mobileNumber).trim(),
100 | password = encryptionManager.encrypt(password).trim(),
101 | note = note.trim(),
102 | createdAt = currentTimeInMillis
103 | )
104 |
105 | viewModelScope.launch {
106 | db.insertAccount(account)
107 | messages.tryEmit("Credentials Added!")
108 | success.value = true
109 | }
110 | }
111 |
112 | }
113 |
114 | fun validationAndUpdate(id: Int) {
115 | if (validateFields()) {
116 | viewModelScope.launch {
117 | val currentTimeInMillis = System.currentTimeMillis()
118 | val accountEntity = AccountEntity(
119 | id = id,
120 | accountName = accountName.trim(),
121 | userName = encryptionManager.encrypt(username.trim()),
122 | email = encryptionManager.encrypt(email.trim()),
123 | mobileNumber = encryptionManager.encrypt(mobileNumber),
124 | password = encryptionManager.encrypt(password.trim()),
125 | note = note.trim(),
126 | createdAt = currentTimeInMillis
127 | )
128 | db.updateAccount(accountEntity)
129 | messages.tryEmit("Successfully Updated!")
130 | success.value = true
131 | }
132 | }
133 | }
134 | fun filter(accountName: String) {
135 | suggestions.clear()
136 | if (accountName.isNotEmpty()) {
137 | suggestions.addAll(accountSuggestions.filter { it.contains(accountName, true) })
138 | }
139 | }
140 |
141 | fun resetSuggestions() {
142 | suggestions.clear()
143 | }
144 |
145 | fun generateRandomPassword() {
146 | password = generatePassword(
147 | length = getRandomNumber(),
148 | lowerCase = true,
149 | upperCase = true,
150 | digits = true,
151 | specialCharacters = true
152 | )
153 | }
154 |
155 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/ui/settings/SettingsViewModel.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.ui.settings
2 |
3 | import androidx.compose.runtime.getValue
4 | import androidx.compose.runtime.mutableStateOf
5 | import androidx.compose.runtime.setValue
6 | import androidx.lifecycle.ViewModel
7 | import androidx.lifecycle.viewModelScope
8 | import dagger.hilt.android.lifecycle.HiltViewModel
9 | import dev.prince.securify.database.AccountDao
10 | import dev.prince.securify.database.CardDao
11 | import dev.prince.securify.local.SharedPrefHelper
12 | import kotlinx.coroutines.Dispatchers
13 | import kotlinx.coroutines.launch
14 | import javax.inject.Inject
15 |
16 | @HiltViewModel
17 | class SettingsViewModel @Inject constructor(
18 | private val dbAccount: AccountDao,
19 | private val dbCard: CardDao,
20 | private val prefs: SharedPrefHelper
21 | ) : ViewModel() {
22 |
23 | var checked by mutableStateOf(prefs.getSwitchState())
24 |
25 | var showAllDataDeleteDialog by mutableStateOf(false)
26 |
27 | fun setSwitchState(checked: Boolean) {
28 | prefs.setSwitchState(checked)
29 | }
30 |
31 | fun deleteAll() {
32 | prefs.resetMasterKeyAndSwitch()
33 | viewModelScope.launch(Dispatchers.IO) {
34 | dbAccount.deleteAllAccounts()
35 | dbCard.deleteAllCards()
36 | }
37 | }
38 |
39 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val Purple40 = Color(0xFF6650a4)
6 | val PurpleGrey40 = Color(0xFF625b71)
7 | val Pink40 = Color(0xFF7D5260)
8 |
9 | val Blue = Color(0xFF2196F3)
10 | val LightBlue = Color(0xFF79B7E9)
11 |
12 | val White = Color(0xFFEBEBEB)
13 | val Gray = Color(0xD3515151)
14 | val LightGray = Color(0xD3BBBBBB)
15 |
16 | val LightBlack = Color(0xD3323232)
17 | val BgBlack = Color.Black
18 |
19 | val Red = Color(0xD3E72121)
20 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.ui.theme
2 |
3 | import android.app.Activity
4 | import android.os.Build
5 | import androidx.compose.material3.MaterialTheme
6 | import androidx.compose.material3.MaterialTheme.colorScheme
7 | import androidx.compose.material3.lightColorScheme
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.SideEffect
10 | import androidx.compose.ui.graphics.Color
11 | import androidx.compose.ui.graphics.compositeOver
12 | import androidx.compose.ui.graphics.toArgb
13 | import androidx.compose.ui.platform.LocalView
14 | import androidx.core.view.WindowCompat
15 |
16 | private val LightColorScheme = lightColorScheme(
17 | primary = Purple40,
18 | secondary = PurpleGrey40,
19 | tertiary = Pink40
20 |
21 | /* Other default colors to override
22 | background = Color(0xFFFFFBFE),
23 | surface = Color(0xFFFFFBFE),
24 | onPrimary = Color.White,
25 | onSecondary = Color.White,
26 | onTertiary = Color.White,
27 | onBackground = Color(0xFF1C1B1F),
28 | onSurface = Color(0xFF1C1B1F),
29 | */
30 | )
31 |
32 | @Composable
33 | fun SecurifyTheme(
34 | content: @Composable () -> Unit
35 | ) {
36 |
37 | val view = LocalView.current
38 | if (!view.isInEditMode) {
39 | SideEffect {
40 | val activity = view.context as Activity
41 | val colorInt = BgBlack.toArgb()
42 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
43 | activity.window.navigationBarColor = colorInt
44 | activity.window.statusBarColor = colorInt
45 | }
46 | }
47 | }
48 |
49 | MaterialTheme(
50 | colorScheme = LightColorScheme,
51 | typography = Typography,
52 | content = content
53 | )
54 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.ui.theme
2 |
3 | import androidx.compose.material3.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.Font
6 | import androidx.compose.ui.text.font.FontFamily
7 | import androidx.compose.ui.text.font.FontWeight
8 | import androidx.compose.ui.unit.sp
9 | import dev.prince.securify.R
10 |
11 | val poppinsFamily = FontFamily(
12 | Font(R.font.poppins_light, FontWeight.Light),
13 | Font(R.font.poppins_regular, FontWeight.Normal),
14 | Font(R.font.poppins_medium, FontWeight.Medium),
15 | Font(R.font.poppins_semibold, FontWeight.Bold)
16 | )
17 |
18 | // Set of Material typography styles to start with
19 | val Typography = Typography(
20 | bodyLarge = TextStyle(
21 | fontFamily = FontFamily.Default,
22 | fontWeight = FontWeight.Normal,
23 | fontSize = 16.sp,
24 | lineHeight = 24.sp,
25 | letterSpacing = 0.5.sp
26 | )
27 | /* Other default text styles to override
28 | titleLarge = TextStyle(
29 | fontFamily = FontFamily.Default,
30 | fontWeight = FontWeight.Normal,
31 | fontSize = 22.sp,
32 | lineHeight = 28.sp,
33 | letterSpacing = 0.sp
34 | ),
35 | labelSmall = TextStyle(
36 | fontFamily = FontFamily.Default,
37 | fontWeight = FontWeight.Medium,
38 | fontSize = 11.sp,
39 | lineHeight = 16.sp,
40 | letterSpacing = 0.5.sp
41 | )
42 | */
43 | )
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/util/AccountOrCard.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.util
2 |
3 | import dev.prince.securify.database.AccountEntity
4 | import dev.prince.securify.database.CardEntity
5 |
6 | sealed class AccountOrCard {
7 | data class AccountItem(val account: AccountEntity) : AccountOrCard()
8 | data class CardItem(val card: CardEntity) : AccountOrCard()
9 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/util/CardUtils.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.util
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import androidx.compose.ui.text.AnnotatedString
5 | import androidx.compose.ui.text.input.OffsetMapping
6 | import androidx.compose.ui.text.input.TransformedText
7 | import androidx.compose.ui.text.input.VisualTransformation
8 | import java.util.Calendar
9 | import java.util.Date
10 | import kotlin.math.absoluteValue
11 | import kotlin.random.Random
12 |
13 |
14 | class MaskVisualTransformation(private val mask: String) : VisualTransformation {
15 |
16 | private val specialSymbolsIndices = mask.indices.filter { mask[it] != '#' }
17 |
18 | override fun filter(text: AnnotatedString): TransformedText {
19 | var out = ""
20 | var maskIndex = 0
21 | text.forEach { char ->
22 | while (specialSymbolsIndices.contains(maskIndex)) {
23 | out += mask[maskIndex]
24 | maskIndex++
25 | }
26 | out += char
27 | maskIndex++
28 | }
29 | return TransformedText(AnnotatedString(out), offsetTranslator())
30 | }
31 |
32 | private fun offsetTranslator() = object : OffsetMapping {
33 | override fun originalToTransformed(offset: Int): Int {
34 | val offsetValue = offset.absoluteValue
35 | if (offsetValue == 0) return 0
36 | var numberOfHashtags = 0
37 | val masked = mask.takeWhile {
38 | if (it == '#') numberOfHashtags++
39 | numberOfHashtags < offsetValue
40 | }
41 | return masked.length + 1
42 | }
43 |
44 | override fun transformedToOriginal(offset: Int): Int {
45 | return mask.take(offset.absoluteValue).count { it == '#' }
46 | }
47 | }
48 | }
49 |
50 | // Making XXXX-XXXX-XXXX-XXXX string.
51 | val visualTransformation = object : VisualTransformation {
52 | override fun filter(text: AnnotatedString): TransformedText {
53 | val trimmed = if (text.text.length >= 16) text.text.substring(0..15) else text.text
54 | var out = ""
55 |
56 | for (i in trimmed.indices) {
57 | out += trimmed[i]
58 | if (i % 4 == 3 && i != 15) out += " "
59 | }
60 | return TransformedText(
61 | AnnotatedString(out),
62 | creditCardOffsetMapping
63 | )
64 | }
65 | }
66 |
67 | val creditCardOffsetMapping = object : OffsetMapping {
68 | override fun originalToTransformed(offset: Int): Int {
69 | if (offset <= 3) return offset
70 | if (offset <= 7) return offset + 1
71 | if (offset <= 11) return offset + 2
72 | if (offset <= 16) return offset + 3
73 | return 19
74 | }
75 |
76 | override fun transformedToOriginal(offset: Int): Int {
77 | if (offset <= 4) return offset
78 | if (offset <= 9) return offset - 1
79 | if (offset <= 14) return offset - 2
80 | if (offset <= 19) return offset - 3
81 | return 16
82 | }
83 | }
84 |
85 | fun formatCardNumber(cardNumber: String): String {
86 | val trimmed = if (cardNumber.length >= 16) cardNumber.substring(0..15) else cardNumber
87 | var out = ""
88 |
89 | for (i in trimmed.indices) {
90 | out += trimmed[i]
91 | if (i % 4 == 3 && i != 15) out += " "
92 | }
93 |
94 | return out
95 | }
96 |
97 | fun formatExpiryDate(expiryDate: String): String {
98 | val trimmed = if (expiryDate.length >= 4) expiryDate.substring(0..3) else expiryDate
99 | var out = ""
100 |
101 | for (i in trimmed.indices) {
102 | out += trimmed[i]
103 | if (i == 1) out += "/" // Add a slash after the first two characters
104 | }
105 |
106 | return out
107 | }
108 |
109 | private val gradientOptions = listOf(
110 | listOf(Color(0xFF6C72CB), Color(0xFF0078FF)), // Blue Gradient
111 | listOf(Color(0xFF8A2387), Color(0xFFE94057)), // Pink Gradient
112 | listOf(Color(0xFF56CCF2), Color(0xFF2F80ED)), // Sky Blue Gradient
113 | listOf(Color(0xFFFFD23F), Color(0xFFFF6B6B)), // Sunset Gradient
114 | listOf(Color(0xFF6A3093), Color(0xFFA044FF)), // Purple Gradient
115 | listOf(Color(0xFFF7B733), Color(0xFFFC4A1A)), // Orange Gradient
116 | listOf(Color(0xFF00C9FF), Color(0xFF92FE9D)), // Turquoise Gradient
117 | listOf(Color(0xFF00F260), Color(0xFF0575E6)), // Green Gradient
118 | listOf(Color(0xFF693B52), Color(0xFF1B1B1E)), // Dark Red Gradient
119 | listOf(Color(0xFF00B4DB), Color(0xFF0083B0)) // Ocean Blue Gradient
120 | )
121 |
122 | private val randomIndex = Random.nextInt(gradientOptions.size)
123 | val randomGradient = gradientOptions[randomIndex]
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/util/Constants.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.util
2 |
3 | import dev.prince.securify.R
4 |
5 | const val ENCRYPTED_SHARED_PREFS_NAME = "encrypted_shared_prefs"
6 |
7 | val accountSuggestions = listOf(
8 | "Amazon Prime",
9 | "Behance",
10 | "Discord",
11 | "Dribbble",
12 | "Facebook",
13 | "Github",
14 | "Gmail",
15 | "Instagram",
16 | "LinkedIn",
17 | "Medium",
18 | "Messenger",
19 | "Netflix",
20 | "Pinterest",
21 | "Quora",
22 | "Reddit",
23 | "Snapchat",
24 | "Spotify",
25 | "Stackoverflow",
26 | "Tumblr",
27 | "Twitter",
28 | "Whatsapp",
29 | "Wordpress",
30 | "YouTube"
31 | )
32 |
33 | val suggestionsWithImages = listOf(
34 | "Amazon Prime" to R.drawable.icon_amazon_prime_video,
35 | "Behance" to R.drawable.icon_behance,
36 | "Dribbble" to R.drawable.icon_dribbble,
37 | "Discord" to R.drawable.icon_discord,
38 | "Facebook" to R.drawable.icon_facebook,
39 | "Github" to R.drawable.icon_github,
40 | "Gmail" to R.drawable.icon_gmail,
41 | "Instagram" to R.drawable.icon_instagram,
42 | "LinkedIn" to R.drawable.icon_linkedin,
43 | "Medium" to R.drawable.icon_medium,
44 | "Messenger" to R.drawable.icon_messenger,
45 | "Netflix" to R.drawable.icon_netflix,
46 | "Pinterest" to R.drawable.icon_pinterest,
47 | "Quora" to R.drawable.icon_quora,
48 | "Reddit" to R.drawable.icon_reddit,
49 | "Snapchat" to R.drawable.icon_snapchat,
50 | "Spotify" to R.drawable.icon_spotify,
51 | "Stackoverflow" to R.drawable.icon_stackoverflow,
52 | "Tumblr" to R.drawable.icon_tumblr,
53 | "Twitter" to R.drawable.icon_twitterx,
54 | "Whatsapp" to R.drawable.icon_whatsapp,
55 | "Wordpress" to R.drawable.icon_wordpress,
56 | "YouTube" to R.drawable.icon_youtube
57 | )
58 |
59 | val cardSuggestions = listOf(
60 | "Visa" to R.drawable.icon_visa,
61 | "Mastercard" to R.drawable.icon_mastercard,
62 | "American Express" to R.drawable.icon_american_express,
63 | "Rupay" to R.drawable.icon_rupay,
64 | "Diners Club" to R.drawable.icon_diners_club,
65 | "Other" to R.drawable.icon_card
66 | )
67 |
68 | fun generatePassword(
69 | length: Int,
70 | lowerCase: Boolean,
71 | upperCase: Boolean,
72 | digits: Boolean,
73 | specialCharacters: Boolean
74 | ): String {
75 |
76 | val chars = mutableListOf()
77 | val symbols = "!@#$%&*+=-~?/_"
78 | if (lowerCase) chars.addAll('a'..'z')
79 | if (upperCase) chars.addAll('A'..'Z')
80 | if (digits) chars.addAll('0'..'9')
81 | if (specialCharacters) chars.addAll(symbols.toList())
82 |
83 | val password = StringBuilder()
84 | for (i in 0 until length) {
85 | password.append(chars.random())
86 | }
87 |
88 | return password.toString()
89 |
90 | }
91 |
92 | fun getRandomNumber(): Int {
93 | val random = kotlin.random.Random
94 | return random.nextInt(6, 21)
95 | }
96 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/util/CryptoUtils.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.util
2 |
3 | import java.security.MessageDigest
4 | import javax.crypto.spec.SecretKeySpec
5 |
6 | const val BYTE_SIZE = 16
7 | const val AES_ALGORITHM = "AES"
8 | const val SHA_ALGORITHM = "SHA-256"
9 | const val BLOCK_MODE = "CBC"
10 | const val PADDING = "PKCS5Padding"
11 | const val TRANSFORMATION = "$AES_ALGORITHM/$BLOCK_MODE/$PADDING"
12 |
13 | fun generateKey(uid: String, email: String): SecretKeySpec {
14 | val combinedKey = uid + email
15 | val md = MessageDigest.getInstance(SHA_ALGORITHM)
16 | val hashBytes = md.digest(combinedKey.toByteArray(Charsets.UTF_8))
17 | val keyBytes = hashBytes.copyOf(BYTE_SIZE) // AES-128 requires a 16-byte key
18 | return SecretKeySpec(keyBytes, AES_ALGORITHM)
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/dev/prince/securify/util/Util.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify.util
2 |
3 | import android.content.Context
4 | import androidx.biometric.BiometricManager
5 | import androidx.compose.foundation.clickable
6 | import androidx.compose.foundation.interaction.MutableInteractionSource
7 | import androidx.compose.material.ripple.rememberRipple
8 | import androidx.compose.runtime.compositionLocalOf
9 | import androidx.compose.runtime.remember
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.composed
12 | import dev.prince.securify.ui.destinations.CardScreenDestination
13 | import dev.prince.securify.ui.destinations.Destination
14 | import dev.prince.securify.ui.destinations.IntroScreenDestination
15 | import dev.prince.securify.ui.destinations.MasterKeyScreenDestination
16 | import dev.prince.securify.ui.destinations.PasswordScreenDestination
17 | import dev.prince.securify.ui.destinations.UnlockScreenDestination
18 | import kotlinx.coroutines.channels.BufferOverflow
19 | import kotlinx.coroutines.flow.MutableSharedFlow
20 |
21 | fun oneShotFlow() = MutableSharedFlow(
22 | extraBufferCapacity = 1,
23 | onBufferOverflow = BufferOverflow.DROP_OLDEST
24 | )
25 |
26 | fun Destination.shouldShowBottomBar(): Boolean {
27 |
28 | return (this !in listOf(
29 | IntroScreenDestination,
30 | UnlockScreenDestination,
31 | MasterKeyScreenDestination,
32 | PasswordScreenDestination,
33 | CardScreenDestination
34 | ))
35 | }
36 |
37 | fun Modifier.clickWithRipple(bounded: Boolean = true, onClick: () -> Unit) = composed {
38 | this.clickable(
39 | interactionSource = remember { MutableInteractionSource() },
40 | indication = rememberRipple(bounded = bounded),
41 | onClick = { onClick() }
42 | )
43 | }
44 |
45 | val LocalSnackbar = compositionLocalOf<(String) -> Unit> { { } }
46 |
47 | fun isBiometricSupported(context: Context): Boolean {
48 | val biometricManager = BiometricManager.from(context)
49 | val canAuthenticate =
50 | biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK)
51 | when (canAuthenticate) {
52 | BiometricManager.BIOMETRIC_SUCCESS -> {
53 | // The user can authenticate with biometrics, continue with the authentication process
54 | return true
55 | }
56 |
57 | BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE, BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE, BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> {
58 | // Handle the error cases as needed in your app
59 | return false
60 | }
61 |
62 | else -> {
63 | // Biometric status unknown or another error occurred
64 | return false
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
13 |
16 |
19 |
22 |
25 |
28 |
31 |
34 |
37 |
40 |
43 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_add.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_amazon_prime_video.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/icon_amazon_prime_video.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_american_express.xml:
--------------------------------------------------------------------------------
1 |
7 |
9 |
10 |
16 |
17 |
18 |
19 |
20 |
21 |
26 |
31 |
34 |
39 |
44 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_arrow_drop_down.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_arrow_left.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_behance.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/icon_behance.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_call.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_card.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_card_number.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_copy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/icon_copy.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_cvv.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_date.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_delete.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_diners_club.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
13 |
15 |
16 |
22 |
23 |
24 |
25 |
26 |
27 |
29 |
30 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_discord.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/icon_discord.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_dribbble.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/icon_dribbble.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_edit.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_email.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_facebook.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/icon_facebook.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_filter.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_fingerprint.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/icon_github.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_gmail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/icon_gmail.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_home.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_info.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_instagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/icon_instagram.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_key.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_linkedin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/icon_linkedin.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_lock.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_lock_open.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_logout.xml:
--------------------------------------------------------------------------------
1 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_mastercard.xml:
--------------------------------------------------------------------------------
1 |
7 |
9 |
10 |
16 |
17 |
18 |
19 |
20 |
21 |
24 |
26 |
27 |
33 |
34 |
35 |
36 |
37 |
38 |
41 |
42 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_medium.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/icon_medium.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_messenger.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/icon_messenger.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_more.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_netflix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/icon_netflix.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_note.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_others.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/icon_others.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_pass.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_pinterest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/icon_pinterest.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_quora.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/icon_quora.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_reddit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/icon_reddit.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_regenerate.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_rupay.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/icon_rupay.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_search.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_selected.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_settings.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_share.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_snapchat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/icon_snapchat.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_spotify.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/icon_spotify.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_stackoverflow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/icon_stackoverflow.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_tumblr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/icon_tumblr.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_twitterx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/icon_twitterx.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_unselected.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_username.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_visa.xml:
--------------------------------------------------------------------------------
1 |
7 |
9 |
10 |
16 |
17 |
18 |
19 |
20 |
21 |
26 |
31 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_visibility.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_visibility_off.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_whatsapp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/icon_whatsapp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_wordpress.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/icon_wordpress.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_youtube.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/icon_youtube.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icons_snapchat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/icons_snapchat.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/img_empty_box.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/img_empty_box.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/key.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/key.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/lock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/lock.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/surviellance.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/drawable/surviellance.jpg
--------------------------------------------------------------------------------
/app/src/main/res/font/poppins_light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/font/poppins_light.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/poppins_medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/font/poppins_medium.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/poppins_regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/font/poppins_regular.ttf
--------------------------------------------------------------------------------
/app/src/main/res/font/poppins_semibold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/font/poppins_semibold.ttf
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #32ACF4
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Securify
3 | Securely Managing Digital Life
4 | You can now manage your Passwords, Cards from Anywhere, Anytime.
5 | Let\'s make your digital data secured
6 | Setup Master Key
7 | Create a Master Key to secure your credentials with end-to-end encryption
8 | Home
9 | Settings
10 | Generate
11 | Securify: Elevating Your Digital Security for Passwords and Banking Cards. Simplify your online life with our trusted app, where passwords and financial details coexist in a secure vault. No more juggling countless credentials – Securify is your solution. Enjoy hassle-free access to both your accounts and banking cards, all within an encrypted and safeguarded environment. Elevate your digital security and experience peace of mind in the online and financial realms with Securify.
12 | Get Started
13 | Reset Master Key
14 | Reset your Master Key to secure your credentials with end-to-end encryption
15 | 392611333096-pued9mjnmvfkfnvs6vqulkrd44c19rvl.apps.googleusercontent.com
16 | Check out Securify app here: https://play.google.com/store/apps/details?id=%s
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/test/java/dev/prince/securify/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package dev.prince.securify
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | buildscript {
2 | dependencies {
3 | classpath("com.google.gms:google-services:4.4.0")
4 | }
5 | }
6 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
7 | @Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
8 | plugins {
9 | alias(libs.plugins.androidApplication) apply false
10 | alias(libs.plugins.kotlinAndroid) apply false
11 | id("com.google.devtools.ksp") version "1.9.0-1.0.12" apply false
12 | id ("com.google.dagger.hilt.android") version "2.48" apply false
13 | // Add the dependency for the Crashlytics Gradle plugin
14 | id("com.google.firebase.crashlytics") version "2.9.9" apply false
15 | id("com.google.gms.google-services") version "4.4.0" apply false
16 | }
17 | true // Needed to make the Suppress annotation work for the plugins block
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | agp = "8.3.0-alpha05"
3 | biometric = "1.2.0-alpha05"
4 | coil-compose = "2.2.2"
5 | firebase-auth-ktx = "22.2.0"
6 | hilt-android = "2.48"
7 | hilt-navigation-compose = "1.0.0"
8 | kotlin = "1.9.0"
9 | core-ktx = "1.12.0"
10 | junit = "4.13.2"
11 | androidx-test-ext-junit = "1.1.5"
12 | espresso-core = "3.5.1"
13 | lifecycle-runtime-ktx = "2.6.2"
14 | activity-compose = "1.7.2"
15 | compose-bom = "2023.08.00"
16 | play-services-auth = "20.7.0"
17 | security-crypto = "1.0.0"
18 | security-crypto-ktx = "1.0.0"
19 | firebase-auth = "22.2.0"
20 |
21 | [libraries]
22 | androidx-biometric = { module = "androidx.biometric:biometric", version.ref = "biometric" }
23 | androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hilt-navigation-compose" }
24 | androidx-security-crypto = { module = "androidx.security:security-crypto", version.ref = "security-crypto" }
25 | coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil-compose" }
26 | core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" }
27 | firebase-auth-ktx = { module = "com.google.firebase:firebase-auth-ktx", version.ref = "firebase-auth-ktx" }
28 | hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt-android" }
29 | hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt-android" }
30 | junit = { group = "junit", name = "junit", version.ref = "junit" }
31 | androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" }
32 | espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" }
33 | lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle-runtime-ktx" }
34 | activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity-compose" }
35 | compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
36 | play-services-auth = { module = "com.google.android.gms:play-services-auth", version.ref = "play-services-auth" }
37 | ui = { group = "androidx.compose.ui", name = "ui" }
38 | ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
39 | ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
40 | ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
41 | ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
42 | ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
43 | material3 = { group = "androidx.compose.material3", name = "material3" }
44 | androidx-security-crypto-ktx = { group = "androidx.security", name = "security-crypto-ktx", version.ref = "security-crypto-ktx" }
45 | firebase-auth = { group = "com.google.firebase", name = "firebase-auth", version.ref = "firebase-auth" }
46 |
47 | [plugins]
48 | androidApplication = { id = "com.android.application", version.ref = "agp" }
49 | kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
50 |
51 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/devprincefahad/Securify/5daeca9727cd3454376c226f2912949dc71785e5/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Sep 27 00:49:34 IST 2023
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-rc-2-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/privacy-policy/PrivacyPolicy:
--------------------------------------------------------------------------------
1 | Privacy Policy for Securify App
2 |
3 | Effective Date: December 10, 2024
4 |
5 | This Privacy Policy describes how Securify, a password and credit/debit card manager mobile application ("the App"), collects, uses, and shares your personal information when you use the App. Please read this Privacy Policy carefully before using the App.
6 |
7 | 1. Information We Collect:
8 |
9 | a. Personal Information:
10 |
11 | Name
12 | Email address
13 | Mobile number
14 |
15 | b. Sensitive Information:
16 |
17 | Passwords of social accounts
18 | Credit/debit card information (card number, card holder name, expiry date, CVV)
19 |
20 | 2. How We Use Your Information:
21 |
22 | a. Providing Services:
23 |
24 | Managing and storing passwords and credit/debit card information securely.
25 | Facilitating easy access to your stored information.
26 |
27 | b. Communication:
28 |
29 | Sending important notices, updates, and relevant information about the App.
30 |
31 | c. Improvement of Services:
32 |
33 | Analyzing usage patterns to enhance the functionality and features of the App.
34 |
35 | 3. Information Security:
36 |
37 | a. We employ industry-standard security measures to protect your personal and sensitive information from unauthorized access, disclosure, alteration, and destruction.
38 |
39 | b. Passwords and credit/debit card information are stored in an encrypted format to ensure maximum security.
40 |
41 | 4. Information Sharing:
42 |
43 | a. We do not sell, trade, or otherwise transfer your personal information to third parties without your consent.
44 |
45 | b. Your information may be disclosed to trusted third-party service providers who assist us in operating the App and conducting our business, as long as they agree to keep this information confidential.
46 |
47 | 5. Your Choices:
48 |
49 | a. You have the option to review, edit, or delete your personal information within the App.
50 |
51 | b. You may opt-out of receiving non-essential communications from us.
52 |
53 | 6. Contact Information:
54 |
55 | For any inquiries or concerns regarding this Privacy Policy, please contact us at:
56 | Email: 1pfchouhan@gmail.com
57 |
58 | 7. App Ownership:
59 |
60 | Securify is owned and operated by:
61 |
62 | Owner: Prince Fahad
63 | Owner Type: Individual
64 |
65 | 8. Changes to this Privacy Policy:
66 |
67 | We reserve the right to modify this Privacy Policy at any time. Any changes will be effective immediately upon posting the updated Privacy Policy on the App.
68 |
69 | By using the App, you signify your acceptance of this Privacy Policy. If you do not agree to this Privacy Policy, please do not use the App.
70 |
71 | Last Updated: December 10, 2024
72 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google {
4 | content {
5 | includeGroupByRegex("com\\.android.*")
6 | includeGroupByRegex("com\\.google.*")
7 | includeGroupByRegex("androidx.*")
8 | }
9 | }
10 | mavenCentral()
11 | gradlePluginPortal()
12 | }
13 | }
14 | dependencyResolutionManagement {
15 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
16 | repositories {
17 | google()
18 | mavenCentral()
19 | }
20 | }
21 |
22 | rootProject.name = "Securify"
23 | include(":app")
24 |
--------------------------------------------------------------------------------