Migrate from Bucket

This commit is contained in:
2024-10-28 20:47:35 +00:00
commit 445e130ebf
84 changed files with 4416 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/build
+131
View File
@@ -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
//}
+29
View File
@@ -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"
}
+21
View File
@@ -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
@@ -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)
}
}
+37
View File
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:name=".MyWalletApp"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/Theme.MyWallet"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.MyWallet">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:host="localhost" />
</intent-filter>
</activity>
</application>
</manifest>
@@ -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()
}
}
@@ -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)
}
}
@@ -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)
@@ -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<User> ->
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,
}
@@ -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<OnBackPressedDispatcherOwner?> { 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()
}
}
}
@@ -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 "Dont 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<Card>
) {
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<Password>
) {
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)
}
}
}
}
}
}
}
@@ -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)
// }
// }
// }
//}
@@ -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())
}
}
@@ -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<TabItem> {
val menus: MutableList<TabItem> = 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<List<Card>>
@Query("SELECT * from card WHERE count > 0 AND userName = :userName ORDER BY count DESC LIMIT 1")
fun getTopCard(userName: String): Flow<List<Card>>
@Query("SELECT * FROM card ORDER BY bankName ASC")
fun getAllCards(): Flow<List<Card>>
@Query("SELECT * FROM card ORDER BY cardName ASC")
fun getUserCard(): Flow<List<Card>>
}
@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<List<Password>>
@Query("SELECT * from password WHERE count > 0 AND userName = :userName ORDER BY count DESC LIMIT 5")
fun getTopPassword(userName: String): Flow<List<Password>>
@Query("SELECT * FROM password ORDER BY accountName ASC")
fun getAllPasswords(): Flow<List<Password>>
@Query("SELECT * FROM password ORDER BY accountName ASC")
fun getUserPassword(): Flow<List<Password>>
}
@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<List<User>>
}
//DAO
//REPO
interface CardRepository {
fun getAllItemsStream(): Flow<List<Card>>
fun getItemStream(id: UUID): Flow<List<Card>>
fun getTopCard(userName: String): Flow<List<Card>>
fun getUserCard(): Flow<List<Card>>
suspend fun insertItem(cards: Card)
suspend fun deleteItem(cards: Card)
suspend fun updateItem(cards: Card)
}
interface PasswordRepository {
fun getAllItemsStream(): Flow<List<Password>>
fun getItemStream(id: UUID): Flow<List<Password>>
fun getTopPassword(userName: String): Flow<List<Password>>
fun getUserPassword(): Flow<List<Password>>
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<List<User>>
}
//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<List<User>> = userDao.findByUser(userName)
}
class OfflineCardRepository(private val cardsDao: CardDao) : CardRepository {
override fun getAllItemsStream(): Flow<List<Card>> = cardsDao.getAllCards()
override fun getItemStream(id: UUID): Flow<List<Card>> = cardsDao.findById(id)
override fun getTopCard(userName: String): Flow<List<Card>> = cardsDao.getTopCard(userName)
override fun getUserCard(): Flow<List<Card>> = 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<List<Password>> = passwordsDao.getAllPasswords()
override fun getTopPassword(userName: String): Flow<List<Password>> = passwordsDao.getTopPassword(userName)
override fun getUserPassword(): Flow<List<Password>> = passwordsDao.getUserPassword()
override fun getItemStream(id: UUID): Flow<List<Password>> = 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
@@ -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
)
}
}
@@ -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
)
@@ -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()
}
@@ -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()
}
@@ -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()
}
@@ -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()
}
@@ -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> = cardUIState.asStateFlow()
val cardDataList: StateFlow<CardsList> =
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<Card> = listOf())
// fun cardDataList(userName: String): StateFlow<CardsList> =
// cardsRepository.getUserCard(userName).map { CardsList(it) }.stateIn(
// scope = viewModelScope,
// started = SharingStarted.WhileSubscribed(5_000L),
// initialValue = CardsList()
// )
@@ -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<CardsListHome> =
cardRepository.getTopCard(userName).map { CardsListHome(it) }.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000L),
initialValue = CardsListHome()
)
fun passwordDataList(userName: String): StateFlow<PasswordListHome> =
passwordRepository.getTopPassword(userName).map { PasswordListHome(it) }.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000L),
initialValue = PasswordListHome()
)
}
data class PasswordListHome(val passwordList: List<Password> = listOf())
data class CardsListHome(val cardsList: List<Card> = listOf())
@@ -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<Boolean> = MutableLiveData()
val loginUIState = mutableStateOf(LoginUIState())
var allValidationsPassed = mutableStateOf(false)
var loginInProgress = mutableStateOf(false)
var loginStatus by mutableStateOf(false)
var failureMessage = mutableStateOf("")
var userList: StateFlow<UserList> = 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<User> = listOf())
@@ -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> = passwordUIState.asStateFlow()
var allValidationsPassed = mutableStateOf(false)
val passwordDataList: StateFlow<PasswordList> =
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<Password> = listOf())
@@ -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
// )
// )
// }
}
}
}
}
}
@@ -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(),
""
)
}
@@ -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,"")
}
@@ -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<Card>,
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, ""
)
}
@@ -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<Card>
) {
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<Password>
) {
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,"",)
}
@@ -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<User>) -> 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<User> -> },
rememberNavController(),
)
}
@@ -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
// }
@@ -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<Password>,
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(),"")
}
@@ -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())
}
@@ -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,
)
@@ -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
)
@@ -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
)
@@ -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
)
@@ -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)
@@ -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
)
}
@@ -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
)
*/
)
Binary file not shown.

