Fali jos auto-update kada se oceni nesto
This commit is contained in:
parent
cd3b3f6dcc
commit
2cbd885b62
BIN
Screenshot_20260114_182156.png
Normal file
BIN
Screenshot_20260114_182156.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 MiB |
@ -1,23 +1,28 @@
|
||||
<?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"/>
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Gallery"
|
||||
tools:targetApi="31"
|
||||
android:networkSecurityConfig="@xml/network_security_config">
|
||||
tools:targetApi="31">
|
||||
<activity
|
||||
android:name=".VideoPlayerActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".NewPostActivity"
|
||||
android:exported="false"
|
||||
tools:ignore="Instantiatable"
|
||||
android:windowSoftInputMode="adjustResize|stateHidden"/>
|
||||
android:windowSoftInputMode="adjustResize|stateHidden"
|
||||
tools:ignore="Instantiatable" />
|
||||
<activity
|
||||
android:name=".RegisterActivity"
|
||||
android:exported="false" />
|
||||
@ -33,7 +38,6 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@ -20,7 +20,8 @@ import retrofit2.Response;
|
||||
|
||||
public class LoginActivity extends AppCompatActivity {
|
||||
|
||||
private EditText usernameEdit, passwordEdit;
|
||||
private EditText usernameEdit;
|
||||
private EditText passwordEdit;
|
||||
private Button loginBtn;
|
||||
private TextView registerTV;
|
||||
|
||||
@ -29,12 +30,19 @@ public class LoginActivity extends AppCompatActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_login);
|
||||
|
||||
// Views
|
||||
usernameEdit = findViewById(R.id.usernameTxt);
|
||||
passwordEdit = findViewById(R.id.userpasswordTxt);
|
||||
loginBtn = findViewById(R.id.btnLogin);
|
||||
registerTV = findViewById(R.id.registerTV);
|
||||
|
||||
// Login button
|
||||
loginBtn.setOnClickListener(v -> login());
|
||||
|
||||
// Register text → RegisterActivity
|
||||
registerTV.setOnClickListener(v -> {
|
||||
startActivity(new Intent(LoginActivity.this, RegisterActivity.class));
|
||||
});
|
||||
}
|
||||
|
||||
private void login() {
|
||||
@ -52,27 +60,33 @@ public class LoginActivity extends AppCompatActivity {
|
||||
apiService.login(request).enqueue(new Callback<LoginResponse>() {
|
||||
@Override
|
||||
public void onResponse(Call<LoginResponse> call, Response<LoginResponse> response) {
|
||||
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
|
||||
// 🔑 STORE RAW TOKEN ONLY
|
||||
// ✅ Save auth state
|
||||
getSharedPreferences("MyAppPrefs", MODE_PRIVATE)
|
||||
.edit()
|
||||
.putString("authToken", response.body().getToken())
|
||||
.putBoolean("isLoggedIn", true)
|
||||
.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));
|
||||
finish();
|
||||
|
||||
} else {
|
||||
Toast.makeText(LoginActivity.this, "Login failed", Toast.LENGTH_SHORT).show();
|
||||
Toast.makeText(LoginActivity.this,
|
||||
"Invalid username or password", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -4,6 +4,9 @@ import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
@ -28,111 +31,173 @@ public class MainActivity extends AppCompatActivity {
|
||||
private Button loginBtn, registerBtn, logoutBtn, newPostBtn;
|
||||
private RecyclerView recyclerView;
|
||||
private PostsAdapter postsAdapter;
|
||||
private ImageButton burgerMenuBtn;
|
||||
private LinearLayout dropdownMenu;
|
||||
private TextView menuLogout, menuNewPost;
|
||||
private ImageButton filterBtnToolbar;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
// Initialize all views BEFORE using them
|
||||
loginBtn = findViewById(R.id.Loginbtn);
|
||||
registerBtn = findViewById(R.id.Registerbtn);
|
||||
logoutBtn = findViewById(R.id.btnLogout);
|
||||
newPostBtn = findViewById(R.id.newPostBtn);
|
||||
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));
|
||||
postsAdapter = new PostsAdapter(new ArrayList<>());
|
||||
|
||||
boolean isLoggedIn = getSharedPreferences("MyAppPrefs", MODE_PRIVATE)
|
||||
.getBoolean("isLoggedIn", false);
|
||||
|
||||
postsAdapter = new PostsAdapter(this, new ArrayList<>(), isLoggedIn);
|
||||
recyclerView.setAdapter(postsAdapter);
|
||||
|
||||
setupButtons();
|
||||
setupDropdownMenu();
|
||||
updateAuthUI();
|
||||
loadPublications();
|
||||
setupFilterButton();
|
||||
}
|
||||
|
||||
private void setupButtons() {
|
||||
if (loginBtn != null) {
|
||||
loginBtn.setOnClickListener(v ->
|
||||
startActivity(new Intent(MainActivity.this, LoginActivity.class))
|
||||
);
|
||||
}
|
||||
loginBtn.setOnClickListener(v ->
|
||||
startActivity(new Intent(this, LoginActivity.class)));
|
||||
|
||||
if (registerBtn != null) {
|
||||
registerBtn.setOnClickListener(v ->
|
||||
startActivity(new Intent(MainActivity.this, RegisterActivity.class))
|
||||
);
|
||||
}
|
||||
registerBtn.setOnClickListener(v ->
|
||||
startActivity(new Intent(this, RegisterActivity.class)));
|
||||
|
||||
if (logoutBtn != null) {
|
||||
logoutBtn.setOnClickListener(v -> {
|
||||
getSharedPreferences("MyAppPrefs", MODE_PRIVATE).edit().clear().apply();
|
||||
updateAuthUI();
|
||||
Toast.makeText(MainActivity.this, "Logged out", Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
}
|
||||
logoutBtn.setOnClickListener(v -> {
|
||||
getSharedPreferences("MyAppPrefs", MODE_PRIVATE)
|
||||
.edit().clear().apply();
|
||||
updateAuthUI();
|
||||
Toast.makeText(this, "Logged out", Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
|
||||
if (newPostBtn != null) {
|
||||
newPostBtn.setOnClickListener(v ->
|
||||
startActivityForResult(
|
||||
new Intent(MainActivity.this, NewPostActivity.class), 100)
|
||||
);
|
||||
}
|
||||
newPostBtn.setOnClickListener(v ->
|
||||
startActivityForResult(
|
||||
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() {
|
||||
boolean isLoggedIn = getSharedPreferences("MyAppPrefs", MODE_PRIVATE)
|
||||
.getBoolean("isLoggedIn", false);
|
||||
|
||||
if (loginBtn != null) loginBtn.setVisibility(isLoggedIn ? View.GONE : View.VISIBLE);
|
||||
if (registerBtn != null) registerBtn.setVisibility(isLoggedIn ? View.GONE : View.VISIBLE);
|
||||
if (logoutBtn != null) logoutBtn.setVisibility(isLoggedIn ? View.VISIBLE : View.GONE);
|
||||
if (newPostBtn != null) newPostBtn.setVisibility(isLoggedIn ? View.VISIBLE : View.GONE);
|
||||
loginBtn.setVisibility(isLoggedIn ? View.GONE : View.VISIBLE);
|
||||
registerBtn.setVisibility(isLoggedIn ? View.GONE : View.VISIBLE);
|
||||
logoutBtn.setVisibility(isLoggedIn ? View.VISIBLE : View.GONE);
|
||||
newPostBtn.setVisibility(isLoggedIn ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
private void loadPublications() {
|
||||
ApiService apiService = ApiClient.getClient().create(ApiService.class);
|
||||
|
||||
public void loadPublications() {
|
||||
ApiService api = ApiClient.getClient().create(ApiService.class);
|
||||
String token = getSharedPreferences("MyAppPrefs", MODE_PRIVATE)
|
||||
.getString("authToken", null);
|
||||
String authHeader = token != null ? "Token " + token : null;
|
||||
|
||||
Call<PublicationListResponse> call = apiService.getPublications(authHeader);
|
||||
call.enqueue(new Callback<PublicationListResponse>() {
|
||||
api.getPublications(authHeader).enqueue(new Callback<PublicationListResponse>() {
|
||||
@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) {
|
||||
List<Publication> posts = response.body().results;
|
||||
Toast.makeText(MainActivity.this,
|
||||
"Posts loaded: " + posts.size(),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
postsAdapter.updateData(posts);
|
||||
List<Publication> publications = response.body().results;
|
||||
|
||||
// Pass the fully populated list to adapter
|
||||
postsAdapter.setPosts(publications);
|
||||
} else {
|
||||
Toast.makeText(MainActivity.this,
|
||||
"Failed to load publications: " + response.code(), Toast.LENGTH_SHORT).show();
|
||||
"Failed to load posts",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Call<PublicationListResponse> call, Throwable t) {
|
||||
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
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
updateAuthUI();
|
||||
loadPublications();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,7 @@ import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
@ -22,6 +23,7 @@ public class RegisterActivity extends AppCompatActivity {
|
||||
|
||||
EditText username, email, ime, prezime, index, password;
|
||||
Button btnRegister;
|
||||
TextView loginTV;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@ -35,9 +37,14 @@ public class RegisterActivity extends AppCompatActivity {
|
||||
index = findViewById(R.id.indexEditText);
|
||||
password = findViewById(R.id.passwordEditText);
|
||||
btnRegister = findViewById(R.id.btnRegister);
|
||||
loginTV = findViewById(R.id.loginTV);
|
||||
|
||||
Toast.makeText(this, "RegisterActivity loaded", Toast.LENGTH_SHORT).show();
|
||||
|
||||
loginTV.setOnClickListener(v -> {
|
||||
startActivity(new Intent(RegisterActivity.this, LoginActivity.class));
|
||||
});
|
||||
|
||||
btnRegister.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@ -48,9 +55,7 @@ public class RegisterActivity extends AppCompatActivity {
|
||||
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;
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,104 +1,244 @@
|
||||
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.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RatingBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.example.gallery.R;
|
||||
import com.example.gallery.VideoPlayerActivity;
|
||||
import com.example.gallery.api.ApiClient;
|
||||
import com.example.gallery.api.ApiService;
|
||||
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 retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
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) {
|
||||
this.publications = publications;
|
||||
private final List<Publication> allPosts = new ArrayList<>();
|
||||
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 updateData(List<Publication> newData) {
|
||||
publications.clear();
|
||||
publications.addAll(newData);
|
||||
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));
|
||||
}
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
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);
|
||||
return new PostViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull PostViewHolder holder, int position) {
|
||||
Publication pub = publications.get(position);
|
||||
|
||||
holder.descriptionTV.setText(
|
||||
pub.description != null ? pub.description : ""
|
||||
);
|
||||
Publication post = visiblePosts.get(position);
|
||||
|
||||
// ---------- 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(
|
||||
pub.content_type != null ? pub.content_type.toUpperCase() : ""
|
||||
);
|
||||
post.content_type == null ? "MEDIA" : post.content_type.toUpperCase());
|
||||
|
||||
String mediaPath = null;
|
||||
// ---------- MEDIA ----------
|
||||
if ("video".equalsIgnoreCase(post.content_type)) {
|
||||
Glide.with(context).asBitmap().load(post.video).into(holder.postImage);
|
||||
holder.videoPlayButton.setVisibility(View.VISIBLE);
|
||||
|
||||
if ("image".equals(pub.content_type)) {
|
||||
mediaPath = pub.image;
|
||||
} else if ("video".equals(pub.content_type)) {
|
||||
mediaPath = pub.video;
|
||||
}
|
||||
|
||||
if (mediaPath != null && !mediaPath.isEmpty()) {
|
||||
|
||||
String fullUrl;
|
||||
|
||||
// ✅ CRITICAL FIX: handle both relative & absolute URLs
|
||||
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);
|
||||
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.mediaIV.setImageResource(R.drawable.image_placeholder_background);
|
||||
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)
|
||||
);
|
||||
|
||||
notifyItemChanged(adapterPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<RatingResponse> call,
|
||||
@NonNull Throwable t) {
|
||||
Toast.makeText(context,
|
||||
"Network error", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return publications.size();
|
||||
return visiblePosts.size();
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,8 @@ import com.example.gallery.models.LoginRequest;
|
||||
import com.example.gallery.models.LoginResponse;
|
||||
import com.example.gallery.models.Publication;
|
||||
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.RegisterResponse;
|
||||
|
||||
@ -17,11 +19,11 @@ import retrofit2.http.Header;
|
||||
import retrofit2.http.Multipart;
|
||||
import retrofit2.http.POST;
|
||||
import retrofit2.http.Part;
|
||||
import retrofit2.http.Path;
|
||||
|
||||
public interface ApiService {
|
||||
|
||||
/* ===================== AUTH ===================== */
|
||||
|
||||
/* ===================== AUTHENTICATION ===================== */
|
||||
@POST("api/auth/register/")
|
||||
Call<RegisterResponse> register(@Body RegisterRequest request);
|
||||
|
||||
@ -32,12 +34,10 @@ public interface ApiService {
|
||||
Call<UserResponse> getMe(@Header("Authorization") String token);
|
||||
|
||||
/* ================== PUBLICATIONS ================= */
|
||||
|
||||
@GET("api/publications/")
|
||||
Call<PublicationListResponse> getPublications(
|
||||
@Header("Authorization") String token
|
||||
);
|
||||
|
||||
@Multipart
|
||||
@POST("api/publications/")
|
||||
Call<Publication> createPublication(
|
||||
@ -48,4 +48,10 @@ public interface ApiService {
|
||||
@Part("category") RequestBody category,
|
||||
@Part MultipartBody.Part file
|
||||
);
|
||||
/* ================== RATING ================= */
|
||||
@POST("api/publications/rate/")
|
||||
Call<RatingResponse> ratePublication(
|
||||
@Header("Authorization") String authHeader,
|
||||
@Body RatingRequest request
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,11 +1,20 @@
|
||||
package com.example.gallery.models;
|
||||
|
||||
public class Publication {
|
||||
import java.io.Serializable;
|
||||
|
||||
public class Publication implements Serializable {
|
||||
|
||||
public int pk;
|
||||
public String description;
|
||||
public String image;
|
||||
public String video;
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
6
app/src/main/java/com/example/gallery/models/Rating.java
Normal file
6
app/src/main/java/com/example/gallery/models/Rating.java
Normal file
@ -0,0 +1,6 @@
|
||||
package com.example.gallery.models;
|
||||
|
||||
public class Rating {
|
||||
public float score;
|
||||
public String user;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package com.example.gallery.models;
|
||||
|
||||
public class RatingResponse {
|
||||
public String detail;
|
||||
public Float average_score;
|
||||
public Float user_score;
|
||||
}
|
||||
10
app/src/main/res/layout/activity_video_player.xml
Normal file
10
app/src/main/res/layout/activity_video_player.xml
Normal 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>
|
||||
@ -40,7 +40,6 @@
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#011f4b"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Media Type Badge -->
|
||||
@ -57,7 +56,6 @@
|
||||
android:paddingEnd="14dp"
|
||||
android:paddingTop="6dp"
|
||||
android:paddingBottom="6dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Divider -->
|
||||
@ -67,6 +65,9 @@
|
||||
android:background="#b3cde0"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<!-- Post Content/Image -->
|
||||
<ImageView
|
||||
android:id="@+id/postImage"
|
||||
@ -76,18 +77,7 @@
|
||||
android:background="@drawable/image_placeholder_background"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:contentDescription="Post content"/>
|
||||
|
||||
<!-- 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"/>
|
||||
-->
|
||||
</FrameLayout>
|
||||
<!-- Post Description -->
|
||||
<TextView
|
||||
android:id="@+id/postDescription"
|
||||
@ -107,6 +97,7 @@
|
||||
|
||||
<!-- Average Rating Section -->
|
||||
<TextView
|
||||
android:id="@+id/txtAverage"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Average Rating"
|
||||
@ -118,58 +109,73 @@
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="24dp">
|
||||
android:orientation="horizontal">
|
||||
|
||||
<RatingBar
|
||||
android:id="@+id/averageRatingBar"
|
||||
style="?android:attr/ratingBarStyleSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:numStars="5"
|
||||
android:rating="4.5"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:isIndicator="true"
|
||||
android:numStars="5"
|
||||
android:stepSize="0.1"
|
||||
android:rating="0"
|
||||
android:progressTint="#005b96"
|
||||
android:scaleX="1.2"
|
||||
android:scaleY="1.2"
|
||||
android:layout_marginEnd="12dp"/>
|
||||
android:secondaryProgressTint="#aaccee"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/averageRatingText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="4.5"
|
||||
android:text="0.0"
|
||||
android:textColor="#011f4b"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#011f4b"/>
|
||||
|
||||
android:textStyle="bold"/>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- User Rating Section -->
|
||||
<TextView
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Your Rating"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#03396c"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Your Rating"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#03396c"
|
||||
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
|
||||
android:id="@+id/userRatingBar"
|
||||
style="?android:attr/ratingBarStyleSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:numStars="5"
|
||||
android:stepSize="1"
|
||||
android:rating="0"
|
||||
android:progressTint="#005b96"
|
||||
android:scaleX="1.3"
|
||||
android:scaleY="1.3"
|
||||
android:isIndicator="false"
|
||||
android:layout_gravity="center"
|
||||
android:transformPivotX="0dp"
|
||||
android:transformPivotY="0dp"/>
|
||||
|
||||
android:progressTint="#005b96"
|
||||
android:secondaryProgressTint="#aaccee"/>
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user