1+ package com.example.blockchainaccess
2+
3+ import android.annotation.SuppressLint
4+ import android.os.Bundle
5+ import android.content.Intent
6+ import androidx.activity.ComponentActivity
7+ import androidx.activity.compose.setContent
8+ import androidx.activity.enableEdgeToEdge
9+ import androidx.compose.foundation.Image
10+ import androidx.compose.foundation.layout.*
11+ import androidx.compose.ui.platform.LocalContext
12+ import androidx.compose.foundation.shape.RoundedCornerShape
13+ import androidx.compose.foundation.background
14+ import androidx.compose.material3.ButtonDefaults
15+ import androidx.compose.material3.MaterialTheme
16+ import androidx.compose.material3.Scaffold
17+ import androidx.compose.material3.Text
18+ import androidx.compose.material3.Button
19+ import androidx.compose.material3.DropdownMenu
20+ import androidx.compose.material3.DropdownMenuItem
21+ import androidx.compose.material3.TextField
22+ import androidx.compose.runtime.Composable
23+ import androidx.compose.ui.graphics.Color
24+ import androidx.compose.ui.graphics.graphicsLayer
25+ import androidx.compose.ui.text.font.FontWeight
26+ import androidx.compose.ui.text.input.PasswordVisualTransformation
27+ import androidx.compose.ui.text.style.TextAlign
28+ import androidx.compose.ui.unit.dp
29+ import androidx.compose.ui.unit.sp
30+ import androidx.compose.ui.Alignment
31+ import androidx.compose.ui.Modifier
32+ import android.content.Context
33+ import androidx.compose.runtime.*
34+ import androidx.compose.ui.tooling.preview.Preview
35+ import androidx.compose.ui.layout.ContentScale
36+ import androidx.compose.ui.res.painterResource
37+ import com.example.blockchainaccess.ui.theme.BlockchainAccessTheme
38+ import kotlinx.coroutines.*
39+ import android.widget.Toast
40+
41+ class LoginActivity : ComponentActivity () {
42+ override fun onCreate (savedInstanceState : Bundle ? ) {
43+ super .onCreate(savedInstanceState)
44+ enableEdgeToEdge()
45+ setContent {
46+ BlockchainAccessTheme {
47+ Scaffold (modifier = Modifier .fillMaxSize()) { innerPadding ->
48+ LoginScreen (
49+ name = " Android" ,
50+ modifier = Modifier .padding(innerPadding)
51+ )
52+ }
53+ }
54+ }
55+ }
56+ }
57+
58+ @SuppressLint(" UnusedMaterial3ScaffoldPaddingParameter" )
59+ @Composable
60+ fun LoginScreen (name : String , modifier : Modifier = Modifier ) {
61+ val context = LocalContext .current
62+ val coroutineScope = rememberCoroutineScope()
63+ var username by remember { mutableStateOf(" " ) }
64+ var password by remember { mutableStateOf(" " ) }
65+ var selectedPort by remember { mutableStateOf(" 8081" ) }
66+ var showPortDropdown by remember { mutableStateOf(false ) }
67+ Scaffold {
68+ Box (
69+ modifier = modifier
70+ .fillMaxSize()
71+ ) {
72+ Image (
73+ painter = painterResource(id = R .drawable.wavy),
74+ contentDescription = null ,
75+ contentScale = ContentScale .Crop ,
76+ modifier = Modifier
77+ .fillMaxSize()
78+ .graphicsLayer(alpha = 0.5f )
79+ )
80+ Box (
81+ modifier = Modifier
82+ .fillMaxSize()
83+ .background(Color .Black .copy(alpha = 0.6f ))
84+ )
85+ Column (
86+ modifier = Modifier
87+ .fillMaxSize()
88+ .padding(top = 28 .dp, start = 0 .dp, bottom = 100 .dp, end = 0 .dp),
89+ horizontalAlignment = Alignment .CenterHorizontally ,
90+ verticalArrangement = Arrangement .Center
91+ ) {
92+ Column (
93+ modifier = Modifier .fillMaxWidth(),
94+ horizontalAlignment = Alignment .Start // Align text to the left
95+ ) {
96+ Text (
97+ text = " Welcome!" ,
98+ color = Color .White ,
99+ fontSize = 20 .sp,
100+ fontWeight = FontWeight .Bold ,
101+ textAlign = TextAlign .Left ,
102+ modifier = Modifier .padding(28 .dp, 0 .dp, 0 .dp, 0 .dp)
103+ )
104+ Text (
105+ text = " Sign in to your profile profile, provided you are authorized by the system administrator." ,
106+ color = Color .White .copy(alpha = 0.8f ),
107+ fontSize = 14 .sp,
108+ textAlign = TextAlign .Left ,
109+ modifier = Modifier .padding(28 .dp, 8 .dp, 10 .dp, 50 .dp)
110+ )
111+ }
112+ TextField (
113+ value = username,
114+ onValueChange = { username = it },
115+ label = { Text (" Username" ) },
116+ modifier = Modifier
117+ .fillMaxWidth(0.88f )
118+ .padding(vertical = 6 .dp)
119+ )
120+
121+ TextField (
122+ value = password,
123+ onValueChange = { password = it },
124+ label = { Text (" Password" ) },
125+ visualTransformation = PasswordVisualTransformation (),
126+ modifier = Modifier
127+ .fillMaxWidth(0.88f )
128+ .padding(vertical = 6 .dp)
129+ )
130+ Spacer (modifier = Modifier .height(34 .dp))
131+ Button (
132+ onClick = { login(context, username,password,selectedPort, coroutineScope) },
133+ shape = RoundedCornerShape (50 ),
134+ colors = ButtonDefaults .buttonColors(containerColor = MaterialTheme .colorScheme.primary),
135+ elevation = ButtonDefaults .buttonElevation(defaultElevation = 6 .dp),
136+ modifier = Modifier
137+ .fillMaxWidth(0.5f )
138+ .padding(vertical = 8 .dp)
139+ ) {
140+ Text (" Sign in" , color = Color .White )
141+ }
142+ }
143+ Box (
144+ modifier = Modifier
145+ .align(Alignment .BottomStart )
146+ .padding(16 .dp)
147+ ) {
148+ Button (onClick = { showPortDropdown = true }) {
149+ Text (text = " Port: $selectedPort " )
150+ }
151+ DropdownMenu (
152+ expanded = showPortDropdown,
153+ onDismissRequest = { showPortDropdown = false }
154+ ) {
155+ val ports = listOf (" 8081" , " 8082" , " 8083" , " 8084" )
156+ ports.forEach { port ->
157+ DropdownMenuItem (
158+ text = { Text (port) },
159+ onClick = {
160+ selectedPort = port
161+ showPortDropdown = false
162+ // Reset session when port changes
163+ coroutineScope.launch {
164+ SessionManager .clearSession(context)
165+ }
166+ Toast .makeText(context, " Session reset. Please log in again." , Toast .LENGTH_SHORT ).show()
167+ }
168+ )
169+ }
170+ }
171+ }
172+ }
173+ }
174+ }
175+
176+ @Preview
177+ @Composable
178+ fun PreviewLoginScreen () {
179+ BlockchainAccessTheme {
180+ LoginScreen (" Android" )
181+ }
182+ }
183+
184+ fun login (
185+ context : Context ,
186+ username : String ,
187+ password : String ,
188+ port : String ,
189+ coroutineScope : CoroutineScope
190+ ) {
191+ if (username.isBlank() || password.isBlank()) {
192+ Toast .makeText(context, " All fields must be filled." , Toast .LENGTH_SHORT ).show()
193+ return
194+ }
195+
196+
197+ coroutineScope.launch(Dispatchers .IO ) {
198+
199+ val result = NetworkClient .loginToProfile(username, password, port)
200+
201+
202+ withContext(Dispatchers .Main ) {
203+ result.fold(
204+ onSuccess = { (id, whitelist) ->
205+ Toast .makeText(context, " Logged in successfully!" , Toast .LENGTH_SHORT ).show()
206+
207+ SessionManager .saveSession(context, id, username, port)
208+
209+ SessionManager .saveWhitelist(context, whitelist)
210+
211+ val intent = Intent (context, UserActivity ::class .java)
212+ context.startActivity(intent)
213+ },
214+ onFailure = { error ->
215+ Toast .makeText(context, error.message ? : " Unknown error" , Toast .LENGTH_SHORT ).show()
216+ }
217+ )
218+ }
219+ }
220+ }
0 commit comments