Fali jos auto-update kada se oceni nesto

This commit is contained in:
Daniel 2026-01-15 18:24:44 +01:00
parent cd3b3f6dcc
commit 2cbd885b62
14 changed files with 477 additions and 163 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

View File

@ -1,23 +1,28 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.INTERNET" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.Gallery" android:theme="@style/Theme.Gallery"
tools:targetApi="31" tools:targetApi="31">
android:networkSecurityConfig="@xml/network_security_config"> <activity
android:name=".VideoPlayerActivity"
android:exported="false" />
<activity <activity
android:name=".NewPostActivity" android:name=".NewPostActivity"
android:exported="false" android:exported="false"
tools:ignore="Instantiatable" android:windowSoftInputMode="adjustResize|stateHidden"
android:windowSoftInputMode="adjustResize|stateHidden"/> tools:ignore="Instantiatable" />
<activity <activity
android:name=".RegisterActivity" android:name=".RegisterActivity"
android:exported="false" /> android:exported="false" />
@ -33,7 +38,6 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
</application> </application>
</manifest> </manifest>

View File

@ -20,7 +20,8 @@ import retrofit2.Response;
public class LoginActivity extends AppCompatActivity { public class LoginActivity extends AppCompatActivity {
private EditText usernameEdit, passwordEdit; private EditText usernameEdit;
private EditText passwordEdit;
private Button loginBtn; private Button loginBtn;
private TextView registerTV; private TextView registerTV;
@ -29,12 +30,19 @@ public class LoginActivity extends AppCompatActivity {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login); setContentView(R.layout.activity_login);
// Views
usernameEdit = findViewById(R.id.usernameTxt); usernameEdit = findViewById(R.id.usernameTxt);
passwordEdit = findViewById(R.id.userpasswordTxt); passwordEdit = findViewById(R.id.userpasswordTxt);
loginBtn = findViewById(R.id.btnLogin); loginBtn = findViewById(R.id.btnLogin);
registerTV = findViewById(R.id.registerTV); registerTV = findViewById(R.id.registerTV);
// Login button
loginBtn.setOnClickListener(v -> login()); loginBtn.setOnClickListener(v -> login());
// Register text RegisterActivity
registerTV.setOnClickListener(v -> {
startActivity(new Intent(LoginActivity.this, RegisterActivity.class));
});
} }
private void login() { private void login() {
@ -52,27 +60,33 @@ public class LoginActivity extends AppCompatActivity {
apiService.login(request).enqueue(new Callback<LoginResponse>() { apiService.login(request).enqueue(new Callback<LoginResponse>() {
@Override @Override
public void onResponse(Call<LoginResponse> call, Response<LoginResponse> response) { public void onResponse(Call<LoginResponse> call, Response<LoginResponse> response) {
if (response.isSuccessful() && response.body() != null) { if (response.isSuccessful() && response.body() != null) {
// 🔑 STORE RAW TOKEN ONLY // Save auth state
getSharedPreferences("MyAppPrefs", MODE_PRIVATE) getSharedPreferences("MyAppPrefs", MODE_PRIVATE)
.edit() .edit()
.putString("authToken", response.body().getToken()) .putString("authToken", response.body().getToken())
.putBoolean("isLoggedIn", true) .putBoolean("isLoggedIn", true)
.apply(); .apply();
Toast.makeText(LoginActivity.this, "Login successful", Toast.LENGTH_SHORT).show(); Toast.makeText(LoginActivity.this,
"Login successful", Toast.LENGTH_SHORT).show();
startActivity(new Intent(LoginActivity.this, MainActivity.class)); startActivity(new Intent(LoginActivity.this, MainActivity.class));
finish(); finish();
} else { } else {
Toast.makeText(LoginActivity.this, "Login failed", Toast.LENGTH_SHORT).show(); Toast.makeText(LoginActivity.this,
"Invalid username or password", Toast.LENGTH_SHORT).show();
} }
} }
@Override @Override
public void onFailure(Call<LoginResponse> call, Throwable t) { public void onFailure(Call<LoginResponse> call, Throwable t) {
Toast.makeText(LoginActivity.this, "Server error: " + t.getMessage(), Toast.LENGTH_SHORT).show(); Toast.makeText(LoginActivity.this,
"Server error: " + t.getMessage(),
Toast.LENGTH_SHORT).show();
} }
}); });
} }

View File

@ -4,6 +4,9 @@ import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
@ -28,107 +31,169 @@ public class MainActivity extends AppCompatActivity {
private Button loginBtn, registerBtn, logoutBtn, newPostBtn; private Button loginBtn, registerBtn, logoutBtn, newPostBtn;
private RecyclerView recyclerView; private RecyclerView recyclerView;
private PostsAdapter postsAdapter; private PostsAdapter postsAdapter;
private ImageButton burgerMenuBtn;
private LinearLayout dropdownMenu;
private TextView menuLogout, menuNewPost;
private ImageButton filterBtnToolbar;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); setContentView(R.layout.activity_main);
// Initialize all views BEFORE using them
loginBtn = findViewById(R.id.Loginbtn); loginBtn = findViewById(R.id.Loginbtn);
registerBtn = findViewById(R.id.Registerbtn); registerBtn = findViewById(R.id.Registerbtn);
logoutBtn = findViewById(R.id.btnLogout); logoutBtn = findViewById(R.id.btnLogout);
newPostBtn = findViewById(R.id.newPostBtn); newPostBtn = findViewById(R.id.newPostBtn);
recyclerView = findViewById(R.id.postsRecyclerView); recyclerView = findViewById(R.id.postsRecyclerView);
burgerMenuBtn = findViewById(R.id.burgerMenuBtn);
dropdownMenu = findViewById(R.id.dropdownMenu);
menuLogout = findViewById(R.id.menuLogout);
menuNewPost = findViewById(R.id.menuNewPost);
filterBtnToolbar = findViewById(R.id.filterBtnToolbar);
// RecyclerView setup
recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.setLayoutManager(new LinearLayoutManager(this));
postsAdapter = new PostsAdapter(new ArrayList<>());
boolean isLoggedIn = getSharedPreferences("MyAppPrefs", MODE_PRIVATE)
.getBoolean("isLoggedIn", false);
postsAdapter = new PostsAdapter(this, new ArrayList<>(), isLoggedIn);
recyclerView.setAdapter(postsAdapter); recyclerView.setAdapter(postsAdapter);
setupButtons(); setupButtons();
setupDropdownMenu();
updateAuthUI(); updateAuthUI();
loadPublications(); loadPublications();
setupFilterButton();
} }
private void setupButtons() { private void setupButtons() {
if (loginBtn != null) {
loginBtn.setOnClickListener(v -> loginBtn.setOnClickListener(v ->
startActivity(new Intent(MainActivity.this, LoginActivity.class)) startActivity(new Intent(this, LoginActivity.class)));
);
}
if (registerBtn != null) {
registerBtn.setOnClickListener(v -> registerBtn.setOnClickListener(v ->
startActivity(new Intent(MainActivity.this, RegisterActivity.class)) startActivity(new Intent(this, RegisterActivity.class)));
);
}
if (logoutBtn != null) {
logoutBtn.setOnClickListener(v -> { logoutBtn.setOnClickListener(v -> {
getSharedPreferences("MyAppPrefs", MODE_PRIVATE).edit().clear().apply(); getSharedPreferences("MyAppPrefs", MODE_PRIVATE)
.edit().clear().apply();
updateAuthUI(); updateAuthUI();
Toast.makeText(MainActivity.this, "Logged out", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "Logged out", Toast.LENGTH_SHORT).show();
}); });
}
if (newPostBtn != null) {
newPostBtn.setOnClickListener(v -> newPostBtn.setOnClickListener(v ->
startActivityForResult( startActivityForResult(
new Intent(MainActivity.this, NewPostActivity.class), 100) new Intent(this, NewPostActivity.class), 100));
);
}
} }
private void setupDropdownMenu() {
burgerMenuBtn.setOnClickListener(v ->
dropdownMenu.setVisibility(
dropdownMenu.getVisibility() == View.VISIBLE
? View.GONE : View.VISIBLE));
menuLogout.setOnClickListener(v -> {
getSharedPreferences("MyAppPrefs", MODE_PRIVATE)
.edit().clear().apply();
updateAuthUI();
dropdownMenu.setVisibility(View.GONE);
});
menuNewPost.setOnClickListener(v -> {
startActivityForResult(
new Intent(this, NewPostActivity.class), 100);
dropdownMenu.setVisibility(View.GONE);
});
}
//Sortiranje i filtriranje.
private void setupFilterButton() {
filterBtnToolbar.setOnClickListener(v -> {
String[] options = {
"Show all posts",
"Only pinned",
"Not pinned",
"Only images",
"Only videos",
"Sort by newest",
"Sort by rating"
};
new androidx.appcompat.app.AlertDialog.Builder(this)
.setTitle("Filter & Sort")
.setItems(options, (dialog, which) -> {
switch (which) {
case 0: //All.
postsAdapter.filterPinned(null);
postsAdapter.filterContentType(null);
break;
case 1: //Pinned.
postsAdapter.filterPinned(true);
break;
case 2: //Not pinned.
postsAdapter.filterPinned(false);
break;
case 3: // images
postsAdapter.filterContentType("image");
break;
case 4: // videos
postsAdapter.filterContentType("video");
break;
case 5: // newest
postsAdapter.sortBy(PostsAdapter.SortMode.TIME);
break;
case 6: // rating
postsAdapter.sortBy(PostsAdapter.SortMode.RATING);
break;
}
})
.show();
});
}
private void updateAuthUI() { private void updateAuthUI() {
boolean isLoggedIn = getSharedPreferences("MyAppPrefs", MODE_PRIVATE) boolean isLoggedIn = getSharedPreferences("MyAppPrefs", MODE_PRIVATE)
.getBoolean("isLoggedIn", false); .getBoolean("isLoggedIn", false);
if (loginBtn != null) loginBtn.setVisibility(isLoggedIn ? View.GONE : View.VISIBLE); loginBtn.setVisibility(isLoggedIn ? View.GONE : View.VISIBLE);
if (registerBtn != null) registerBtn.setVisibility(isLoggedIn ? View.GONE : View.VISIBLE); registerBtn.setVisibility(isLoggedIn ? View.GONE : View.VISIBLE);
if (logoutBtn != null) logoutBtn.setVisibility(isLoggedIn ? View.VISIBLE : View.GONE); logoutBtn.setVisibility(isLoggedIn ? View.VISIBLE : View.GONE);
if (newPostBtn != null) newPostBtn.setVisibility(isLoggedIn ? View.VISIBLE : View.GONE); newPostBtn.setVisibility(isLoggedIn ? View.VISIBLE : View.GONE);
} }
public void loadPublications() {
private void loadPublications() { ApiService api = ApiClient.getClient().create(ApiService.class);
ApiService apiService = ApiClient.getClient().create(ApiService.class);
String token = getSharedPreferences("MyAppPrefs", MODE_PRIVATE) String token = getSharedPreferences("MyAppPrefs", MODE_PRIVATE)
.getString("authToken", null); .getString("authToken", null);
String authHeader = token != null ? "Token " + token : null; String authHeader = token != null ? "Token " + token : null;
api.getPublications(authHeader).enqueue(new Callback<PublicationListResponse>() {
Call<PublicationListResponse> call = apiService.getPublications(authHeader);
call.enqueue(new Callback<PublicationListResponse>() {
@Override @Override
public void onResponse(Call<PublicationListResponse> call, Response<PublicationListResponse> response) { public void onResponse(Call<PublicationListResponse> call,
Response<PublicationListResponse> response) {
if (response.isSuccessful() && response.body() != null) { if (response.isSuccessful() && response.body() != null) {
List<Publication> posts = response.body().results; List<Publication> publications = response.body().results;
Toast.makeText(MainActivity.this,
"Posts loaded: " + posts.size(), // Pass the fully populated list to adapter
Toast.LENGTH_SHORT).show(); postsAdapter.setPosts(publications);
postsAdapter.updateData(posts);
} else { } else {
Toast.makeText(MainActivity.this, Toast.makeText(MainActivity.this,
"Failed to load publications: " + response.code(), Toast.LENGTH_SHORT).show(); "Failed to load posts",
Toast.LENGTH_SHORT).show();
} }
} }
@Override @Override
public void onFailure(Call<PublicationListResponse> call, Throwable t) { public void onFailure(Call<PublicationListResponse> call, Throwable t) {
Toast.makeText(MainActivity.this, Toast.makeText(MainActivity.this,
"Failed to load publications: " + t.getMessage(), Toast.LENGTH_SHORT).show(); "Network error",
Toast.LENGTH_SHORT).show();
} }
}); });
} }
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 100 && resultCode == RESULT_OK) {
loadPublications(); // reload feed after new post
}
}
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();

