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" }