diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 53b468a..fab25da 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -65,4 +65,12 @@ dependencies { val room_version = "2.8.4" implementation("androidx.room:room-runtime:$room_version") ksp("androidx.room:room-compiler:$room_version") + + // Retrofit + Gson + implementation("com.squareup.retrofit2:retrofit:2.9.0") + implementation("com.squareup.retrofit2:converter-gson:2.9.0") + // OkHttp logging (debug) + implementation("com.squareup.okhttp3:logging-interceptor:4.9.3") + // AndroidX Security (EncryptedSharedPreferences) + implementation("androidx.security:security-crypto:1.1.0-alpha03") } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 22dbb81..e7ec6c5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + + + @POST("api/refresh") + suspend fun refresh(@Body req: Map): Response> +} \ No newline at end of file diff --git a/app/src/main/java/de/miaurizius/shap_planner/network/RetrofitProvider.kt b/app/src/main/java/de/miaurizius/shap_planner/network/RetrofitProvider.kt new file mode 100644 index 0000000..e233e1f --- /dev/null +++ b/app/src/main/java/de/miaurizius/shap_planner/network/RetrofitProvider.kt @@ -0,0 +1,28 @@ +package de.miaurizius.shap_planner.network + +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory + +object RetrofitProvider { + + fun create(serverUrl: String): APIService { + val base = if (serverUrl.endsWith("/")) serverUrl else "$serverUrl/" + + val logger = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BASIC } + + val client = OkHttpClient.Builder() + .addInterceptor(logger) + .build() + + val retrofit = Retrofit.Builder() + .baseUrl(base) + .client(client) + .addConverterFactory(GsonConverterFactory.create()) + .build() + + return retrofit.create(APIService::class.java) + } + +} \ No newline at end of file diff --git a/app/src/main/java/de/miaurizius/shap_planner/room/AppDatabase.kt b/app/src/main/java/de/miaurizius/shap_planner/room/AppDatabase.kt index c1ec6ef..f3c3852 100644 --- a/app/src/main/java/de/miaurizius/shap_planner/room/AppDatabase.kt +++ b/app/src/main/java/de/miaurizius/shap_planner/room/AppDatabase.kt @@ -7,7 +7,7 @@ import androidx.room.RoomDatabase import de.miaurizius.shap_planner.entities.Account import de.miaurizius.shap_planner.entities.AccountDao -@Database(entities = [Account::class], version = 1) +@Database(entities = [Account::class], version = 2) abstract class AppDatabase : RoomDatabase() { abstract fun accountDao(): AccountDao companion object { @@ -20,7 +20,7 @@ abstract class AppDatabase : RoomDatabase() { context.applicationContext, AppDatabase::class.java, "shap_planner_database" - ).build() + ).fallbackToDestructiveMigration(true).build() INSTANCE = instance instance } diff --git a/app/src/main/java/de/miaurizius/shap_planner/viewmodels/LoginViewModel.kt b/app/src/main/java/de/miaurizius/shap_planner/viewmodels/LoginViewModel.kt index dd376b5..1387f77 100644 --- a/app/src/main/java/de/miaurizius/shap_planner/viewmodels/LoginViewModel.kt +++ b/app/src/main/java/de/miaurizius/shap_planner/viewmodels/LoginViewModel.kt @@ -1,25 +1,65 @@ package de.miaurizius.shap_planner.viewmodels +import android.content.Context import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import de.miaurizius.shap_planner.TokenStorage import de.miaurizius.shap_planner.UserPreferences import de.miaurizius.shap_planner.entities.Account +import de.miaurizius.shap_planner.network.LoginRequest +import de.miaurizius.shap_planner.network.RetrofitProvider +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.util.UUID -class LoginViewModel(private val prefs: UserPreferences) : ViewModel() { +class LoginViewModel(private val prefs: UserPreferences, private val appContext: Context) : ViewModel() { + + private val tokenStorage = TokenStorage(appContext) val isLoggedIn = prefs.isLoggedInFlow.stateIn(viewModelScope, SharingStarted.Lazily, false) val lastUserId = prefs.lastUserLoginFlow.stateIn(viewModelScope, SharingStarted.Lazily, null) fun login(serverUrl: String, username: String, password: String, viewModel: MainViewModel) { - val uuid = UUID.randomUUID(); - val acc = Account(uuid, username, "Pfadi-WG", null, serverUrl) //TODO: get data from backend - viewModel.addAccount(acc) - println("Logged in as ${username} in ${serverUrl}") - viewModelScope.launch { prefs.saveLogin(uuid.toString()) } + viewModelScope.launch { + val api = RetrofitProvider.create(serverUrl) + + try { + val response = withContext(Dispatchers.IO) { + api.login(LoginRequest(username, password)) + } + + if(response.isSuccessful) { + val body = response.body() ?: run { + return@launch + } + + val access = body.access_token + val refresh = body.refresh_token + + tokenStorage.saveTokens(body.user.id, access, refresh) + + val account = Account( + id = UUID.fromString(body.user.id), + name = username, + wgName = body.wgName, + avatarUrl = null, + serverUrl = serverUrl, + role = body.user.role + ) + + viewModel.addAccount(account) + + prefs.saveLogin(body.user.id) + } else { + println("Login failed: ${response.code()} ${response.errorBody()?.toString()}") + } + } catch(e: Exception) { + e.printStackTrace() + } + } } fun logout() {