Implement Login API

This commit is contained in:
Fawkes100 2025-01-18 20:46:05 +01:00
parent 71bc0623a5
commit 46852a9b5a
19 changed files with 404 additions and 14 deletions

View File

@ -41,9 +41,13 @@ dependencies {
implementation(libs.navigation.fragment)
implementation(libs.navigation.ui)
implementation(libs.room.runtime)
implementation(libs.annotation)
testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core)
annotationProcessor(libs.room.compiler)
implementation("com.github.chrisbanes:PhotoView:2.3.0")
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.9.1")
}

View File

@ -1,7 +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"
android:dataExtractionRules="@xml/data_extraction_rules"

View File

@ -1,9 +1,8 @@
package com.stormtales.notevault;
import android.os.Bundle;
import android.view.View;
import android.view.MenuItem;
import android.view.Menu;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.navigation.NavigationView;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
@ -12,6 +11,7 @@ import androidx.navigation.ui.NavigationUI;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.appcompat.app.AppCompatActivity;
import com.stormtales.notevault.databinding.ActivityMainBinding;
import com.stormtales.notevault.network.auth.AuthService;
public class MainActivity extends AppCompatActivity {
@ -26,14 +26,17 @@ public class MainActivity extends AppCompatActivity {
setContentView(binding.getRoot());
setSupportActionBar(binding.appBarMain.toolbar);
/*binding.appBarMain.fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null)
.setAnchorView(R.id.fab).show();
binding.appBarMain.toolbar.setOnMenuItemClickListener(item -> {
if (item.getItemId() == R.id.auth_action) {
if (new AuthService(getApplicationContext()).isLoggedIn()) {
//performLogout(); // Logout-Logik aufrufen
} else {
showLoginDialog(); // Zeige das Login-Fenster an
}
});*/
return true;
}
return false;
});
DrawerLayout drawer = binding.drawerLayout;
NavigationView navigationView = binding.navView;
// Passing each menu ID as a set of Ids because each
@ -60,4 +63,20 @@ public class MainActivity extends AppCompatActivity {
return NavigationUI.navigateUp(navController, mAppBarConfiguration)
|| super.onSupportNavigateUp();
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem loginItem = menu.findItem(R.id.auth_action);
if (new AuthService(this).isLoggedIn()) {
loginItem.setIcon(R.drawable.logout); // Setze das Logout-Symbol
} else {
loginItem.setIcon(R.drawable.login); // Setze das Login-Symbol
}
return super.onPrepareOptionsMenu(menu);
}
public void showLoginDialog() {
/*LoginFragment loginFragment = new LoginFragment();
loginFragment.show(getSupportFragmentManager(), "login");*/
}
}

View File

@ -0,0 +1,23 @@
package com.stormtales.notevault.data.model;
/**
* Data class that captures user information for logged in users retrieved from LoginRepository
*/
public class LoggedInUser {
private String userId;
private String displayName;
public LoggedInUser(String userId, String displayName) {
this.userId = userId;
this.displayName = displayName;
}
public String getUserId() {
return userId;
}
public String getDisplayName() {
return displayName;
}
}

View File

@ -0,0 +1,6 @@
package com.stormtales.notevault.network;
public interface APICallback {
void onSuccess();
void onError(String error);
}

View File

