Skip to content

Commit f244239

Browse files
Merge pull request #90 from TranHoan-backend-dev/feat/tracking-read-progess
last update
2 parents 5f093f9 + f7a5082 commit f244239

18 files changed

Lines changed: 362 additions & 35 deletions

File tree

android-app/app/src/main/java/com/se1853_jv/labverse/data/api/annotation/AnnotationApiHandler.java

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,25 @@ public class AnnotationApiHandler {
2929
private static final String BASE_URL = Constants.GROUP_ENDPOINT_URL + "annotations/";
3030
private final AnnotationApi apiService;
3131

32+
/**
33+
* Normalize token: ensure it doesn't have duplicate "Bearer " prefix
34+
*/
35+
private String normalizeToken(String token) {
36+
if (token == null) {
37+
return null;
38+
}
39+
// Remove "Bearer " prefix if present (case-insensitive)
40+
String normalized = token.trim();
41+
while (normalized.startsWith("Bearer ") || normalized.startsWith("bearer ")) {
42+
normalized = normalized.substring(7).trim();
43+
}
44+
// Add "Bearer " prefix
45+
String result = "Bearer " + normalized;
46+
Log.d(TAG, "Token normalized: original length=" + (token != null ? token.length() : 0) +
47+
", normalized length=" + result.length());
48+
return result;
49+
}
50+
3251
public AnnotationApiHandler() {
3352
var gson = new GsonBuilder()
3453
.setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
@@ -184,9 +203,13 @@ public void onFailure(@NonNull Call<com.se1853_jv.labverse.data.dto.response.Bas
184203
public void exportAnnotations(String token, String paperId, String collectionId,
185204
ApiCallback<AnnotationApi.ExportAnnotationsResponse> callback) {
186205
Log.d(TAG, "Exporting annotations for paper: " + paperId);
206+
Log.d(TAG, "Original token (first 50 chars): " + (token != null && token.length() > 50 ? token.substring(0, 50) + "..." : token));
207+
208+
String authToken = normalizeToken(token);
209+
Log.d(TAG, "Normalized token (first 50 chars): " + (authToken != null && authToken.length() > 50 ? authToken.substring(0, 50) + "..." : authToken));
187210

188211
Call<BaseJsonResponse<AnnotationApi.ExportAnnotationsResponse>> call =
189-
apiService.exportAnnotations("Bearer " + token, paperId, collectionId);
212+
apiService.exportAnnotations(authToken, paperId, collectionId);
190213

191214
call.enqueue(new Callback<>() {
192215
@Override
@@ -197,8 +220,28 @@ public void onResponse(@NonNull Call<BaseJsonResponse<AnnotationApi.ExportAnnota
197220
callback.onSuccess(result);
198221
Log.d(TAG, "Annotations exported successfully");
199222
} else {
200-
Log.e(TAG, "Error exporting annotations: " + response.message());
201-
callback.onError(response.message());
223+
String errorMessage = "Error exporting annotations: " + response.message();
224+
if (response.errorBody() != null) {
225+
try {
226+
String errorBody = response.errorBody().string();
227+
Log.e(TAG, "Error response body: " + errorBody);
228+
// Try to parse error response
229+
try {
230+
var gson = new com.google.gson.Gson();
231+
var errorResponse = gson.fromJson(errorBody, BaseJsonResponse.class);
232+
if (errorResponse != null && errorResponse.getMessage() != null) {
233+
errorMessage = errorResponse.getMessage();
234+
}
235+
} catch (Exception e) {
236+
// If parsing fails, use the raw error body
237+
errorMessage = errorBody;
238+
}
239+
} catch (Exception e) {
240+
Log.e(TAG, "Error reading error body", e);
241+
}
242+
}
243+
Log.e(TAG, errorMessage);
244+
callback.onError(errorMessage);
202245
}
203246
}
204247

android-app/app/src/main/java/com/se1853_jv/labverse/data/api/workflow/ReadingWorkflowApiHandler.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,8 @@ public ReadingWorkflowApiHandler(Context context) {
7171
* Update reading progress
7272
*/
7373
public void updateProgress(ReadingWorkflowProgressRequest request, ApiCallback<String> callback) {
74-
request.setCollectionId(EncoderUtils.decode(request.getCollectionId()));
75-
request.setPaperId(EncoderUtils.decode(request.getPaperId()));
76-
request.setUsersid(EncoderUtils.decode(request.getUsersid()));
74+
// IDs should be sent as-is (encoded). Backend will handle decoding.
75+
// Do not decode here to avoid double-decoding issues.
7776

7877
Log.d(TAG, "Updating reading progress: paperId=" + request.getPaperId() +
7978
", progress=" + request.getProgress() + "%, lastPage=" + request.getLastPage());
@@ -114,9 +113,8 @@ public void onFailure(@NonNull Call<BaseJsonResponse<String>> call,
114113
* Update reading status
115114
*/
116115
public void updateStatus(ReadingWorkflowStatusRequest request, ApiCallback<String> callback) {
117-
request.setCollectionId(EncoderUtils.decode(request.getCollectionId()));
118-
request.setPaperId(EncoderUtils.decode(request.getPaperId()));
119-
request.setUsersid(EncoderUtils.decode(request.getUsersid()));
116+
// IDs should be sent as-is (encoded). Backend will handle decoding.
117+
// Do not decode here to avoid double-decoding issues.
120118

121119
Log.d(TAG, "Updating reading status: paperId=" + request.getPaperId() +
122120
", status=" + request.getStatus());

android-app/app/src/main/java/com/se1853_jv/labverse/presentation/feed/adapter/PersonalLibraryAdapter.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package com.se1853_jv.labverse.presentation.feed.adapter;
22

3+
import android.graphics.drawable.GradientDrawable;
34
import android.view.LayoutInflater;
45
import android.view.View;
56
import android.view.ViewGroup;
67
import android.widget.ProgressBar;
78
import android.widget.TextView;
89

910
import androidx.annotation.NonNull;
11+
import androidx.core.content.ContextCompat;
1012
import androidx.recyclerview.widget.RecyclerView;
1113

1214
import com.se1853_jv.labverse.R;
@@ -76,13 +78,15 @@ static class PaperViewHolder extends RecyclerView.ViewHolder {
7678
private TextView textTitle;
7779
private TextView textAuthors;
7880
private TextView textJournal;
81+
private TextView textStatusChip;
7982
private ProgressBar progressBar;
8083

8184
public PaperViewHolder(@NonNull View itemView) {
8285
super(itemView);
8386
textTitle = itemView.findViewById(R.id.text_paper_title);
8487
textAuthors = itemView.findViewById(R.id.text_paper_authors);
8588
textJournal = itemView.findViewById(R.id.text_paper_journal);
89+
textStatusChip = itemView.findViewById(R.id.text_status_chip);
8690
progressBar = itemView.findViewById(R.id.progress_bar);
8791
}
8892

@@ -116,6 +120,43 @@ public void bind(PaperResearch paper, OnPaperClickListener listener, boolean sho
116120
}
117121
}
118122

123+
// Handle status chip visibility and status calculation (only for recently_read tab)
124+
if (textStatusChip != null) {
125+
if (showProgress) {
126+
textStatusChip.setVisibility(View.VISIBLE);
127+
String statusText;
128+
int backgroundColorRes;
129+
int textColorRes;
130+
131+
// Handle null progress as 0 (Unread)
132+
int progressValue = (progress != null) ? progress : 0;
133+
134+
if (progressValue >= 100) {
135+
statusText = "Finished";
136+
backgroundColorRes = R.color.third_green;
137+
textColorRes = R.color.white;
138+
} else if (progressValue == 0) {
139+
statusText = "Unread";
140+
backgroundColorRes = R.color.gray_200;
141+
textColorRes = R.color.text_secondary;
142+
} else {
143+
statusText = "Reading";
144+
backgroundColorRes = R.color.blue;
145+
textColorRes = R.color.white;
146+
}
147+
148+
textStatusChip.setText(statusText);
149+
// Set background color
150+
GradientDrawable drawable = (GradientDrawable) textStatusChip.getBackground();
151+
if (drawable != null) {
152+
drawable.setColor(ContextCompat.getColor(itemView.getContext(), backgroundColorRes));
153+
}
154+
textStatusChip.setTextColor(ContextCompat.getColor(itemView.getContext(), textColorRes));
155+
} else {
156+
textStatusChip.setVisibility(View.GONE);
157+
}
158+
}
159+
119160
// Handle card click
120161
itemView.setOnClickListener(v -> {
121162
if (listener != null) {

android-app/app/src/main/java/com/se1853_jv/labverse/presentation/search/SearchActivity.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@
1818
import com.se1853_jv.labverse.R;
1919
import com.se1853_jv.labverse.data.api.ApiCallback;
2020
import com.se1853_jv.labverse.data.api.paper.PaperApiHandler;
21+
import com.se1853_jv.labverse.data.utils.SessionManager;
2122
import com.se1853_jv.labverse.domain.infrastructure.paper.model.PaperResearch;
2223
import com.se1853_jv.labverse.presentation.common.BaseActivity;
2324
import com.se1853_jv.labverse.presentation.common.HeaderHelper;
2425
import com.se1853_jv.labverse.presentation.search.adapter.SearchResultAdapter;
2526

27+
import java.util.HashMap;
2628
import java.util.List;
29+
import java.util.Map;
2730

2831
public class SearchActivity extends BaseActivity implements FilterDialogFragment.FilterDialogListener {
2932
private static final String TAG = "SearchActivity";
@@ -236,6 +239,9 @@ public void onSuccess(List<PaperResearch> papers) {
236239
hideEmptyState();
237240
adapter.setPapers(papers);
238241

242+
// Load reading progress for papers
243+
loadReadingProgress(papers);
244+
239245
// Calculate total pages based on results
240246
// Note: Backend should return total count, but for now we estimate
241247
if (papers.size() < PAGE_SIZE) {
@@ -324,5 +330,65 @@ private void hideEmptyState() {
324330
recyclerView.setVisibility(View.VISIBLE);
325331
paginationControls.setVisibility(View.VISIBLE);
326332
}
333+
334+
@Override
335+
protected void onResume() {
336+
super.onResume();
337+
// Reload reading progress for currently displayed papers
338+
// This updates progress when user returns from reading a paper
339+
if (adapter != null && adapter.getItemCount() > 0) {
340+
List<PaperResearch> currentPapers = adapter.getPapers();
341+
if (currentPapers != null && !currentPapers.isEmpty()) {
342+
loadReadingProgress(currentPapers);
343+
}
344+
}
345+
}
346+
347+
/**
348+
* Load reading progress for papers from ReadingWorkflow
349+
*/
350+
private void loadReadingProgress(List<PaperResearch> papers) {
351+
// Get user ID
352+
SessionManager sessionManager = new SessionManager(this);
353+
String userId = sessionManager.getUserId();
354+
355+
if (userId == null) {
356+
return;
357+
}
358+
359+
// Get workflow repository
360+
var db = com.se1853_jv.labverse.domain.db.DatabaseClient.getInstance(this).getAppDatabase();
361+
var workflowRepository = db.readingWorkflowRepository();
362+
363+
// Load reading progress for each paper on background thread
364+
new Thread(() -> {
365+
try {
366+
// Use PERSONAL_LIBRARY as collectionId for search papers (not in a collection)
367+
String collectionId = "PERSONAL_LIBRARY";
368+
Map<String, Integer> progressMap = new HashMap<>();
369+
370+
for (PaperResearch paper : papers) {
371+
try {
372+
com.se1853_jv.labverse.domain.infrastructure.workflow.model.ReadingWorkflow workflow =
373+
workflowRepository.getByCompositeKey(userId, paper.getId(), collectionId);
374+
375+
if (workflow != null && workflow.getProgress() != null) {
376+
progressMap.put(paper.getId(), workflow.getProgress());
377+
}
378+
} catch (Exception e) {
379+
Log.e(TAG, "Error loading progress for paper: " + paper.getId(), e);
380+
}
381+
}
382+
383+
// Update UI on main thread
384+
runOnUiThread(() -> {
385+
adapter.setPaperProgressData(progressMap);
386+
});
387+
388+
} catch (Exception e) {
389+
Log.e(TAG, "Error loading reading progress", e);
390+
}
391+
}).start();
392+
}
327393
}
328394

0 commit comments

Comments
 (0)