├── .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 | 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 | 41 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 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 | Download from Playstore 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 | 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 | --------------------------------------------------------------------------------