After

Width:  |  Height:  |  Size: 706 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 886 B

@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>
@@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 882 B

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
</adaptive-icon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 854 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 876 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 846 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>
+25
View File
@@ -0,0 +1,25 @@
<resources>
<string name="app_name">MyWallet</string>
<string name="enter_your_email">Enter Your Email</string>
<string name="enter_your_firstname">Enter Your First Name</string>
<string name="enter_your_lastname">Enter Your Last Name</string>
<string name="login_to_my_wallet">Login to My Wallet</string>
<string name="enter_your_password">Enter Your Password</string>
<string name="forgot_password">Forgot Password?</string>
<string name="login">Login</string>
<string name="signup">Signup</string>
<string name="don_t_have_an_account">"Dont have an account? "</string>
<string name="account_password">Account Password</string>
<string name="account_name">Account Name</string>
<string name="name_on_card">Name on Card</string>
<string name="card_number">Card Number</string>
<string name="bank_name">Bank Name</string>
<string name="expiry_date">Expiry Date</string>
<string name="cvv">CVV</string>
<string name="add_password">Add Password</string>
<string name="add_card">Add Card</string>
<string name="api_end_point">https://bin-ip-checker.p.rapidapi.com/?bin=</string>
<string name="api_key">f5204a5f14msh4f6ce93e987ac89p10f514jsn1152d1ec2f1a</string>
<string name="currency">Currency</string>
</resources>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.MyWallet" parent="android:Theme.Material.Light.NoActionBar" />
</resources>
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older that API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>
@@ -0,0 +1,17 @@
package ica.tees.mywallet
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
+18
View File
@@ -0,0 +1,18 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "8.0.2" apply false
id("org.jetbrains.kotlin.android") version "1.8.10" apply false
id ("com.android.library") version "8.0.2" apply false
id("com.google.gms.google-services")version "4.4.0" apply false
id("com.google.devtools.ksp") version "1.8.10-1.0.9" apply false
}
buildscript {
extra.apply {
set("room_version", "2.5.2")
}
dependencies {
classpath("com.google.gms:google-services:4.4.0")
}
}
+23
View File
@@ -0,0 +1,23 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
Binary file not shown.
+6
View File
@@ -0,0 +1,6 @@
#Tue Oct 24 16:34:04 BST 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored Executable
+185
View File
@@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# 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.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"
Vendored
+89
View File
@@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+18
View File
@@ -0,0 +1,18 @@
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "MyWallet"
include(":app")