Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions app/res/layout/item_login_connect_home_corrupt_apps.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rootCardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:clickable="true"
android:focusable="true"
android:foreground="?android:attr/selectableItemBackground"
android:shadowColor="@color/connect_light_grey_transparent"
android:shadowDx="30"
android:shadowDy="30"
android:shadowRadius="50"
app:cardBackgroundColor="@color/connect_red"
app:cardCornerRadius="12dp"
app:cardElevation="5dp">

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="18dp">

<org.commcare.views.connect.connecttextview.ConnectMediumTextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:lineSpacingExtra="5dp"
android:minLines="1"
android:textColor="@color/black"
android:textSize="14sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<org.commcare.views.connect.connecttextview.ConnectMediumTextView
android:id="@+id/tvDescription"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:lineSpacingExtra="5dp"
android:layout_marginTop="2dp"
android:minLines="1"
android:text="@string/connect_corrupt_job_text"
android:textColor="@color/black"
android:textSize="12sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvTitle" />

</androidx.constraintlayout.widget.ConstraintLayout>

</androidx.cardview.widget.CardView>
1 change: 1 addition & 0 deletions app/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,7 @@
<string name="connect_last_update">Updated: %s</string>
<string name="connect_title">Connect</string>
<string name="connect_no_jobs">You have not been invited to any active opportunities. Please come back later.</string>
<string name="connect_corrupt_job_text">Failed to load this opportunity</string>

<string name="connect_job_intro_title">Opportunity</string>

Expand Down
54 changes: 42 additions & 12 deletions app/src/org/commcare/adapters/JobListConnectHomeAppsAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.commcare.activities.LoginActivity;
import org.commcare.dalvik.R;
import org.commcare.dalvik.databinding.ItemLoginConnectHomeAppsBinding;
import org.commcare.dalvik.databinding.ItemLoginConnectHomeCorruptAppsBinding;
import org.commcare.interfaces.OnJobSelectionClick;
import org.commcare.models.connect.ConnectLoginJobListModel;

Expand All @@ -25,42 +26,68 @@
import static org.commcare.connect.ConnectConstants.JOB_DELIVERY;
import static org.commcare.connect.ConnectConstants.JOB_LEARNING;

