commit 445e130ebfa155fc618caefc5a5dee6ac41d1033 Author: Agboola Onasanya Date: Mon Oct 28 20:47:35 2024 +0000 Migrate from Bucket diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..9f2719c --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,131 @@ +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("com.google.gms.google-services") + id("kotlin-android") + id("com.google.devtools.ksp") + +} + +android { + + configurations.all { + resolutionStrategy.dependencySubstitution { + substitute(module("org.hamcrest:hamcrest-core:1.1")).using(module("junit:junit:4.13.2")) + } + } + + namespace = "ica.tees.mywallet" + compileSdk = 34 + + defaultConfig { + applicationId = "ica.tees.mywallet" + minSdk = 24 +// targetSdk = 33 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary = true + } + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = "17" + } + buildFeatures { + compose = true + viewBinding = true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.4.3" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } + + +} + + + +dependencies { + + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("com.google.android.material:material:1.10.0") + implementation("androidx.constraintlayout:constraintlayout:2.1.4") + implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.2") + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2") + + implementation("androidx.core:core-ktx:1.12.0") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2") + implementation("androidx.activity:activity-compose:1.8.1") + implementation(platform("androidx.compose:compose-bom:2023.10.01")) + implementation("androidx.compose.material:material-icons-extended-android:1.5.4") + implementation("androidx.compose.ui:ui") + implementation("androidx.compose.ui:ui-graphics") + implementation("androidx.compose.ui:ui-tooling-preview") + implementation("androidx.compose.material3:material3") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + androidTestImplementation(platform("androidx.compose:compose-bom:2023.10.01")) + androidTestImplementation("androidx.compose.ui:ui-test-junit4") + debugImplementation("androidx.compose.ui:ui-tooling") + debugImplementation("androidx.compose.ui:ui-test-manifest") + implementation("androidx.fragment:fragment-ktx:1.6.2") + + val nav_version = "2.7.5" + + implementation("androidx.navigation:navigation-compose:$nav_version") + implementation("androidx.navigation:navigation-fragment-ktx:$nav_version") + implementation("androidx.navigation:navigation-ui-ktx:$nav_version") + implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2") + + //firebase + implementation("com.google.firebase:firebase-firestore:24.10.0") + implementation(platform("com.google.firebase:firebase-bom:32.6.0")) + implementation("com.google.firebase:firebase-auth-ktx") + implementation("com.google.firebase:firebase-storage-ktx") + //authentication + + //Room + //noinspection GradleDependency + implementation("androidx.room:room-runtime:${rootProject.extra["room_version"]}") + implementation("androidx.core:core-ktx:1.12.0") + ksp("androidx.room:room-compiler:${rootProject.extra["room_version"]}") + //noinspection GradleDependency + implementation("androidx.room:room-ktx:${rootProject.extra["room_version"]}") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + + // define a BOM and its version + implementation(platform("com.squareup.okhttp3:okhttp-bom:4.12.0")) + + // define any required OkHttp artifacts without version + implementation("com.squareup.okhttp3:okhttp") + implementation("com.squareup.okhttp3:logging-interceptor") + + implementation("com.googlecode.json-simple:json-simple:1.1.1") + +} + +//kapt { +// correctErrorTypes = true +//} diff --git a/app/google-services.json b/app/google-services.json new file mode 100644 index 0000000..74d909a --- /dev/null +++ b/app/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "841021611450", + "project_id": "thewallet-1d35d", + "storage_bucket": "thewallet-1d35d.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:841021611450:android:67f8db38bf46393e929183", + "android_client_info": { + "package_name": "ica.tees.mywallet" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyDrueny5znsdv6q5hLlhlF2ILenXRwJ5EE" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/ica/tees/mywallet/ExampleInstrumentedTest.kt b/app/src/androidTest/java/ica/tees/mywallet/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..f7b5cb0 --- /dev/null +++ b/app/src/androidTest/java/ica/tees/mywallet/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package ica.tees.mywallet + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("ica.tees.mywallet", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..9d302be --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/ica/tees/mywallet/MainActivity.kt b/app/src/main/java/ica/tees/mywallet/MainActivity.kt new file mode 100644 index 0000000..8689d2b --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/MainActivity.kt @@ -0,0 +1,30 @@ +package ica.tees.mywallet + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview +import ica.tees.mywallet.components.MyWallet +import ica.tees.mywallet.ui.theme.MyWalletTheme +import android.content.Intent +import android.net.Uri + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + MyWalletTheme { + MyWallet() + } + } + } +} + +@Preview(showBackground = true) +@Composable +fun GreetingPreview() { + MyWalletTheme { + MyWallet() + } +} \ No newline at end of file diff --git a/app/src/main/java/ica/tees/mywallet/MyWalletApp.kt b/app/src/main/java/ica/tees/mywallet/MyWalletApp.kt new file mode 100644 index 0000000..5708b4a --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/MyWalletApp.kt @@ -0,0 +1,41 @@ +package ica.tees.mywallet + +import android.app.Application +import com.google.firebase.Firebase +import com.google.firebase.FirebaseApp +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.firestore.FirebaseFirestore +import com.google.firebase.firestore.firestore +import com.google.firebase.firestore.firestoreSettings +import com.google.firebase.firestore.memoryCacheSettings +import com.google.firebase.firestore.persistentCacheSettings +import ica.tees.mywallet.data.AppContainer +import ica.tees.mywallet.data.AppDataContainer + +class MyWalletApp : Application() { + + // var client = OkHttpClient() + lateinit var container: AppContainer + lateinit var auth: FirebaseAuth + lateinit var db: FirebaseFirestore + + val settings = firestoreSettings { + // Use memory cache + setLocalCacheSettings(memoryCacheSettings {}) + // Use persistent disk cache (default) + setLocalCacheSettings(persistentCacheSettings {}) + } + + + override fun onCreate() { + super.onCreate() + // client = OkHttpClient.Builder().build() + container = AppDataContainer(this) + auth = FirebaseAuth.getInstance() + db.firestoreSettings = settings + db = FirebaseFirestore.getInstance() + FirebaseApp.initializeApp(this) + + } +} + diff --git a/app/src/main/java/ica/tees/mywallet/components/AppViewModelProvider.kt b/app/src/main/java/ica/tees/mywallet/components/AppViewModelProvider.kt new file mode 100644 index 0000000..6fb6620 --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/components/AppViewModelProvider.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ica.tees.mywallet.components + +import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory +import androidx.lifecycle.viewmodel.CreationExtras +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory +import ica.tees.mywallet.MyWalletApp +import ica.tees.mywallet.model.CardModel +import ica.tees.mywallet.model.HomeModel +import ica.tees.mywallet.model.LoginModel +import ica.tees.mywallet.model.PasswordModel +import ica.tees.mywallet.model.SignUpModel + + +object AppViewModelProvider { + val Factory = viewModelFactory { + initializer { + CardModel( + //this.createSavedStateHandle(), + myWalletApp().container.cardRepository + ) + } + + initializer { + PasswordModel( + myWalletApp().container.passwordRepository + ) + } + + initializer { + HomeModel( + myWalletApp().container.cardRepository, + myWalletApp().container.passwordRepository + ) + } + + initializer { + LoginModel( + myWalletApp().container.userRepository, + myWalletApp().auth + ) + } + + initializer { + SignUpModel( + myWalletApp().container.userRepository, + myWalletApp().db + ) + } + + } +} + + +fun CreationExtras.myWalletApp(): MyWalletApp = + (this[AndroidViewModelFactory.APPLICATION_KEY] as MyWalletApp) diff --git a/app/src/main/java/ica/tees/mywallet/components/MyWalletNavigation.kt b/app/src/main/java/ica/tees/mywallet/components/MyWalletNavigation.kt new file mode 100644 index 0000000..1339ddb --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/components/MyWalletNavigation.kt @@ -0,0 +1,187 @@ +package ica.tees.mywallet.components + +import androidx.annotation.StringRes +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import androidx.navigation.navArgument +import ica.tees.mywallet.data.Card +import ica.tees.mywallet.data.User +import ica.tees.mywallet.screen.AddCardScreen +import ica.tees.mywallet.screen.AddPasswordScreen +import ica.tees.mywallet.screen.CardsScreen +import ica.tees.mywallet.screen.LoginForm +import ica.tees.mywallet.screen.MainScreen +import ica.tees.mywallet.screen.PasswordScreen +import ica.tees.mywallet.screen.SignupForm + +@Composable +fun MyWallet( +) { + var returnValueNew by remember { mutableStateOf(false) } + var canNavigateBack by remember { mutableStateOf(false) } + val navController = rememberNavController() + var firstName by rememberSaveable { mutableStateOf("") } + var theName by rememberSaveable { mutableStateOf("") } + var showOnHome by rememberSaveable { mutableStateOf(false) } + + Scaffold( + + topBar = { + ScaffoldTopBar( + modifier = Modifier, + canNavigateBack = canNavigateBack, + navigateUp = { navController.navigateUp() }, + barText = firstName, + showOnHome = showOnHome + ) + } + ) { innerPadding -> + + NavHost( + navController = navController, + startDestination = MyWalletScreen.Login.name, + modifier = Modifier + .padding(innerPadding) + + ) { + composable(route = MyWalletScreen.Login.name) { + canNavigateBack = false + showOnHome = false + LoginForm( + modifier = Modifier, + navController = navController, + onButtonClicked = { userList: List -> + firstName = userList.first().firstName + theName = userList.first().userName + } + ) + } + composable(route = MyWalletScreen.SignUp.name) { + canNavigateBack = false + showOnHome = false + SignupForm( + modifier = Modifier, + navController = navController, + onButtonClicked = { returnValue: Boolean -> + returnValueNew = returnValue + if (returnValueNew) { + navController.navigate(route = MyWalletScreen.Login.name) + } + } + ) + } + composable(route = MyWalletScreen.Home.name) { + canNavigateBack = false + showOnHome = true + MainScreen( + modifier = Modifier, + navController = navController, + theName = theName + ) + } + composable(MyWalletScreen.Cards.name) { + canNavigateBack = false + showOnHome = true + CardsScreen( + modifier = Modifier, + navController = navController, + theName = theName + ) + } + composable(route = MyWalletScreen.Passwords.name) { + canNavigateBack = false + showOnHome = true + PasswordScreen( + modifier = Modifier, + navController = navController, + theName = theName + ) + } + composable(route = MyWalletScreen.AddCards.name) { + canNavigateBack = true + showOnHome = false + AddCardScreen( + modifier = Modifier, + navController = navController, + theName = theName, + ) + } + composable(route = MyWalletScreen.AddPasswords.name) { + canNavigateBack = true + showOnHome = false + AddPasswordScreen( + modifier = Modifier, + navController = navController, + theName = theName + ) + } + + } + } +} + + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ScaffoldTopBar( + canNavigateBack: Boolean, + navigateUp: () -> Unit, + modifier: Modifier, + barText: String, + showOnHome: Boolean +) { + + TopAppBar( + + title = { + if (showOnHome) { + Text(text = "Welcome, $barText") + } + }, + colors = TopAppBarDefaults.mediumTopAppBarColors( + containerColor = MaterialTheme.colorScheme.primaryContainer + ), + modifier = modifier, + navigationIcon = { + if (canNavigateBack) { + IconButton(onClick = navigateUp) { + Icon( + imageVector = Icons.Filled.ArrowBack, + contentDescription = "Back" + ) + } + } + } + ) +} + + +enum class MyWalletScreen { + Login, + SignUp, + Home, + Cards, + AddCards, + Passwords, + AddPasswords, +} \ No newline at end of file diff --git a/app/src/main/java/ica/tees/mywallet/components/SystemBackButtonHandler.kt b/app/src/main/java/ica/tees/mywallet/components/SystemBackButtonHandler.kt new file mode 100644 index 0000000..935ac65 --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/components/SystemBackButtonHandler.kt @@ -0,0 +1,54 @@ +package ica.tees.mywallet.components + +import androidx.activity.ComponentActivity +import androidx.activity.OnBackPressedCallback +import androidx.activity.OnBackPressedDispatcherOwner +import androidx.compose.runtime.* +import androidx.compose.ui.platform.LocalLifecycleOwner + +private val LocalBackPressedDispatcher = + staticCompositionLocalOf { null } + + +private class ComposableBackNavigationHandler(enabled: Boolean) : OnBackPressedCallback(enabled) { + lateinit var onBackPressed: () -> Unit + + override fun handleOnBackPressed() { + onBackPressed() + } + +} + + +@Composable +internal fun ComposableHandler( + enabled: Boolean = true, + onBackPressed: () -> Unit +) { + val dispatcher = (LocalBackPressedDispatcher.current ?: return).onBackPressedDispatcher + + val handler = remember { ComposableBackNavigationHandler(enabled) } + + DisposableEffect(dispatcher) { + dispatcher.addCallback(handler) + + + onDispose { handler.remove() } + } + + LaunchedEffect(enabled) { + handler.isEnabled = enabled + handler.onBackPressed = onBackPressed + } +} + +@Composable +internal fun SystemBackButtonHandler(onBackPressed: () -> Unit) { + CompositionLocalProvider( + LocalBackPressedDispatcher provides LocalLifecycleOwner.current as ComponentActivity + ) { + ComposableHandler { + onBackPressed() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ica/tees/mywallet/components/UiComponents.kt b/app/src/main/java/ica/tees/mywallet/components/UiComponents.kt new file mode 100644 index 0000000..c4ef4c3 --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/components/UiComponents.kt @@ -0,0 +1,307 @@ +package ica.tees.mywallet.components + +import androidx.annotation.StringRes +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.ClickableText +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Visibility +import androidx.compose.material.icons.filled.VisibilityOff +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.withStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import ica.tees.mywallet.data.Card +import ica.tees.mywallet.data.Password +import ica.tees.mywallet.ui.theme.Primary + + +@Composable +fun ClickableLoginTextComponent(tryingToLogin: Boolean = true, onTextSelected: (String) -> Unit) { + val initialText = + if (tryingToLogin) "Already have an account? " else "Don’t have an account yet? " + val loginText = if (tryingToLogin) "Login" else "Register" + + val annotatedString = buildAnnotatedString { + append(initialText) + withStyle(style = SpanStyle(color = Primary)) { + pushStringAnnotation(tag = loginText, annotation = loginText) + append(loginText) + } + } + + ClickableText( + modifier = Modifier + .fillMaxWidth() + .heightIn(min = 40.dp), + style = TextStyle( + fontSize = 21.sp, + fontWeight = FontWeight.Normal, + fontStyle = FontStyle.Normal, + textAlign = TextAlign.Center + ), + text = annotatedString, + onClick = { offset -> + + annotatedString.getStringAnnotations(offset, offset) + .firstOrNull()?.also { span -> + if (span.item == loginText) { + onTextSelected(span.item) + } + } + + }, + ) +} + +@Composable +fun TextFieldComponent( + value: String, + onValueChanged: (String) -> Unit, + @StringRes label: Int, + keyboardOptions: KeyboardOptions, + leadingIcon: ImageVector, + visualTransformation: VisualTransformation = VisualTransformation.None, + +) { + + TextField( + modifier = Modifier + .fillMaxWidth(), + value = value, + leadingIcon = { Icon(imageVector = leadingIcon, contentDescription = "") }, + onValueChange = onValueChanged, + visualTransformation = visualTransformation, + label = { Text(stringResource(label)) }, + singleLine = true, + keyboardOptions = keyboardOptions, + + ) +} + +@Composable +fun PasswordFieldComponent( + value: String, + onValueChanged: (String) -> Unit, + @StringRes label: Int, + keyboardOptions: KeyboardOptions, + leadingIcon: ImageVector +) { + + var passwordVisible by remember { mutableStateOf(false) } + + TextField( + modifier = Modifier + .fillMaxWidth(), + value = value, + onValueChange = onValueChanged, + label = { Text(stringResource(label)) }, + singleLine = true, + leadingIcon = { Icon(imageVector = leadingIcon, contentDescription = "") }, + keyboardOptions = keyboardOptions, + visualTransformation = if (passwordVisible) VisualTransformation.None else PasswordVisualTransformation(), + trailingIcon = { + val image = if (passwordVisible) + Icons.Filled.Visibility + else Icons.Filled.VisibilityOff + + // Localized description for accessibility services + val description = if (passwordVisible) "Hide password" else "Show password" + + // Toggle button to hide or display password + IconButton(onClick = { passwordVisible = !passwordVisible }) { + Icon(imageVector = image, description) + } + } + ) +} + + +@Composable +fun ButtonComponent( + modifier: Modifier, + onButtonClicked: () -> Unit = {}, + enabled: Boolean, + @StringRes buttonText: Int + +) { + + Button( + onClick = onButtonClicked, + shape = RoundedCornerShape(5.dp), + colors = ButtonDefaults.buttonColors(containerColor = Color(0xff9e9e9e)), + modifier = modifier + .fillMaxWidth(), + enabled = enabled + + ) { + + Text(text = stringResource(buttonText)) + } +} + + +@Composable +fun DisplayDialogCard( + showDialog: Boolean, + onDismissRequest: () -> Unit, + selectedIndex: Int, + modifier: Modifier, + cardsLists: List +) { + + if (showDialog) { + Dialog( + onDismissRequest = onDismissRequest, + properties = DialogProperties( + decorFitsSystemWindows = true, + usePlatformDefaultWidth = true + ) + + ) { +// (LocalView.current.parent as DialogWindowProvider)?.window?.setDimAmount(1f) + Card( + modifier = modifier + .fillMaxWidth() + .padding(1.dp), + ) { + Row { + Column { + ElevatedCard( + elevation = + CardDefaults.cardElevation( + defaultElevation = 15.dp + ), + modifier = modifier.size(width = 160.dp, height = 100.dp) + + + ) { + Image( + painter = painterResource(cardsLists[selectedIndex].cardType), + contentDescription = null, + modifier = modifier + .fillMaxWidth() + .size(width = 80.dp, height = 40.dp) + .padding(end = 10.dp, top = 5.dp) + .wrapContentWidth(Alignment.End) + ) + } + } + Spacer(modifier = Modifier.width(20.dp)) + Column { + Text( + text = cardsLists[selectedIndex].bankName, + fontWeight = FontWeight.Bold, + fontSize = 20.sp, + modifier = modifier + ) + Text( + text = cardsLists[selectedIndex].cardName + ) + Text( + text = cardsLists[selectedIndex].cardNumber + ) + Text( + text = cardsLists[selectedIndex].cardExpiry + ) + Text( + text = cardsLists[selectedIndex].cardSecurity + ) + } + } + } + } + } +} + + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DisplayDialogPassword( + showDialog: Boolean, + onDismissRequest: () -> Unit, + selectedIndex: Int, + modifier: Modifier, + passwordLists: List +) { + + if (showDialog) { + + Dialog( + + onDismissRequest = onDismissRequest, + properties = DialogProperties( + decorFitsSystemWindows = true, + usePlatformDefaultWidth = true + ) + ) { + Card( + modifier = modifier + .fillMaxWidth() + .padding(1.dp), + + ) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.Start, + verticalArrangement = Arrangement.Center + ) { + Text( + passwordLists[selectedIndex].accountName, + fontWeight = FontWeight.Bold + ) + SelectionContainer { + Column( + modifier = modifier + ) { + Text(text = passwordLists[selectedIndex].email) + Text(text = passwordLists[selectedIndex].password) + } + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ica/tees/mywallet/data/ApiCalls.kt b/app/src/main/java/ica/tees/mywallet/data/ApiCalls.kt new file mode 100644 index 0000000..dd1b447 --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/data/ApiCalls.kt @@ -0,0 +1,98 @@ +package ica.tees.mywallet.data + +import android.util.JsonReader +import android.util.Log +import androidx.annotation.StringRes +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import okhttp3.Response +import org.json.simple.JSONObject +import org.json.simple.parser.JSONParser +import java.net.SocketTimeoutException +import java.util.concurrent.TimeUnit + + +class ApiCalls { + + private val client = OkHttpClient.Builder() + .connectTimeout(5, TimeUnit.SECONDS) + .writeTimeout(1, TimeUnit.SECONDS) + .readTimeout(1, TimeUnit.SECONDS) + .build() + + private val errorMsg: JSONObject = JSONObject() + private var jsonReturnObject = JSONObject() + private var jsonHeaderObject = JSONObject() + private val jsonApiObject = JSONObject() + var response: Response? = null + + fun validateCard(cardNumber: String): JSONObject { + + errorMsg["code"] = "001" + errorMsg["valid"] = "false" + errorMsg["result"] = "No Result" + val errorMsgMain = JSONObject() + errorMsgMain["BIN"] = errorMsg + val apiEndPoint = "https://bin-ip-checker.p.rapidapi.com/?bin=" + val apiKey = "f5204a5f14msh4f6ce93e987ac89p10f514jsn1152d1ec2f1a" + + try { + + jsonApiObject.put("bin", cardNumber.substring(0, 6)) + + val mediaType = "application/json".toMediaTypeOrNull() + val body = jsonApiObject.toString().toRequestBody(mediaType) + val request = Request.Builder() + .url("${apiEndPoint}${cardNumber.substring(0, 6)}") + .post(body) + .addHeader("content-type", "application/json") + .addHeader("X-RapidAPI-Key", apiKey) + .addHeader("X-RapidAPI-Host", "bin-ip-checker.p.rapidapi.com") + .build() + + response = client.newCall(request).execute() +// Log.d("BANKNAME", response.body.toString()) +// jsonHeaderObject = JSONParser().parse( +// response?.body?.string() ?: errorMsgMain.toString() +// ) as JSONObject +// val code = jsonHeaderObject.get("code").toString() + + jsonReturnObject = JSONParser().parse(response?.body?.string() ?: errorMsgMain.toString()) as JSONObject +// Log.d("BANKNAME", jsonReturnObject.toJSONString()) + return jsonReturnObject + + } catch (exception: SocketTimeoutException) { + exception.localizedMessage + // jsonReturnObject = JSONParser().parse(response?.body?.string() ?: errorMsgMain.toString()) as JSONObject + return errorMsgMain + } + + } +} + +//sealed class UiText { +// +// data class StringValue(val str: String) : UiText() +// +// class StringResource( +// @StringRes val resourceId: Int, +// vararg val args: Any +// ) : UiText() +// +// @Composable +// fun asString(): String { +// return when (this) { +// is StringValue -> { +// str +// } +// +// is StringResource -> { +// stringResource(id = resourceId, formatArgs = args) +// } +// } +// } +//} \ No newline at end of file diff --git a/app/src/main/java/ica/tees/mywallet/data/AppContainer.kt b/app/src/main/java/ica/tees/mywallet/data/AppContainer.kt new file mode 100644 index 0000000..54fb2c4 --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/data/AppContainer.kt @@ -0,0 +1,26 @@ +package ica.tees.mywallet.data + +import android.content.Context + +interface AppContainer { + + val cardRepository: CardRepository + val passwordRepository: PasswordRepository + val userRepository: UserRepository +} + +class AppDataContainer(private val context: Context) : AppContainer { + + override val cardRepository: CardRepository by lazy { + OfflineCardRepository(MyWalletDatabase.getDatabase(context).CardDao()) + } + + override val passwordRepository: PasswordRepository by lazy { + OfflinePasswordRepository(MyWalletDatabase.getDatabase(context).PasswordDao()) + } + + override val userRepository: UserRepository by lazy { + OfflineUserRepository(MyWalletDatabase.getDatabase(context).UserDao()) + } + +} \ No newline at end of file diff --git a/app/src/main/java/ica/tees/mywallet/data/DataSource.kt b/app/src/main/java/ica/tees/mywallet/data/DataSource.kt new file mode 100644 index 0000000..7a457ab --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/data/DataSource.kt @@ -0,0 +1,297 @@ +package ica.tees.mywallet.data + +import android.content.Context +import androidx.room.Dao +import androidx.room.Database +import androidx.room.Delete +import androidx.room.Entity +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.PrimaryKey +import androidx.room.Query +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.Update +import ica.tees.mywallet.R +import kotlinx.coroutines.flow.Flow +import java.util.Currency +import java.util.UUID + +data class TabItem( + val title: String, + val unselectedIcon: Int, + val selectedIcon: Int +) + +private fun menuList(): MutableList { + + val menus: MutableList = mutableListOf() + for (i in menuName.indices) { + menus.add(TabItem(menuName[i], menuImage[i], menuImage[i])) + } + return menus +} + +private val menuName = listOf( + "Home", + "Cards", + "Passwords" +) + +private val menuImage = listOf( + + R.drawable.home, + R.drawable.card_icon, + R.drawable.password +) + + +object DataSource { + val tabItem = menuList() +} + + +enum class FieldType { + EXPIRATION, + CARD_NUMBER +} + +////DATABASE RELATED ITEMS +//DATABASE +@Database( + entities = [Card::class, Password::class, + User::class], + version = 1, + exportSchema = false +) +abstract class MyWalletDatabase : RoomDatabase() { + + abstract fun PasswordDao(): PasswordDao + abstract fun CardDao(): CardDao + abstract fun UserDao(): UserDao + + companion object { + + @Volatile + private var Instance: MyWalletDatabase? = null + + fun getDatabase(context: Context): MyWalletDatabase { + + return Instance ?: synchronized(this) { + + Room.databaseBuilder(context, MyWalletDatabase::class.java, "wallet_database") + .fallbackToDestructiveMigration() + .build() + .also { Instance = it } + } + } + } +} +//DATABASE + +//ENTITY +@Entity(tableName = "card") +data class Card( + @PrimaryKey + val id: UUID, + val count: Int, + val cardName: String, + val cardNumber: String, + val cardSecurity: String, + val cardExpiry: String, + val bankName: String, + val cardType: Int, + val userName: String, + val currency: String +) + +@Entity(tableName = "password") +data class Password( + @PrimaryKey + val id: UUID, + val count: Int, + val accountName: String, + val email: String, + val password: String, + val userName: String, +) + +@Entity(tableName = "user") +data class User( + @PrimaryKey + val id: UUID, + val firstName: String, + val lastName: String, + val userName: String, +) +//ENTITY + + +//DAO +@Dao +interface CardDao { + + @Insert(onConflict = OnConflictStrategy.IGNORE) + suspend fun insert(cards: Card) + + @Update + suspend fun update(cards: Card) + + @Delete + suspend fun delete(cards: Card) + + @Query("SELECT * FROM card WHERE id = :id") + fun findById(id: UUID): Flow> + + @Query("SELECT * from card WHERE count > 0 AND userName = :userName ORDER BY count DESC LIMIT 1") + fun getTopCard(userName: String): Flow> + + @Query("SELECT * FROM card ORDER BY bankName ASC") + fun getAllCards(): Flow> + + @Query("SELECT * FROM card ORDER BY cardName ASC") + fun getUserCard(): Flow> +} + +@Dao +interface PasswordDao { + + @Insert(onConflict = OnConflictStrategy.IGNORE) + suspend fun insert(passwords: Password) + + @Update + suspend fun update(passwords: Password) + + @Delete + fun delete(passwords: Password) + + @Query("SELECT * FROM password WHERE id = :id") + fun findById(id: UUID): Flow> + + @Query("SELECT * from password WHERE count > 0 AND userName = :userName ORDER BY count DESC LIMIT 5") + fun getTopPassword(userName: String): Flow> + + @Query("SELECT * FROM password ORDER BY accountName ASC") + fun getAllPasswords(): Flow> + + @Query("SELECT * FROM password ORDER BY accountName ASC") + fun getUserPassword(): Flow> +} + +@Dao +interface UserDao { + + @Insert(onConflict = OnConflictStrategy.IGNORE) + suspend fun insert(user: User) + + @Update + suspend fun update(user: User) + + @Delete + suspend fun delete(user: User) + + @Query("SELECT * FROM user WHERE userName = :userName") + fun findByUser(userName: String): Flow> +} +//DAO + + +//REPO +interface CardRepository { + + fun getAllItemsStream(): Flow> + + fun getItemStream(id: UUID): Flow> + + fun getTopCard(userName: String): Flow> + + fun getUserCard(): Flow> + + suspend fun insertItem(cards: Card) + + suspend fun deleteItem(cards: Card) + + suspend fun updateItem(cards: Card) + +} + + +interface PasswordRepository { + + fun getAllItemsStream(): Flow> + + fun getItemStream(id: UUID): Flow> + + fun getTopPassword(userName: String): Flow> + + fun getUserPassword(): Flow> + + suspend fun insertItem(passwords: Password) + + suspend fun deleteItem(passwords: Password) + + suspend fun updateItem(passwords: Password) + +} + +interface UserRepository { + + suspend fun insertItem(user: User) + + suspend fun deleteItem(user: User) + + suspend fun updateItem(user: User) + + fun getUserByName(userName: String): Flow> + +} +//REPO + + +//OFFLINE REPO +class OfflineUserRepository(private val userDao: UserDao) : UserRepository { + + override suspend fun insertItem(user: User) = userDao.insert(user) + + override suspend fun deleteItem(user: User) = userDao.delete(user) + + override suspend fun updateItem(user: User) = userDao.update(user) + + override fun getUserByName(userName: String): Flow> = userDao.findByUser(userName) +} + + +class OfflineCardRepository(private val cardsDao: CardDao) : CardRepository { + override fun getAllItemsStream(): Flow> = cardsDao.getAllCards() + + override fun getItemStream(id: UUID): Flow> = cardsDao.findById(id) + + override fun getTopCard(userName: String): Flow> = cardsDao.getTopCard(userName) + + override fun getUserCard(): Flow> = cardsDao.getUserCard() + + override suspend fun insertItem(cards: Card) = cardsDao.insert(cards) + + override suspend fun deleteItem(cards: Card) = cardsDao.delete(cards) + + override suspend fun updateItem(cards: Card) = cardsDao.update(cards) + +} + +class OfflinePasswordRepository(private val passwordsDao: PasswordDao) : PasswordRepository { + override fun getAllItemsStream(): Flow> = passwordsDao.getAllPasswords() + + override fun getTopPassword(userName: String): Flow> = passwordsDao.getTopPassword(userName) + + override fun getUserPassword(): Flow> = passwordsDao.getUserPassword() + + override fun getItemStream(id: UUID): Flow> = passwordsDao.findById(id) + + override suspend fun insertItem(passwords: Password) = passwordsDao.insert(passwords) + + override suspend fun deleteItem(passwords: Password) = passwordsDao.delete(passwords) + + override suspend fun updateItem(passwords: Password) = passwordsDao.update(passwords) + +} +//OFFLINE REPO \ No newline at end of file diff --git a/app/src/main/java/ica/tees/mywallet/data/InputTransaformation.kt b/app/src/main/java/ica/tees/mywallet/data/InputTransaformation.kt new file mode 100644 index 0000000..95ebfb9 --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/data/InputTransaformation.kt @@ -0,0 +1,81 @@ +package ica.tees.mywallet.data + +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.input.OffsetMapping +import androidx.compose.ui.text.input.TransformedText +import androidx.compose.ui.text.input.VisualTransformation + +class InputTransaformation(private val fieldType: FieldType) : VisualTransformation { + + override fun filter(text: AnnotatedString): TransformedText = when(fieldType) { + FieldType.EXPIRATION -> expirationFilter(text) + FieldType.CARD_NUMBER -> cardNumberFilter(text) + } + + private fun expirationFilter(text: AnnotatedString): TransformedText { + val trimmed = if(text.text.length >= 4) text.text.substring(0..3) else text.text + var out = "" + + for(i in trimmed.indices) { + out += trimmed[i] + if(i == 1) out += "/" // Adding slash after second character + } + + val offsetTranslator = object: OffsetMapping { + override fun originalToTransformed(offset: Int): Int { + if(offset <= 1) return offset // From 0 to 1, offset doesn't change + if(offset <= 4) return offset + 1 // From 2 to 4, offset take into account the added slash + return 5 + } + + override fun transformedToOriginal(offset: Int): Int { + if(offset <= 2) return offset + if(offset <= 5) return offset - 1 + return 4 + } + } + + return TransformedText( + AnnotatedString(out), + offsetTranslator + ) + } + + /** + * Convert the input of cardNumber field to "xxxx xxxx xxxx xxxx" format + */ + private fun cardNumberFilter(text: AnnotatedString): TransformedText { + val trimmer = if(text.text.length >= 16) text.text.substring(0..15) else text.text + var out = "" + + for(i in trimmer.indices) { + out += trimmer[i] + if(i == 3) out += " " + if(i == 7) out += " " + if(i == 11) out += " " + } + + val offsetTranslator = object: OffsetMapping { + override fun originalToTransformed(offset: Int): Int { + if(offset <= 3) return offset + if(offset <= 7) return offset + 1 + if(offset <= 11) return offset + 2 + if(offset <= 16) return offset + 3 + return 19 + } + + override fun transformedToOriginal(offset: Int): Int { + if(offset <= 4) return offset + if(offset <= 8) return offset - 1 + if(offset <= 12) return offset - 2 + if(offset <= 17) return offset - 3 + return 16 + } + } + + return TransformedText( + AnnotatedString(out), + offsetTranslator + ) + } +} diff --git a/app/src/main/java/ica/tees/mywallet/data/Validator.kt b/app/src/main/java/ica/tees/mywallet/data/Validator.kt new file mode 100644 index 0000000..559120b --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/data/Validator.kt @@ -0,0 +1,61 @@ +package ica.tees.mywallet.data + +object Validator { + fun validateFirstName(fName: String): ValidationResult { + return ValidationResult( + (fName.isNotEmpty() && fName.length >= 2) + ) + } + + fun validateLastName(lName: String): ValidationResult { + return ValidationResult( + (lName.isNotEmpty() && lName.length >= 2) + ) + } + + fun validateName(name: String): ValidationResult { + return ValidationResult( + (name.isNotEmpty() && name.length >= 3) + ) + } + + fun validateEmail(email: String): ValidationResult { + return ValidationResult( + (email.isNotEmpty()) + ) + } + + fun validatePassword(password: String): ValidationResult { + return ValidationResult( + (password.isNotEmpty() && password.length >= 4) + ) + } + + fun validateCardNumber(cardNumber: String): ValidationResult { + return ValidationResult( + (cardNumber.isNotEmpty() && cardNumber.length == 16) + ) + } + + fun validateCvv(cvv: String): ValidationResult { + return ValidationResult( + (cvv.isNotEmpty() && cvv.length == 3) + ) + } + + fun validateExpiry(expiry: String): ValidationResult { + return ValidationResult( + (expiry.isNotEmpty() && expiry.length >= 4) + ) + } + + fun validateBankName(bank: String): ValidationResult { + return ValidationResult( + (bank.isNotEmpty() && bank.length >= 2) + ) + } +} + +data class ValidationResult( + val status: Boolean = false +) \ No newline at end of file diff --git a/app/src/main/java/ica/tees/mywallet/event/CardEvent.kt b/app/src/main/java/ica/tees/mywallet/event/CardEvent.kt new file mode 100644 index 0000000..c923d43 --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/event/CardEvent.kt @@ -0,0 +1,15 @@ +package ica.tees.mywallet.event + +sealed class CardEvent { + + data class CardNumberChanged(val cardNumber: String) : CardEvent() + data class CardSecurity(val cardSecurity: String) : CardEvent() + data class CardNameChanged(val cardName: String) : CardEvent() + data class CardExpiryChanged(val cardExpiry: String) : CardEvent() + data class BankNameChanged(val bankName: String) : CardEvent() + data class CurrencyChanged(val currency: String): CardEvent() + data class CardTypeChanged(val cardType: String): CardEvent() + data class UserNameOnInitialise(val userName: String) : CardEvent() + object CardButtonClicked : CardEvent() + +} \ No newline at end of file diff --git a/app/src/main/java/ica/tees/mywallet/event/LoginEvent.kt b/app/src/main/java/ica/tees/mywallet/event/LoginEvent.kt new file mode 100644 index 0000000..5649a80 --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/event/LoginEvent.kt @@ -0,0 +1,9 @@ +package ica.tees.mywallet.event + +sealed class LoginEvent { + + data class EmailChanged(val email: String) : LoginEvent() + data class PasswordChanged(val password: String) : LoginEvent() + object LoginButtonClicked : LoginEvent() + +} diff --git a/app/src/main/java/ica/tees/mywallet/event/PasswordEvent.kt b/app/src/main/java/ica/tees/mywallet/event/PasswordEvent.kt new file mode 100644 index 0000000..1044903 --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/event/PasswordEvent.kt @@ -0,0 +1,10 @@ +package ica.tees.mywallet.event + +sealed class PasswordEvent { + + data class EmailChanged(val email: String) : PasswordEvent() + data class PasswordChanged(val password: String) : PasswordEvent() + data class AccountNameChanged(val accountName: String) : PasswordEvent() + object PasswordButtonClicked : PasswordEvent() + +} \ No newline at end of file diff --git a/app/src/main/java/ica/tees/mywallet/event/SignUpEvent.kt b/app/src/main/java/ica/tees/mywallet/event/SignUpEvent.kt new file mode 100644 index 0000000..165a954 --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/event/SignUpEvent.kt @@ -0,0 +1,11 @@ +package ica.tees.mywallet.event + +sealed class SignUpEvent { + + data class FirstNameChanged(val firstName: String) : SignUpEvent() + data class LastNameChanged(val lastName: String) : SignUpEvent() + data class EmailChanged(val email: String) : SignUpEvent() + data class PasswordChanged(val password: String) : SignUpEvent() + object SignUpButtonClicked : SignUpEvent() + +} \ No newline at end of file diff --git a/app/src/main/java/ica/tees/mywallet/model/CardModel.kt b/app/src/main/java/ica/tees/mywallet/model/CardModel.kt new file mode 100644 index 0000000..1e2bb39 --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/model/CardModel.kt @@ -0,0 +1,213 @@ +package ica.tees.mywallet.model + +import android.os.StrictMode +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import ica.tees.mywallet.R +import ica.tees.mywallet.data.ApiCalls +import ica.tees.mywallet.data.Card +import ica.tees.mywallet.data.CardRepository +import ica.tees.mywallet.data.Validator +import ica.tees.mywallet.event.CardEvent +import ica.tees.mywallet.state.CardUIState +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch +import org.json.simple.JSONObject +import java.util.UUID + +class CardModel( + // savedStateHandle: SavedStateHandle, + private val cardsRepository: CardRepository +) : ViewModel() { + + + var jsonObject = JSONObject() + var cardUIState = MutableStateFlow(CardUIState()) + var _uiState: StateFlow = cardUIState.asStateFlow() + val cardDataList: StateFlow = + cardsRepository.getUserCard().map { CardsList(it) }.stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000L), + initialValue = CardsList() + ) + + var allValidationPassed = mutableStateOf(false) + + fun onEvent(event: CardEvent) { + when (event) { + is CardEvent.UserNameOnInitialise -> { + + cardUIState.value.userName = event.userName + + cardUIState.value = cardUIState.value.copy( + userName = event.userName + ) + } + + is CardEvent.CardNumberChanged -> { + cardUIState.value = cardUIState.value.copy( + cardNumber = event.cardNumber + ) + } + + is CardEvent.CardExpiryChanged -> { + cardUIState.value = cardUIState.value.copy( + cardExpiry = event.cardExpiry + ) + } + + is CardEvent.CardSecurity -> { + cardUIState.value = cardUIState.value.copy( + cardSecurity = event.cardSecurity + ) + } + + is CardEvent.BankNameChanged -> { + cardUIState.value = cardUIState.value.copy( + bankName = event.bankName + ) + } + + is CardEvent.CardNameChanged -> { + cardUIState.value = cardUIState.value.copy( + cardName = event.cardName + ) + } + + is CardEvent.CurrencyChanged -> { + cardUIState.value = cardUIState.value.copy( + currency = event.currency + ) + } + + is CardEvent.CardTypeChanged -> { + cardUIState.value = cardUIState.value.copy( + cardType = event.cardType + ) + } + + is CardEvent.CardButtonClicked -> { + viewModelScope.launch { + addCard() + } + } + } + validateAddCardUIDataWithRules() + } + + private fun validateAddCardUIDataWithRules() { + val cardNumberResult = Validator.validateCardNumber( + cardNumber = cardUIState.value.cardNumber + ) + + val bankNameResult = Validator.validateBankName( + bank = cardUIState.value.bankName + ) + + val cardNameResult = Validator.validateName( + name = cardUIState.value.cardName + ) + + val cvvResult = Validator.validateCvv( + cvv = cardUIState.value.cardSecurity + ) + + val expiryResult = Validator.validateExpiry( + expiry = cardUIState.value.cardExpiry + ) + + cardUIState.value = cardUIState.value.copy( + cardNumberError = cardNumberResult.status, + bankNameError = bankNameResult.status, + cardNameError = cardNameResult.status, + cardSecurityError = cvvResult.status, + cardExpiryError = expiryResult.status, + + ) + + allValidationPassed.value = + cardNumberResult.status && bankNameResult.status && cardNameResult.status && cvvResult.status && expiryResult.status + + } + + private fun addCard() { + + val cardImage = + when (cardUIState.value.cardType) { + "MASTERCARD" -> R.drawable.logo_mastercard + "VISA" -> R.drawable.logo_visa + else -> R.drawable.logo_other + } + + val uuid = UUID.randomUUID() + viewModelScope.launch { + cardsRepository.insertItem( + cards = Card( + uuid, + 0, + cardUIState.value.cardName, + cardUIState.value.cardNumber, + cardUIState.value.cardSecurity, + cardUIState.value.cardExpiry, + cardUIState.value.bankName, + cardImage, + cardUIState.value.userName, + cardUIState.value.currency + ) + ) + } + } + + fun validateCard(): String { + + val policy = StrictMode.ThreadPolicy.Builder().permitAll().build() + StrictMode.setThreadPolicy(policy) + + jsonObject = ApiCalls().validateCard(cardUIState.value.cardNumber) + val code = jsonObject.get("code").toString() + + if (code.startsWith("200")) { + + var bin: JSONObject? = jsonObject.get("BIN") as JSONObject + var issuer: JSONObject? = bin?.get("issuer") as JSONObject + var bankName = issuer?.get("name").toString() + var brand = bin.get("brand").toString() + var currency = bin.get("currency").toString() + + return bankName + "|" + brand + "|" + currency + } else { + + return "||" + } + + } + + suspend fun updateCard(card: Card) { + + val newCount = card.count + 1 + val newCard = Card( + card.id, newCount, card.cardName, + card.cardNumber, card.cardSecurity, card.cardExpiry, + card.bankName, card.cardType, card.userName, card.currency + ) + + cardsRepository.updateItem(newCard) + } +} + +data class CardsList(val cardList: List = listOf()) + + +// fun cardDataList(userName: String): StateFlow = +// cardsRepository.getUserCard(userName).map { CardsList(it) }.stateIn( +// scope = viewModelScope, +// started = SharingStarted.WhileSubscribed(5_000L), +// initialValue = CardsList() +// ) \ No newline at end of file diff --git a/app/src/main/java/ica/tees/mywallet/model/HomeModel.kt b/app/src/main/java/ica/tees/mywallet/model/HomeModel.kt new file mode 100644 index 0000000..c06eb43 --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/model/HomeModel.kt @@ -0,0 +1,35 @@ +package ica.tees.mywallet.model + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import ica.tees.mywallet.data.Card +import ica.tees.mywallet.data.CardRepository +import ica.tees.mywallet.data.Password +import ica.tees.mywallet.data.PasswordRepository +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +class HomeModel( + private val cardRepository: CardRepository, + private val passwordRepository: PasswordRepository +) : ViewModel() { + + fun cardDataList(userName: String): StateFlow = + cardRepository.getTopCard(userName).map { CardsListHome(it) }.stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000L), + initialValue = CardsListHome() + ) + + fun passwordDataList(userName: String): StateFlow = + passwordRepository.getTopPassword(userName).map { PasswordListHome(it) }.stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000L), + initialValue = PasswordListHome() + ) +} + +data class PasswordListHome(val passwordList: List = listOf()) +data class CardsListHome(val cardsList: List = listOf()) \ No newline at end of file diff --git a/app/src/main/java/ica/tees/mywallet/model/LoginModel.kt b/app/src/main/java/ica/tees/mywallet/model/LoginModel.kt new file mode 100644 index 0000000..83b03dd --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/model/LoginModel.kt @@ -0,0 +1,117 @@ +package ica.tees.mywallet.model + +import android.util.Log +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.google.firebase.auth.FirebaseAuth +import ica.tees.mywallet.data.User +import ica.tees.mywallet.data.UserRepository +import ica.tees.mywallet.data.Validator +import ica.tees.mywallet.event.LoginEvent +import ica.tees.mywallet.state.LoginUIState +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +class LoginModel( + private val userRepository: UserRepository, + private val auth: FirebaseAuth +) : ViewModel() { + + // var loginStatus: MutableLiveData = MutableLiveData() + val loginUIState = mutableStateOf(LoginUIState()) + var allValidationsPassed = mutableStateOf(false) + var loginInProgress = mutableStateOf(false) + var loginStatus by mutableStateOf(false) + var failureMessage = mutableStateOf("") + + var userList: StateFlow = MutableStateFlow(UserList()) + + fun onEvent(event: LoginEvent) { + when (event) { + is LoginEvent.EmailChanged -> { + loginUIState.value = loginUIState.value.copy( + email = event.email + ) + } + + is LoginEvent.PasswordChanged -> { + loginUIState.value = loginUIState.value.copy( + password = event.password + ) + } + + is LoginEvent.LoginButtonClicked -> { + login(email = loginUIState.value.email, password = loginUIState.value.password) + } + } + validateLoginUIDataWithRules() + } + + + private fun validateLoginUIDataWithRules() { + val emailResult = Validator.validateEmail( + email = loginUIState.value.email + ) + + val passwordResult = Validator.validatePassword( + password = loginUIState.value.password + ) + + loginUIState.value = loginUIState.value.copy( + emailError = emailResult.status, passwordError = passwordResult.status + ) + + allValidationsPassed.value = emailResult.status && passwordResult.status + } + + private fun login(email: String, password: String) { + + loginInProgress.value = true + + auth + .signInWithEmailAndPassword(email, password) + .addOnSuccessListener { + loginStatus = true + loginInProgress.value = false + failureMessage.value = "" + Log.d("LoginModel - Success", loginStatus.toString()) + loginStatus = true + } + .addOnFailureListener { + + loginStatus = false + loginInProgress.value = false + failureMessage.value = it.localizedMessage ?: "No Error" + Log.d("LoginModel - Failure", loginStatus.toString()) + } +// .addOnCompleteListener { +// loginInProgress.value = false +// +// +// if (it.isSuccessful) { +// loginStatus.value = true +// failureMessage.value = "" +// } else { +// loginStatus.value = false +// failureMessage.value = it.exception?.localizedMessage ?: "No Error" +// +// } +// } + + userList = userRepository.getUserByName(loginUIState.value.email).map { UserList(it) } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000L), + initialValue = UserList() + ) + } + +} + +data class UserList(val userList: List = listOf()) \ No newline at end of file diff --git a/app/src/main/java/ica/tees/mywallet/model/PasswordModel.kt b/app/src/main/java/ica/tees/mywallet/model/PasswordModel.kt new file mode 100644 index 0000000..718311b --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/model/PasswordModel.kt @@ -0,0 +1,116 @@ +package ica.tees.mywallet.model + +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import ica.tees.mywallet.data.Card +import ica.tees.mywallet.data.Password +import ica.tees.mywallet.data.PasswordRepository +import ica.tees.mywallet.data.Validator +import ica.tees.mywallet.event.PasswordEvent +import ica.tees.mywallet.state.CardUIState +import ica.tees.mywallet.state.PasswordUIState +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import java.util.UUID + +class PasswordModel(private val passwordRepository: PasswordRepository) : ViewModel() { + + var passwordUIState = MutableStateFlow(PasswordUIState()) + var _uiState: StateFlow = passwordUIState.asStateFlow() + + var allValidationsPassed = mutableStateOf(false) + val passwordDataList: StateFlow = + passwordRepository.getUserPassword().map { PasswordList(it) }.stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000L), + initialValue = PasswordList() + ) + + suspend fun onEvent(event: PasswordEvent) { + + when (event) { + + is PasswordEvent.PasswordChanged -> { + passwordUIState.value = passwordUIState.value.copy( + password = event.password + ) + } + + is PasswordEvent.AccountNameChanged -> { + passwordUIState.value = passwordUIState.value.copy( + accountName = event.accountName + ) + } + + is PasswordEvent.EmailChanged -> { + + passwordUIState.value = passwordUIState.value.copy( + email = event.email + ) + } + + is PasswordEvent.PasswordButtonClicked -> { + addPassword() + } + } + validateLoginUIDataWithRules() + + } + + private fun validateLoginUIDataWithRules() { + val emailResult = Validator.validateEmail( + email = passwordUIState.value.email + ) + + val passwordResult = Validator.validatePassword( + password = passwordUIState.value.password + ) + + val accountNameResult = Validator.validateName( + name = passwordUIState.value.accountName + ) + + passwordUIState.value = passwordUIState.value.copy( + emailError = emailResult.status, + passwordError = passwordResult.status, + accountError = accountNameResult.status + ) + + allValidationsPassed.value = + emailResult.status && passwordResult.status && accountNameResult.status + } + + private suspend fun addPassword() { + + val uuid = UUID.randomUUID() + passwordRepository.insertItem( + passwords = Password( + uuid, + 0, + passwordUIState.value.accountName, + passwordUIState.value.email, + passwordUIState.value.password, + passwordUIState.value.userName + ) + ) + } + + suspend fun updateCard(password: Password) { + + val newCount = password.count + 1 + val newPassword = Password( + password.id, newCount, password.accountName, + password.email, password.password, password.userName + ) + + passwordRepository.updateItem(newPassword) + } +} + + +data class PasswordList(val passwordList: List = listOf()) \ No newline at end of file diff --git a/app/src/main/java/ica/tees/mywallet/model/SignUpModel.kt b/app/src/main/java/ica/tees/mywallet/model/SignUpModel.kt new file mode 100644 index 0000000..203f86c --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/model/SignUpModel.kt @@ -0,0 +1,138 @@ +package ica.tees.mywallet.model + +import android.util.Log +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.google.firebase.Firebase +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.firestore.FirebaseFirestore +import com.google.firebase.firestore.firestore +import ica.tees.mywallet.data.User +import ica.tees.mywallet.data.UserRepository +import ica.tees.mywallet.data.Validator +import ica.tees.mywallet.event.SignUpEvent +import ica.tees.mywallet.state.SignUpUIState +import kotlinx.coroutines.launch +import java.util.UUID + +class SignUpModel( + private val userRepository: UserRepository, + private val db: FirebaseFirestore, +) : ViewModel() { + + val adb = Firebase.firestore + var signUpUIState = mutableStateOf(SignUpUIState()) + var signUpStatus = mutableStateOf(false) + var allValidationsPassed = mutableStateOf(false) + var failureMessage = mutableStateOf("") + + suspend fun onEvent(event: SignUpEvent) { + + when (event) { + + is SignUpEvent.EmailChanged -> { + signUpUIState.value = signUpUIState.value.copy( + email = event.email + ) + } + + is SignUpEvent.FirstNameChanged -> { + signUpUIState.value = signUpUIState.value.copy( + firstName = event.firstName + ) + } + + is SignUpEvent.LastNameChanged -> { + signUpUIState.value = signUpUIState.value.copy( + lastName = event.lastName + ) + } + + is SignUpEvent.PasswordChanged -> { + signUpUIState.value = signUpUIState.value.copy( + password = event.password + ) + } + + SignUpEvent.SignUpButtonClicked -> { + createUser() + } + } + validateLoginUIDataWithRules() + } + + private fun validateLoginUIDataWithRules() { + val emailResult = Validator.validateEmail( + email = signUpUIState.value.email + ) + + val passwordResult = Validator.validatePassword( + password = signUpUIState.value.password + ) + + val firstNameResult = Validator.validateFirstName( + fName = signUpUIState.value.firstName + ) + + val lastNameResult = Validator.validateLastName( + lName = signUpUIState.value.lastName + ) + + signUpUIState.value = signUpUIState.value.copy( + emailError = emailResult.status, + passwordError = passwordResult.status, + firstNameError = firstNameResult.status, + lastNameError = lastNameResult.status, + ) + + allValidationsPassed.value = + emailResult.status && passwordResult.status && firstNameResult.status && lastNameResult.status + } + + private suspend fun createUser() { + + val email = signUpUIState.value.email + val password = signUpUIState.value.password + + FirebaseAuth + .getInstance() + .createUserWithEmailAndPassword(email, password) + .addOnCompleteListener { it -> + + if (it.isSuccessful) { + signUpStatus.value = true + //viewModelScope.launch { + if (signUpStatus.value) { + + //adb.collection() + adb.collection("myWallet/users") + .add(hashMapOf( + "id" to UUID.randomUUID().toString(), + "firstName" to signUpUIState.value.firstName, + "lastName" to signUpUIState.value.lastName, + "email" to signUpUIState.value.email + )) + + .addOnSuccessListener { + Log.d("SIGNUP",it.id) + } + .addOnFailureListener{ + it -> + it.localizedMessage?.let { it1 -> Log.d("SIGNUP", it1) } + } +// userRepository.insertItem( +// user = User( +// UUID.randomUUID(), +// signUpUIState.value.firstName, +// signUpUIState.value.lastName, +// signUpUIState.value.email +// ) +// ) + // } + } + } + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/ica/tees/mywallet/screen/AddCardScreen.kt b/app/src/main/java/ica/tees/mywallet/screen/AddCardScreen.kt new file mode 100644 index 0000000..88834e5 --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/screen/AddCardScreen.kt @@ -0,0 +1,226 @@ +package ica.tees.mywallet.screen + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.CurrencyExchange +import androidx.compose.material.icons.filled.DateRange +import androidx.compose.material.icons.filled.Face +import androidx.compose.material.icons.filled.Money +import androidx.compose.material.icons.filled.Numbers +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardCapitalization +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavHostController +import androidx.navigation.compose.rememberNavController +import ica.tees.mywallet.R +import ica.tees.mywallet.components.AppViewModelProvider +import ica.tees.mywallet.components.TextFieldComponent +import ica.tees.mywallet.data.FieldType +import ica.tees.mywallet.data.InputTransaformation +import ica.tees.mywallet.event.CardEvent +import ica.tees.mywallet.model.CardModel +import kotlinx.coroutines.launch + +@Composable +fun AddCardScreen( + modifier: Modifier, + navController: NavHostController, + theName: String, + cardModel: CardModel = viewModel(factory = AppViewModelProvider.Factory) +) { + + val coroutineScope = rememberCoroutineScope() + var bankName by remember { mutableStateOf("") } + var nameOnCard by remember { mutableStateOf("") } + var cardNumber by remember { mutableStateOf("") } + var expiryDate by remember { mutableStateOf("") } + var cvv by remember { mutableStateOf("") } + var currency by remember { mutableStateOf("") } + + Column( + modifier = modifier + .fillMaxSize() + .padding(20.dp), + ) { + + Spacer(modifier = Modifier.height(50.dp)) + Text( + text = "Kindly Enter Card Detail", + fontSize = 20.sp, + modifier = modifier + .fillMaxWidth() + ) + Spacer(modifier = modifier.height(10.dp)) + Column( + modifier = modifier + .fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + TextFieldComponent( + value = cardNumber, + visualTransformation = InputTransaformation(FieldType.CARD_NUMBER), + onValueChanged = { + coroutineScope.launch { + cardModel.onEvent(CardEvent.CardNumberChanged(it)) + cardNumber = it + if (cardNumber.length == 6) { + val returnVal: String? = cardModel.validateCard() + val bankNameNull: String? = returnVal?.split("|")?.get(0) + val currencyNull: String? = returnVal?.split("|")?.get(2) + val brandNull: String? = returnVal?.split("|")?.get(1) + if (bankNameNull != null) { + cardModel.onEvent(CardEvent.BankNameChanged(bankNameNull)) + bankName = bankNameNull + } + if (currencyNull != null) { + cardModel.onEvent(CardEvent.CurrencyChanged(currencyNull)) + currency = currencyNull + } + if (brandNull != null) { + cardModel.onEvent(CardEvent.CardTypeChanged(brandNull)) + } + } + } + }, + label = R.string.card_number, + leadingIcon = Icons.Default.Numbers, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Number, + imeAction = if (cardNumber.length == 16) ImeAction.Next else ImeAction.None + ), + ) + Spacer(modifier = modifier.height(10.dp)) + TextFieldComponent( + value = bankName, + onValueChanged = { + coroutineScope.launch { + cardModel.onEvent(CardEvent.BankNameChanged(it)) + bankName = it + } + }, + label = R.string.bank_name, + leadingIcon = Icons.Default.Money, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Text, + imeAction = if (cardNumber.length >= 3) ImeAction.Next else ImeAction.None + ), + ) + Spacer(modifier = modifier.height(10.dp)) + TextFieldComponent( + value = currency, + onValueChanged = { + coroutineScope.launch { + cardModel.onEvent(CardEvent.CurrencyChanged(it)) + currency = it + } + }, + label = R.string.currency, + leadingIcon = Icons.Default.CurrencyExchange, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Text, + capitalization = KeyboardCapitalization.Characters, + imeAction = if (cardNumber.length >= 3) ImeAction.Next else ImeAction.None + ), + ) + Spacer(modifier = modifier.height(10.dp)) + TextFieldComponent( + value = nameOnCard, + onValueChanged = { + coroutineScope.launch { + cardModel.onEvent(CardEvent.CardNameChanged(it)) + nameOnCard = it + } + }, + label = R.string.name_on_card, + leadingIcon = Icons.Default.Face, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Text, + capitalization = KeyboardCapitalization.Characters, + imeAction = if (cardNumber.length >= 3) ImeAction.Next else ImeAction.None + ), + + ) + Spacer(modifier = modifier.height(10.dp)) + TextFieldComponent( + value = expiryDate, + visualTransformation = InputTransaformation(FieldType.EXPIRATION), + onValueChanged = { + coroutineScope.launch { + cardModel.onEvent(CardEvent.CardExpiryChanged(it)) + expiryDate = it + } + }, + label = R.string.expiry_date, + leadingIcon = Icons.Default.DateRange, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Number, + imeAction = if (expiryDate.length == 4) ImeAction.Next else ImeAction.None + ) + ) + Spacer(modifier = modifier.height(10.dp)) + TextFieldComponent( + value = cvv, + onValueChanged = { + coroutineScope.launch { + cardModel.onEvent(CardEvent.CardSecurity(it)) + cvv = it + } + }, + label = R.string.cvv, + leadingIcon = Icons.Default.Numbers, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Number, + imeAction = if (cvv.length == 3) ImeAction.Done else ImeAction.None + ), + ) + Spacer(modifier = modifier.height(50.dp)) + Button( + onClick = { + coroutineScope.launch { + cardModel._uiState.value.userName = theName + cardModel.onEvent(CardEvent.CardButtonClicked) + navController.navigateUp() + } + }, + enabled = cardModel.allValidationPassed.value, + modifier = modifier + .fillMaxWidth() + .padding(start = 20.dp, end = 20.dp) + ) /**/{ + Text(text = stringResource(R.string.add_card)) + } + } + } +} + + +@Preview +@Composable +fun AddCardScreenPreview() { + AddCardScreen( + modifier = Modifier, + navController = rememberNavController(), + "" + ) +} \ No newline at end of file diff --git a/app/src/main/java/ica/tees/mywallet/screen/AddPasswordScreen.kt b/app/src/main/java/ica/tees/mywallet/screen/AddPasswordScreen.kt new file mode 100644 index 0000000..b261282 --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/screen/AddPasswordScreen.kt @@ -0,0 +1,162 @@ +package ica.tees.mywallet.screen + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AlternateEmail +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.Email +import androidx.compose.material.icons.filled.Face +import androidx.compose.material.icons.filled.Password +import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavHostController +import androidx.navigation.compose.rememberNavController +import ica.tees.mywallet.R +import ica.tees.mywallet.components.AppViewModelProvider +import ica.tees.mywallet.components.PasswordFieldComponent +import ica.tees.mywallet.components.TextFieldComponent +import ica.tees.mywallet.event.LoginEvent +import ica.tees.mywallet.event.PasswordEvent +import ica.tees.mywallet.model.CardModel +import ica.tees.mywallet.model.PasswordModel +import kotlinx.coroutines.launch + +class AddPassword { +} + +@Composable +fun AddPasswordScreen( + modifier: Modifier, + navController: NavHostController, + theName: String, + passwordModel: PasswordModel = viewModel(factory = AppViewModelProvider.Factory) +) { + + val coroutineScope = rememberCoroutineScope() + var accountName by remember { mutableStateOf("") } + var accountLogin by remember { mutableStateOf("") } + var accountPassword by remember { mutableStateOf("") } + + Column( + modifier = modifier + .fillMaxSize() + .padding(20.dp), + ) { + + Spacer(modifier = modifier.height(50.dp)) + Text( + text = "Kindly Enter Account Detail", + fontSize = 20.sp, + modifier = modifier + .fillMaxWidth() + + ) + Spacer(modifier = modifier.height(10.dp)) + Column( + modifier = modifier + .fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + + TextFieldComponent( + value = accountName, + onValueChanged = { + coroutineScope.launch { + passwordModel.onEvent(PasswordEvent.AccountNameChanged(it)) + accountName = it + } + }, + label = R.string.account_name, + leadingIcon = Icons.Default.Face, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Email, + imeAction = ImeAction.Next + ) + ) + Spacer(modifier = modifier.height(10.dp)) + TextFieldComponent( + value = accountLogin, + onValueChanged = { + coroutineScope.launch { + passwordModel.onEvent(PasswordEvent.EmailChanged(it)) + accountLogin = it + } + }, + label = R.string.enter_your_email, + leadingIcon = Icons.Default.AlternateEmail, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Email, + imeAction = ImeAction.Next + ) + ) + Spacer(modifier = modifier.height(10.dp)) + PasswordFieldComponent( + value = accountPassword, + onValueChanged = { + coroutineScope.launch { + passwordModel.onEvent(PasswordEvent.PasswordChanged(it)) + accountPassword = it + } + }, + label = R.string.account_password, + leadingIcon = Icons.Default.Password, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Password, + imeAction = ImeAction.Done + ) + ) + Spacer(modifier = modifier.height(50.dp)) + Button( + onClick = { + coroutineScope.launch { + passwordModel._uiState.value.userName = theName + passwordModel.onEvent(PasswordEvent.PasswordButtonClicked) + navController.navigateUp() + } + }, + enabled = passwordModel.allValidationsPassed.value, + modifier = modifier + .fillMaxWidth() + .padding(start = 20.dp, end = 20.dp) + ) /**/{ + Text(text = stringResource(R.string.add_password)) + } + } + } + +} + +@Preview +@Composable +private fun AppPasswordScreenPreview() { + var navController = rememberNavController() + + AddPasswordScreen(Modifier, navController,"") +} \ No newline at end of file diff --git a/app/src/main/java/ica/tees/mywallet/screen/CardsScreen.kt b/app/src/main/java/ica/tees/mywallet/screen/CardsScreen.kt new file mode 100644 index 0000000..f83f3a5 --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/screen/CardsScreen.kt @@ -0,0 +1,222 @@ +package ica.tees.mywallet.screen + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.selection.selectable +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ElevatedButton +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavHostController +import androidx.navigation.compose.rememberNavController +import ica.tees.mywallet.components.AppViewModelProvider +import ica.tees.mywallet.components.DisplayDialogCard +import ica.tees.mywallet.components.MyWalletScreen +import ica.tees.mywallet.data.Card +import ica.tees.mywallet.model.CardModel +import ica.tees.mywallet.model.LoginModel +import ica.tees.mywallet.state.CardUIState +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch + +@Composable +fun CardsScreen( + modifier: Modifier, + navController: NavHostController, + theName: String, + cardModel: CardModel = viewModel(factory = AppViewModelProvider.Factory), +) { + + val uiState by cardModel._uiState.collectAsState() + uiState.userName = theName + val cardList by cardModel.cardDataList.collectAsState() + + val state = rememberLazyListState() + var showDialog by remember { mutableStateOf(false) } + var selectedIndex by remember { mutableIntStateOf(0) } + + Column( + modifier = modifier + .padding(start = 20.dp) + .fillMaxSize() + ) { + Spacer(modifier = modifier.height(50.dp)) + DisplayDialogCard( + showDialog = showDialog, + modifier = modifier, + onDismissRequest = { showDialog = !showDialog }, + selectedIndex = selectedIndex, + cardsLists = cardList.cardList + ) + CardList( + modifier = modifier, + state = state, + onClick = { showDialogClicked: Boolean, selectedIndexClicked: Int -> + showDialog = showDialogClicked + selectedIndex = selectedIndexClicked + }, + cardsLists = cardList.cardList, + theName = theName, + cardModel = cardModel + ) + + } + Row( + verticalAlignment = Alignment.Bottom, + horizontalArrangement = Arrangement.End, + modifier = modifier + .fillMaxSize() + .padding(bottom = 10.dp,end = 10.dp) + + ) { + + ElevatedButton( + onClick = { + navController.navigate(route = MyWalletScreen.AddCards.name) + }, + shape = CircleShape, + modifier = modifier + .size(70.dp) + ) { + Icon( + Icons.Filled.Add, + contentDescription = null, + modifier = modifier.size(ButtonDefaults.IconSize) + ) + } + } +} + +@Composable +fun CardList( + modifier: Modifier, + onClick: (Boolean, Int) -> Unit, + state: LazyListState, + cardsLists: List, + theName: String, + cardModel: CardModel +) { + + val coroutineScope = rememberCoroutineScope() + var selectedValue by remember { mutableIntStateOf(0) } + val isSelected by remember { mutableStateOf(true) } + + if (cardsLists.isNotEmpty()) { + LazyColumn( + state = state, + contentPadding = PaddingValues(top = 10.dp, bottom = 10.dp), + verticalArrangement = Arrangement.spacedBy(10.dp), + + ) { + itemsIndexed(cardsLists) { index, item -> + if (item.userName == theName) { + Row( + modifier = Modifier + + ) { + Column { + ElevatedCard( + modifier = modifier + .size(width = 120.dp, height = 80.dp) + .selectable( + selected = selectedValue == index, + enabled = true, + onClick = { + coroutineScope.launch { + selectedValue = index + onClick(isSelected, index) + cardModel.updateCard(item) + } + }) + .clickable(enabled = true, onClick = { + coroutineScope.launch { + selectedValue = index + onClick(isSelected, index) + cardModel.updateCard(item) + } + }), + elevation = CardDefaults.cardElevation( + defaultElevation = 15.dp + ), + ) { + Image( + painter = painterResource(item.cardType), + contentDescription = null, + modifier = modifier + .fillMaxWidth() + .size(width = 80.dp, height = 40.dp) + .padding(end = 10.dp, top = 5.dp) + .wrapContentWidth(Alignment.End) + ) + } + } + Spacer(modifier = Modifier.width(20.dp)) + Column { + Text( + text = item.bankName, + fontWeight = FontWeight.Bold, + fontSize = 20.sp, + modifier = modifier + ) + Text( + text = item.cardNumber.replaceRange(0, 12, "**** **** **** ") + ) + } + } + } + } + } + } else { + + Text(text = "No Cards Added") + } +} + +@Preview +@Composable +fun CardsScreenPreview() { + + val navController = rememberNavController() + CardsScreen( + modifier = Modifier, navController, "" + ) +} diff --git a/app/src/main/java/ica/tees/mywallet/screen/HomeScreen.kt b/app/src/main/java/ica/tees/mywallet/screen/HomeScreen.kt new file mode 100644 index 0000000..0229744 --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/screen/HomeScreen.kt @@ -0,0 +1,183 @@ +package ica.tees.mywallet.screen + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.requiredHeight +import androidx.compose.foundation.layout.requiredWidth +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Divider +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import ica.tees.mywallet.components.AppViewModelProvider +import ica.tees.mywallet.data.Card +import ica.tees.mywallet.data.Password +import ica.tees.mywallet.model.HomeModel + + +@Composable +fun HomeScreen( + modifier: Modifier, + theName: String, + homeModel: HomeModel = viewModel(factory = AppViewModelProvider.Factory) +) { + + val cardList by homeModel.cardDataList(theName).collectAsState() + val passwordList by homeModel.passwordDataList(theName).collectAsState() + + Column( + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.CenterHorizontally, + modifier = modifier + .fillMaxSize() + ) { + Spacer(modifier = modifier.height(50.dp)) + BoxWithConstraints { + CardViewHomePage( + modifier = modifier, + card = cardList.cardsList + ) + + } + Spacer(modifier.height(80.dp)) + BoxWithConstraints { + PasswordViewHomePage( + modifier = modifier, + passwords = passwordList.passwordList + ) + } + + + } +} + +@Composable +fun CardViewHomePage( + modifier: Modifier, + card: List +) { + + var showDialog by remember { mutableStateOf(false) } + + if (card.isNotEmpty()) { + ElevatedCard( + content = { + Row( + modifier = modifier + .padding(20.dp) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = card[0].cardNumber, + fontSize = 20.sp + ) + Image( + painter = painterResource(card[0].cardType), + contentDescription = null, + modifier = modifier + .fillMaxWidth() + .wrapContentWidth(Alignment.End) + .requiredWidth(80.dp) + .requiredHeight(40.dp) + ) + } + Text( + text = card[0].cardName, + fontSize = 25.sp, + modifier = modifier + .padding(start = 20.dp) + ) + Row( + modifier = modifier + .padding(start = 20.dp) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = card[0].cardExpiry, + fontSize = 20.sp + ) + Text( + text = card[0].cardSecurity, + fontSize = 20.sp, + modifier = modifier + .fillMaxWidth() + .padding(end = 20.dp) + .wrapContentWidth(Alignment.End) + ) + } + }, + modifier = modifier + .requiredWidth(width = 340.dp) + .requiredHeight(height = 170.dp) + .clip(shape = RoundedCornerShape(20.dp)) + + ) + } else { + + Text(text = "No Cards") + } +} + + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun PasswordViewHomePage( + modifier: Modifier, + passwords: List +) { + + if (passwords.isNotEmpty()) { + LazyColumn( + state = rememberLazyListState(), + //verticalArrangement = Arrangement.SpaceBetween, + modifier = modifier + .padding(20.dp) + .fillMaxSize(), + ) { + items(passwords) { passwords -> + Text(passwords.accountName) + Text(passwords.email) + Divider(thickness = 1.dp) + } + } + } else { + Text(text = "No Passwords") + } +} + + +@Preview(widthDp = 360, heightDp = 800) +@Composable +private fun HomePagePreview() { + + // HomeScreen(Modifier,"",) +} \ No newline at end of file diff --git a/app/src/main/java/ica/tees/mywallet/screen/LoginScreen.kt b/app/src/main/java/ica/tees/mywallet/screen/LoginScreen.kt new file mode 100644 index 0000000..47f01bf --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/screen/LoginScreen.kt @@ -0,0 +1,152 @@ +package ica.tees.mywallet.screen + +import android.util.Log +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AlternateEmail +import androidx.compose.material.icons.filled.Password +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController +import ica.tees.mywallet.R +import ica.tees.mywallet.components.AppViewModelProvider +import ica.tees.mywallet.components.ButtonComponent +import ica.tees.mywallet.components.ClickableLoginTextComponent +import ica.tees.mywallet.components.MyWalletScreen +import ica.tees.mywallet.components.PasswordFieldComponent +import ica.tees.mywallet.components.TextFieldComponent +import ica.tees.mywallet.data.User +import ica.tees.mywallet.event.LoginEvent +import ica.tees.mywallet.model.LoginModel + +@Composable +fun LoginForm( + modifier: Modifier, + onButtonClicked: (List) -> Unit, + navController: NavController, + loginModel: LoginModel = viewModel(factory = AppViewModelProvider.Factory) +) { + var username by remember { mutableStateOf("") } + var password by remember { mutableStateOf("") } + + val userList by loginModel.userList.collectAsState() + + Column( + modifier = Modifier + .fillMaxSize() + .padding(20.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + + ) { + Text( + text = stringResource(R.string.login_to_my_wallet), + fontWeight = FontWeight.Bold, + fontSize = 30.sp + ) + Spacer(modifier = modifier.height(30.dp)) + TextFieldComponent( + value = username, + onValueChanged = { + loginModel.onEvent(LoginEvent.EmailChanged(it)) + username = it + }, + label = R.string.enter_your_email, + leadingIcon = Icons.Default.AlternateEmail, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Email, + imeAction = ImeAction.Next + ) + ) + Spacer(modifier = modifier.height(15.dp)) + PasswordFieldComponent( + value = password, + onValueChanged = { + loginModel.onEvent(LoginEvent.PasswordChanged(it)) + password = it + }, + label = R.string.enter_your_password, + leadingIcon = Icons.Filled.Password, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Password, + imeAction = ImeAction.Done + ) + ) + Spacer(modifier = modifier.height(15.dp)) + + ButtonComponent( + onButtonClicked = { + + loginModel.onEvent(LoginEvent.LoginButtonClicked) + Log.d("LoginScreen",loginModel.loginStatus.toString()) + if (loginModel.loginStatus) { + onButtonClicked(userList.userList) + navController.navigate(route = MyWalletScreen.Home.name) + } + }, + modifier = modifier, + enabled = loginModel.allValidationsPassed.value, + buttonText = R.string.login + ) + Spacer(modifier = modifier.height(30.dp)) + Text( + text = loginModel.failureMessage.value, + color = Color.Red, + fontWeight = FontWeight.SemiBold, + modifier = modifier + + ) + ClickableLoginTextComponent( + tryingToLogin = false, onTextSelected = { + navController.navigate(route = MyWalletScreen.SignUp.name) + }) + + if (loginModel.loginInProgress.value) { + CircularProgressIndicator() + } + } + +// SystemBackButtonHandler { +// navController.navigate(route = MyWalletScreen.Home.name) +// } +} + + +@Preview +@Composable +private fun LoginFormPreview() { + + LoginForm( + modifier = Modifier, + onButtonClicked = { users: List -> }, + rememberNavController(), + ) +} \ No newline at end of file diff --git a/app/src/main/java/ica/tees/mywallet/screen/MainScreen.kt b/app/src/main/java/ica/tees/mywallet/screen/MainScreen.kt new file mode 100644 index 0000000..c390af7 --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/screen/MainScreen.kt @@ -0,0 +1,113 @@ +package ica.tees.mywallet.screen + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Icon +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Tab +import androidx.compose.material3.TabRow +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavHostController +import androidx.navigation.compose.rememberNavController +import ica.tees.mywallet.data.DataSource.tabItem +import ica.tees.mywallet.model.LoginModel + +@Composable +fun MainScreen( + modifier: Modifier, + navController: NavHostController, + theName: String +) { + + var selectedIndex by rememberSaveable { mutableIntStateOf(0) } + + Scaffold( + bottomBar = { + TabRow( + modifier = modifier + .padding(top = 10.dp), + selectedTabIndex = selectedIndex + ) { + tabItem.forEachIndexed { index, item -> + Tab( + selected = index == selectedIndex, + onClick = { + selectedIndex = index + }, + text = { + Text(text = item.title) + }, + icon = { + Icon(painter = painterResource(item.selectedIcon), contentDescription = item.title) + }, + ) + } + } + + } + ) { innerPadding -> + Box( + contentAlignment = Alignment.Center, + modifier = modifier + .fillMaxSize() + .padding(innerPadding) + ) { + when (selectedIndex) { + 0 -> HomeScreen( + modifier = modifier, + theName = theName, + + ) + + 1 -> CardsScreen( + modifier = Modifier, + navController = navController, + theName = theName + ) + + 2 -> PasswordScreen( + modifier = modifier, + navController = navController, + theName = theName, + ) + } + } + } + +} + + +@Preview +@Composable +fun BottomNavPreview() { + val navController = rememberNavController() + MainScreen(modifier = Modifier, rememberNavController(), "") +} + + +// LaunchedEffect(indexSelected) { +// pagerState.animateScrollToPage(indexSelected) +// } +// LaunchedEffect(pagerState.currentPage, pagerState.isScrollInProgress) { +// if (!pagerState.isScrollInProgress) { +// indexSelected = pagerState.currentPage +// } +// } + +/**/ +// val pagerState = rememberPagerState { +// tabItem.size +// } \ No newline at end of file diff --git a/app/src/main/java/ica/tees/mywallet/screen/PasswordScreen.kt b/app/src/main/java/ica/tees/mywallet/screen/PasswordScreen.kt new file mode 100644 index 0000000..0e744e6 --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/screen/PasswordScreen.kt @@ -0,0 +1,186 @@ +package ica.tees.mywallet.screen + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.selection.selectable +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Divider +import androidx.compose.material3.ElevatedButton +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavHostController +import androidx.navigation.compose.rememberNavController +import ica.tees.mywallet.components.AppViewModelProvider +import ica.tees.mywallet.components.DisplayDialogPassword +import ica.tees.mywallet.components.MyWalletScreen +import ica.tees.mywallet.data.Password +import ica.tees.mywallet.model.PasswordModel +import kotlinx.coroutines.launch + +@Composable +fun PasswordScreen( + modifier: Modifier, + navController: NavHostController, + theName: String, + passwordModel: PasswordModel = viewModel(factory = AppViewModelProvider.Factory) +) { + + val uiState by passwordModel._uiState.collectAsState() + uiState.userName = theName + val passwordList by passwordModel.passwordDataList.collectAsState() + + val state = rememberLazyListState() + var showDialog by remember { mutableStateOf(false) } + var selectedIndex by remember { mutableIntStateOf(0) } + + Column( + modifier = modifier + .padding(start = 20.dp) + .fillMaxSize(), + ) { + Spacer(modifier = Modifier.height(50.dp)) + PasswordList( + modifier = modifier, + onClick = { showDialogClicked: Boolean, selectedIndexClicked: Int -> + showDialog = showDialogClicked + selectedIndex = selectedIndexClicked + }, + state = state, + accountList = passwordList.passwordList, + theName = theName, + passwordModel = passwordModel + ) + DisplayDialogPassword( + showDialog = showDialog, + modifier = modifier, + onDismissRequest = { showDialog = !showDialog }, + selectedIndex = selectedIndex, + passwordLists = passwordList.passwordList + + ) + } + Row( + verticalAlignment = Alignment.Bottom, + horizontalArrangement = Arrangement.End, + modifier = modifier + .fillMaxSize() + .padding(bottom = 10.dp,end = 10.dp) + ) { + ElevatedButton( + onClick = { + navController.navigate(route = MyWalletScreen.AddPasswords.name) + }, + shape = CircleShape, + modifier = modifier + .size(70.dp) + ) { + Icon( + Icons.Filled.Add, + contentDescription = null, + modifier = modifier.size(ButtonDefaults.IconSize) + ) + } + } +} + +@Composable +fun PasswordList( + modifier: Modifier, + onClick: (Boolean, Int) -> Unit, + state: LazyListState, + accountList: List, + theName: String, + passwordModel: PasswordModel +) { + + val coroutineScope = rememberCoroutineScope() + var selectedValue by remember { mutableIntStateOf(0) } + val isSelected by remember { mutableStateOf(true) } + + if (accountList.isNotEmpty()) { + LazyColumn( + state = state, + contentPadding = PaddingValues(top = 10.dp, bottom = 10.dp), + verticalArrangement = Arrangement.spacedBy(10.dp), + ) { + itemsIndexed(accountList) { index, item -> + if (item.userName == theName) { + Column( + modifier = modifier + .selectable( + selected = selectedValue == index, + onClick = { + coroutineScope.launch { + selectedValue = index + onClick(isSelected,index) + passwordModel.updateCard(item) + } + } + ) + .clickable(enabled = true, onClick = { + coroutineScope.launch { + selectedValue = index + onClick(isSelected,index) + passwordModel.updateCard(item) + } + }) + ) { + Text( + item.accountName, + fontWeight = FontWeight.Bold + ) + SelectionContainer { + Column( + modifier = modifier + ) { + Text(text = item.email) + } + } + Divider(thickness = 2.dp) + Spacer(modifier = Modifier.height(5.dp)) + } + } + } + } + } else { + Text(text = "Not Password to Display") + } +} + + +@Preview +@Composable +private fun PasswordScreenPreview() { + PasswordScreen(modifier = Modifier, + navController = rememberNavController(),"") +} \ No newline at end of file diff --git a/app/src/main/java/ica/tees/mywallet/screen/SignUpScreen.kt b/app/src/main/java/ica/tees/mywallet/screen/SignUpScreen.kt new file mode 100644 index 0000000..5d07fa8 --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/screen/SignUpScreen.kt @@ -0,0 +1,174 @@ +package ica.tees.mywallet.screen + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AlternateEmail +import androidx.compose.material.icons.filled.Face +import androidx.compose.material.icons.filled.Password +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController +import ica.tees.mywallet.R +import ica.tees.mywallet.components.AppViewModelProvider +import ica.tees.mywallet.components.ButtonComponent +import ica.tees.mywallet.components.ClickableLoginTextComponent +import ica.tees.mywallet.components.MyWalletScreen +import ica.tees.mywallet.components.PasswordFieldComponent +import ica.tees.mywallet.components.TextFieldComponent +import ica.tees.mywallet.event.SignUpEvent +import ica.tees.mywallet.model.SignUpModel +import kotlinx.coroutines.launch + +class SignUpScreen { +} + +@Composable +fun SignupForm( + modifier: Modifier, + navController: NavController, + onButtonClicked: (Boolean) -> Unit = {}, + signUpModel: SignUpModel = viewModel(factory = AppViewModelProvider.Factory) +) { + + val coroutineScope = rememberCoroutineScope() + var firstName by remember { mutableStateOf("") } + var lastName by remember { mutableStateOf("") } + var email by remember { mutableStateOf("") } + var password by remember { mutableStateOf("") } + + Column( + modifier = modifier + .fillMaxSize() + .padding(20.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Text( + text = "Create Wallet Account", + fontWeight = FontWeight.Bold, + fontSize = 30.sp + ) + Spacer(modifier = modifier.height(30.dp)) + TextFieldComponent( + value = firstName, + onValueChanged = { + coroutineScope.launch { + signUpModel.onEvent(SignUpEvent.FirstNameChanged(it)) + firstName = it + } + }, + label = R.string.enter_your_firstname, + leadingIcon = Icons.Default.Face, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Email, + imeAction = ImeAction.Next + ) + ) + Spacer(modifier = modifier.height(15.dp)) + TextFieldComponent( + value = lastName, + onValueChanged = { + coroutineScope.launch { + signUpModel.onEvent(SignUpEvent.LastNameChanged(it)) + lastName = it + } + }, + label = R.string.enter_your_lastname, + leadingIcon = Icons.Default.Face, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Email, + imeAction = ImeAction.Next + ) + ) + Spacer(modifier = modifier.height(15.dp)) + TextFieldComponent( + value = email, + onValueChanged = { + coroutineScope.launch { + signUpModel.onEvent(SignUpEvent.EmailChanged(it)) + email = it + } + }, + label = R.string.enter_your_email, + leadingIcon = Icons.Default.AlternateEmail, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Email, + imeAction = ImeAction.Next + ) + ) + Spacer(modifier = modifier.height(15.dp)) + PasswordFieldComponent( + value = password, + onValueChanged = { + coroutineScope.launch { + signUpModel.onEvent(SignUpEvent.PasswordChanged(it)) + password = it + } + }, + label = R.string.enter_your_password, + leadingIcon = Icons.Filled.Password, + keyboardOptions = KeyboardOptions.Default.copy( + keyboardType = KeyboardType.Password, + imeAction = ImeAction.Done + ) + ) + Spacer(modifier = modifier.height(15.dp)) + ButtonComponent( + onButtonClicked = { + coroutineScope.launch { + signUpModel.onEvent(SignUpEvent.SignUpButtonClicked) + if (signUpModel.signUpStatus.value) { + onButtonClicked(true) + } else { + onButtonClicked(false) + } + } + }, + modifier = modifier, + enabled = signUpModel.allValidationsPassed.value, + buttonText = R.string.signup + ) + Spacer(modifier = modifier.height(15.dp)) + Text( + text = signUpModel.failureMessage.value, + color = Color.Red, + fontWeight = FontWeight.SemiBold, + modifier = modifier + + ) + Spacer(modifier = modifier.height(15.dp)) + ClickableLoginTextComponent( + tryingToLogin = true, onTextSelected = { + navController.navigate(route = MyWalletScreen.Login.name) + }) + } +} + + +@Preview(widthDp = 360, heightDp = 800) +@Composable +private fun SignupFormPreview() { + SignupForm(Modifier, rememberNavController()) +} \ No newline at end of file diff --git a/app/src/main/java/ica/tees/mywallet/state/CardUIState.kt b/app/src/main/java/ica/tees/mywallet/state/CardUIState.kt new file mode 100644 index 0000000..d66aaa5 --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/state/CardUIState.kt @@ -0,0 +1,18 @@ +package ica.tees.mywallet.state + +data class CardUIState( + + var bankName: String = "", + var cardNumber: String = "", + var cardName: String = "", + var cardSecurity: String = "", + var cardExpiry: String = "", + var cardType: String = "", + var userName: String = "", + var currency: String = "", + var bankNameError: Boolean = false, + var cardNumberError: Boolean = false, + var cardNameError: Boolean = false, + var cardSecurityError: Boolean = false, + var cardExpiryError: Boolean = false, +) diff --git a/app/src/main/java/ica/tees/mywallet/state/LoginUIState.kt b/app/src/main/java/ica/tees/mywallet/state/LoginUIState.kt new file mode 100644 index 0000000..e8a2fdc --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/state/LoginUIState.kt @@ -0,0 +1,11 @@ +package ica.tees.mywallet.state + +data class LoginUIState( + + var email: String = "", + var password: String = "", + var userName: String = "", + var emailError: Boolean = false, + var passwordError: Boolean = false + +) \ No newline at end of file diff --git a/app/src/main/java/ica/tees/mywallet/state/PasswordUIState.kt b/app/src/main/java/ica/tees/mywallet/state/PasswordUIState.kt new file mode 100644 index 0000000..4c18f40 --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/state/PasswordUIState.kt @@ -0,0 +1,13 @@ +package ica.tees.mywallet.state + +data class PasswordUIState ( + + var email :String = "", + var password :String = "", + var accountName :String = "", + var userName: String = "", + var accountError: Boolean = false, + var emailError :Boolean = false, + var passwordError : Boolean = false + +) diff --git a/app/src/main/java/ica/tees/mywallet/state/SignUpUIState.kt b/app/src/main/java/ica/tees/mywallet/state/SignUpUIState.kt new file mode 100644 index 0000000..6a17c51 --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/state/SignUpUIState.kt @@ -0,0 +1,14 @@ +package ica.tees.mywallet.state + +data class SignUpUIState ( + + var firstName: String = "", + var lastName: String = "", + var email: String = "", + var password: String = "", + var firstNameError: Boolean = false, + var lastNameError: Boolean = false, + var emailError: Boolean = false, + var passwordError: Boolean = false + +) \ No newline at end of file diff --git a/app/src/main/java/ica/tees/mywallet/ui/theme/Color.kt b/app/src/main/java/ica/tees/mywallet/ui/theme/Color.kt new file mode 100644 index 0000000..7387b97 --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/ui/theme/Color.kt @@ -0,0 +1,20 @@ +package ica.tees.mywallet.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val Purple40 = Color(0xFF6650a4) +val PurpleGrey40 = Color(0xFF625b71) +val Pink40 = Color(0xFF7D5260) + + +val Primary = Color(0xFF92A3FD) +val Secondary = Color(0xFF9DCEFF) +val TextColor = Color(0xFF1D1617) +val AccentColor = Color(0xFFC58BF2) +val GrayColor = Color(0xFF7B6F72) +val WhiteColor = Color(0xFFFFFFFF) +val BgColor = Color(0xFFF7F8F8) \ No newline at end of file diff --git a/app/src/main/java/ica/tees/mywallet/ui/theme/Theme.kt b/app/src/main/java/ica/tees/mywallet/ui/theme/Theme.kt new file mode 100644 index 0000000..fa731e2 --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/ui/theme/Theme.kt @@ -0,0 +1,92 @@ +package ica.tees.mywallet.ui.theme + +import android.app.Activity +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.core.view.WindowCompat + +private val DarkColorScheme = darkColorScheme( + primary = Primary, + tertiary = Primary, + secondary = Secondary +) + +private val LightColorScheme = lightColorScheme( + primary = Primary, + tertiary = Primary, + secondary = Secondary + + /* Other default colors to override + background = Color.White, + surface = Color.White, + onPrimary = Color.White, + onSecondary = Color.Black, + onBackground = Color.Black, + onSurface = Color.Black, + */ +) + + +//private val DarkColorScheme = darkColorScheme( +// primary = Purple80, +// secondary = PurpleGrey80, +// tertiary = Pink80 +//) +// +//private val LightColorScheme = lightColorScheme( +// primary = Purple40, +// secondary = PurpleGrey40, +// tertiary = Pink40 +// +// /* Other default colors to override +// background = Color(0xFFFFFBFE), +// surface = Color(0xFFFFFBFE), +// onPrimary = Color.White, +// onSecondary = Color.White, +// onTertiary = Color.White, +// onBackground = Color(0xFF1C1B1F), +// onSurface = Color(0xFF1C1B1F), +// */ +//) + +@Composable +fun MyWalletTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + val view = LocalView.current + if (!view.isInEditMode) { + SideEffect { + val window = (view.context as Activity).window + window.statusBarColor = colorScheme.primary.toArgb() + WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme + } + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} \ No newline at end of file diff --git a/app/src/main/java/ica/tees/mywallet/ui/theme/Type.kt b/app/src/main/java/ica/tees/mywallet/ui/theme/Type.kt new file mode 100644 index 0000000..18c5c6f --- /dev/null +++ b/app/src/main/java/ica/tees/mywallet/ui/theme/Type.kt @@ -0,0 +1,34 @@ +package ica.tees.mywallet.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + /* Other default text styles to override + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) + */ +) \ No newline at end of file diff --git a/app/src/main/res/drawable/card_icon.png b/app/src/main/res/drawable/card_icon.png new file mode 100644 index 0000000..cbb4950 Binary files /dev/null and b/app/src/main/res/drawable/card_icon.png differ diff --git a/app/src/main/res/drawable/eye_password.png b/app/src/main/res/drawable/eye_password.png new file mode 100644 index 0000000..6e24630 Binary files /dev/null and b/app/src/main/res/drawable/eye_password.png differ diff --git a/app/src/main/res/drawable/home.png b/app/src/main/res/drawable/home.png new file mode 100644 index 0000000..c2299bc Binary files /dev/null and b/app/src/main/res/drawable/home.png differ diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/logo_mastercard.png b/app/src/main/res/drawable/logo_mastercard.png new file mode 100644 index 0000000..5b8cc08 Binary files /dev/null and b/app/src/main/res/drawable/logo_mastercard.png differ diff --git a/app/src/main/res/drawable/logo_other.png b/app/src/main/res/drawable/logo_other.png new file mode 100644 index 0000000..8f64a5f Binary files /dev/null and b/app/src/main/res/drawable/logo_other.png differ diff --git a/app/src/main/res/drawable/logo_visa.png b/app/src/main/res/drawable/logo_visa.png new file mode 100644 index 0000000..4660b2e Binary files /dev/null and b/app/src/main/res/drawable/logo_visa.png differ diff --git a/app/src/main/res/drawable/password.png b/app/src/main/res/drawable/password.png new file mode 100644 index 0000000..804eb1d Binary files /dev/null and b/app/src/main/res/drawable/password.png differ diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..345888d --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..f58fb5d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_background.png b/app/src/main/res/mipmap-hdpi/ic_launcher_background.png new file mode 100644 index 0000000..e859f93 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..6c858c4 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png b/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000..c84a671 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..7d1a5d1 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_background.png b/app/src/main/res/mipmap-mdpi/ic_launcher_background.png new file mode 100644 index 0000000..20f7742 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..061361d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png b/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000..3b9df19 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..6cda981 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png new file mode 100644 index 0000000..811d675 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..495e506 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000..ccab17a Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..0cfb86f Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png new file mode 100644 index 0000000..4484669 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..c1810b6 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000..00045ac Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..231b4c7 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png new file mode 100644 index 0000000..b5126f5 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..b851eea Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000..320d544 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..31bd9ba --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,25 @@ + + MyWallet + Enter Your Email + Enter Your First Name + Enter Your Last Name + Login to My Wallet + Enter Your Password + Forgot Password? + Login + Signup + "Don’t have an account? " + Account Password + Account Name + Name on Card + Card Number + Bank Name + Expiry Date + CVV + Add Password + Add Card + https://bin-ip-checker.p.rapidapi.com/?bin= + f5204a5f14msh4f6ce93e987ac89p10f514jsn1152d1ec2f1a + Currency + + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..7b6081c --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,6 @@ + + + +