From e3f465d925c13ee367bcf3c0b5960a1905e9854b Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 13 Dec 2025 18:31:36 +0100 Subject: [PATCH] Odradjeno register i login primer 26124006: password --- app/build.gradle.kts | 6 +- app/src/main/AndroidManifest.xml | 6 +- .../com/example/gallery/LoginActivity.java | 99 ++++++++++++-- .../com/example/gallery/MainActivity.java | 55 +++++--- .../com/example/gallery/RegisterActivity.java | 129 ++++++++++++++++-- .../com/example/gallery/UserResponse.java | 40 ++++++ .../com/example/gallery/api/ApiClient.java | 21 +++ .../com/example/gallery/api/ApiService.java | 25 ++++ .../example/gallery/models/LoginRequest.java | 11 ++ .../example/gallery/models/LoginResponse.java | 9 ++ .../gallery/models/RegisterRequest.java | 30 ++++ .../gallery/models/RegisterResponse.java | 14 ++ app/src/main/res/layout/activity_login.xml | 36 +++-- app/src/main/res/layout/activity_main.xml | 12 ++ app/src/main/res/layout/activity_register.xml | 96 ++++++++++++- .../main/res/xml/network_security_config.xml | 8 ++ 16 files changed, 541 insertions(+), 56 deletions(-) create mode 100644 app/src/main/java/com/example/gallery/UserResponse.java create mode 100644 app/src/main/java/com/example/gallery/api/ApiClient.java create mode 100644 app/src/main/java/com/example/gallery/api/ApiService.java create mode 100644 app/src/main/java/com/example/gallery/models/LoginRequest.java create mode 100644 app/src/main/java/com/example/gallery/models/LoginResponse.java create mode 100644 app/src/main/java/com/example/gallery/models/RegisterRequest.java create mode 100644 app/src/main/java/com/example/gallery/models/RegisterResponse.java create mode 100644 app/src/main/res/xml/network_security_config.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7c0f6f5..da8f44d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -37,7 +37,11 @@ dependencies { implementation(libs.material) implementation(libs.activity) implementation(libs.constraintlayout) + + implementation("com.squareup.retrofit2:retrofit:2.9.0") + implementation("com.squareup.retrofit2:converter-gson:2.9.0") + testImplementation(libs.junit) androidTestImplementation(libs.ext.junit) androidTestImplementation(libs.espresso.core) -} \ No newline at end of file +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 07e89ff..a0eaf6e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ - + + tools:targetApi="31" + android:networkSecurityConfig="@xml/network_security_config"> @@ -27,6 +28,7 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/example/gallery/LoginActivity.java b/app/src/main/java/com/example/gallery/LoginActivity.java index f17ee2e..9f5cdd1 100644 --- a/app/src/main/java/com/example/gallery/LoginActivity.java +++ b/app/src/main/java/com/example/gallery/LoginActivity.java @@ -1,24 +1,103 @@ package com.example.gallery; +import android.content.Intent; import android.os.Bundle; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Toast; -import androidx.activity.EdgeToEdge; import androidx.appcompat.app.AppCompatActivity; -import androidx.core.graphics.Insets; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsCompat; + +import com.example.gallery.api.ApiClient; +import com.example.gallery.api.ApiService; +import com.example.gallery.models.LoginRequest; +import com.example.gallery.models.LoginResponse; +import com.example.gallery.UserResponse; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; public class LoginActivity extends AppCompatActivity { + EditText usernameEdit, passwordEdit; + Button loginBtn; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - EdgeToEdge.enable(this); setContentView(R.layout.activity_login); - ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { - Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); - return insets; + + // Updated ids from your XML + usernameEdit = findViewById(R.id.usernameTxt); + passwordEdit = findViewById(R.id.userpasswordTxt); + loginBtn = findViewById(R.id.btnLogin); + + loginBtn.setOnClickListener(v -> { + String username = usernameEdit.getText().toString().trim(); + String password = passwordEdit.getText().toString().trim(); + + if (username.isEmpty() || password.isEmpty()) { + Toast.makeText(LoginActivity.this, "All fields are required", Toast.LENGTH_SHORT).show(); + return; + } + + ApiService apiService = ApiClient.getClient().create(ApiService.class); + LoginRequest request = new LoginRequest(username, password); + + // 🔹 Login API call + apiService.login(request).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + String token = "Token " + response.body().getToken(); + + // 🔹 Save token & login state in SharedPreferences + getSharedPreferences("MyAppPrefs", MODE_PRIVATE) + .edit() + .putString("authToken", token) + .putBoolean("isLoggedIn", true) + .apply(); + + Toast.makeText(LoginActivity.this, "Login successful", Toast.LENGTH_SHORT).show(); + + // 🔹 Verify token with /api/auth/me/ + apiService.getMe(token).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + Toast.makeText(LoginActivity.this, + "Welcome " + response.body().getUsername(), + Toast.LENGTH_SHORT).show(); + + // Go to MainActivity + startActivity(new Intent(LoginActivity.this, MainActivity.class)); + finish(); + } else { + Toast.makeText(LoginActivity.this, + "Failed to verify user", + Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + Toast.makeText(LoginActivity.this, + "Verification error: " + t.getMessage(), + Toast.LENGTH_SHORT).show(); + } + }); + + } else { + Toast.makeText(LoginActivity.this, "Login failed", Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + Toast.makeText(LoginActivity.this, "Server error: " + t.getMessage(), Toast.LENGTH_SHORT).show(); + } + }); }); } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/example/gallery/MainActivity.java b/app/src/main/java/com/example/gallery/MainActivity.java index 2082858..3611ac7 100644 --- a/app/src/main/java/com/example/gallery/MainActivity.java +++ b/app/src/main/java/com/example/gallery/MainActivity.java @@ -7,13 +7,11 @@ import android.widget.Button; import androidx.activity.EdgeToEdge; import androidx.appcompat.app.AppCompatActivity; -import androidx.core.graphics.Insets; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsCompat; public class MainActivity extends AppCompatActivity { - Button loginBtn, registerBtn; + Button loginBtn, registerBtn, logoutBtn; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -22,21 +20,46 @@ public class MainActivity extends AppCompatActivity { loginBtn = findViewById(R.id.Loginbtn); registerBtn = findViewById(R.id.Registerbtn); + logoutBtn = findViewById(R.id.btnLogout); - loginBtn.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent intent = new Intent(MainActivity.this, LoginActivity.class); - startActivity(intent); - } + // Button listeners + loginBtn.setOnClickListener(v -> { + Intent intent = new Intent(MainActivity.this, LoginActivity.class); + startActivity(intent); }); - registerBtn.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent intent = new Intent(MainActivity.this, RegisterActivity.class); - startActivity(intent); - } + registerBtn.setOnClickListener(v -> { + Intent intent = new Intent(MainActivity.this, RegisterActivity.class); + startActivity(intent); + }); + + // Check login state from SharedPreferences + boolean isLoggedIn = getSharedPreferences("MyAppPrefs", MODE_PRIVATE) + .getBoolean("isLoggedIn", false); + + if (isLoggedIn) { + // User is logged in → hide login/register, show logout + loginBtn.setVisibility(View.GONE); + registerBtn.setVisibility(View.GONE); + logoutBtn.setVisibility(View.VISIBLE); + } else { + // User not logged in → show login/register, hide logout + loginBtn.setVisibility(View.VISIBLE); + registerBtn.setVisibility(View.VISIBLE); + logoutBtn.setVisibility(View.GONE); + } + + // Logout button + logoutBtn.setOnClickListener(v -> { + // Clear token and login state + getSharedPreferences("MyAppPrefs", MODE_PRIVATE) + .edit() + .remove("authToken") // remove saved token + .putBoolean("isLoggedIn", false) // set logged out + .apply(); + + // Refresh activity to reflect state change + recreate(); }); } } \ No newline at end of file diff --git a/app/src/main/java/com/example/gallery/RegisterActivity.java b/app/src/main/java/com/example/gallery/RegisterActivity.java index ee098cd..5b8b555 100644 --- a/app/src/main/java/com/example/gallery/RegisterActivity.java +++ b/app/src/main/java/com/example/gallery/RegisterActivity.java @@ -1,24 +1,133 @@ package com.example.gallery; +import android.content.Intent; import android.os.Bundle; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Toast; -import androidx.activity.EdgeToEdge; import androidx.appcompat.app.AppCompatActivity; -import androidx.core.graphics.Insets; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsCompat; + +import com.example.gallery.api.ApiClient; +import com.example.gallery.api.ApiService; +import com.example.gallery.models.RegisterRequest; +import com.example.gallery.models.RegisterResponse; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; public class RegisterActivity extends AppCompatActivity { + EditText username, email, ime, prezime, index, password; + Button btnRegister; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - EdgeToEdge.enable(this); setContentView(R.layout.activity_register); - ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { - Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); - return insets; + + username = findViewById(R.id.usernameEditText); + email = findViewById(R.id.emailEditText); + ime = findViewById(R.id.imeEditText); + prezime = findViewById(R.id.prezimeEditText); + index = findViewById(R.id.indexEditText); + password = findViewById(R.id.passwordEditText); + btnRegister = findViewById(R.id.btnRegister); + + Toast.makeText(this, "RegisterActivity loaded", Toast.LENGTH_SHORT).show(); + + btnRegister.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String u = username.getText().toString().trim(); + String e = email.getText().toString().trim(); + String f = ime.getText().toString().trim(); + String l = prezime.getText().toString().trim(); + String s = index.getText().toString().trim(); + String p = password.getText().toString().trim(); + + // --------------------------- + // ✅ FRONTEND VALIDATIONS + // --------------------------- + if (u.isEmpty() || u.length() > 150) { + username.setError("Username must be 1-150 characters"); + return; + } + + if (e.isEmpty() || !android.util.Patterns.EMAIL_ADDRESS.matcher(e).matches()) { + email.setError("Enter a valid email"); + return; + } + + if (f.isEmpty() || f.length() > 30) { + ime.setError("First name must be 1-30 characters"); + return; + } + + if (l.isEmpty() || l.length() > 30) { + prezime.setError("Last name must be 1-30 characters"); + return; + } + + if (s.isEmpty() || s.length() > 8) { + index.setError("School index must be 1-8 characters"); + return; + } + + if (p.isEmpty() || p.length() < 8) { + password.setError("Password must be at least 8 characters"); + return; + } + + // --------------------------- + // 🔹 API CALL + // --------------------------- + ApiService apiService = ApiClient.getClient().create(ApiService.class); + RegisterRequest request = new RegisterRequest(u, e, f, l, s, p); + + apiService.register(request).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + + Toast.makeText(RegisterActivity.this, + "HTTP code: " + response.code(), + Toast.LENGTH_SHORT).show(); + + if (response.isSuccessful() && response.body() != null) { + Toast.makeText(RegisterActivity.this, + response.body().getMessage(), + Toast.LENGTH_SHORT).show(); + // Go to login + Intent intent = new Intent(RegisterActivity.this, LoginActivity.class); + startActivity(intent); // go back to previous activity + } else { + try { + String errorBody = response.errorBody() != null + ? response.errorBody().string() + : "No error body"; + + Toast.makeText(RegisterActivity.this, + errorBody, + Toast.LENGTH_LONG).show(); + + } catch (Exception e) { + Toast.makeText(RegisterActivity.this, + "Error reading errorBody", + Toast.LENGTH_SHORT).show(); + } + } + } + + @Override + public void onFailure(Call call, Throwable t) { + Toast.makeText(RegisterActivity.this, + "Server error: " + t.getMessage(), + Toast.LENGTH_SHORT).show(); + } + }); + } }); } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/example/gallery/UserResponse.java b/app/src/main/java/com/example/gallery/UserResponse.java new file mode 100644 index 0000000..28cd888 --- /dev/null +++ b/app/src/main/java/com/example/gallery/UserResponse.java @@ -0,0 +1,40 @@ +package com.example.gallery; + +import com.google.gson.annotations.SerializedName; + +public class UserResponse { + + private String username; + + @SerializedName("email") + private String email; + + @SerializedName("first_name") + private String firstName; + + @SerializedName("last_name") + private String lastName; + + @SerializedName("school_index") + private String schoolIndex; + + public String getUsername() { + return username; + } + + public String getEmail() { + return email; + } + + public String getFirstName() { + return firstName; + } + + public String getLastName() { + return lastName; + } + + public String getSchoolIndex() { + return schoolIndex; + } +} diff --git a/app/src/main/java/com/example/gallery/api/ApiClient.java b/app/src/main/java/com/example/gallery/api/ApiClient.java new file mode 100644 index 0000000..a66e98f --- /dev/null +++ b/app/src/main/java/com/example/gallery/api/ApiClient.java @@ -0,0 +1,21 @@ +package com.example.gallery.api; + +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +public class ApiClient { + + private static final String BASE_URL = "https://gallery.steve-dekart.xyz/"; + + private static Retrofit retrofit; + + public static Retrofit getClient() { + if (retrofit == null) { + retrofit = new Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(GsonConverterFactory.create()) + .build(); + } + return retrofit; + } +} diff --git a/app/src/main/java/com/example/gallery/api/ApiService.java b/app/src/main/java/com/example/gallery/api/ApiService.java new file mode 100644 index 0000000..64ef21b --- /dev/null +++ b/app/src/main/java/com/example/gallery/api/ApiService.java @@ -0,0 +1,25 @@ +package com.example.gallery.api; + +import com.example.gallery.UserResponse; +import com.example.gallery.models.LoginRequest; +import com.example.gallery.models.LoginResponse; +import com.example.gallery.models.RegisterRequest; +import com.example.gallery.models.RegisterResponse; + +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.GET; +import retrofit2.http.Header; +import retrofit2.http.POST; + +public interface ApiService { + + @POST("api/auth/register/") + Call register(@Body RegisterRequest request); + + @POST("api/auth/login/") + Call login(@Body LoginRequest request); + + @GET("api/auth/me/") + Call getMe(@Header("Authorization") String token); +} \ No newline at end of file diff --git a/app/src/main/java/com/example/gallery/models/LoginRequest.java b/app/src/main/java/com/example/gallery/models/LoginRequest.java new file mode 100644 index 0000000..d2ebf07 --- /dev/null +++ b/app/src/main/java/com/example/gallery/models/LoginRequest.java @@ -0,0 +1,11 @@ +package com.example.gallery.models; + +public class LoginRequest { + private String username; + private String password; + + public LoginRequest(String username, String password) { + this.username = username; + this.password = password; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/gallery/models/LoginResponse.java b/app/src/main/java/com/example/gallery/models/LoginResponse.java new file mode 100644 index 0000000..18edd1b --- /dev/null +++ b/app/src/main/java/com/example/gallery/models/LoginResponse.java @@ -0,0 +1,9 @@ +package com.example.gallery.models; + +public class LoginResponse { + private String token; + + public String getToken() { + return token; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/gallery/models/RegisterRequest.java b/app/src/main/java/com/example/gallery/models/RegisterRequest.java new file mode 100644 index 0000000..cda4d15 --- /dev/null +++ b/app/src/main/java/com/example/gallery/models/RegisterRequest.java @@ -0,0 +1,30 @@ +package com.example.gallery.models; + +import com.google.gson.annotations.SerializedName; + +public class RegisterRequest { + + private String username; + private String email; + + @SerializedName("first_name") + private String ime; + + @SerializedName("last_name") + private String prezime; + + @SerializedName("school_index") + private String index; + + private String password; + + public RegisterRequest(String username, String email, String ime, + String prezime, String index, String password) { + this.username = username; + this.email = email; + this.ime = ime; + this.prezime = prezime; + this.index = index; + this.password = password; + } +} diff --git a/app/src/main/java/com/example/gallery/models/RegisterResponse.java b/app/src/main/java/com/example/gallery/models/RegisterResponse.java new file mode 100644 index 0000000..f5d5d14 --- /dev/null +++ b/app/src/main/java/com/example/gallery/models/RegisterResponse.java @@ -0,0 +1,14 @@ +package com.example.gallery.models; + +public class RegisterResponse { + private boolean success; + private String message; + + public boolean isSuccess() { + return success; + } + + public String getMessage() { + return message; + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index 739a315..4fcf6f2 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -10,30 +10,38 @@ + android:orientation="vertical"> + + android:textSize="21dp" /> + + android:layout_height="72dp" + android:hint="Korisničko ime" /> + + android:layout_width="126dp" + android:layout_height="31dp" + android:text="Password" /> + + android:layout_width="322dp" + android:layout_height="76dp" + android:hint="Lozinka" /> + +