View File

@ -5,6 +5,7 @@ import android.os.Bundle;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
@ -22,6 +23,7 @@ public class RegisterActivity extends AppCompatActivity {
EditText username, email, ime, prezime, index, password; EditText username, email, ime, prezime, index, password;
Button btnRegister; Button btnRegister;
TextView loginTV;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -35,9 +37,14 @@ public class RegisterActivity extends AppCompatActivity {
index = findViewById(R.id.indexEditText); index = findViewById(R.id.indexEditText);
password = findViewById(R.id.passwordEditText); password = findViewById(R.id.passwordEditText);
btnRegister = findViewById(R.id.btnRegister); btnRegister = findViewById(R.id.btnRegister);
loginTV = findViewById(R.id.loginTV);
Toast.makeText(this, "RegisterActivity loaded", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "RegisterActivity loaded", Toast.LENGTH_SHORT).show();
loginTV.setOnClickListener(v -> {
startActivity(new Intent(RegisterActivity.this, LoginActivity.class));
});
btnRegister.setOnClickListener(new View.OnClickListener() { btnRegister.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -48,9 +55,7 @@ public class RegisterActivity extends AppCompatActivity {
String s = index.getText().toString().trim(); String s = index.getText().toString().trim();
String p = password.getText().toString().trim(); String p = password.getText().toString().trim();
// ---------------------------
// FRONTEND VALIDATIONS
// ---------------------------
if (u.isEmpty() || u.length() > 150) { if (u.isEmpty() || u.length() > 150) {
username.setError("Username must be 1-150 characters"); username.setError("Username must be 1-150 characters");
return; return;

View File

@ -0,0 +1,32 @@
package com.example.gallery;
import android.net.Uri;
import android.os.Bundle;
import android.widget.MediaController;
import android.widget.VideoView;
import androidx.appcompat.app.AppCompatActivity;
public class VideoPlayerActivity extends AppCompatActivity {
private VideoView videoView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video_player);
videoView = findViewById(R.id.videoView);
String videoUrl = getIntent().getStringExtra("videoUrl");
if (videoUrl != null) {
Uri videoUri = Uri.parse(videoUrl);
videoView.setVideoURI(videoUri);
MediaController mediaController = new MediaController(this);
mediaController.setAnchorView(videoView);
videoView.setMediaController(mediaController);
videoView.start();
}
}
}

View File

@ -1,104 +1,244 @@
package com.example.gallery.adapters; package com.example.gallery.adapters;
import android.util.Log; import android.content.Context;
import android.content.Intent;
import android.view.Gravity;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.RatingBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import com.example.gallery.R; import com.example.gallery.R;
import com.example.gallery.VideoPlayerActivity;
import com.example.gallery.api.ApiClient; import com.example.gallery.api.ApiClient;
import com.example.gallery.api.ApiService;
import com.example.gallery.models.Publication; import com.example.gallery.models.Publication;
import com.example.gallery.models.RatingRequest;
import com.example.gallery.models.RatingResponse;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class PostsAdapter extends RecyclerView.Adapter<PostsAdapter.PostViewHolder> { public class PostsAdapter extends RecyclerView.Adapter<PostsAdapter.PostViewHolder> {
private final List<Publication> publications; private final Context context;
private final ApiService apiService;
private final boolean isLoggedIn;
public PostsAdapter(List<Publication> publications) { private final List<Publication> allPosts = new ArrayList<>();
this.publications = publications; private final List<Publication> visiblePosts = new ArrayList<>();
private Boolean pinnedFilter = null;
private String contentTypeFilter = null;
private SortMode sortMode = SortMode.TIME;
public enum SortMode { TIME, RATING }
public PostsAdapter(Context context, List<Publication> posts, boolean isLoggedIn) {
this.context = context;
this.isLoggedIn = isLoggedIn;
this.apiService = ApiClient.getClient().create(ApiService.class);
setPosts(posts);
}
public void setPosts(List<Publication> posts) {
allPosts.clear();
if (posts != null) allPosts.addAll(posts);
applyFilters();
}
public void filterPinned(Boolean pinned) {
pinnedFilter = pinned;
applyFilters();
}
public void filterContentType(String type) {
contentTypeFilter = type;
applyFilters();
}
public void sortBy(SortMode mode) {
sortMode = mode;
applyFilters();
}
private void applyFilters() {
visiblePosts.clear();
for (Publication p : allPosts) {
if (pinnedFilter != null && p.is_pinned != pinnedFilter) continue;
if (contentTypeFilter != null &&
!contentTypeFilter.equalsIgnoreCase(p.content_type)) continue;
visiblePosts.add(p);
}
if (sortMode == SortMode.TIME) {
Collections.sort(visiblePosts,
(a, b) -> b.time_created.compareTo(a.time_created));
} else {
Collections.sort(visiblePosts,
(a, b) -> Float.compare(b.average_score, a.average_score));
} }
public void updateData(List<Publication> newData) {
publications.clear();
publications.addAll(newData);
notifyDataSetChanged(); notifyDataSetChanged();
} }
@NonNull @NonNull
@Override @Override
public PostViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public PostViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()) View view = LayoutInflater.from(context)
.inflate(R.layout.item_post, parent, false); .inflate(R.layout.item_post, parent, false);
return new PostViewHolder(view); return new PostViewHolder(view);
} }
@Override @Override
public void onBindViewHolder(@NonNull PostViewHolder holder, int position) { public void onBindViewHolder(@NonNull PostViewHolder holder, int position) {
Publication pub = publications.get(position);
holder.descriptionTV.setText( Publication post = visiblePosts.get(position);
pub.description != null ? pub.description : ""
);
// ---------- USER SCORE ----------
int userScore = post.user_score != null ? post.user_score : 0;
holder.userRatingBar.setOnRatingBarChangeListener(null);
holder.userRatingBar.setRating(userScore);
holder.yourRating.setText(String.valueOf(userScore));
// ---------- TEXT ----------
holder.postDescription.setText(post.description == null ? "" : post.description);
holder.postUserName.setText(post.username == null ? "User" : post.username);
holder.mediaTypeBadge.setText( holder.mediaTypeBadge.setText(
pub.content_type != null ? pub.content_type.toUpperCase() : "" post.content_type == null ? "MEDIA" : post.content_type.toUpperCase());
// ---------- MEDIA ----------
if ("video".equalsIgnoreCase(post.content_type)) {
Glide.with(context).asBitmap().load(post.video).into(holder.postImage);
holder.videoPlayButton.setVisibility(View.VISIBLE);
View.OnClickListener play = v -> {
Intent i = new Intent(context, VideoPlayerActivity.class);
i.putExtra("videoUrl", post.video);
context.startActivity(i);
};
holder.postImage.setOnClickListener(play);
holder.videoPlayButton.setOnClickListener(play);
} else {
holder.videoPlayButton.setVisibility(View.GONE);
Glide.with(context).load(post.image).into(holder.postImage);
}
// ---------- AVERAGE ----------
holder.txtAverageValue.setText(String.format("%.1f", post.average_score));
holder.averageRatingBar.setRating(post.average_score);
if (!isLoggedIn) {
holder.userRatingBar.setIsIndicator(true);
return;
}
holder.userRatingBar.setIsIndicator(false);
holder.userRatingBar.setOnRatingBarChangeListener((bar, rating, fromUser) -> {
if (!fromUser) return;
int score = Math.round(rating);
int adapterPosition = holder.getAdapterPosition();
if (adapterPosition == RecyclerView.NO_POSITION) return;
Publication current = visiblePosts.get(adapterPosition);
String token = context.getSharedPreferences("MyAppPrefs", Context.MODE_PRIVATE)
.getString("authToken", null);
apiService.ratePublication("Token " + token,
new RatingRequest(current.pk, score))
.enqueue(new Callback<RatingResponse>() {
@Override
public void onResponse(@NonNull Call<RatingResponse> call,
@NonNull Response<RatingResponse> response) {
if (!response.isSuccessful() || response.body() == null) return;
RatingResponse body = response.body();
current.user_score = score;
if (body.average_score != null) {
current.average_score = body.average_score;
}
for (Publication p : allPosts) {
if (p.pk == current.pk) {
p.user_score = current.user_score;
p.average_score = current.average_score;
break;
}
}
holder.yourRating.setText(String.valueOf(current.user_score));
holder.averageRatingBar.setRating(current.average_score);
holder.txtAverageValue.setText(
String.format("%.1f", current.average_score)
); );
String mediaPath = null; notifyItemChanged(adapterPosition);
if ("image".equals(pub.content_type)) {
mediaPath = pub.image;
} else if ("video".equals(pub.content_type)) {
mediaPath = pub.video;
} }
if (mediaPath != null && !mediaPath.isEmpty()) { @Override
public void onFailure(@NonNull Call<RatingResponse> call,
String fullUrl; @NonNull Throwable t) {
Toast.makeText(context,
// CRITICAL FIX: handle both relative & absolute URLs "Network error", Toast.LENGTH_SHORT).show();
if (mediaPath.startsWith("http")) {
fullUrl = mediaPath;
} else {
fullUrl = ApiClient.BASE_URL + mediaPath;
}
Log.d("POST_IMAGE_URL", fullUrl);
Glide.with(holder.itemView.getContext())
.load(fullUrl)
.placeholder(R.drawable.image_placeholder_background)
.error(R.drawable.image_placeholder_background)
.centerCrop()
.into(holder.mediaIV);
} else {
holder.mediaIV.setImageResource(R.drawable.image_placeholder_background);
} }
});
});
} }
@Override @Override
public int getItemCount() { public int getItemCount() {
return publications.size(); return visiblePosts.size();
} }
static class PostViewHolder extends RecyclerView.ViewHolder { static class PostViewHolder extends RecyclerView.ViewHolder {
TextView descriptionTV;
TextView mediaTypeBadge;
ImageView mediaIV;
public PostViewHolder(@NonNull View itemView) { RatingBar averageRatingBar, userRatingBar;
TextView txtAverageValue, yourRating, postDescription, postUserName, mediaTypeBadge;
ImageView postImage, videoPlayButton;
PostViewHolder(View itemView) {
super(itemView); super(itemView);
descriptionTV = itemView.findViewById(R.id.postDescription);
mediaIV = itemView.findViewById(R.id.postImage); averageRatingBar = itemView.findViewById(R.id.averageRatingBar);
userRatingBar = itemView.findViewById(R.id.userRatingBar);
txtAverageValue = itemView.findViewById(R.id.averageRatingText);
yourRating = itemView.findViewById(R.id.yourRating);
postDescription = itemView.findViewById(R.id.postDescription);
postUserName = itemView.findViewById(R.id.postUserName);
mediaTypeBadge = itemView.findViewById(R.id.mediaTypeBadge); mediaTypeBadge = itemView.findViewById(R.id.mediaTypeBadge);
postImage = itemView.findViewById(R.id.postImage);
FrameLayout parent = (FrameLayout) postImage.getParent();
videoPlayButton = new ImageView(itemView.getContext());
videoPlayButton.setImageResource(R.drawable.ic_launcher_background);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT
);
params.gravity = Gravity.CENTER;
parent.addView(videoPlayButton);
videoPlayButton.setVisibility(View.GONE);
} }
} }
} }

View File

@ -5,6 +5,8 @@ import com.example.gallery.models.LoginRequest;
import com.example.gallery.models.LoginResponse; import com.example.gallery.models.LoginResponse;
import com.example.gallery.models.Publication; import com.example.gallery.models.Publication;
import com.example.gallery.models.PublicationListResponse; import com.example.gallery.models.PublicationListResponse;
import com.example.gallery.models.RatingRequest;
import com.example.gallery.models.RatingResponse;
import com.example.gallery.models.RegisterRequest; import com.example.gallery.models.RegisterRequest;
import com.example.gallery.models.RegisterResponse; import com.example.gallery.models.RegisterResponse;
@ -17,11 +19,11 @@ import retrofit2.http.Header;
import retrofit2.http.Multipart; import retrofit2.http.Multipart;
import retrofit2.http.POST; import retrofit2.http.POST;
import retrofit2.http.Part; import retrofit2.http.Part;
import retrofit2.http.Path;
public interface ApiService { public interface ApiService {
/* ===================== AUTH ===================== */ /* ===================== AUTHENTICATION ===================== */
@POST("api/auth/register/") @POST("api/auth/register/")
Call<RegisterResponse> register(@Body RegisterRequest request); Call<RegisterResponse> register(@Body RegisterRequest request);
@ -32,12 +34,10 @@ public interface ApiService {
Call<UserResponse> getMe(@Header("Authorization") String token); Call<UserResponse> getMe(@Header("Authorization") String token);
/* ================== PUBLICATIONS ================= */ /* ================== PUBLICATIONS ================= */
@GET("api/publications/") @GET("api/publications/")
Call<PublicationListResponse> getPublications( Call<PublicationListResponse> getPublications(
@Header("Authorization") String token @Header("Authorization") String token
); );
@Multipart @Multipart
@POST("api/publications/") @POST("api/publications/")
Call<Publication> createPublication( Call<Publication> createPublication(
@ -48,4 +48,10 @@ public interface ApiService {
@Part("category") RequestBody category, @Part("category") RequestBody category,
@Part MultipartBody.Part file @Part MultipartBody.Part file
); );
/* ================== RATING ================= */
@POST("api/publications/rate/")
Call<RatingResponse> ratePublication(
@Header("Authorization") String authHeader,
@Body RatingRequest request
);
} }

View File

@ -1,11 +1,20 @@
package com.example.gallery.models; package com.example.gallery.models;
public class Publication { import java.io.Serializable;
public class Publication implements Serializable {
public int pk; public int pk;
public String description;
public String image; public String image;
public String video; public String video;
public String content_type; public String content_type;
public String description;
public boolean is_pinned;
public Float average_score;
public Integer user_score;
public String username;
public String time_created; public String time_created;
} }

View File

@ -0,0 +1,6 @@
package com.example.gallery.models;
public class Rating {
public float score;
public String user;
}

View File

@ -0,0 +1,10 @@
package com.example.gallery.models;
public class RatingRequest {
public int publication;
public float score;
public RatingRequest(int publication, float score) {
this.publication = publication;
this.score = score;
}
}

View File

@ -0,0 +1,7 @@
package com.example.gallery.models;
public class RatingResponse {
public String detail;
public Float average_score;
public Float user_score;
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<VideoView
android:id="@+id/videoView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>

View File

@ -40,7 +40,6 @@
android:textSize="16sp" android:textSize="16sp"
android:textStyle="bold" android:textStyle="bold"
android:textColor="#011f4b"/> android:textColor="#011f4b"/>
</LinearLayout> </LinearLayout>
<!-- Media Type Badge --> <!-- Media Type Badge -->
@ -57,7 +56,6 @@
android:paddingEnd="14dp" android:paddingEnd="14dp"
android:paddingTop="6dp" android:paddingTop="6dp"
android:paddingBottom="6dp"/> android:paddingBottom="6dp"/>
</LinearLayout> </LinearLayout>
<!-- Divider --> <!-- Divider -->
@ -67,6 +65,9 @@
android:background="#b3cde0" android:background="#b3cde0"
android:layout_marginBottom="16dp"/> android:layout_marginBottom="16dp"/>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- Post Content/Image --> <!-- Post Content/Image -->
<ImageView <ImageView
android:id="@+id/postImage" android:id="@+id/postImage"
@ -76,18 +77,7 @@
android:background="@drawable/image_placeholder_background" android:background="@drawable/image_placeholder_background"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
android:contentDescription="Post content"/> android:contentDescription="Post content"/>
</FrameLayout>
<!-- Post Title
<TextView
android:id="@+id/postTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Post Title"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="#011f4b"
android:layout_marginBottom="8dp"/>
-->
<!-- Post Description --> <!-- Post Description -->
<TextView <TextView
android:id="@+id/postDescription" android:id="@+id/postDescription"
@ -107,6 +97,7 @@
<!-- Average Rating Section --> <!-- Average Rating Section -->
<TextView <TextView
android:id="@+id/txtAverage"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Average Rating" android:text="Average Rating"
@ -118,35 +109,41 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:layout_marginBottom="24dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:layout_marginBottom="24dp"> android:orientation="horizontal">
<RatingBar <RatingBar
android:id="@+id/averageRatingBar" android:id="@+id/averageRatingBar"
style="?android:attr/ratingBarStyleSmall" style="?android:attr/ratingBarStyleSmall"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:numStars="5" android:layout_marginEnd="12dp"
android:rating="4.5"
android:isIndicator="true" android:isIndicator="true"
android:numStars="5"
android:stepSize="0.1"
android:rating="0"
android:progressTint="#005b96" android:progressTint="#005b96"
android:scaleX="1.2" android:secondaryProgressTint="#aaccee"/>
android:scaleY="1.2"
android:layout_marginEnd="12dp"/>
<TextView <TextView
android:id="@+id/averageRatingText" android:id="@+id/averageRatingText"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="4.5" android:text="0.0"
android:textColor="#011f4b"
android:textSize="20sp" android:textSize="20sp"
android:textStyle="bold" android:textStyle="bold"/>
android:textColor="#011f4b"/>
</LinearLayout> </LinearLayout>
<!-- User Rating Section --> <!-- User Rating Section -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="12dp"
android:gravity="center_vertical">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -154,22 +151,31 @@
android:textSize="14sp" android:textSize="14sp"
android:textStyle="bold" android:textStyle="bold"
android:textColor="#03396c" android:textColor="#03396c"
android:layout_marginBottom="12dp"/> android:layout_marginEnd="8dp"/>
<TextView
android:id="@+id/yourRating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:textColor="#011f4b"
android:textSize="20sp"
android:textStyle="bold"
android:layout_marginStart="8dp"/>
</LinearLayout>
<RatingBar <RatingBar
android:id="@+id/userRatingBar" android:id="@+id/userRatingBar"
style="?android:attr/ratingBarStyleSmall"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:numStars="5" android:numStars="5"
android:stepSize="1" android:stepSize="1"
android:rating="0" android:rating="0"
android:progressTint="#005b96" android:isIndicator="false"
android:scaleX="1.3"
android:scaleY="1.3"
android:layout_gravity="center" android:layout_gravity="center"
android:transformPivotX="0dp" android:progressTint="#005b96"
android:transformPivotY="0dp"/> android:secondaryProgressTint="#aaccee"/>
</LinearLayout> </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>