From cb36335bdb1200838ddb86d6205573a33c9980f5 Mon Sep 17 00:00:00 2001 From: odweta Date: Fri, 4 Sep 2026 11:26:28 +0200 Subject: [PATCH] realize that i need to call the serveer, not become the server - i was mentally drained when making that i presume --- android/app/build.gradle.kts | 9 ++++ android/app/src/main/AndroidManifest.xml | 3 +- .../java/app/odweta/mts/MainApplication.kt | 7 +++ .../mts/{ => activities}/MainActivity.kt | 16 +++---- .../app/odweta/mts/data/TicketRepository.kt | 22 +++++++++ .../java/app/odweta/mts/data/api/TicketApi.kt | 20 ++++++++ .../java/app/odweta/mts/data/model/Ticket.kt | 12 +++++ .../app/odweta/mts/data/model/TicketStatus.kt | 7 +++ .../odweta/mts/data/network/RetrofitClient.kt | 29 ++++++++++++ .../interceptors/AuthorizationInterceptor.kt | 16 +++++++ .../interceptors/RequestInterceptor.kt | 12 +++++ .../java/app/odweta/mts/nav/NavigationHost.kt | 46 +++++++++++++++++++ .../src/main/java/app/odweta/mts/nav/Route.kt | 16 +++++++ .../mts/ui/screens/create/CreateScreen.kt | 26 +++++++++++ .../screens/create/CreateScreenViewModel.kt | 4 ++ .../mts/ui/screens/detail/DetailScreen.kt | 27 +++++++++++ .../odweta/mts/ui/screens/list/ListScreen.kt | 36 +++++++++++++++ android/gradle/libs.versions.toml | 10 ++++ 18 files changed, 308 insertions(+), 10 deletions(-) create mode 100644 android/app/src/main/java/app/odweta/mts/MainApplication.kt rename android/app/src/main/java/app/odweta/mts/{ => activities}/MainActivity.kt (57%) create mode 100644 android/app/src/main/java/app/odweta/mts/data/TicketRepository.kt create mode 100644 android/app/src/main/java/app/odweta/mts/data/api/TicketApi.kt create mode 100644 android/app/src/main/java/app/odweta/mts/data/model/Ticket.kt create mode 100644 android/app/src/main/java/app/odweta/mts/data/model/TicketStatus.kt create mode 100644 android/app/src/main/java/app/odweta/mts/data/network/RetrofitClient.kt create mode 100644 android/app/src/main/java/app/odweta/mts/data/network/interceptors/AuthorizationInterceptor.kt create mode 100644 android/app/src/main/java/app/odweta/mts/data/network/interceptors/RequestInterceptor.kt create mode 100644 android/app/src/main/java/app/odweta/mts/nav/NavigationHost.kt create mode 100644 android/app/src/main/java/app/odweta/mts/nav/Route.kt create mode 100644 android/app/src/main/java/app/odweta/mts/ui/screens/create/CreateScreen.kt create mode 100644 android/app/src/main/java/app/odweta/mts/ui/screens/create/CreateScreenViewModel.kt create mode 100644 android/app/src/main/java/app/odweta/mts/ui/screens/detail/DetailScreen.kt create mode 100644 android/app/src/main/java/app/odweta/mts/ui/screens/list/ListScreen.kt diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 7b73c05..4de6b82 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -46,6 +46,7 @@ dependencies { implementation(libs.androidx.compose.ui.tooling.preview) implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.navigation.compose) testImplementation(libs.junit) androidTestImplementation(platform(libs.androidx.compose.bom)) androidTestImplementation(libs.androidx.compose.ui.test.junit4) @@ -56,4 +57,12 @@ dependencies { implementation(libs.hilt.android) ksp(libs.hilt.android.compiler) + + // retrofit + implementation(libs.retrofit) + // GSON + implementation(libs.converter.gson) + // coroutines + implementation(libs.kotlinx.coroutines.android) + implementation(libs.kotlinx.coroutines.core) } \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 1ad1a09..ba24189 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ xmlns:tools="http://schemas.android.com/tools"> ? = usersResponse.body() + } +} \ No newline at end of file diff --git a/android/app/src/main/java/app/odweta/mts/data/api/TicketApi.kt b/android/app/src/main/java/app/odweta/mts/data/api/TicketApi.kt new file mode 100644 index 0000000..07d52c5 --- /dev/null +++ b/android/app/src/main/java/app/odweta/mts/data/api/TicketApi.kt @@ -0,0 +1,20 @@ +package app.odweta.mts.data.api + +import app.odweta.mts.data.model.Ticket +import retrofit2.Call +import retrofit2.Response +import retrofit2.http.GET +import retrofit2.http.PATCH +import retrofit2.http.POST +import retrofit2.http.Path + +interface TicketApi { + @GET("/tickets") + suspend fun getTickets(): Call> + + @POST("/tickets") + suspend fun createTicket(): Call + + @PATCH("/tickets/{id}") + suspend fun updateTicket(@Path("id") id: Long) +} \ No newline at end of file diff --git a/android/app/src/main/java/app/odweta/mts/data/model/Ticket.kt b/android/app/src/main/java/app/odweta/mts/data/model/Ticket.kt new file mode 100644 index 0000000..121d1b7 --- /dev/null +++ b/android/app/src/main/java/app/odweta/mts/data/model/Ticket.kt @@ -0,0 +1,12 @@ +package app.odweta.mts.data.model + +import java.time.LocalDateTime + +data class Ticket( + private val id: Long, + private val title: String, + private val description: String, + private val status: TicketStatus, + private val createdAt: LocalDateTime, + private val updatedAt: LocalDateTime +) \ No newline at end of file diff --git a/android/app/src/main/java/app/odweta/mts/data/model/TicketStatus.kt b/android/app/src/main/java/app/odweta/mts/data/model/TicketStatus.kt new file mode 100644 index 0000000..70dc2d1 --- /dev/null +++ b/android/app/src/main/java/app/odweta/mts/data/model/TicketStatus.kt @@ -0,0 +1,7 @@ +package app.odweta.mts.data.model + +enum class TicketStatus { + Open, + InProgress, + Closed +} \ No newline at end of file diff --git a/android/app/src/main/java/app/odweta/mts/data/network/RetrofitClient.kt b/android/app/src/main/java/app/odweta/mts/data/network/RetrofitClient.kt new file mode 100644 index 0000000..ca901b5 --- /dev/null +++ b/android/app/src/main/java/app/odweta/mts/data/network/RetrofitClient.kt @@ -0,0 +1,29 @@ +package app.odweta.mts.data.network + +import app.odweta.mts.data.network.interceptors.AuthorizationInterceptor +import app.odweta.mts.data.network.interceptors.RequestInterceptor +import okhttp3.OkHttpClient +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory + +object RetrofitClient { + private const val BASE_URL = "http://localhost:8090/" + + val okHttpClient = OkHttpClient() + .newBuilder() + .addInterceptor(RequestInterceptor) + .addInterceptor(AuthorizationInterceptor) + .build() + + fun getClient(): Retrofit = + Retrofit.Builder() + .client(okHttpClient) + .baseUrl(BASE_URL) + .addConverterFactory(GsonConverterFactory.create()) + .build() +} + +/* + TODO: add token generation to the server + TODO: add a real auth call for that token in the AuthorizationInterceptor + */ \ No newline at end of file diff --git a/android/app/src/main/java/app/odweta/mts/data/network/interceptors/AuthorizationInterceptor.kt b/android/app/src/main/java/app/odweta/mts/data/network/interceptors/AuthorizationInterceptor.kt new file mode 100644 index 0000000..91d2669 --- /dev/null +++ b/android/app/src/main/java/app/odweta/mts/data/network/interceptors/AuthorizationInterceptor.kt @@ -0,0 +1,16 @@ +package app.odweta.mts.data.network.interceptors + +import okhttp3.Interceptor +import okhttp3.Response +import java.util.UUID + +object AuthorizationInterceptor : Interceptor { + override fun intercept(chain: Interceptor.Chain): Response { + val requestWithHeader = chain.request() + .newBuilder() + .header( + "Authorization", UUID.randomUUID().toString() + ).build() + return chain.proceed(requestWithHeader) + } +} \ No newline at end of file diff --git a/android/app/src/main/java/app/odweta/mts/data/network/interceptors/RequestInterceptor.kt b/android/app/src/main/java/app/odweta/mts/data/network/interceptors/RequestInterceptor.kt new file mode 100644 index 0000000..b2ed6f3 --- /dev/null +++ b/android/app/src/main/java/app/odweta/mts/data/network/interceptors/RequestInterceptor.kt @@ -0,0 +1,12 @@ +package app.odweta.mts.data.network.interceptors + +import okhttp3.Interceptor +import okhttp3.Response + +object RequestInterceptor : Interceptor { + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + println("Outgoing request to ${request.url()}") + return chain.proceed(request) + } +} \ No newline at end of file diff --git a/android/app/src/main/java/app/odweta/mts/nav/NavigationHost.kt b/android/app/src/main/java/app/odweta/mts/nav/NavigationHost.kt new file mode 100644 index 0000000..8a6d92e --- /dev/null +++ b/android/app/src/main/java/app/odweta/mts/nav/NavigationHost.kt @@ -0,0 +1,46 @@ +package app.odweta.mts.nav + +import androidx.compose.animation.SharedTransitionLayout +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import androidx.navigation.toRoute +import app.odweta.mts.ui.screens.list.ListScreen +import app.odweta.mts.ui.screens.create.CreateScreen +import app.odweta.mts.ui.screens.detail.DetailScreen + +@Composable +fun NavigationHost() { + val navController = rememberNavController() + + val startDestination = remember { Route.ListScreen } + + NavHost( + navController = navController, + startDestination = startDestination + ) { + composable { + ListScreen(navController = navController) + } + + composable { + CreateScreen(navController = navController) + } + + composable { backStackEntry -> + val ticketId = backStackEntry.arguments?.getLong("ticketId") + ?: throw IllegalArgumentException("Missing required argument: ticketId for DetailScreen") + DetailScreen( + navController = navController, + ticketId = ticketId + ) + } + } +} \ No newline at end of file diff --git a/android/app/src/main/java/app/odweta/mts/nav/Route.kt b/android/app/src/main/java/app/odweta/mts/nav/Route.kt new file mode 100644 index 0000000..799aab2 --- /dev/null +++ b/android/app/src/main/java/app/odweta/mts/nav/Route.kt @@ -0,0 +1,16 @@ +package app.odweta.mts.nav + +import kotlinx.serialization.Serializable + +sealed interface Route { + @Serializable + data object ListScreen : Route + + @Serializable + data object CreateScreen : Route + + @Serializable + data class DetailScreen( + val ticketId: Long + ) : Route +} \ No newline at end of file diff --git a/android/app/src/main/java/app/odweta/mts/ui/screens/create/CreateScreen.kt b/android/app/src/main/java/app/odweta/mts/ui/screens/create/CreateScreen.kt new file mode 100644 index 0000000..5a268e4 --- /dev/null +++ b/android/app/src/main/java/app/odweta/mts/ui/screens/create/CreateScreen.kt @@ -0,0 +1,26 @@ +package app.odweta.mts.ui.screens.create + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.navigation.NavController + +@Composable +fun CreateScreen( + navController: NavController, + modifier: Modifier = Modifier +) { + Column( + modifier = Modifier.fillMaxSize() + ) { + Text("create a ticket") + TextButton(onClick = { + navController.popBackStack() + }) { + Text("back!") + } + } +} \ No newline at end of file diff --git a/android/app/src/main/java/app/odweta/mts/ui/screens/create/CreateScreenViewModel.kt b/android/app/src/main/java/app/odweta/mts/ui/screens/create/CreateScreenViewModel.kt new file mode 100644 index 0000000..8d7a0f8 --- /dev/null +++ b/android/app/src/main/java/app/odweta/mts/ui/screens/create/CreateScreenViewModel.kt @@ -0,0 +1,4 @@ +package app.odweta.mts.ui.screens.create + +class CreateScreenViewModel { +} \ No newline at end of file diff --git a/android/app/src/main/java/app/odweta/mts/ui/screens/detail/DetailScreen.kt b/android/app/src/main/java/app/odweta/mts/ui/screens/detail/DetailScreen.kt new file mode 100644 index 0000000..8412afc --- /dev/null +++ b/android/app/src/main/java/app/odweta/mts/ui/screens/detail/DetailScreen.kt @@ -0,0 +1,27 @@ +package app.odweta.mts.ui.screens.detail + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.navigation.NavController + +@Composable +fun DetailScreen( + navController: NavController, + ticketId: Long, + modifier: Modifier = Modifier +) { + Column( + modifier = Modifier.fillMaxSize() + ) { + Text("detail of ticket id $ticketId") + TextButton(onClick = { + navController.popBackStack() + }) { + Text("back!") + } + } +} \ No newline at end of file diff --git a/android/app/src/main/java/app/odweta/mts/ui/screens/list/ListScreen.kt b/android/app/src/main/java/app/odweta/mts/ui/screens/list/ListScreen.kt new file mode 100644 index 0000000..7a00be2 --- /dev/null +++ b/android/app/src/main/java/app/odweta/mts/ui/screens/list/ListScreen.kt @@ -0,0 +1,36 @@ +package app.odweta.mts.ui.screens.list + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.navigation.NavController +import app.odweta.mts.nav.Route + +@Composable +fun ListScreen( + navController: NavController, + modifier: Modifier = Modifier +) { + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text("all of the tickets") + TextButton(onClick = { + navController.navigate(Route.CreateScreen) + }) { + Text("to create screen!") + } + TextButton(onClick = { + navController.navigate(Route.DetailScreen(1)) + }) { + Text("to detail screen!") + } + } +} \ No newline at end of file diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml index 52b2e53..34b0065 100644 --- a/android/gradle/libs.versions.toml +++ b/android/gradle/libs.versions.toml @@ -1,19 +1,25 @@ [versions] agp = "9.3.1" +converterGson = "3.0.0" coreKtx = "1.10.1" hiltAndroid = "2.60.1" junit = "4.13.2" junitVersion = "1.1.5" espressoCore = "3.5.1" +kotlinxCoroutinesAndroid = "1.11.0" +kotlinxCoroutinesCore = "1.11.0" lifecycleRuntimeKtx = "2.6.1" activityCompose = "1.8.0" kotlin = "2.2.10" composeBom = "2026.02.01" ksp = "2.3.11" dagger-hilt = "2.60.1" +navigationCompose = "2.9.8" +retrofit = "3.0.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "converterGson" } hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltAndroid" } hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hiltAndroid" } junit = { group = "junit", name = "junit", version.ref = "junit" } @@ -29,6 +35,10 @@ androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "u androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } +androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" } +kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesAndroid" } +kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" } +retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" }