From 93057d43d5e647725996a8494e8c7b6b2ace8abf Mon Sep 17 00:00:00 2001 From: Reimar <mail@reim.ar> Date: Tue, 29 Apr 2025 13:18:43 +0200 Subject: [PATCH] Implement send http requests to the api --- app/.idea/compiler.xml | 2 +- app/.idea/misc.xml | 3 +- app/app/build.gradle.kts | 10 ++- app/app/src/main/AndroidManifest.xml | 1 + .../tech/mercantec/easyeat/LoginActivity.kt | 29 ++++++-- .../tech/mercantec/easyeat/helpers/api.kt | 73 +++++++++++++++++++ .../tech/mercantec/easyeat/helpers/auth.kt | 28 +++++++ .../src/main/res/layout/activity_login.xml | 2 + app/build.gradle.kts | 1 + app/gradle.properties | 4 +- app/gradle/libs.versions.toml | 2 + 11 files changed, 141 insertions(+), 14 deletions(-) create mode 100644 app/app/src/main/java/tech/mercantec/easyeat/helpers/api.kt create mode 100644 app/app/src/main/java/tech/mercantec/easyeat/helpers/auth.kt diff --git a/app/.idea/compiler.xml b/app/.idea/compiler.xml index b86273d..b589d56 100644 --- a/app/.idea/compiler.xml +++ b/app/.idea/compiler.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <project version="4"> <component name="CompilerConfiguration"> - <bytecodeTargetLevel target="21" /> + <bytecodeTargetLevel target="17" /> </component> </project> \ No newline at end of file diff --git a/app/.idea/misc.xml b/app/.idea/misc.xml index 2731899..ec453bb 100644 --- a/app/.idea/misc.xml +++ b/app/.idea/misc.xml @@ -1,9 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> <project version="4"> <component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="FrameworkDetectionExcludesConfiguration"> <file type="web" url="file://$PROJECT_DIR$" /> </component> - <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK"> + <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK"> <output url="file://$PROJECT_DIR$/build/classes" /> </component> <component name="ProjectType"> diff --git a/app/app/build.gradle.kts b/app/app/build.gradle.kts index 160f3f0..14fe66a 100644 --- a/app/app/build.gradle.kts +++ b/app/app/build.gradle.kts @@ -1,6 +1,9 @@ +import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties + plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) + id("org.jetbrains.kotlin.plugin.serialization") } android { @@ -15,6 +18,8 @@ android { versionName = "1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + + buildConfigField("String", "API_BASE_URL", gradleLocalProperties(projectDir, providers).getProperty("API_BASE_URL")) } buildTypes { @@ -36,7 +41,6 @@ android { } dependencies { - implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) implementation(libs.material) @@ -48,7 +52,5 @@ dependencies { implementation(libs.navigation.fragment.ktx) implementation(libs.navigation.ui.ktx) implementation(libs.androidx.activity) - testImplementation(libs.junit) - androidTestImplementation(libs.androidx.junit) - androidTestImplementation(libs.androidx.espresso.core) + implementation(libs.kotlinx.serialization.json) } \ No newline at end of file diff --git a/app/app/src/main/AndroidManifest.xml b/app/app/src/main/AndroidManifest.xml index 830e77d..215816d 100644 --- a/app/app/src/main/AndroidManifest.xml +++ b/app/app/src/main/AndroidManifest.xml @@ -1,6 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> + <uses-permission android:name="android.permission.INTERNET" /> <application android:allowBackup="true" diff --git a/app/app/src/main/java/tech/mercantec/easyeat/LoginActivity.kt b/app/app/src/main/java/tech/mercantec/easyeat/LoginActivity.kt index 244e8e1..72d2b91 100644 --- a/app/app/src/main/java/tech/mercantec/easyeat/LoginActivity.kt +++ b/app/app/src/main/java/tech/mercantec/easyeat/LoginActivity.kt @@ -3,10 +3,12 @@ package tech.mercantec.easyeat import android.content.Intent import android.os.Bundle import android.widget.Button -import androidx.activity.enableEdgeToEdge +import android.widget.EditText +import android.widget.Toast import androidx.appcompat.app.AppCompatActivity -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat +import tech.mercantec.easyeat.helpers.ApiRequestException +import tech.mercantec.easyeat.helpers.login +import kotlin.concurrent.thread class LoginActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -15,11 +17,24 @@ class LoginActivity : AppCompatActivity() { setContentView(R.layout.activity_login) findViewById<Button>(R.id.login).setOnClickListener { - // TODO authenticate + val email = findViewById<EditText>(R.id.email).text.toString() + val password = findViewById<EditText>(R.id.password).text.toString() - val intent = Intent(this, MainActivity::class.java) - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK // Prevent going back to login screen - startActivity(intent) + thread { + try { + login(email, password) + } catch (e: ApiRequestException) { + runOnUiThread { + Toast.makeText(this, e.message, Toast.LENGTH_LONG).show() + } + + return@thread + } + + val intent = Intent(this, MainActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK // Prevent going back to login screen + startActivity(intent) + } } findViewById<Button>(R.id.back).setOnClickListener { diff --git a/app/app/src/main/java/tech/mercantec/easyeat/helpers/api.kt b/app/app/src/main/java/tech/mercantec/easyeat/helpers/api.kt new file mode 100644 index 0000000..cd66815 --- /dev/null +++ b/app/app/src/main/java/tech/mercantec/easyeat/helpers/api.kt @@ -0,0 +1,73 @@ +package tech.mercantec.easyeat.helpers + +import android.content.Context +import android.util.Log +import android.widget.Toast +import kotlinx.serialization.Serializable +import kotlinx.serialization.SerializationException +import tech.mercantec.easyeat.BuildConfig +import java.net.HttpURLConnection +import java.net.URL +import kotlinx.serialization.json.* +import kotlinx.serialization.serializer +import java.io.IOException + +class ApiRequestException(message: String, cause: Throwable?) : Exception(message, cause) + +class HttpResponse(val body: String, val code: Int) + +fun request(method: String, path: String, data: String?): HttpResponse { + val url = URL(BuildConfig.API_BASE_URL + path) + + try { + with(url.openConnection() as HttpURLConnection) { + requestMethod = method + + if (data != null) { + outputStream.write(data.toByteArray()) + outputStream.flush() + } + + if (responseCode >= 400) + return HttpResponse(errorStream.readBytes().toString(), responseCode) + + return HttpResponse(inputStream.readBytes().toString(), responseCode) + } + } catch (e: IOException) { + Log.e("EasyEat", e.toString()) + + throw ApiRequestException("Failed to connect to server: " + e.message, e) + } +} + +@Serializable +class HttpErrorResponse(val message: String) + +inline fun <reified Req, reified Res> requestJson(method: String, path: String, data: Req?): Res { + val requestJson = + if (data != null) + Json.encodeToString(serializer<Req>(), data) + else null + + val response = request(method, path, requestJson) + + if (response.code >= 400) { + try { + val error = Json.decodeFromString<HttpErrorResponse>(response.body) + + throw ApiRequestException(error.message, null) + } catch (e: SerializationException) { + Log.e("EasyEat", response.body) + + throw ApiRequestException("Request failed with HTTP status code ${response.code}", e) + } + } + + try { + return Json.decodeFromString<Res>(response.body) + } catch (e: SerializationException) { + Log.e("EasyEat", response.body) + + throw ApiRequestException("Failed to parse response: $response", e) + } +} diff --git a/app/app/src/main/java/tech/mercantec/easyeat/helpers/auth.kt b/app/app/src/main/java/tech/mercantec/easyeat/helpers/auth.kt new file mode 100644 index 0000000..12b2b29 --- /dev/null +++ b/app/app/src/main/java/tech/mercantec/easyeat/helpers/auth.kt @@ -0,0 +1,28 @@ +package tech.mercantec.easyeat.helpers + +import android.content.Context +import kotlinx.serialization.Serializable + +@Serializable +data class LoginRequest(val emailUsr: String, val password: String) + +@Serializable +data class LoginResponse(val token: String, val username: String, val id: Int, val refreshToken: String) + +fun login(email: String, password: String) { + val request = LoginRequest(email, password) + + val response = requestJson<LoginRequest, LoginResponse>("POST", "/api/User/login", request) + + // TODO save tokens + +} + +@Serializable +data class CreateUserRequest(val email: String, val userName: String, val password: String) + +fun register(email: String, username: String, password: String) { + val request = CreateUserRequest(email, username, password) + + requestJson<CreateUserRequest, Boolean>("POST", "/api/User/create", request) +} diff --git a/app/app/src/main/res/layout/activity_login.xml b/app/app/src/main/res/layout/activity_login.xml index e701822..09b4025 100644 --- a/app/app/src/main/res/layout/activity_login.xml +++ b/app/app/src/main/res/layout/activity_login.xml @@ -24,6 +24,7 @@ /> <EditText + android:id="@+id/email" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/email_hint" @@ -39,6 +40,7 @@ /> <EditText + android:id="@+id/password" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/password_hint" diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 922f551..340ba5f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -2,4 +2,5 @@ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.kotlin.android) apply false + id("org.jetbrains.kotlin.plugin.serialization") version "1.7.10" apply false } \ No newline at end of file diff --git a/app/gradle.properties b/app/gradle.properties index 20e2a01..0e84737 100644 --- a/app/gradle.properties +++ b/app/gradle.properties @@ -20,4 +20,6 @@ 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 \ No newline at end of file +android.nonTransitiveRClass=true + +android.defaults.buildfeatures.buildconfig=true diff --git a/app/gradle/libs.versions.toml b/app/gradle/libs.versions.toml index 4111c3f..7e9628f 100644 --- a/app/gradle/libs.versions.toml +++ b/app/gradle/libs.versions.toml @@ -6,6 +6,7 @@ junit = "4.13.2" junitVersion = "1.1.5" espressoCore = "3.5.1" appcompat = "1.6.1" +kotlinxSerializationJson = "1.2.2" material = "1.10.0" constraintlayout = "2.1.4" lifecycleLivedataKtx = "2.6.1" @@ -22,6 +23,7 @@ junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } material = { group = "com.google.android.material", name = "material", version.ref = "material" } androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } androidx-lifecycle-livedata-ktx = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "lifecycleLivedataKtx" }