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" />
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 5fbc82b..9bc7363 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -59,4 +59,16 @@
android:text="Register"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_register.xml b/app/src/main/res/layout/activity_register.xml
index 2e23034..7adabd8 100644
--- a/app/src/main/res/layout/activity_register.xml
+++ b/app/src/main/res/layout/activity_register.xml
@@ -6,10 +6,100 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".RegisterActivity">
-
+ android:orientation="vertical">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml
new file mode 100644
index 0000000..13af1d6
--- /dev/null
+++ b/app/src/main/res/xml/network_security_config.xml
@@ -0,0 +1,8 @@
+
+
+
+ 10.0.2.2
+
+
+