public class JobListConnectHomeAppsAdapter extends RecyclerView.Adapter<JobListConnectHomeAppsAdapter.ViewHolder> {
public class JobListConnectHomeAppsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

private Context mContext;
private final ArrayList<ConnectLoginJobListModel> jobList;
private final OnJobSelectionClick launcher;
private final ArrayList<ConnectLoginJobListModel> corruptJobs;
private static int NON_CORRUPT_JOB_VIEW = 4983;
private static int CORRUPT_JOB_VIEW = 9533;

public JobListConnectHomeAppsAdapter(Context context, ArrayList<ConnectLoginJobListModel> jobList, OnJobSelectionClick launcher) {
public JobListConnectHomeAppsAdapter(Context context, ArrayList<ConnectLoginJobListModel> jobList, ArrayList<ConnectLoginJobListModel> corruptJobs, OnJobSelectionClick launcher) {
this.mContext = context;
this.jobList = jobList;
this.corruptJobs = corruptJobs;
this.launcher = launcher;
}

@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
mContext = parent.getContext();
// Inflate the layout for each item using View Binding
ItemLoginConnectHomeAppsBinding binding = ItemLoginConnectHomeAppsBinding.inflate(
LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(binding);
if(viewType==NON_CORRUPT_JOB_VIEW){
ItemLoginConnectHomeAppsBinding binding = ItemLoginConnectHomeAppsBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new NonCorruptJobViewHolder(binding);
}else{
ItemLoginConnectHomeCorruptAppsBinding binding = ItemLoginConnectHomeCorruptAppsBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new CorruptJobViewHolder(binding);
}
}

@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
bind(mContext, holder.binding, jobList.get(position), launcher);
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if(holder instanceof NonCorruptJobViewHolder){
bind(mContext, ((NonCorruptJobViewHolder)holder).binding, jobList.get(position), launcher);
} else if (holder instanceof CorruptJobViewHolder) {
bind(mContext, ((CorruptJobViewHolder)holder).binding, corruptJobs.get(position-jobList.size()));
}
}

@Override
public int getItemCount() {
return jobList.size();
return jobList.size()+corruptJobs.size();
}

public static class ViewHolder extends RecyclerView.ViewHolder {
@Override
public int getItemViewType(int position) {
return position<jobList.size()?NON_CORRUPT_JOB_VIEW:CORRUPT_JOB_VIEW;
}

public static class NonCorruptJobViewHolder extends RecyclerView.ViewHolder {
private final ItemLoginConnectHomeAppsBinding binding;

public ViewHolder(ItemLoginConnectHomeAppsBinding binding) {
public NonCorruptJobViewHolder(ItemLoginConnectHomeAppsBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
}

public static class CorruptJobViewHolder extends RecyclerView.ViewHolder {
private final ItemLoginConnectHomeCorruptAppsBinding binding;

public CorruptJobViewHolder(ItemLoginConnectHomeCorruptAppsBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
Expand All @@ -73,11 +100,14 @@ private static String formatDate(String dateStr) {
Date date = inputFormat.parse(dateStr);
return outputFormat.format(date);
} catch (ParseException e) {
e.printStackTrace();
return null;
}
}

public void bind(Context mContext, ItemLoginConnectHomeCorruptAppsBinding binding, ConnectLoginJobListModel connectLoginJobListModel) {
binding.tvTitle.setText(connectLoginJobListModel.getName());
}

public void bind(Context mContext, ItemLoginConnectHomeAppsBinding binding, ConnectLoginJobListModel connectLoginJobListModel, OnJobSelectionClick launcher) {
binding.tvTitle.setText(connectLoginJobListModel.getName());
binding.tvDate.setText(formatDate(connectLoginJobListModel.getDate().toString()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,15 @@ public ConnectJobRecord() {
dailyFinishTime = "";
}

public static ConnectJobRecord fromJson(JSONObject json) throws JSONException, ParseException {
public static ConnectJobRecord corruptJobfromJson(JSONObject json)throws JSONException{
ConnectJobRecord job = new ConnectJobRecord();
job.title = json.has(META_NAME) ? json.getString(META_NAME) : "";
job.description = json.has(META_DESCRIPTION) ? json.getString(META_DESCRIPTION) : "";
job.organization = json.has(META_ORGANIZATION) ? json.getString(META_ORGANIZATION) : "";
return job;
}

public static ConnectJobRecord fromJson(JSONObject json) throws JSONException {
ConnectJobRecord job = new ConnectJobRecord();

job.jobId = json.getInt(META_JOB_ID);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public ConnectPaymentUnitRecord() {

}

public static ConnectPaymentUnitRecord fromJson(JSONObject json, int jobId) throws JSONException, ParseException {
public static ConnectPaymentUnitRecord fromJson(JSONObject json, int jobId) throws JSONException {
try {
ConnectPaymentUnitRecord paymentUnit = new ConnectPaymentUnitRecord();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
Expand Down Expand Up @@ -55,7 +57,6 @@

import java.io.IOException;
import java.io.InputStream;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
Expand All @@ -72,6 +73,7 @@ public class ConnectJobsListsFragment extends Fragment {
private TextView updateText;
private IConnectAppLauncher launcher;
ArrayList<ConnectLoginJobListModel> jobList;
ArrayList<ConnectLoginJobListModel> corruptJobs = new ArrayList<>();
View view;


Expand Down Expand Up @@ -133,6 +135,7 @@ public void onResume() {
}

public void refreshData() {
corruptJobs.clear();
ApiConnect.getConnectOpportunities(getContext(), new IApiCallback() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we call retrieveOpportunities directly here, seems like a lot of code duplication here to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shubham1g5 Sorry I didn't get you. Which retrieveOpportunities are you referring?

Copy link
Contributor

@shubham1g5 shubham1g5 Apr 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ConnectUnlockFragment.retrieveOpportunities

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They both are in different fragment so not sure if we can use that. Already code is separate originally.

@Override
public void processSuccess(int responseCode, InputStream responseData) {
Expand All @@ -146,17 +149,24 @@ public void processSuccess(int responseCode, InputStream responseData) {
JSONArray json = new JSONArray(responseAsString);
List<ConnectJobRecord> jobs = new ArrayList<>(json.length());
for (int i = 0; i < json.length(); i++) {
JSONObject obj = (JSONObject) json.get(i);
jobs.add(ConnectJobRecord.fromJson(obj));
JSONObject obj=null;
try {
obj = (JSONObject)json.get(i);
jobs.add(ConnectJobRecord.fromJson(obj));
}catch (JSONException e) {
Logger.exception("Parsing return from Opportunities request", e);
handleCorruptJob(obj);
}
}

//Store retrieved jobs
totalJobs = jobs.size();
newJobs = ConnectJobUtils.storeJobs(getContext(), jobs, true);
setJobListData(jobs);
}
} catch (IOException | JSONException | ParseException e) {
Logger.exception("Parsing return from Opportunities request", e);
} catch (IOException | JSONException e) {
Logger.exception("Parsing / database error return from Opportunities request", e);
throw new RuntimeException(e);
Comment on lines +167 to +169
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we only want to crash for JSONException and not IOException here. IOException doesn't necessarily mean a code error and can happen due to reasons outside the control of our app.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shubham1g5 You are right for other cases but here IOException is raised if any error while reading the network response so I think we should include it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that signifies an error in network stream which should not be related to our application logic.

}

reportApiCall(true, totalJobs, newJobs);
Expand Down Expand Up @@ -202,6 +212,16 @@ private void refreshUi() {
}
}

private void handleCorruptJob(JSONObject obj) {
if(obj!=null) {
try {
corruptJobs.add(createJobModel(ConnectJobRecord.corruptJobfromJson(obj)));
} catch (JSONException e) {
Logger.exception("JSONException while retrieving corrupt opportunity title", e);
}
}
}

private void updateSecondaryPhoneConfirmationTile(Context context) {
boolean show = ConnectManager.shouldShowSecondaryPhoneConfirmationTile(context);

Expand All @@ -220,9 +240,9 @@ private void initRecyclerView() {
RecyclerView rvJobList = view.findViewById(R.id.rvJobList);

TextView noJobsText = view.findViewById(R.id.connect_no_jobs_text);
noJobsText.setVisibility(jobList.size() > 0 ? View.GONE : View.VISIBLE);
noJobsText.setVisibility((corruptJobs.size()>0 || jobList.size() > 0) ? View.GONE : View.VISIBLE);

JobListConnectHomeAppsAdapter adapter = new JobListConnectHomeAppsAdapter(getContext(), jobList, (job, isLearning, appId, jobType) -> {
JobListConnectHomeAppsAdapter adapter = new JobListConnectHomeAppsAdapter(getContext(), jobList,corruptJobs, (job, isLearning, appId, jobType) -> {
if (jobType.equals(JOB_NEW_OPPORTUNITY)) {
launchJobInfo(job);
} else {
Expand Down Expand Up @@ -354,6 +374,15 @@ private ConnectLoginJobListModel createJobModel(
);
}

private ConnectLoginJobListModel createJobModel( // Keeping only title as of now as other information might be corrupt
ConnectJobRecord job
) {
return new ConnectLoginJobListModel(
job.getTitle(),
job
);
}

private String getAppIdForType(ConnectJobRecord job, String jobType) {
return jobType.equalsIgnoreCase(JOB_LEARNING)
? job.getLearnAppInfo().getAppId()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,20 @@ public void processSuccess(int responseCode, InputStream responseData) {
JSONArray json = new JSONArray(responseAsString);
List<ConnectJobRecord> jobs = new ArrayList<>(json.length());
for (int i = 0; i < json.length(); i++) {
JSONObject obj = (JSONObject) json.get(i);
ConnectJobRecord job = ConnectJobRecord.fromJson(obj);
jobs.add(job);
try {
JSONObject obj = (JSONObject) json.get(i);
ConnectJobRecord job = ConnectJobRecord.fromJson(obj);
jobs.add(job);
}catch (JSONException e) {
Logger.exception("Parsing return from Opportunities request", e);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this mean we would swallow exception without showing any errors to the user ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shubham1g5 Here, there is no UI to display and code is just retrieving opportunities only to store locally. Also, local store should only have good opportunities and not corrupt ones.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree on DB containing only good opportunities. I am trying to think if it's possible for user to not see an error on jobs list page when there is a corrupt opportunity in the response here ? For eg. it would be confusing if the corrupt opportunity shows only some times on UI and not all times due to us reusing the stored jobs list from DB that doesn't have corrupt opportunities.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In both cases, DB has good jobs only. ConnectUnlockFragment is not showing any UI for opportunities.

}
}
new JobStoreManager(requireContext()).storeJobs(requireContext(), jobs, true);
}
} catch (IOException | JSONException | ParseException e) {
} catch (IOException | JSONException e) {
Toast.makeText(requireContext(), R.string.connect_job_list_api_failure, Toast.LENGTH_SHORT).show();
Logger.exception("Parsing return from Opportunities request", e);
throw new RuntimeException(e);
}

setFragmentRedirection();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ public ConnectLoginJobListModel(
public ConnectLoginJobListModel() {
}

public ConnectLoginJobListModel(
String name,
ConnectJobRecord job
) {
this.name = name;
this.job = job;
}
protected ConnectLoginJobListModel(Parcel in) {
name = in.readString();
id = in.readString();
Expand Down