@ -0,0 +1,36 @@
package com.stormtales.notevault.network;
import android.content.Context;
import android.content.SharedPreferences;
import com.stormtales.notevault.network.auth.AuthInterceptor;
import com.stormtales.notevault.network.auth.TokenManager;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class NetworkModule {
private static final String BASE_URL = "https://192.168.178.30:8000/";
private static Retrofit retrofit;
public static Retrofit getRetrofitInstance(Context context) {
if (retrofit == null) {
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
TokenManager tokenManager = new TokenManager(context);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new AuthInterceptor(tokenManager))
.addInterceptor(loggingInterceptor)
.build();
retrofit = new Retrofit.Builder()
.baseUrl("http://192.168.178.30:8000/")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}

View File

@ -0,0 +1,16 @@
package com.stormtales.notevault.network.auth;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.Headers;
import retrofit2.http.POST;
public interface AuthAPI {
@POST("/login/")
Call<LoginResponse> login(@Body LoginRequest loginRequest);
/*@POST("/register/")
@Headers("Content-Type: application/json")
Call<StatusResponse> registration(@Body RegisterRequest registerRequest);*/
}

View File

@ -0,0 +1,29 @@
package com.stormtales.notevault.network.auth;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import java.io.IOException;
public class AuthInterceptor implements Interceptor {
private TokenManager tokenManager;
public AuthInterceptor(TokenManager tokenManager) {
this.tokenManager = tokenManager;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
String token = tokenManager.getToken();
if (token == null) {
return chain.proceed(originalRequest);
}
Request authenticatedRequest = originalRequest.newBuilder()
.header("Authorization", "Bearer " + token).build();
return chain.proceed(authenticatedRequest);
}
}

View File

@ -0,0 +1,81 @@
package com.stormtales.notevault.network.auth;
import android.content.Context;
import android.text.TextUtils;
import com.stormtales.notevault.network.APICallback;
import com.stormtales.notevault.network.NetworkModule;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class AuthService {
private final AuthAPI authAPI;
private final TokenManager tokenManager;
public AuthService(Context context) {
this.authAPI = NetworkModule.getRetrofitInstance(context).create(AuthAPI.class);
this.tokenManager = new TokenManager(context);
}
public void performLogin(String email, String password, APICallback callback) {
LoginRequest loginRequest = new LoginRequest(email, password);
authAPI.login(loginRequest).enqueue(new Callback<LoginResponse>() {
@Override
public void onResponse(Call<LoginResponse> call, Response<LoginResponse> response) {
if (response.isSuccessful() && response.body() != null) {
String token = response.body().getToken();
saveToken(token);
// Erfolgsrückmeldung an den Callback senden
callback.onSuccess();
} else {
// Fehlermeldung an den Callback senden
callback.onError("Login fehlgeschlagen. Überprüfe Benutzername und Passwort.");
}
}
@Override
public void onFailure(Call<LoginResponse> call, Throwable t) {
// Netzwerkfehler an den Callback senden
callback.onError("Netzwerkfehler: " + t.getMessage());
}
});
}
public void performRegistration(String email, String username, String password, APICallback callback) {
/*RegisterRequest registerRequest = new RegisterRequest(username, password, email);
authService.registration(registerRequest).enqueue(new Callback<StatusResponse>() {
@Override
public void onResponse(Call<StatusResponse> call, Response<StatusResponse> response) {
if(response.isSuccessful() && response.body() != null) {
callback.onSuccess();
} else {
callback.onError("Registration fehlgeschlagen. Überprüfe Benutzername und Passwort.");
}
}
@Override
public void onFailure(Call<StatusResponse> call, Throwable throwable) {
callback.onError("Netzwerkfehler: " + throwable.getMessage());
}
});*/
}
private void saveToken(String token) {
tokenManager.saveToken(token);
}
public String getToken() {
return tokenManager.getToken();
}
public void logout() {
tokenManager.clearToken();
}
public boolean isLoggedIn() {
return !TextUtils.isEmpty(tokenManager.getToken());
}
}

View File

@ -0,0 +1,11 @@
package com.stormtales.notevault.network.auth;
public class LoginRequest {
private String email;
private String password;
public LoginRequest(String email, String password) {
this.email = email;
this.password = password;
}
}

View File

@ -0,0 +1,22 @@
package com.stormtales.notevault.network.auth;
public class LoginResponse {
private String token;
private String username;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}

View File

@ -0,0 +1,31 @@
package com.stormtales.notevault.network.auth;
import android.content.Context;
import android.content.SharedPreferences;
public class TokenManager {
private static final String PREF_NAME = "app_preferences";
private static final String KEY_TOKEN = "jwt_token";
private SharedPreferences sharedPreferences;
public TokenManager(Context context) {
sharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
}
public void saveToken(String token) {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(KEY_TOKEN, token);
editor.apply();
}
public String getToken() {
return sharedPreferences.getString(KEY_TOKEN, null);
}
public void clearToken() {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.remove(KEY_TOKEN);
editor.apply();
}
}

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M480,840v-80h280v-560L480,200v-80h280q33,0 56.5,23.5T840,200v560q0,33 -23.5,56.5T760,840L480,840ZM400,680 L345,622 447,520L120,520v-80h327L345,338l55,-58 200,200 -200,200Z"
android:fillColor="#e8eaed"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M200,840q-33,0 -56.5,-23.5T120,760v-560q0,-33 23.5,-56.5T200,120h280v80L200,200v560h280v80L200,840ZM640,680 L585,622 687,520L360,520v-80h327L585,338l55,-58 200,200 -200,200Z"
android:fillColor="#e8eaed"/>
</vector>

View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/fragment_vertical_margin"
android:paddingLeft="@dimen/fragment_horizontal_margin"
android:paddingRight="@dimen/fragment_horizontal_margin"
android:paddingTop="@dimen/fragment_vertical_margin"
tools:context=".ui.login.LoginFragment">
<EditText
android:id="@+id/username"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="96dp"
android:layout_marginEnd="24dp"
android:autofillHints="@string/prompt_email"
android:hint="@string/prompt_email"
android:inputType="textEmailAddress"
android:selectAllOnFocus="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<EditText
android:id="@+id/password"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="24dp"
android:autofillHints="@string/prompt_password"
android:hint="@string/prompt_password"
android:imeActionLabel="@string/action_sign_in_short"
android:imeOptions="actionDone"
android:inputType="textPassword"
android:selectAllOnFocus="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/username"/>
<Button
android:id="@+id/login"
android:enabled="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:layout_marginStart="48dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="48dp"
android:layout_marginBottom="64dp"
android:text="@string/action_sign_in"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/password"
app:layout_constraintVertical_bias="0.2"/>
<ProgressBar
android:id="@+id/loading"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="32dp"
android:layout_marginTop="64dp"
android:layout_marginEnd="32dp"
android:layout_marginBottom="64dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/password"
app:layout_constraintStart_toStartOf="@+id/password"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.3"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/action_settings"
android:title="@string/action_settings"
android:orderInCategory="100"
app:showAsAction="never"/>
<item android:id="@+id/auth_action"
android:title="@string/login"
android:icon="@drawable/login"
app:showAsAction="always"/>
</menu>

View File

@ -5,4 +5,7 @@
<dimen name="nav_header_vertical_spacing">8dp</dimen>
<dimen name="nav_header_height">176dp</dimen>
<dimen name="fab_margin">16dp</dimen>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="fragment_horizontal_margin">16dp</dimen>
<dimen name="fragment_vertical_margin">16dp</dimen>
</resources>

View File

@ -12,4 +12,14 @@
<string name="menu_slideshow">Slideshow</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
<string name="login">Login</string>
<!-- Strings related to login -->
<string name="prompt_email">Email</string>
<string name="prompt_password">Password</string>
<string name="action_sign_in">Sign in or register</string>
<string name="action_sign_in_short">Sign in</string>
<string name="welcome">"Welcome!"</string>
<string name="invalid_username">Not a valid username</string>
<string name="invalid_password">Password must be >5 characters</string>
<string name="login_failed">"Login failed"</string>
</resources>

View File

@ -12,6 +12,7 @@ navigationFragment = "2.8.5"
navigationUi = "2.8.5"
roomRuntime = "2.6.1"
roomCompiler = "2.6.1"
annotation = "1.9.1"
[libraries]
junit = { group = "junit", name = "junit", version.ref = "junit" }
@ -26,6 +27,7 @@ navigation-fragment = { group = "androidx.navigation", name = "navigation-fragme
navigation-ui = { group = "androidx.navigation", name = "navigation-ui", version.ref = "navigationUi" }
room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "roomRuntime" }
room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "roomCompiler" }
annotation = { group = "androidx.annotation", name = "annotation", version.ref = "annotation" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }