diff --git a/app/res/drawable-hdpi/ic_connect_delivery.png b/app/res/drawable-hdpi/ic_connect_delivery.png
new file mode 100644
index 0000000000..f9edd59c31
Binary files /dev/null and b/app/res/drawable-hdpi/ic_connect_delivery.png differ
diff --git a/app/res/drawable-hdpi/ic_connect_learning.png b/app/res/drawable-hdpi/ic_connect_learning.png
new file mode 100644
index 0000000000..265feb1286
Binary files /dev/null and b/app/res/drawable-hdpi/ic_connect_learning.png differ
diff --git a/app/res/drawable-hdpi/ic_connect_new_opportunity.png b/app/res/drawable-hdpi/ic_connect_new_opportunity.png
new file mode 100644
index 0000000000..ca8029e39c
Binary files /dev/null and b/app/res/drawable-hdpi/ic_connect_new_opportunity.png differ
diff --git a/app/res/drawable-ldpi/drawable-ldpi/ic_connect_delivery.png b/app/res/drawable-ldpi/drawable-ldpi/ic_connect_delivery.png
new file mode 100644
index 0000000000..e34e6eaedf
Binary files /dev/null and b/app/res/drawable-ldpi/drawable-ldpi/ic_connect_delivery.png differ
diff --git a/app/res/drawable-ldpi/ic_connect_delivery.png b/app/res/drawable-ldpi/ic_connect_delivery.png
new file mode 100644
index 0000000000..e34e6eaedf
Binary files /dev/null and b/app/res/drawable-ldpi/ic_connect_delivery.png differ
diff --git a/app/res/drawable-ldpi/ic_connect_learning.png b/app/res/drawable-ldpi/ic_connect_learning.png
new file mode 100644
index 0000000000..8a0fde1acd
Binary files /dev/null and b/app/res/drawable-ldpi/ic_connect_learning.png differ
diff --git a/app/res/drawable-ldpi/ic_connect_new_opportunity.png b/app/res/drawable-ldpi/ic_connect_new_opportunity.png
new file mode 100644
index 0000000000..d266ca3fe5
Binary files /dev/null and b/app/res/drawable-ldpi/ic_connect_new_opportunity.png differ
diff --git a/app/res/drawable-mdpi/ic_connect_delivery.png b/app/res/drawable-mdpi/ic_connect_delivery.png
new file mode 100644
index 0000000000..2905af432d
Binary files /dev/null and b/app/res/drawable-mdpi/ic_connect_delivery.png differ
diff --git a/app/res/drawable-mdpi/ic_connect_learning.png b/app/res/drawable-mdpi/ic_connect_learning.png
new file mode 100644
index 0000000000..9df63695d5
Binary files /dev/null and b/app/res/drawable-mdpi/ic_connect_learning.png differ
diff --git a/app/res/drawable-mdpi/ic_connect_new_opportunity.png b/app/res/drawable-mdpi/ic_connect_new_opportunity.png
new file mode 100644
index 0000000000..19799a681d
Binary files /dev/null and b/app/res/drawable-mdpi/ic_connect_new_opportunity.png differ
diff --git a/app/res/drawable-xhdpi/ic_connect_delivery.png b/app/res/drawable-xhdpi/ic_connect_delivery.png
new file mode 100644
index 0000000000..2e3a984904
Binary files /dev/null and b/app/res/drawable-xhdpi/ic_connect_delivery.png differ
diff --git a/app/res/drawable-xhdpi/ic_connect_learning.png b/app/res/drawable-xhdpi/ic_connect_learning.png
new file mode 100644
index 0000000000..0cad2265e8
Binary files /dev/null and b/app/res/drawable-xhdpi/ic_connect_learning.png differ
diff --git a/app/res/drawable-xhdpi/ic_connect_new_opportunity.png b/app/res/drawable-xhdpi/ic_connect_new_opportunity.png
new file mode 100644
index 0000000000..19ea813313
Binary files /dev/null and b/app/res/drawable-xhdpi/ic_connect_new_opportunity.png differ
diff --git a/app/res/drawable-xxhdpi/ic_connect_delivery.png b/app/res/drawable-xxhdpi/ic_connect_delivery.png
new file mode 100644
index 0000000000..9d2dd19e6b
Binary files /dev/null and b/app/res/drawable-xxhdpi/ic_connect_delivery.png differ
diff --git a/app/res/drawable-xxhdpi/ic_connect_learning.png b/app/res/drawable-xxhdpi/ic_connect_learning.png
new file mode 100644
index 0000000000..4d0774b815
Binary files /dev/null and b/app/res/drawable-xxhdpi/ic_connect_learning.png differ
diff --git a/app/res/drawable-xxhdpi/ic_connect_new_opportunity.png b/app/res/drawable-xxhdpi/ic_connect_new_opportunity.png
new file mode 100644
index 0000000000..d0f53d654c
Binary files /dev/null and b/app/res/drawable-xxhdpi/ic_connect_new_opportunity.png differ
diff --git a/app/res/drawable-xxxhdpi/ic_connect_delivery.png b/app/res/drawable-xxxhdpi/ic_connect_delivery.png
new file mode 100644
index 0000000000..ae9864730f
Binary files /dev/null and b/app/res/drawable-xxxhdpi/ic_connect_delivery.png differ
diff --git a/app/res/drawable-xxxhdpi/ic_connect_learning.png b/app/res/drawable-xxxhdpi/ic_connect_learning.png
new file mode 100644
index 0000000000..03e11daaae
Binary files /dev/null and b/app/res/drawable-xxxhdpi/ic_connect_learning.png differ
diff --git a/app/res/drawable-xxxhdpi/ic_connect_new_opportunity.png b/app/res/drawable-xxxhdpi/ic_connect_new_opportunity.png
new file mode 100644
index 0000000000..113f7e78a6
Binary files /dev/null and b/app/res/drawable-xxxhdpi/ic_connect_new_opportunity.png differ
diff --git a/app/res/drawable/connect_bottom_sheet_rounded_corners.xml b/app/res/drawable/connect_bottom_sheet_rounded_corners.xml
new file mode 100644
index 0000000000..1738d92bae
--- /dev/null
+++ b/app/res/drawable/connect_bottom_sheet_rounded_corners.xml
@@ -0,0 +1,67 @@
+
+
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/res/drawable/connect_login_bottomsheet_tab_textcolor.xml b/app/res/drawable/connect_login_bottomsheet_tab_textcolor.xml
new file mode 100644
index 0000000000..a213b2c350
--- /dev/null
+++ b/app/res/drawable/connect_login_bottomsheet_tab_textcolor.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/res/drawable/connect_login_new_job_pup_icon.png b/app/res/drawable/connect_login_new_job_pup_icon.png
new file mode 100644
index 0000000000..ebb81d4747
Binary files /dev/null and b/app/res/drawable/connect_login_new_job_pup_icon.png differ
diff --git a/app/res/drawable/connect_rounded_corner_light_green.xml b/app/res/drawable/connect_rounded_corner_light_green.xml
new file mode 100644
index 0000000000..671ea8c44e
--- /dev/null
+++ b/app/res/drawable/connect_rounded_corner_light_green.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/res/drawable/connect_rounded_corner_orange_yellow.xml b/app/res/drawable/connect_rounded_corner_orange_yellow.xml
new file mode 100644
index 0000000000..497a0abb34
--- /dev/null
+++ b/app/res/drawable/connect_rounded_corner_orange_yellow.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/res/drawable/connect_rounded_corner_teslish_blue.xml b/app/res/drawable/connect_rounded_corner_teslish_blue.xml
new file mode 100644
index 0000000000..6f24f72015
--- /dev/null
+++ b/app/res/drawable/connect_rounded_corner_teslish_blue.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/res/drawable/ic_connect_pager_indicator.xml b/app/res/drawable/ic_connect_pager_indicator.xml
new file mode 100644
index 0000000000..e3d8f5124c
--- /dev/null
+++ b/app/res/drawable/ic_connect_pager_indicator.xml
@@ -0,0 +1,17 @@
+
+
+ -
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/res/drawable/ic_snackbar_done.png b/app/res/drawable/ic_snackbar_done.png
new file mode 100644
index 0000000000..9388b5fc40
Binary files /dev/null and b/app/res/drawable/ic_snackbar_done.png differ
diff --git a/app/res/layout/bottomsheet_login_joblist.xml b/app/res/layout/bottomsheet_login_joblist.xml
new file mode 100644
index 0000000000..ac35f717de
--- /dev/null
+++ b/app/res/layout/bottomsheet_login_joblist.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/res/layout/custom_snack_bar.xml b/app/res/layout/custom_snack_bar.xml
new file mode 100644
index 0000000000..0fef7df9cd
--- /dev/null
+++ b/app/res/layout/custom_snack_bar.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/res/layout/custom_tab.xml b/app/res/layout/custom_tab.xml
new file mode 100644
index 0000000000..336d63550c
--- /dev/null
+++ b/app/res/layout/custom_tab.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
diff --git a/app/res/layout/fragment_connect_login_combine_apps.xml b/app/res/layout/fragment_connect_login_combine_apps.xml
new file mode 100644
index 0000000000..79467f891f
--- /dev/null
+++ b/app/res/layout/fragment_connect_login_combine_apps.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/res/layout/fragment_connect_login_commcare_apps.xml b/app/res/layout/fragment_connect_login_commcare_apps.xml
new file mode 100644
index 0000000000..9156e45990
--- /dev/null
+++ b/app/res/layout/fragment_connect_login_commcare_apps.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/res/layout/fragment_connect_login_commcare_home.xml b/app/res/layout/fragment_connect_login_commcare_home.xml
new file mode 100644
index 0000000000..8913340773
--- /dev/null
+++ b/app/res/layout/fragment_connect_login_commcare_home.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/res/layout/item_install_commcare_apps.xml b/app/res/layout/item_install_commcare_apps.xml
new file mode 100644
index 0000000000..91532f022b
--- /dev/null
+++ b/app/res/layout/item_install_commcare_apps.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/res/layout/item_login_commcare_apps.xml b/app/res/layout/item_login_commcare_apps.xml
new file mode 100644
index 0000000000..585bebbc83
--- /dev/null
+++ b/app/res/layout/item_login_commcare_apps.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/res/layout/item_login_connect_home_apps.xml b/app/res/layout/item_login_connect_home_apps.xml
new file mode 100644
index 0000000000..e29d3a7c69
--- /dev/null
+++ b/app/res/layout/item_login_connect_home_apps.xml
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/res/layout/screen_login.xml b/app/res/layout/screen_login.xml
index cd30b61afe..e167903902 100644
--- a/app/res/layout/screen_login.xml
+++ b/app/res/layout/screen_login.xml
@@ -1,11 +1,10 @@
@@ -13,7 +12,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
-
@@ -26,7 +25,7 @@
android:orientation="vertical"
android:visibility="gone">
-
+ android:layout_height="wrap_content">
-
+ android:layout_marginHorizontal="12dp"
+ android:layout_marginVertical="20dp"
+ android:background="@drawable/shape_border_8px_white"
+ android:orientation="vertical">
-
-
+
-
+
+
-
-
-
-
+ android:paddingTop="20dp"
+ android:paddingBottom="25dp">
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ android:layout_margin="@dimen/standard_spacer"
+ android:textSize="@dimen/font_size_medium"
+ android:visibility="gone"
+ tools:ignore="MissingConstraints" />
-
+
-
+
+
+
+
-
-
-
-
+ android:layout_height="wrap_content"
+ android:background="@drawable/tile_drop_shadow_small_margins"
+ android:visibility="gone">
+
+
+
+
+
+
+
-
+
diff --git a/app/res/layout/temp_login.xml b/app/res/layout/temp_login.xml
deleted file mode 100644
index 051bfafe8a..0000000000
--- a/app/res/layout/temp_login.xml
+++ /dev/null
@@ -1,350 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/res/layout/testing_recyclerview_joblist.xml b/app/res/layout/testing_recyclerview_joblist.xml
new file mode 100644
index 0000000000..9a4c585788
--- /dev/null
+++ b/app/res/layout/testing_recyclerview_joblist.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/res/values-fr/strings.xml b/app/res/values-fr/strings.xml
index 8bb056d7ac..ee07050d30 100644
--- a/app/res/values-fr/strings.xml
+++ b/app/res/values-fr/strings.xml
@@ -270,4 +270,13 @@ License.
Une erreur s\'est produite lors de la connexion au serveur.
Oui
Non
+
+ Nouvelle opportunité
+ Apprendre
+ Livraison
+ Application CommCare
+ Installer
+ Installer l\'application
+
+ Installer de nouvelles applications \nCommCare
diff --git a/app/res/values-pt/strings.xml b/app/res/values-pt/strings.xml
index 3e8176287b..38eee57b9f 100644
--- a/app/res/values-pt/strings.xml
+++ b/app/res/values-pt/strings.xml
@@ -449,4 +449,12 @@
Ocorreu um erro ao ligar ao servidor.
Sim
Não
+
+ Nova oportunidade
+ Aprender
+ Entrega
+ Aplicativo CommCare
+ Instalar aplicativo
+ Instalar
+ Instale novos aplicativos \nCommCare
\ No newline at end of file
diff --git a/app/res/values/colors.xml b/app/res/values/colors.xml
index 2cdb96c759..da52ec36b6 100644
--- a/app/res/values/colors.xml
+++ b/app/res/values/colors.xml
@@ -151,6 +151,15 @@
#1A3A42C7
#3942C7
#3A42C7
+ #E7EAF8
#F3B34D
#D9D9D9
+ #1AD9D9D9
+ #E5E7EB
+ #9CA3AF
+ #FFEFD5
+ #16A085
+ #E6F5E5
+ #f2f5ff
+ #EA6944
diff --git a/app/res/values/strings.xml b/app/res/values/strings.xml
index 52fbe7dd4c..968a9af04e 100644
--- a/app/res/values/strings.xml
+++ b/app/res/values/strings.xml
@@ -791,4 +791,17 @@
An error occurred while connecting to the server.
Yes
No
+
+
+ New Opportunity
+ Learn
+ Delivery
+ CommCare App
+
+ Install App
+ Install new \nCommCare Apps
+ Install
+
+
+ Hello blank fragment
diff --git a/app/src/org/commcare/activities/LoginActivity.java b/app/src/org/commcare/activities/LoginActivity.java
index 660f3e4dbd..03963e8f73 100644
--- a/app/src/org/commcare/activities/LoginActivity.java
+++ b/app/src/org/commcare/activities/LoginActivity.java
@@ -1,5 +1,12 @@
package org.commcare.activities;
+import static org.commcare.android.database.connect.models.ConnectJobRecord.STATUS_AVAILABLE;
+import static org.commcare.android.database.connect.models.ConnectJobRecord.STATUS_AVAILABLE_NEW;
+import static org.commcare.android.database.connect.models.ConnectJobRecord.STATUS_DELIVERING;
+import static org.commcare.android.database.connect.models.ConnectJobRecord.STATUS_LEARNING;
+import static org.commcare.connect.ConnectManager.isAppInstalled;
+
+import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
@@ -16,22 +23,38 @@
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.Toast;
+import androidx.annotation.NonNull;
+import androidx.core.app.ActivityCompat;
+import androidx.core.util.Pair;
+import androidx.preference.PreferenceManager;
+import androidx.work.WorkManager;
+
import com.scottyab.rootbeer.RootBeer;
import org.commcare.CommCareApp;
import org.commcare.CommCareApplication;
+import org.commcare.activities.connect.ConnectActivity;
import org.commcare.android.database.app.models.UserKeyRecord;
+import org.commcare.android.database.connect.models.ConnectAppRecord;
import org.commcare.android.database.connect.models.ConnectJobRecord;
+import org.commcare.android.database.connect.models.ConnectLinkedAppRecord;
import org.commcare.android.database.global.models.ApplicationRecord;
+import org.commcare.connect.ConnectConstants;
+import org.commcare.connect.ConnectDatabaseHelper;
import org.commcare.connect.ConnectManager;
+import org.commcare.connect.network.ApiConnect;
+import org.commcare.connect.network.ConnectNetworkHelper;
+import org.commcare.connect.network.IApiCallback;
import org.commcare.dalvik.BuildConfig;
import org.commcare.dalvik.R;
import org.commcare.engine.resource.AppInstallStatus;
import org.commcare.engine.resource.ResourceInstallUtils;
+import org.commcare.fragments.connect.login_job_fragments.ConnectLoginJobListBottomSheetFragment;
import org.commcare.google.services.analytics.FirebaseAnalyticsUtil;
import org.commcare.interfaces.CommCareActivityUIController;
import org.commcare.interfaces.RuntimePermissionRequester;
import org.commcare.interfaces.WithUIController;
+import org.commcare.models.connect.ConnectLoginJobListModel;
import org.commcare.models.database.user.DemoUserBuilder;
import org.commcare.preferences.DevSessionRestorer;
import org.commcare.preferences.HiddenPreferences;
@@ -55,17 +78,21 @@
import org.commcare.views.notifications.NotificationMessage;
import org.commcare.views.notifications.NotificationMessageFactory;
import org.commcare.views.notifications.NotificationMessageFactory.StockMessages;
+import org.javarosa.core.io.StreamsUtil;
import org.javarosa.core.services.Logger;
import org.javarosa.core.services.locale.Localization;
-
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
-
-import androidx.annotation.NonNull;
-import androidx.core.app.ActivityCompat;
-import androidx.core.util.Pair;
-import androidx.preference.PreferenceManager;
-import androidx.work.WorkManager;
+import java.util.List;
+import java.util.Locale;
/**
* @author ctsims
@@ -99,6 +126,19 @@ public class LoginActivity extends CommCareActivity
public static final String CONNECTID_MANAGED_LOGIN = "cid-managed-login";
public static final String GO_TO_CONNECT_JOB_STATUS = "go-to-connect-job-status";
+ public static final String SELECTED_CONNECT_JOB = "selected-connect-job";
+ public static final String SELECTED_COMM_CARE_JOB = "selected-comm-care-job";
+
+ public static final String JOB_NEW_OPPORTUNITY = "job-new-opportunity";
+ public static final String JOB_LEARNING = "job-learning";
+ public static final String JOB_DELIVERY = "job-delivery";
+ public static final String JOB_TRENDING = "job-trending";
+
+ public static final String NEW_APP = "new-app";
+ public static final String LEARN_APP = "learn-app";
+ public static final String DELIVERY_APP = "delivery-app";
+ public static final String TRADITIONAL_APP = "traditional-app";
+
private static final int TASK_KEY_EXCHANGE = 1;
private static final int TASK_UPGRADE_INSTALL = 2;
@@ -113,6 +153,9 @@ public class LoginActivity extends CommCareActivity
private String presetAppId;
private boolean appLaunchedFromConnect;
private boolean connectLaunchPerformed;
+ private List jobList;
+ private List traditionalJobList;
+ ConnectLoginJobListBottomSheetFragment connectLoginJobListBottomSheetFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -202,9 +245,10 @@ protected void onSaveInstanceState(Bundle savedInstanceState) {
* upon successful login
*/
protected void initiateLoginAttempt(boolean restoreSession) {
- if(isConnectJobsSelected()) {
+ if (isConnectJobsSelected()) {
ConnectManager.unlockConnect(this, success -> {
- if(success) {
+
+ if (success) {
ConnectManager.goToConnectJobsList(this);
}
});
@@ -215,15 +259,14 @@ protected void initiateLoginAttempt(boolean restoreSession) {
String seatedAppId = CommCareApplication.instance().getCurrentApp().getUniqueId();
String username = uiController.getEnteredUsername();
- if(!appLaunchedFromConnect && uiController.loginManagedByConnectId()) {
+ if (!appLaunchedFromConnect && uiController.loginManagedByConnectId()) {
ConnectManager.unlockConnect(this, success -> {
- if(success) {
+ if (success) {
String pass = ConnectManager.getStoredPasswordForApp(seatedAppId, username);
doLogin(loginMode, restoreSession, pass);
}
});
- }
- else {
+ } else {
String passwordOrPin = uiController.getEnteredPasswordOrPin();
doLogin(loginMode, restoreSession, passwordOrPin);
}
@@ -288,7 +331,7 @@ protected void onResume() {
if (shouldFinish()) {
return;
}
-
+ initiateJobListAPI();
// Otherwise, refresh the activity for current conditions
uiController.refreshView();
@@ -304,7 +347,7 @@ protected boolean checkForSeatedAppChange() {
String currentSeatedId = CommCareApplication.instance().getCurrentApp().getUniqueId();
if (!lastSeatedId.equals(currentSeatedId)) {
disableWorkForLastSeatedApp();
- prefs.edit().putString(KEY_LAST_APP, currentSeatedId).commit();
+ prefs.edit().putString(KEY_LAST_APP, currentSeatedId).apply();
return true;
}
return false;
@@ -453,7 +496,7 @@ public void dataPullCompleted() {
ViewUtil.hideVirtualKeyboard(LoginActivity.this);
CommCareApplication.notificationManager().clearNotifications(NOTIFICATION_MESSAGE_LOGIN);
- if(handleConnectSignIn()) {
+ if (handleConnectSignIn()) {
setResultAndFinish(false);
}
}
@@ -471,7 +514,8 @@ private void setResultAndFinish(boolean goToJobInfo) {
public void handleConnectButtonPress() {
selectedAppIndex = -1;
ConnectManager.unlockConnect(this, success -> {
- if(success) {
+
+ if (success) {
ConnectManager.goToConnectJobsList(this);
setResult(RESULT_OK);
finish();
@@ -480,12 +524,13 @@ public void handleConnectButtonPress() {
}
public boolean handleConnectSignIn() {
- if(ConnectManager.isConnectIdConfigured()) {
+
+ if (ConnectManager.isConnectIdConfigured()) {
ConnectManager.completeSignin();
String appId = CommCareApplication.instance().getCurrentApp().getUniqueId();
ConnectJobRecord job = ConnectManager.setConnectJobForApp(this, appId);
- if(job != null) {
+ if (job != null) {
ConnectManager.updateAppAccess(this, appId, getUniformUsername());
//Update job status
@@ -498,9 +543,10 @@ public boolean handleConnectSignIn() {
uiController.loginManagedByConnectId(), appId,
getUniformUsername(),
uiController.getEnteredPasswordOrPin(), success -> {
+
ConnectManager.updateAppAccess(this, appId, getUniformUsername());
setResultAndFinish(false);
- });
+ });
}
return false;
@@ -511,9 +557,9 @@ public boolean handleConnectSignIn() {
public void handleFailedConnectSignIn() {
ApplicationRecord record = CommCareApplication.instance().getCurrentApp().getAppRecord();
- if(appLaunchedFromConnect) {
+ if (appLaunchedFromConnect) {
FirebaseAnalyticsUtil.reportCccAppFailedAutoLogin(record.getApplicationId());
- } else if(ConnectManager.isLoginManagedByConnectId(record.getUniqueId(), getUniformUsername())) {
+ } else if (ConnectManager.isLoginManagedByConnectId(record.getUniqueId(), getUniformUsername())) {
//TODO: Display an additional message that the user will need to login with their password to restore CID login
//ConnectManager.forgetAppCredentials(record.getUniqueId(), getUniformUsername());
//checkForSavedCredentials();
@@ -535,13 +581,13 @@ private void checkForSavedCredentials() {
uiController.setConnectButtonVisible(false);
uiController.setUsername(getString(R.string.login_input_auto));
uiController.setPasswordOrPin(getString(R.string.login_input_auto));
- if(!seatAppIfNeeded(presetAppId)) {
+ if (!seatAppIfNeeded(presetAppId)) {
connectLaunchPerformed = true;
initiateLoginAttempt(uiController.isRestoreSessionChecked());
}
} else {
int selectorIndex = uiController.getSelectedAppIndex();
- if(selectorIndex > 0) {
+ if (selectorIndex > 0) {
String selectedAppId = appIdDropdownList.size() > 0 ? appIdDropdownList.get(selectorIndex) : "";
String seatedAppId = CommCareApplication.instance().getCurrentApp().getUniqueId();
if (!uiController.isAppSelectorVisible() || selectedAppId.equals(seatedAppId)) {
@@ -728,38 +774,42 @@ protected void populateAppSpinner(ArrayList readyApps) {
appIdDropdownList.add(r.getUniqueId());
}
- // Want to set the spinner's selection to match whatever the currently seated app is
- String currAppId = CommCareApplication.instance().getCurrentApp().getUniqueId();
+ String currAppId;
+ if (getDisplayNameByAppId(readyApps, uiController.getLastSelectedAppID()) != null) {
+ uiController.isAppInstalled = true;
+ currAppId = CommCareApplication.instance().getCurrentApp().getUniqueId();
+ uiController.cetAppSelector.setText(getDisplayNameByAppId(readyApps, currAppId));
+ uiController.setLoginInputsVisibility(true);
+ } else {
+ managePreference();
+ currAppId = uiController.sharedPreferences.getString(ConnectConstants.APP_ID, "");
+ uiController.cetAppSelector.setText(uiController.sharedPreferences.getString(ConnectConstants.JOB_TITLE, ""));
+ uiController.handleButtonText();
+ uiController.setLoginInputsVisibility(false);
+ }
int position = 0;
if (selectedAppIndex >= 0) {
position = selectedAppIndex;
} else if (appIdDropdownList.contains(currAppId)) {
position = appIdDropdownList.indexOf(currAppId);
}
+
uiController.setMultipleAppsUiState(appNames, position);
selectedAppIndex = -1;
}
+ private void managePreference() {
+ uiController.isAppInstalled = false;
+ uiController.selectedAppType = uiController.sharedPreferences.getString(ConnectConstants.JOB_TYPE, "");
+ uiController.selectedJobId = uiController.sharedPreferences.getString(ConnectConstants.JOB_ID, "");
+ }
+
private boolean isConnectJobsSelected() {
- return ConnectManager.isConnectIdConfigured() && uiController.getSelectedAppIndex() == 0;
+ return ConnectManager.isConnectIdConfigured() && uiController.sharedPreferences.getString(ConnectConstants.JOB_TYPE, "").equals(SELECTED_COMM_CARE_JOB);
}
@Override
public void onItemSelected(AdapterView> parent, View view, int position, long id) {
- boolean selectedConnect = isConnectJobsSelected();
- if(selectedConnect) {
- uiController.setLoginInputsVisibility(false);
- } else {
- // Retrieve the app record corresponding to the app selected
- selectedAppIndex = position;
- String appId = appIdDropdownList.get(selectedAppIndex);
- if (appId.length() > 0) {
- uiController.setLoginInputsVisibility(true);
- if (!seatAppIfNeeded(appId)) {
- checkForSavedCredentials();
- }
- }
- }
}
protected boolean seatAppIfNeeded(String appId) {
@@ -787,6 +837,7 @@ public void onNothingSelected(AdapterView> parent) {
private void installPendingUpdate() {
InstallStagedUpdateTask task =
new InstallStagedUpdateTask(TASK_UPGRADE_INSTALL) {
+ @SuppressLint("StaticFieldLeak")
@Override
protected void deliverResult(LoginActivity receiver,
AppInstallStatus result) {
@@ -950,7 +1001,7 @@ public void handlePullTaskError() {
private void checkManagedConfiguration() {
// Check for managed configuration
RestrictionsManager restrictionsManager =
- (RestrictionsManager)getSystemService(Context.RESTRICTIONS_SERVICE);
+ (RestrictionsManager) getSystemService(Context.RESTRICTIONS_SERVICE);
if (restrictionsManager == null) {
return;
}
@@ -966,4 +1017,324 @@ private void checkManagedConfiguration() {
protected String getPresetAppId() {
return presetAppId;
}
+
+ public void openJobListBottomSheet() {
+ connectLoginJobListBottomSheetFragment = ConnectLoginJobListBottomSheetFragment.newInstance(
+ jobList, traditionalJobList, this::onJobSelected
+ );
+ connectLoginJobListBottomSheetFragment.show(getSupportFragmentManager(), "connectLoginJobListBottomSheetFragment");
+ }
+
+ private void onJobSelected(String jobId, String appId, String jobName, String appType) {
+ uiController.saveSelectedItemData(jobId, isAppInstalled(appId), appId, jobName, appType);
+ updateUIForJobSelection(jobName);
+ if (appType.equals(SELECTED_COMM_CARE_JOB)) {
+ uiController.setLoginInputsVisibility(true);
+ } else {
+ if (isAppInstalled(appId)) {
+ handleInstalledApp(appId);
+ } else {
+ handleUninstalledApp(jobId, appType);
+ }
+ }
+ }
+
+ private void updateUIForJobSelection(String jobName) {
+ uiController.setAppNameOnSelector(jobName);
+ dismissBottomSheet();
+ }
+
+ private void handleInstalledApp(String appId) {
+ uiController.isAppInstalled = true;
+ uiController.setLoginInputsVisibility(true);
+ JobSelection(appId);
+ }
+
+ private void handleUninstalledApp(String jobId, String appType) {
+ uiController.isAppInstalled = false;
+ uiController.handleButtonText();
+ uiController.setLoginInputsVisibility(false);
+ uiController.selectedAppType = appType;
+ uiController.selectedJobId = jobId;
+ }
+
+ public void JobSelection(String appId) {
+ boolean selectedConnect = isConnectJobsSelected();
+ if (selectedConnect) {
+ uiController.setLoginInputsVisibility(false);
+ } else {
+ if (appId.length() > 0) {
+ uiController.setLoginInputsVisibility(true);
+ if (!seatAppIfNeeded(appId)) {
+ checkForSavedCredentials();
+ }
+ }
+ }
+ }
+
+ public void dismissBottomSheet() {
+ if (connectLoginJobListBottomSheetFragment != null) {
+ connectLoginJobListBottomSheetFragment.dismiss();
+ }
+ }
+
+ public void initiateJobListAPI() {
+ setJobListData(ConnectDatabaseHelper.getJobs(LoginActivity.this, -1, null));
+ ApiConnect.getConnectOpportunities(LoginActivity.this, new IApiCallback() {
+ @Override
+ public void processSuccess(int responseCode, InputStream responseData) {
+ try {
+ String responseAsString = new String(StreamsUtil.inputStreamToByteArray(responseData));
+ if (responseAsString.length() > 0) {
+ JSONArray json = new JSONArray(responseAsString);
+ List jobs = new ArrayList<>(json.length());
+ for (int i = 0; i < json.length(); i++) {
+ JSONObject obj = (JSONObject) json.get(i);
+ jobs.add(ConnectJobRecord.fromJson(obj));
+ }
+ ConnectDatabaseHelper.storeJobs(LoginActivity.this, jobs, true);
+ setJobListData(jobs);
+ }
+ } catch (IOException | JSONException | ParseException e) {
+ setJobListData(ConnectDatabaseHelper.getJobs(LoginActivity.this, -1, null));
+ Toast.makeText(LoginActivity.this, R.string.connect_job_list_api_failure, Toast.LENGTH_SHORT).show();
+ Logger.exception("Parsing return from Opportunities request", e);
+ }
+ }
+
+ @Override
+ public void processFailure(int responseCode, IOException e) {
+ setJobListData(ConnectDatabaseHelper.getJobs(LoginActivity.this, -1, null));
+ Toast.makeText(LoginActivity.this, R.string.connect_job_list_api_failure, Toast.LENGTH_SHORT).show();
+ Logger.log("ERROR", String.format(Locale.getDefault(), "Opportunities call failed: %d", responseCode));
+ }
+
+ @Override
+ public void processNetworkFailure() {
+ setJobListData(ConnectDatabaseHelper.getJobs(LoginActivity.this, -1, null));
+ Toast.makeText(LoginActivity.this, R.string.recovery_network_unavailable, Toast.LENGTH_SHORT).show();
+ Logger.log("ERROR", "Failed (network)");
+ }
+
+ @Override
+ public void processOldApiError() {
+ setJobListData(ConnectDatabaseHelper.getJobs(LoginActivity.this, -1, null));
+ Toast.makeText(LoginActivity.this, R.string.connect_job_list_api_failure, Toast.LENGTH_SHORT).show();
+ ConnectNetworkHelper.showOutdatedApiError(LoginActivity.this);
+ }
+ });
+ }
+
+ private void storeTraditionalApp(ConnectJobRecord job) {
+ traditionalJobList = new ArrayList<>();
+ ConnectAppRecord appRecord = ConnectDatabaseHelper.getAppRecord(LoginActivity.this, String.valueOf(job.getJobId()));
+ if (appRecord == null) {
+ traditionalJobList.add(storeJobListData(
+ job.getDeliveryAppInfo().getName(),
+ String.valueOf(job.getJobId()),
+ job.getDeliveryAppInfo().getAppId(),
+ job.getProjectStartDate(),
+ job.getDescription(),
+ job.getOrganization(),
+ true,
+ false,
+ false,
+ false,
+ processJobRecords(job, JOB_TRENDING),
+ job.getLearningPercentComplete(),
+ job.getCompletedLearningModules(),
+ JOB_TRENDING,
+ TRADITIONAL_APP
+ ));
+ }
+ }
+
+ private ConnectLoginJobListModel storeJobListData(
+ String name,
+ String id,
+ String appId,
+ Date date,
+ String description,
+ String organization,
+ boolean isAppInstalled,
+ boolean isNew,
+ boolean isLeaningApp,
+ boolean isDeliveryApp,
+ Date lastAssessedDate,
+ int learningProgress,
+ int deliveryProgress,
+ String jobType,
+ String appType
+ ) {
+ return new ConnectLoginJobListModel(
+ name,
+ id,
+ appId,
+ date,
+ description,
+ organization,
+ isAppInstalled,
+ isNew,
+ isLeaningApp,
+ isDeliveryApp,
+ lastAssessedDate,
+ learningProgress,
+ deliveryProgress,
+ jobType,
+ appType
+ );
+ }
+
+ public Date processJobRecords(ConnectJobRecord job, String jobType) {
+ Date lastAssessedDate = new Date();
+ try {
+ String learnAppId = job.getLearnAppInfo().getAppId();
+ String deliverAppId = job.getDeliveryAppInfo().getAppId();
+ if (jobType.equalsIgnoreCase(JOB_LEARNING)) {
+ ConnectLinkedAppRecord learnRecord = ConnectDatabaseHelper.getAppData(LoginActivity.this, learnAppId, "");
+ return learnRecord != null ? learnRecord.getLastAccessed() : lastAssessedDate;
+
+ } else if (jobType.equalsIgnoreCase(JOB_DELIVERY)) {
+ ConnectLinkedAppRecord deliverRecord = ConnectDatabaseHelper.getAppData(LoginActivity.this, deliverAppId, "");
+ return deliverRecord != null ? deliverRecord.getLastAccessed() : lastAssessedDate;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return lastAssessedDate;
+ }
+
+ public static String formatDate(String dateStr) {
+ try {
+ SimpleDateFormat inputFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy", Locale.ENGLISH);
+ SimpleDateFormat outputFormat = new SimpleDateFormat("dd MMM, yyyy", Locale.ENGLISH);
+ Date date = inputFormat.parse(dateStr);
+ return outputFormat.format(date);
+ } catch (ParseException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public String getDisplayNameByAppId(ArrayList appRecords, String appId) {
+ for (ApplicationRecord appRecord : appRecords) {
+ if (appRecord.getUniqueId().equals(appId)) {
+ return appRecord.getDisplayName();
+ }
+ }
+ // Return null or a default value if no record with the appId is found
+ return null;
+ }
+
+ public void handleAppInstallation(String appType, String jobId) {
+ switch (appType) {
+ case LEARN_APP, NEW_APP -> goToConnectScreen(ConnectActivity.DOWNLOAD_LEARN_APP, jobId);
+
+ case DELIVERY_APP -> goToConnectScreen(ConnectActivity.DOWNLOAD_DELIVERY_APP, jobId);
+ }
+ }
+
+ private void goToConnectScreen(String action, String jobId) {
+ Intent intent = new Intent(getApplicationContext(), ConnectActivity.class);
+ intent.putExtra("action", action);
+ intent.putExtra("opportunity_id", jobId);
+ startActivity(intent);
+ }
+
+ private void setJobListData(List jobs) {
+ uiController.isNewJobAvailable = false;
+ jobList = new ArrayList<>();
+
+ for (ConnectJobRecord job : jobs) {
+ storeTraditionalApp(job);
+
+ int jobStatus = job.getStatus();
+ boolean isLearnAppInstalled = isAppInstalled(job.getLearnAppInfo().getAppId());
+ boolean isDeliverAppInstalled = isAppInstalled(job.getDeliveryAppInfo().getAppId());
+
+ switch (jobStatus) {
+ case STATUS_AVAILABLE_NEW, STATUS_AVAILABLE:
+ jobList.add(storeJobListData(
+ job.getTitle(),
+ String.valueOf(job.getJobId()),
+ "",
+ job.getProjectStartDate(),
+ job.getDescription(),
+ job.getOrganization(),
+ false,
+ true,
+ false,
+ false,
+ processJobRecords(job, JOB_NEW_OPPORTUNITY),
+ job.getLearningPercentComplete(),
+ job.getCompletedLearningModules(),
+ JOB_NEW_OPPORTUNITY,
+ NEW_APP
+ ));
+ uiController.isNewJobAvailable = true;
+ break;
+
+ case STATUS_LEARNING:
+ jobList.add(storeJobListData(
+ job.getLearnAppInfo().getName(),
+ String.valueOf(job.getJobId()),
+ job.getLearnAppInfo().getAppId(),
+ job.getProjectStartDate(),
+ job.getLearnAppInfo().getDescription(),
+ job.getLearnAppInfo().getOrganization(),
+ isLearnAppInstalled,
+ false,
+ true,
+ false,
+ processJobRecords(job, JOB_LEARNING),
+ job.getLearningPercentComplete(),
+ job.getCompletedLearningModules(),
+ JOB_LEARNING,
+ LEARN_APP
+ ));
+ break;
+
+ case STATUS_DELIVERING:
+ jobList.add(storeJobListData(
+ job.getLearnAppInfo().getName(),
+ String.valueOf(job.getJobId()),
+ job.getLearnAppInfo().getAppId(),
+ job.getProjectStartDate(),
+ job.getLearnAppInfo().getDescription(),
+ job.getLearnAppInfo().getOrganization(),
+ isLearnAppInstalled,
+ false,
+ true,
+ false,
+ processJobRecords(job, JOB_LEARNING),
+ job.getLearningPercentComplete(),
+ job.getCompletedLearningModules(),
+ JOB_LEARNING,
+ LEARN_APP
+ ));
+ jobList.add(storeJobListData(
+ job.getDeliveryAppInfo().getName(),
+ String.valueOf(job.getJobId()),
+ job.getDeliveryAppInfo().getAppId(),
+ job.getProjectStartDate(),
+ job.getDeliveryAppInfo().getDescription(),
+ job.getDeliveryAppInfo().getOrganization(),
+ isDeliverAppInstalled,
+ false,
+ false,
+ true,
+ processJobRecords(job, JOB_DELIVERY),
+ job.getLearningPercentComplete(),
+ job.getCompletedLearningModules(),
+ JOB_DELIVERY,
+ DELIVERY_APP
+ ));
+ break;
+
+ default:
+ break;
+ }
+ }
+ uiController.visibleNewJobReminderUI();
+ }
}
diff --git a/app/src/org/commcare/activities/LoginActivityUiController.java b/app/src/org/commcare/activities/LoginActivityUiController.java
index baf147bc4b..137853b68c 100644
--- a/app/src/org/commcare/activities/LoginActivityUiController.java
+++ b/app/src/org/commcare/activities/LoginActivityUiController.java
@@ -2,25 +2,25 @@
import android.content.SharedPreferences;
import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.view.View;
import android.view.inputmethod.EditorInfo;
-import android.widget.ArrayAdapter;
-import android.widget.AutoCompleteTextView;
import android.widget.Button;
import android.widget.CheckBox;
-import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.preference.PreferenceManager;
import org.commcare.CommCareApplication;
import org.commcare.CommCareNoficationManager;
import org.commcare.android.database.app.models.UserKeyRecord;
import org.commcare.android.database.global.models.ApplicationRecord;
+import org.commcare.connect.ConnectConstants;
import org.commcare.connect.ConnectDatabaseHelper;
import org.commcare.connect.ConnectManager;
import org.commcare.dalvik.R;
@@ -29,14 +29,16 @@
import org.commcare.models.database.SqlStorage;
import org.commcare.preferences.DevSessionRestorer;
import org.commcare.preferences.HiddenPreferences;
-import org.commcare.preferences.LocalePreferences;
import org.commcare.utils.MultipleAppsUtil;
import org.commcare.views.CustomBanner;
import org.commcare.views.ManagedUi;
import org.commcare.views.ManagedUiFramework;
-import org.commcare.views.PasswordShow;
import org.commcare.views.RectangleButtonWithText;
import org.commcare.views.UiElement;
+import org.commcare.views.connect.ConnectEditText;
+import org.commcare.views.connect.RoundedButton;
+import org.commcare.views.connect.connecttextview.ConnectBoldTextView;
+import org.commcare.views.connect.connecttextview.ConnectMediumTextView;
import org.javarosa.core.services.locale.Localization;
import java.util.ArrayList;
@@ -44,8 +46,6 @@
import javax.annotation.Nullable;
-import androidx.preference.PreferenceManager;
-
/**
* Handles login activity UI
*
@@ -54,6 +54,9 @@
@ManagedUi(R.layout.screen_login)
public class LoginActivityUiController implements CommCareActivityUIController {
+ @UiElement(value = R.id.screen_login_main)
+ private View screen_login_main;
+
@UiElement(value = R.id.screen_login_error_view)
private View errorContainer;
@@ -73,19 +76,19 @@ public class LoginActivityUiController implements CommCareActivityUIController {
private TextView orLabel;
@UiElement(value = R.id.edit_username, locale = "login.username")
- private AutoCompleteTextView username;
+ private ConnectEditText username;
@UiElement(value = R.id.edit_password)
- private EditText passwordOrPin;
+ private ConnectEditText passwordOrPin;
- @UiElement(value = R.id.show_password)
- private Button showPasswordButton;
+ @UiElement(value = R.id.tvAppsTitle)
+ private ConnectBoldTextView tvAppsTitle;
@UiElement(R.id.screen_login_banner_pane)
private View banner;
@UiElement(value = R.id.login_button)
- private Button loginButton;
+ private RoundedButton loginButton;
@UiElement(value = R.id.restore_session_checkbox)
private CheckBox restoreSessionCheckbox;
@@ -94,17 +97,33 @@ public class LoginActivityUiController implements CommCareActivityUIController {
private Spinner spinner;
@UiElement(R.id.welcome_msg)
- private TextView welcomeMessage;
+ private ConnectMediumTextView welcomeMessage;
+
+ @UiElement(R.id.cet_app_selector)
+ public ConnectEditText cetAppSelector;
@UiElement(value = R.id.primed_password_message, locale = "login.primed.prompt")
private TextView loginPrimedMessage;
+ @UiElement(value = R.id.tv_new_app_available)
+ private ConnectMediumTextView tvNewAppAvailable;
+
+ @UiElement(value = R.id.img_new_job_popup_icon)
+ private ImageView imgNewJobPopupIcon;
+
protected final LoginActivity activity;
private LoginMode loginMode;
private boolean manuallySwitchedToPasswordMode;
private boolean loginManagedByConnectId;
+ boolean isNewJobAvailable = false;
+ boolean isAppInstalled = true;
+ String selectedAppType = LoginActivity.LEARN_APP;
+ String selectedJobId = "";
+
+ SharedPreferences sharedPreferences;
+ SharedPreferences.Editor editor;
private final TextWatcher usernameTextWatcher = new TextWatcher() {
@Override
@@ -137,6 +156,7 @@ public void afterTextChanged(Editable s) {
}
};
+
public LoginActivityUiController(LoginActivity activity) {
this.activity = activity;
this.loginMode = LoginMode.PASSWORD;
@@ -147,12 +167,18 @@ public void setupUI() {
setupUsernameEntryBox();
setLoginBoxesColorNormal();
setTextChangeListeners();
- setBannerLayoutLogic();
+ setAppSelectorClickListener();
+ setLeftRightDrawableClickListener();
+ sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity);
+ editor = sharedPreferences.edit();
- connectLoginButton.setOnClickListener(arg0 -> activity.handleConnectButtonPress());
loginButton.setOnClickListener(arg0 -> {
- FirebaseAnalyticsUtil.reportLoginClicks();
- activity.initiateLoginAttempt(isRestoreSessionChecked());
+ if (isAppInstalled) {
+ FirebaseAnalyticsUtil.reportLoginClicks();
+ activity.initiateLoginAttempt(isRestoreSessionChecked());
+ } else {
+ activity.handleAppInstallation(selectedAppType, selectedJobId);
+ }
});
passwordOrPin.setOnEditorActionListener((v, actionId, event) -> {
@@ -174,9 +200,23 @@ public void setupUI() {
.performIntentCalloutToNotificationsView(activity));
}
+ public void setAppSelectorClickListener() {
+ cetAppSelector.setOnClickListener(view -> {
+ activity.openJobListBottomSheet();
+ });
+ }
+
+ public void setLeftRightDrawableClickListener() {
+ username.setOnDrawableStartClickListener(() -> {
+ });
+ username.setOnDrawableEndClickListener(() -> username.getText().clear());
+ passwordOrPin.setOnDrawableStartClickListener(() -> {
+ });
+ passwordOrPin.setOnDrawableEndClickListener(() -> passwordOrPin.getText().clear());
+ }
+
public void setConnectButtonVisible(Boolean visible) {
- connectLoginButton.setVisibility(visible ? View.VISIBLE : View.GONE);
- orLabel.setVisibility(visible ? View.VISIBLE : View.GONE);
+ orLabel.setVisibility(View.GONE);
}
private void setTextChangeListeners() {
@@ -190,27 +230,6 @@ private void setupUsernameEntryBox() {
username.setHint(Localization.get("login.username"));
}
- private void setBannerLayoutLogic() {
- final View activityRootView = activity.findViewById(R.id.screen_login_main);
- activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(
- () -> {
- int hideAll = getResources().getInteger(
- R.integer.login_screen_hide_all_cuttoff);
- int hideBanner = getResources().getInteger(
- R.integer.login_screen_hide_banner_cuttoff);
- int height = activityRootView.getHeight();
-
- if (height < hideAll) {
- banner.setVisibility(View.GONE);
- } else if (height < hideBanner) {
- banner.setVisibility(View.GONE);
- } else {
- banner.setVisibility(View.VISIBLE);
- updateBanner();
- }
- });
- }
-
@Override
public void refreshView() {
updateBanner();
@@ -219,7 +238,10 @@ public void refreshView() {
// Decide whether or not to show the app selection spinner based upon # of usable apps
ArrayList readyApps = MultipleAppsUtil.getUsableAppRecords();
-
+ /*Log.e("DEBUG_TESTING", "refreshView: "+readyApps.size());
+ for(int i = 0 ; i < readyApps.size() ; i++){
+ Log.e("DEBUG_TESTING", "refreshView: "+readyApps.get(i).getDisplayName());
+ }*/
ApplicationRecord presetAppRecord = getPresetAppRecord(readyApps);
boolean noApps = readyApps.isEmpty();
setLoginInputsVisibility(!noApps);
@@ -231,7 +253,7 @@ public void refreshView() {
ApplicationRecord r = presetAppRecord != null ? presetAppRecord : readyApps.get(0);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
prefs.edit().putString(LoginActivity.KEY_LAST_APP, r.getUniqueId()).apply();
- setSingleAppUiState();
+// setSingleAppUiState();
activity.seatAppIfNeeded(r.getUniqueId());
} else {
activity.populateAppSpinner(readyApps);
@@ -263,6 +285,7 @@ public void refreshView() {
public void setLoginInputsVisibility(boolean visible) {
username.setVisibility(visible ? View.VISIBLE : View.GONE);
passwordOrPin.setVisibility(visible ? View.VISIBLE : View.GONE);
+ tvAppsTitle.setVisibility(visible ? View.VISIBLE : View.GONE);
}
public void updateConnectLoginState() {
@@ -385,17 +408,25 @@ private void setPrimedLoginMode() {
protected void setNormalPasswordMode() {
loginMode = LoginMode.PASSWORD;
loginPrimedMessage.setVisibility(View.GONE);
- passwordOrPin.setVisibility(View.VISIBLE);
+ if (activity.getDisplayNameByAppId(MultipleAppsUtil.getUsableAppRecords(), getLastSelectedAppID()) != null) {
+ passwordOrPin.setVisibility(View.VISIBLE);
+ } else {
+ passwordOrPin.setVisibility(View.GONE);
+ }
passwordOrPin.setHint(Localization.get("login.password"));
passwordOrPin.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
- new PasswordShow(showPasswordButton, passwordOrPin).setupPasswordVisibility();
+// new PasswordShow(showPasswordButton, passwordOrPin).setupPasswordVisibility();
manuallySwitchedToPasswordMode = false;
}
private void setPinPasswordMode() {
loginMode = LoginMode.PIN;
loginPrimedMessage.setVisibility(View.GONE);
- passwordOrPin.setVisibility(View.VISIBLE);
+ if (activity.getDisplayNameByAppId(MultipleAppsUtil.getUsableAppRecords(), getLastSelectedAppID()) != null) {
+ passwordOrPin.setVisibility(View.VISIBLE);
+ } else {
+ passwordOrPin.setVisibility(View.GONE);
+ }
passwordOrPin.setHint(Localization.get("login.pin.password"));
passwordOrPin.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
manuallySwitchedToPasswordMode = false;
@@ -417,44 +448,18 @@ protected LoginMode getLoginMode() {
}
protected void setErrorMessageUi(String message, boolean showNotificationButton) {
- setLoginBoxesColorError();
-
- username.setCompoundDrawablesWithIntrinsicBounds(
- getResources().getDrawable(R.drawable.icon_user_attnneg),
- null, null, null);
-
- passwordOrPin.setCompoundDrawablesWithIntrinsicBounds(
- getResources().getDrawable(R.drawable.icon_lock_attnneg),
- null, null, null);
-
- errorContainer.setVisibility(View.VISIBLE);
- errorTextView.setText(message);
- notificationButtonView.setVisibility(showNotificationButton ? View.VISIBLE : View.GONE);
+ username.showErrorState();
+ passwordOrPin.showErrorState();
+ Toast.makeText(activity, message, Toast.LENGTH_SHORT).show();
}
private void setLoginBoxesColorNormal() {
- int normalColor = getResources().getColor(R.color.login_edit_text_color);
- username.setTextColor(normalColor);
- passwordOrPin.setTextColor(normalColor);
- }
-
- private void setLoginBoxesColorError() {
- int errorColor = getResources().getColor(R.color.login_edit_text_color_error);
- username.setTextColor(errorColor);
- passwordOrPin.setTextColor(errorColor);
+ username.setNormalBorder();
+ passwordOrPin.setNormalBorder();
}
private void setStyleDefault() {
setLoginBoxesColorNormal();
- Drawable usernameDrawable = getResources().getDrawable(R.drawable.icon_user_neutral50);
- Drawable passwordDrawable = getResources().getDrawable(R.drawable.icon_lock_neutral50);
- if (LocalePreferences.isLocaleRTL()) {
- username.setCompoundDrawablesWithIntrinsicBounds(null, null, usernameDrawable, null);
- passwordOrPin.setCompoundDrawablesWithIntrinsicBounds(null, null, passwordDrawable, null);
- } else {
- username.setCompoundDrawablesWithIntrinsicBounds(usernameDrawable, null, null, null);
- passwordOrPin.setCompoundDrawablesWithIntrinsicBounds(passwordDrawable, null, null, null);
- }
if (loginButton.isEnabled()) {
clearErrorMessage();
}
@@ -464,22 +469,8 @@ protected void clearErrorMessage() {
errorContainer.setVisibility(View.GONE);
}
- private void setSingleAppUiState() {
- spinner.setVisibility(View.GONE);
- welcomeMessage.setText(Localization.get("login.welcome.single"));
- }
-
protected void setMultipleAppsUiState(ArrayList appNames, int position) {
welcomeMessage.setText(Localization.get("login.welcome.multiple"));
-
- ArrayAdapter adapter = new ArrayAdapter<>(activity,
- R.layout.spinner_text_view, appNames);
- adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- spinner.setAdapter(adapter);
- spinner.setOnItemSelectedListener(activity);
-
- spinner.setSelection(position);
- spinner.setVisibility(View.VISIBLE);
}
protected boolean isAppSelectorVisible() {
@@ -544,24 +535,32 @@ protected void setConnectIdLoginState(boolean useConnectId) {
String text;
if (useConnectId) {
- text = activity.getString(R.string.login_button_connectid);
+ if (activity.getDisplayNameByAppId(MultipleAppsUtil.getUsableAppRecords(), getLastSelectedAppID()) != null) {
+ text = activity.getString(R.string.login_button_connectid);
+ } else {
+ text = activity.getString(R.string.connect_install_app);
+ }
} else {
text = Localization.get("login.button");
}
loginButton.setText(text);
- /**
- * included these lines because when a user changes the language from the system settings while in the app,
- * the strings should be translate correctly when they return to the app. That's why I added this code.
- */
- connectLoginButton.setText(activity.getString(R.string.connect_button_logged_in));
- passwordOrPin.setBackgroundColor(getResources().getColor(useConnectId ? R.color.grey_light : R.color.white));
if (useConnectId) {
passwordOrPin.setText(R.string.login_password_by_connect);
passwordOrPin.clearFocus();
}
passwordOrPin.setInputType(useConnectId ? InputType.TYPE_CLASS_TEXT : (InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD));
+
+ if (useConnectId) {
+ passwordOrPin.setGreyBackground();
+ } else {
+ passwordOrPin.setNormalBorder();
+ }
+ }
+
+ public void handleButtonText() {
+ loginButton.setText(R.string.connect_install_app);
}
private void updateBanner() {
@@ -575,4 +574,26 @@ private void updateBanner() {
private Resources getResources() {
return activity.getResources();
}
+
+ void visibleNewJobReminderUI() {
+ int visibility = isNewJobAvailable ? View.VISIBLE : View.GONE;
+ tvNewAppAvailable.setVisibility(visibility);
+ imgNewJobPopupIcon.setVisibility(visibility);
+ }
+
+ public void setAppNameOnSelector(String appName) {
+ cetAppSelector.setText(appName);
+ }
+
+ public void saveSelectedItemData(String jobId, boolean isInstalled, String appId, String jobTitle, String appType) {
+ editor.putString(ConnectConstants.JOB_ID, jobId).commit();
+ editor.putBoolean(ConnectConstants.IS_APP_INSTALLED, isInstalled).commit();
+ editor.putString(ConnectConstants.APP_ID, appId).commit();
+ editor.putString(ConnectConstants.JOB_TITLE, jobTitle).commit();
+ editor.putString(ConnectConstants.JOB_TYPE, appType).commit();
+ }
+
+ public String getLastSelectedAppID() {
+ return sharedPreferences.getString(ConnectConstants.APP_ID, "");
+ }
}
diff --git a/app/src/org/commcare/activities/connect/ConnectActivity.java b/app/src/org/commcare/activities/connect/ConnectActivity.java
index 0ebc1f23c2..48479401d9 100644
--- a/app/src/org/commcare/activities/connect/ConnectActivity.java
+++ b/app/src/org/commcare/activities/connect/ConnectActivity.java
@@ -3,11 +3,21 @@
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
+import android.util.Log;
import android.widget.Toast;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.appcompat.app.ActionBar;
+import androidx.fragment.app.Fragment;
+import androidx.navigation.NavController;
+import androidx.navigation.NavOptions;
+import androidx.navigation.fragment.NavHostFragment;
+
import org.commcare.activities.CommCareActivity;
import org.commcare.activities.CommCareVerificationActivity;
import org.commcare.android.database.connect.models.ConnectJobRecord;
+import org.commcare.connect.ConnectConstants;
import org.commcare.connect.ConnectDatabaseHelper;
import org.commcare.connect.ConnectManager;
import org.commcare.connect.network.ApiConnect;
@@ -33,14 +43,6 @@
import javax.annotation.Nullable;
-import androidx.activity.result.ActivityResultLauncher;
-import androidx.activity.result.contract.ActivityResultContracts;
-import androidx.appcompat.app.ActionBar;
-import androidx.fragment.app.Fragment;
-import androidx.navigation.NavController;
-import androidx.navigation.NavOptions;
-import androidx.navigation.fragment.NavHostFragment;
-
public class ConnectActivity extends CommCareActivity {
private boolean backButtonEnabled = true;
private boolean waitDialogEnabled = true;
@@ -51,6 +53,8 @@ public class ConnectActivity extends CommCareActivity {
private static final String CCC_LEARN_PROGRESS = "ccc_learn_progress";
private static final String CCC_DELIVERY_PROGRESS = "ccc_delivery_progress";
private static final String CCC_PAYMENTS = "ccc_payment";
+ public static final String DOWNLOAD_LEARN_APP = "download_learn_app";
+ public static final String DOWNLOAD_DELIVERY_APP = "download_delivery_app";
NavController.OnDestinationChangedListener destinationListener = null;
@@ -95,8 +99,9 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
.build();
navController.navigate(fragmentId, bundle, options);
} else if (redirectionAction != null) {
+ Log.e("Debug_testing", "onCreate: " + ConnectManager.isConnectIdConfigured());
ConnectManager.init(this);
- ConnectManager.unlockConnect(this, success -> {
+ ConnectManager.unlockConnect(ConnectActivity.this, success -> {
if (success) {
getJobDetails();
}
@@ -113,15 +118,13 @@ protected void onCreate(@Nullable Bundle savedInstanceState) {
* @return The ID of the fragment to be displayed.
*/
private int getFragmentId() {
- int fragmentId;
- if (redirectionAction.equals(CCC_OPPORTUNITY_SUMMARY_PAGE)) {
- fragmentId = R.id.connect_job_intro_fragment;
- } else if (redirectionAction.equals(CCC_LEARN_PROGRESS)) {
- fragmentId = R.id.connect_job_learning_progress_fragment;
- } else {
- fragmentId = R.id.connect_job_delivery_progress_fragment;
- }
- return fragmentId;
+ return switch (redirectionAction) {
+ case CCC_OPPORTUNITY_SUMMARY_PAGE, DOWNLOAD_LEARN_APP ->
+ R.id.connect_job_intro_fragment;
+ case CCC_LEARN_PROGRESS -> R.id.connect_job_learning_progress_fragment;
+ case DOWNLOAD_DELIVERY_APP -> R.id.connect_job_delivery_details_fragment;
+ default -> R.id.connect_job_delivery_progress_fragment;
+ };
}
private void getIntentData() {
@@ -282,4 +285,14 @@ public void processOldApiError() {
}
});
}
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
+ super.onActivityResult(requestCode, resultCode, intent);
+ if (requestCode == ConnectConstants.CONNECT_UNLOCK_PIN) {
+ if (resultCode == Activity.RESULT_OK) {
+ ConnectManager.handleFinishedActivity(this, requestCode, resultCode, intent);
+ }
+ }
+ }
}
diff --git a/app/src/org/commcare/adapters/JobListCombinedAdapter.java b/app/src/org/commcare/adapters/JobListCombinedAdapter.java
new file mode 100644
index 0000000000..1f47bd04a7
--- /dev/null
+++ b/app/src/org/commcare/adapters/JobListCombinedAdapter.java
@@ -0,0 +1,165 @@
+package org.commcare.adapters;
+
+import static org.commcare.activities.LoginActivity.JOB_DELIVERY;
+import static org.commcare.activities.LoginActivity.JOB_LEARNING;
+import static org.commcare.activities.LoginActivity.JOB_NEW_OPPORTUNITY;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.core.content.ContextCompat;
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.commcare.activities.LoginActivity;
+import org.commcare.dalvik.R;
+import org.commcare.dalvik.databinding.ItemLoginCommcareAppsBinding;
+import org.commcare.dalvik.databinding.ItemLoginConnectHomeAppsBinding;
+import org.commcare.interfaces.JobListCallBack;
+import org.commcare.models.connect.ConnectCombineJobListModel;
+import org.commcare.models.connect.ConnectLoginJobListModel;
+
+import java.util.List;
+
+public class JobListCombinedAdapter extends RecyclerView.Adapter {
+
+ public static final int VIEW_TYPE_COMMCARE = 0;
+ public static final int VIEW_TYPE_CONNECT_HOME = 1;
+ private final List items;
+ private final Context mContext;
+ private final JobListCallBack mCallback;
+
+ public JobListCombinedAdapter(Context context, List items, JobListCallBack mCallback) {
+ this.mContext = context;
+ this.items = items;
+ this.mCallback = mCallback;
+ }
+
+ @NonNull
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+ if (viewType == VIEW_TYPE_COMMCARE) {
+ return new CommCareViewHolder(ItemLoginCommcareAppsBinding.inflate(inflater, parent, false));
+ } else {
+ return new ConnectHomeViewHolder(ItemLoginConnectHomeAppsBinding.inflate(inflater, parent, false));
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
+ ConnectCombineJobListModel item = items.get(position);
+ if (holder instanceof CommCareViewHolder) {
+ ((CommCareViewHolder) holder).bind(item.getConnectLoginJobListModel(), mCallback);
+ } else if (holder instanceof ConnectHomeViewHolder) {
+ ((ConnectHomeViewHolder) holder).bind(mContext, item, mCallback);
+ }
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return items.get(position).getListType() == VIEW_TYPE_COMMCARE ? VIEW_TYPE_COMMCARE : VIEW_TYPE_CONNECT_HOME;
+ }
+
+ @Override
+ public int getItemCount() {
+ return items.size();
+ }
+
+ static class CommCareViewHolder extends RecyclerView.ViewHolder {
+ private final ItemLoginCommcareAppsBinding binding;
+
+ public CommCareViewHolder(ItemLoginCommcareAppsBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+
+ public void bind(ConnectLoginJobListModel item, JobListCallBack mCallback) {
+ binding.tvTitle.setText(item.getName());
+
+ binding.rootCardView.setOnClickListener(view -> {
+ mCallback.onClick(item.getId(),item.getAppId(), item.getName(), LoginActivity.SELECTED_COMM_CARE_JOB);
+ });
+ }
+ }
+
+ // Applying SRP - ConnectHomeViewHolder only handles Connect Home item binding logic
+ static class ConnectHomeViewHolder extends RecyclerView.ViewHolder {
+ private final ItemLoginConnectHomeAppsBinding binding;
+
+ public ConnectHomeViewHolder(ItemLoginConnectHomeAppsBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+
+ public void bind(Context context, ConnectCombineJobListModel item, JobListCallBack mCallback) {
+ binding.tvTitle.setText(item.getConnectLoginJobListModel().getName());
+ binding.tvDate.setText(LoginActivity.formatDate(item.getConnectLoginJobListModel().getLastAccessed().toString()));
+ handleProgressBarUI(context, item);
+ configureJobType(context, item);
+
+ clickListener(context, item, mCallback);
+ }
+
+ private void clickListener(Context context, ConnectCombineJobListModel item, JobListCallBack mCallback) {
+ String jobType = item.getConnectLoginJobListModel().getJobType();
+ String appType;
+ if (jobType.equals(JOB_LEARNING) || jobType.equals(JOB_NEW_OPPORTUNITY)) {
+ appType = LoginActivity.LEARN_APP;
+ } else {
+ appType = LoginActivity.DELIVERY_APP;
+ }
+ binding.rootCardView.setOnClickListener(view -> {
+ mCallback.onClick(item.getConnectLoginJobListModel().getId(),item.getConnectLoginJobListModel().getAppId(), item.getConnectLoginJobListModel().getName(), appType);
+ });
+ }
+
+ public void handleProgressBarUI(Context context, ConnectCombineJobListModel item) {
+ int progress = 0;
+ int progressColor = 0;
+ String jobType = item.getConnectLoginJobListModel().getJobType();
+
+ if (jobType.equals(JOB_LEARNING)) {
+ progress = item.getConnectLoginJobListModel().getLearningProgress();
+ progressColor = context.getResources().getColor(R.color.connect_blue_color);
+ } else if (jobType.equals(JOB_DELIVERY)) {
+ progress = item.getConnectLoginJobListModel().getDeliveryProgress();
+ progressColor = context.getResources().getColor(R.color.connect_green);
+ }
+
+ if (progress > 0) {
+ binding.progressBar.setVisibility(View.VISIBLE);
+ binding.progressBar.setProgress(progress);
+ binding.progressBar.setProgressColor(progressColor);
+ } else {
+ binding.progressBar.setVisibility(View.GONE);
+ }
+ }
+
+ private void configureJobType(Context context, ConnectCombineJobListModel item) {
+ if (item.getConnectLoginJobListModel().isNew()) {
+ setJobType(context, R.drawable.connect_rounded_corner_orange_yellow,
+ context.getResources().getString(R.string.connect_new_opportunity), R.drawable.ic_connect_new_opportunity,
+ R.color.connect_yellowish_orange_color);
+ } else if (item.getConnectLoginJobListModel().isLeaningApp()) {
+ setJobType(context, R.drawable.connect_rounded_corner_teslish_blue,
+ context.getResources().getString(R.string.connect_learn), R.drawable.ic_connect_learning,
+ R.color.connect_blue_color);
+ } else if (item.getConnectLoginJobListModel().isDeliveryApp()) {
+ setJobType(context, R.drawable.connect_rounded_corner_light_green,
+ context.getResources().getString(R.string.connect_delivery), R.drawable.ic_connect_delivery,
+ R.color.connect_green);
+ }
+ }
+
+ private void setJobType(Context context, int backgroundResId, String jobTypeText,
+ int iconResId, int textColorResId) {
+ binding.llOpportunity.setBackground(ContextCompat.getDrawable(context, backgroundResId));
+ binding.tvJobType.setText(jobTypeText);
+ binding.imgJobType.setImageDrawable(ContextCompat.getDrawable(context, iconResId));
+ binding.tvJobType.setTextColor(ContextCompat.getColor(context, textColorResId));
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/org/commcare/adapters/JobListCommCareAppsAdapter.java b/app/src/org/commcare/adapters/JobListCommCareAppsAdapter.java
new file mode 100644
index 0000000000..5339515e13
--- /dev/null
+++ b/app/src/org/commcare/adapters/JobListCommCareAppsAdapter.java
@@ -0,0 +1,68 @@
+package org.commcare.adapters;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.commcare.activities.LoginActivity;
+import org.commcare.dalvik.databinding.ItemLoginCommcareAppsBinding;
+import org.commcare.interfaces.JobListCallBack;
+import org.commcare.models.connect.ConnectLoginJobListModel;
+
+import java.util.ArrayList;
+
+public class JobListCommCareAppsAdapter extends RecyclerView.Adapter {
+
+ private final Context mContext;
+ private final ArrayList traditionalJobList;
+ private final JobListCallBack mCallback;
+
+ public JobListCommCareAppsAdapter(Context context, ArrayList traditionalJobList, JobListCallBack mCallback) {
+ this.mContext = context;
+ this.traditionalJobList = traditionalJobList;
+ this.mCallback = mCallback;
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ // Inflate the layout for each item using View Binding
+ ItemLoginCommcareAppsBinding binding = ItemLoginCommcareAppsBinding.inflate(
+ LayoutInflater.from(parent.getContext()), parent, false);
+ return new ViewHolder(binding);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+ holder.bind(mContext, traditionalJobList.get(position), mCallback);
+ }
+
+ @Override
+ public int getItemCount() {
+ return traditionalJobList.size();
+ }
+
+ public static class ViewHolder extends RecyclerView.ViewHolder {
+ private final ItemLoginCommcareAppsBinding binding;
+
+ public ViewHolder(ItemLoginCommcareAppsBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+
+ public void bind(Context mContext, ConnectLoginJobListModel connectLoginJobListModel, JobListCallBack mCallback) {
+ binding.tvTitle.setText(connectLoginJobListModel.getName());
+
+ clickListener(mContext, connectLoginJobListModel, mCallback);
+ }
+
+ private void clickListener(Context mContext, ConnectLoginJobListModel connectLoginJobListModel, JobListCallBack mCallback) {
+ binding.rootCardView.setOnClickListener(view -> {
+ mCallback.onClick(connectLoginJobListModel.getId(),connectLoginJobListModel.getAppId(),connectLoginJobListModel.getName(), LoginActivity.SELECTED_COMM_CARE_JOB);
+ });
+ }
+ }
+}
diff --git a/app/src/org/commcare/adapters/JobListConnectHomeAppsAdapter.java b/app/src/org/commcare/adapters/JobListConnectHomeAppsAdapter.java
new file mode 100644
index 0000000000..a787a08e07
--- /dev/null
+++ b/app/src/org/commcare/adapters/JobListConnectHomeAppsAdapter.java
@@ -0,0 +1,130 @@
+package org.commcare.adapters;
+
+import static org.commcare.activities.LoginActivity.JOB_DELIVERY;
+import static org.commcare.activities.LoginActivity.JOB_LEARNING;
+import static org.commcare.activities.LoginActivity.JOB_NEW_OPPORTUNITY;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.core.content.ContextCompat;
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.commcare.activities.LoginActivity;
+import org.commcare.dalvik.R;
+import org.commcare.dalvik.databinding.ItemLoginConnectHomeAppsBinding;
+import org.commcare.interfaces.JobListCallBack;
+import org.commcare.models.connect.ConnectLoginJobListModel;
+
+import java.util.ArrayList;
+
+public class JobListConnectHomeAppsAdapter extends RecyclerView.Adapter {
+
+ private final Context mContext;
+ private final ArrayList jobList;
+ private JobListCallBack mCallback;
+
+ public JobListConnectHomeAppsAdapter(Context context, ArrayList jobList,JobListCallBack mCallback) {
+ this.mContext = context;
+ this.jobList = jobList;
+ this.mCallback = mCallback;
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ // Inflate the layout for each item using View Binding
+ ItemLoginConnectHomeAppsBinding binding = ItemLoginConnectHomeAppsBinding.inflate(
+ LayoutInflater.from(parent.getContext()), parent, false);
+ return new ViewHolder(binding);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+ holder.bind(mContext, jobList.get(position),mCallback);
+ }
+
+ @Override
+ public int getItemCount() {
+ return jobList.size();
+ }
+
+ public static class ViewHolder extends RecyclerView.ViewHolder {
+ private final ItemLoginConnectHomeAppsBinding binding;
+
+ public ViewHolder(ItemLoginConnectHomeAppsBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+
+ public void bind(Context mContext, ConnectLoginJobListModel connectLoginJobListModel, JobListCallBack mCallback) {
+ binding.tvTitle.setText(connectLoginJobListModel.getName());
+ binding.tvDate.setText(LoginActivity.formatDate(connectLoginJobListModel.getLastAccessed().toString()));
+ handleProgressBarUI(mContext, connectLoginJobListModel);
+ configureJobType(mContext, connectLoginJobListModel);
+
+ clickListener(mContext,connectLoginJobListModel,mCallback);
+ }
+
+ private void clickListener(Context mContext, ConnectLoginJobListModel connectLoginJobListModel, JobListCallBack mCallback) {
+ String appType;
+ if (connectLoginJobListModel.getJobType().equals(JOB_LEARNING) || connectLoginJobListModel.getJobType().equals(JOB_NEW_OPPORTUNITY)) {
+ appType = LoginActivity.LEARN_APP;
+ } else {
+ appType = LoginActivity.DELIVERY_APP;
+ }
+ binding.rootCardView.setOnClickListener(view -> {
+ mCallback.onClick(connectLoginJobListModel.getId(),connectLoginJobListModel.getAppId(),connectLoginJobListModel.getName(),appType);
+ });
+ }
+
+ public void handleProgressBarUI(Context context, ConnectLoginJobListModel item) {
+ int progress = 0;
+ int progressColor = 0;
+ String jobType = item.getJobType();
+
+ if (jobType.equals(JOB_LEARNING)) {
+ progress = item.getLearningProgress();
+ progressColor = context.getResources().getColor(R.color.connect_blue_color);
+ } else if (jobType.equals(JOB_DELIVERY)) {
+ progress = item.getDeliveryProgress();
+ progressColor = context.getResources().getColor(R.color.connect_green);
+ }
+
+ if (progress > 0) {
+ binding.progressBar.setVisibility(View.VISIBLE);
+ binding.progressBar.setProgress(progress);
+ binding.progressBar.setProgressColor(progressColor);
+ } else {
+ binding.progressBar.setVisibility(View.GONE);
+ }
+ }
+
+ private void configureJobType(Context context, ConnectLoginJobListModel item) {
+ if (item.isNew()) {
+ setJobType(context, R.drawable.connect_rounded_corner_orange_yellow,
+ context.getResources().getString(R.string.connect_new_opportunity), R.drawable.ic_connect_new_opportunity,
+ R.color.connect_yellowish_orange_color);
+ } else if (item.isLeaningApp()) {
+ setJobType(context, R.drawable.connect_rounded_corner_teslish_blue,
+ context.getResources().getString(R.string.connect_learn), R.drawable.ic_connect_learning,
+ R.color.connect_blue_color);
+ } else if (item.isDeliveryApp()) {
+ setJobType(context, R.drawable.connect_rounded_corner_light_green,
+ context.getResources().getString(R.string.connect_delivery), R.drawable.ic_connect_delivery,
+ R.color.connect_green);
+ }
+ }
+
+ private void setJobType(Context context, int backgroundResId, String jobTypeText,
+ int iconResId, int textColorResId) {
+ binding.llOpportunity.setBackground(ContextCompat.getDrawable(context, backgroundResId));
+ binding.tvJobType.setText(jobTypeText);
+ binding.imgJobType.setImageDrawable(ContextCompat.getDrawable(context, iconResId));
+ binding.tvJobType.setTextColor(ContextCompat.getColor(context, textColorResId));
+ }
+ }
+}
diff --git a/app/src/org/commcare/adapters/JobListViewPagerAdapter.java b/app/src/org/commcare/adapters/JobListViewPagerAdapter.java
new file mode 100644
index 0000000000..fd1a6a9a15
--- /dev/null
+++ b/app/src/org/commcare/adapters/JobListViewPagerAdapter.java
@@ -0,0 +1,39 @@
+package org.commcare.adapters;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentStatePagerAdapter;
+import androidx.viewpager2.adapter.FragmentStateAdapter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class JobListViewPagerAdapter extends FragmentStateAdapter {
+ private final List fragmentList = new ArrayList<>();
+ private final List titleList = new ArrayList<>();
+
+ public JobListViewPagerAdapter(FragmentActivity fa) {
+ super(fa);
+ }
+
+ @Override
+ public Fragment createFragment(int position) {
+ return fragmentList.get(position);
+ }
+
+ @Override
+ public int getItemCount() {
+ return fragmentList.size();
+ }
+
+ public void add(Fragment fragment, String title) {
+ fragmentList.add(fragment);
+ titleList.add(title);
+ }
+
+ public CharSequence getPageTitle(int position) {
+ return titleList.get(position);
+ }
+}
diff --git a/app/src/org/commcare/android/database/connect/models/ConnectAppRecord.java b/app/src/org/commcare/android/database/connect/models/ConnectAppRecord.java
index 1d2548f313..5ff80cbb4a 100644
--- a/app/src/org/commcare/android/database/connect/models/ConnectAppRecord.java
+++ b/app/src/org/commcare/android/database/connect/models/ConnectAppRecord.java
@@ -93,6 +93,8 @@ public static ConnectAppRecord fromJson(JSONObject json, int jobId, boolean isLe
public boolean getIsLearning() { return isLearning; }
public int getJobId() { return jobId; }
public void setJobId(int jobId) { this.jobId = jobId; }
+ public String getName() { return name; }
+ public void setName(String name) { this.name = name; }
public String getAppId() { return appId; }
public String getDomain() { return domain; }
@@ -102,4 +104,20 @@ public static ConnectAppRecord fromJson(JSONObject json, int jobId, boolean isLe
public String getInstallUrl() { return installUrl; }
public void setLearnModules(List modules) { learnModules = modules; }
public void setLastUpdate(Date lastUpdate) { this.lastUpdate = lastUpdate; }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getOrganization() {
+ return organization;
+ }
+
+ public void setOrganization(String organization) {
+ this.organization = organization;
+ }
}
diff --git a/app/src/org/commcare/connect/ConnectConstants.java b/app/src/org/commcare/connect/ConnectConstants.java
index 9a7976d3de..ee1e8ee066 100644
--- a/app/src/org/commcare/connect/ConnectConstants.java
+++ b/app/src/org/commcare/connect/ConnectConstants.java
@@ -43,6 +43,13 @@ public class ConnectConstants {
public static final String CONNECT_KEY_SECONDARY_PHONE = "secondary_phone";
public static final String CONNECT_KEY_VALIDATE_SECONDARY_PHONE_BY = "secondary_phone_validate_by";
public static final String CONNECT_KEY_DB_KEY = "db_key";
+
+ public static final String JOB_ID = "job_id";
+ public static final String IS_APP_INSTALLED = "is_installed";
+ public static final String APP_ID = "app_id";
+ public static final String JOB_TITLE = "job_title";
+ public static final String JOB_TYPE = "job_type";
+
public final static int CONNECT_NO_ACTIVITY = ConnectConstants.ConnectIdTaskIdOffset;
public final static int CONNECT_REGISTER_OR_RECOVER_DECISION = ConnectConstants.ConnectIdTaskIdOffset + 1;
public final static int CONNECT_REGISTRATION_PRIMARY_PHONE = ConnectConstants.ConnectIdTaskIdOffset + 2;
diff --git a/app/src/org/commcare/fragments/connect/login_job_fragments/ConnectLoginCombineAppsFragment.java b/app/src/org/commcare/fragments/connect/login_job_fragments/ConnectLoginCombineAppsFragment.java
new file mode 100644
index 0000000000..3381e38a57
--- /dev/null
+++ b/app/src/org/commcare/fragments/connect/login_job_fragments/ConnectLoginCombineAppsFragment.java
@@ -0,0 +1,79 @@
+package org.commcare.fragments.connect.login_job_fragments;
+
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.recyclerview.widget.LinearLayoutManager;
+
+import org.commcare.adapters.JobListCombinedAdapter;
+import org.commcare.dalvik.databinding.FragmentConnectLoginCombineAppsBinding;
+import org.commcare.interfaces.JobListCallBack;
+import org.commcare.models.connect.ConnectCombineJobListModel;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * A simple {@link Fragment} subclass.
+ * Use the {@link ConnectLoginCombineAppsFragment#newInstance} factory method to
+ * create an instance of this fragment.
+ */
+public class ConnectLoginCombineAppsFragment extends Fragment {
+
+ private FragmentConnectLoginCombineAppsBinding binding;
+ private static final String ARG_COMBINE_JOB_LIST = "combine_job_list";
+ private ArrayList traditionalJobList;
+ private JobListCallBack mCallback;
+
+ public static ConnectLoginCombineAppsFragment newInstance(List traditionalJobList,JobListCallBack mCallback) {
+ ConnectLoginCombineAppsFragment fragment = new ConnectLoginCombineAppsFragment();
+ Bundle args = new Bundle();
+ // Sort the list of ConnectCombineJobListModel based on the lastAccessed property
+// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+// traditionalJobList.sort(Comparator.comparing(job -> job.getConnectLoginJobListModel().getLastAccessed()));
+// }
+ args.putParcelableArrayList(ARG_COMBINE_JOB_LIST, (ArrayList extends Parcelable>) traditionalJobList);
+ fragment.setArguments(args);
+ fragment.setOnJobListClickedListener(mCallback);
+ return fragment;
+ }
+
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ binding = FragmentConnectLoginCombineAppsBinding.inflate(inflater, container, false);
+
+ // Retrieve the job list from the arguments
+ if (getArguments() != null) {
+ traditionalJobList = getArguments().getParcelableArrayList(ARG_COMBINE_JOB_LIST);
+ }
+
+ initRecyclerView();
+ return binding.getRoot();
+ }
+
+ private void initRecyclerView() {
+ JobListCombinedAdapter adapter = new JobListCombinedAdapter(getContext(), traditionalJobList, (jobId,appId,jobName,appType) -> mCallback.onClick(jobId,appId,jobName,appType));
+ binding.rcCombineApps.setLayoutManager(new LinearLayoutManager(getContext()));
+ binding.rcCombineApps.setNestedScrollingEnabled(true);
+ binding.rcCombineApps.setAdapter(adapter);
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ binding = null;
+ }
+
+ public void setOnJobListClickedListener(JobListCallBack mCallback) {
+ this.mCallback = mCallback;
+ }
+}
diff --git a/app/src/org/commcare/fragments/connect/login_job_fragments/ConnectLoginCommcareAppsFragment.java b/app/src/org/commcare/fragments/connect/login_job_fragments/ConnectLoginCommcareAppsFragment.java
new file mode 100644
index 0000000000..40cb79e120
--- /dev/null
+++ b/app/src/org/commcare/fragments/connect/login_job_fragments/ConnectLoginCommcareAppsFragment.java
@@ -0,0 +1,138 @@
+package org.commcare.fragments.connect.login_job_fragments;
+
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.Fragment;
+import androidx.recyclerview.widget.LinearLayoutManager;
+
+import org.commcare.CommCareApplication;
+import org.commcare.activities.CommCareSetupActivity;
+import org.commcare.activities.DispatchActivity;
+import org.commcare.adapters.JobListCommCareAppsAdapter;
+import org.commcare.dalvik.R;
+import org.commcare.dalvik.databinding.FragmentConnectLoginCommcareAppsBinding;
+import org.commcare.interfaces.JobListCallBack;
+import org.commcare.models.connect.ConnectLoginJobListModel;
+import org.commcare.services.CommCareSessionService;
+import org.commcare.utils.SessionUnavailableException;
+import org.commcare.views.dialogs.StandardAlertDialog;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * A simple {@link Fragment} subclass.
+ * Use the {@link ConnectLoginCommcareAppsFragment#newInstance} factory method to
+ * create an instance of this fragment.
+ */
+public class ConnectLoginCommcareAppsFragment extends Fragment {
+
+ private FragmentConnectLoginCommcareAppsBinding binding;
+ private static final String ARG_TRADITIONAL_JOB_LIST = "traditional_job_list";
+ public static final String KEY_LAUNCH_FROM_MANAGER = "from_manager";
+ private ArrayList traditionalJobList;
+ private JobListCallBack mCallback;
+
+ public static ConnectLoginCommcareAppsFragment newInstance(List traditionalJobList,JobListCallBack mCallback) {
+ ConnectLoginCommcareAppsFragment fragment = new ConnectLoginCommcareAppsFragment();
+ Bundle args = new Bundle();
+ // Sort the jobList by lastAccess date before passing it to the fragment
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ traditionalJobList.sort(Comparator.comparing(ConnectLoginJobListModel::getLastAccessed));
+ }
+ args.putParcelableArrayList(ARG_TRADITIONAL_JOB_LIST, (ArrayList extends Parcelable>) traditionalJobList);
+ fragment.setArguments(args);
+ fragment.setOnJobListClickedListener(mCallback);
+ return fragment;
+ }
+
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ binding = FragmentConnectLoginCommcareAppsBinding.inflate(inflater, container, false);
+
+ // Retrieve the job list from the arguments
+ if (getArguments() != null) {
+ traditionalJobList = getArguments().getParcelableArrayList(ARG_TRADITIONAL_JOB_LIST);
+ }
+
+ initRecyclerView();
+ clickListeners();
+ return binding.getRoot();
+ }
+
+ private void clickListeners() {
+ binding.lytInstallApps.btnInstallNow.setOnClickListener(view -> {
+ installAppClicked();
+ });
+ }
+
+ private void initRecyclerView() {
+ JobListCommCareAppsAdapter adapter = new JobListCommCareAppsAdapter(getContext(), traditionalJobList, (jobId,appId,jobName,appType) -> mCallback.onClick(jobId,appId,jobName,appType));
+ binding.rcCommCareApps.setLayoutManager(new LinearLayoutManager(getContext()));
+ binding.rcCommCareApps.setNestedScrollingEnabled(true);
+ binding.rcCommCareApps.setAdapter(adapter);
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ // Nullify the binding object to avoid memory leaks
+ binding = null;
+ }
+
+ public void installAppClicked() {
+ try {
+ CommCareSessionService s = CommCareApplication.instance().getSession();
+ if (s.isActive()) {
+ triggerLogoutWarning();
+ } else {
+ installApp();
+ }
+ } catch (SessionUnavailableException e) {
+ installApp();
+ }
+ }
+
+ /**
+ * Logs the user out and takes them to the app installation activity.
+ */
+ private void installApp() {
+ Intent i = new Intent(getActivity(), CommCareSetupActivity.class);
+ i.putExtra(KEY_LAUNCH_FROM_MANAGER, true);
+ this.startActivityForResult(i, DispatchActivity.INIT_APP);
+ }
+
+ /**
+ * Warns user that the action they are trying to conduct will result in the current
+ * session being logged out
+ */
+ private void triggerLogoutWarning() {
+ String title = getString(R.string.logging_out);
+ String message = getString(R.string.logout_warning);
+ StandardAlertDialog d = new StandardAlertDialog(getActivity(), title, message);
+ DialogInterface.OnClickListener listener = (dialog, which) -> {
+ if (which == AlertDialog.BUTTON_POSITIVE) {
+ CommCareApplication.instance().expireUserSession();
+ installApp();
+ }
+ };
+ d.setPositiveButton(getString(R.string.ok), listener);
+ d.setNegativeButton(getString(R.string.cancel), listener);
+ }
+
+ public void setOnJobListClickedListener(JobListCallBack mCallback) {
+ this.mCallback = mCallback;
+ }
+}
diff --git a/app/src/org/commcare/fragments/connect/login_job_fragments/ConnectLoginConnectHomeAppsFragment.java b/app/src/org/commcare/fragments/connect/login_job_fragments/ConnectLoginConnectHomeAppsFragment.java
new file mode 100644
index 0000000000..28c1233977
--- /dev/null
+++ b/app/src/org/commcare/fragments/connect/login_job_fragments/ConnectLoginConnectHomeAppsFragment.java
@@ -0,0 +1,79 @@
+package org.commcare.fragments.connect.login_job_fragments;
+
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.recyclerview.widget.LinearLayoutManager;
+
+import org.commcare.adapters.JobListConnectHomeAppsAdapter;
+import org.commcare.dalvik.databinding.FragmentConnectLoginCommcareHomeBinding;
+import org.commcare.interfaces.JobListCallBack;
+import org.commcare.models.connect.ConnectLoginJobListModel;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * A simple {@link Fragment} subclass.
+ * Use the {@link ConnectLoginConnectHomeAppsFragment#newInstance} factory method to
+ * create an instance of this fragment.
+ */
+public class ConnectLoginConnectHomeAppsFragment extends Fragment {
+
+ private static final String ARG_JOB_LIST = "job_list";
+ private FragmentConnectLoginCommcareHomeBinding binding;
+ private ArrayList jobList;
+ private JobListCallBack mCallback;
+
+ public static ConnectLoginConnectHomeAppsFragment newInstance(List jobList,JobListCallBack mCallback) {
+ ConnectLoginConnectHomeAppsFragment fragment = new ConnectLoginConnectHomeAppsFragment();
+ Bundle args = new Bundle();
+ // Sort the jobList by lastAccess date before passing it to the fragment
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ jobList.sort(Comparator.comparing(ConnectLoginJobListModel::getLastAccessed));
+ }
+ args.putParcelableArrayList(ARG_JOB_LIST, (ArrayList extends Parcelable>) jobList);
+ fragment.setArguments(args);
+ fragment.setOnJobListClickedListener(mCallback);
+ return fragment;
+ }
+
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ binding = FragmentConnectLoginCommcareHomeBinding.inflate(inflater, container, false);
+
+ // Retrieve the job list from the arguments
+ if (getArguments() != null) {
+ jobList = getArguments().getParcelableArrayList(ARG_JOB_LIST);
+ }
+
+ initRecyclerView();
+ return binding.getRoot();
+ }
+
+ private void initRecyclerView() {
+ JobListConnectHomeAppsAdapter adapter = new JobListConnectHomeAppsAdapter(getContext(), jobList, (jobId,appId,jobName,appType) -> mCallback.onClick(jobId,appId,jobName,appType));
+ binding.rcConnectHomeApps.setLayoutManager(new LinearLayoutManager(getContext()));
+ binding.rcConnectHomeApps.setNestedScrollingEnabled(true);
+ binding.rcConnectHomeApps.setAdapter(adapter);
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ binding = null;
+ }
+
+ public void setOnJobListClickedListener(JobListCallBack mCallback) {
+ this.mCallback = mCallback;
+ }
+}
diff --git a/app/src/org/commcare/fragments/connect/login_job_fragments/ConnectLoginJobListBottomSheetFragment.java b/app/src/org/commcare/fragments/connect/login_job_fragments/ConnectLoginJobListBottomSheetFragment.java
new file mode 100644
index 0000000000..cc2c64c781
--- /dev/null
+++ b/app/src/org/commcare/fragments/connect/login_job_fragments/ConnectLoginJobListBottomSheetFragment.java
@@ -0,0 +1,181 @@
+package org.commcare.fragments.connect.login_job_fragments;
+
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.android.material.bottomsheet.BottomSheetDialog;
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
+import com.google.android.material.tabs.TabLayoutMediator;
+
+import org.commcare.adapters.JobListCombinedAdapter;
+import org.commcare.adapters.JobListViewPagerAdapter;
+import org.commcare.dalvik.R;
+import org.commcare.dalvik.databinding.BottomsheetLoginJoblistBinding;
+import org.commcare.interfaces.JobListCallBack;
+import org.commcare.models.connect.ConnectCombineJobListModel;
+import org.commcare.models.connect.ConnectLoginJobListModel;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ConnectLoginJobListBottomSheetFragment extends BottomSheetDialogFragment {
+
+ private BottomsheetLoginJoblistBinding binding;
+ private static final String ARG_JOB_LIST = "job_list";
+ private static final String ARG_TRADITIONAL_JOB_LIST = "traditional_job_list";
+ private List jobList;
+ private List traditionalJobList;
+ private JobListCallBack mCallback;
+
+
+ public static ConnectLoginJobListBottomSheetFragment newInstance(List jobList, List traditionalJobList,JobListCallBack mCallback) {
+ ConnectLoginJobListBottomSheetFragment fragment = new ConnectLoginJobListBottomSheetFragment();
+ Bundle args = new Bundle();
+ args.putParcelableArrayList(ARG_JOB_LIST, new ArrayList<>(jobList));
+ args.putParcelableArrayList(ARG_TRADITIONAL_JOB_LIST, new ArrayList<>(traditionalJobList));
+ fragment.setArguments(args);
+ fragment.setOnJobListClickedListener(mCallback);
+ return fragment;
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ binding = BottomsheetLoginJoblistBinding.inflate(inflater, container, false);
+ binding.rootView.setBackgroundResource(R.drawable.connect_bottom_sheet_rounded_corners);
+
+ retrieveArguments();
+ setupUI();
+
+ return binding.getRoot();
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ customizeBottomSheetBackground(view);
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ binding = null;
+ }
+
+ private void retrieveArguments() {
+ if (getArguments() != null) {
+ jobList = getArguments().getParcelableArrayList(ARG_JOB_LIST);
+ traditionalJobList = getArguments().getParcelableArrayList(ARG_TRADITIONAL_JOB_LIST);
+ }
+ }
+
+ private void setupUI() {
+ if (isAppsSeparated()) {
+ setupViewPager();
+ } else {
+ setupCombinedRecyclerView();
+ }
+ }
+
+ /**
+ * Setup ViewPager with tabs for separated job lists (Connect Home and CommCare App).
+ */
+ private void setupViewPager() {
+ JobListViewPagerAdapter viewPagerAdapter = new JobListViewPagerAdapter(requireActivity());
+ viewPagerAdapter.add(ConnectLoginConnectHomeAppsFragment.newInstance(jobList, (jobId,appId,jobName,appType) -> mCallback.onClick(jobId,appId,jobName,appType)), "Connect Home");
+ viewPagerAdapter.add(ConnectLoginCommcareAppsFragment.newInstance(traditionalJobList, (jobId,appId,jobName,appType) -> mCallback.onClick(jobId,appId,jobName,appType)), "CommCare App");
+
+ configureRecyclerViewScrolling();
+
+ binding.viewPager.setAdapter(viewPagerAdapter);
+ new TabLayoutMediator(binding.tabLayout, binding.viewPager,
+ (tab, position) -> tab.setText(viewPagerAdapter.getPageTitle(position))
+ ).attach();
+ }
+
+ /**
+ * Setup RecyclerView with combined job lists if apps are not separated.
+ */
+ private void setupCombinedRecyclerView() {
+ JobListViewPagerAdapter viewPagerAdapter = new JobListViewPagerAdapter(requireActivity());
+ viewPagerAdapter.add(ConnectLoginCombineAppsFragment.newInstance(combineJobLists(), (jobId,appId,jobName,appType) -> mCallback.onClick(jobId,appId,jobName,appType)), "Connect Home");
+
+ configureRecyclerViewScrolling();
+
+ binding.viewPager.setAdapter(viewPagerAdapter);
+ handlePagerUI();
+ }
+
+ private void handlePagerUI() {
+ binding.tabLayout.setVisibility(View.GONE);
+ binding.divider.setVisibility(View.GONE);
+ }
+
+ /**
+ * Combine Connect Home and CommCare job lists into a single list.
+ *
+ * @return Combined list of jobs.
+ */
+ private List combineJobLists() {
+ List combineAppsList = new ArrayList<>();
+ combineAppsList.addAll(createCombinedList(jobList, JobListCombinedAdapter.VIEW_TYPE_CONNECT_HOME));
+ combineAppsList.addAll(createCombinedList(traditionalJobList, JobListCombinedAdapter.VIEW_TYPE_COMMCARE));
+ return combineAppsList;
+ }
+
+ /**
+ * Create a combined list of jobs with the specified view type.
+ *
+ * @param jobList The list of jobs to combine.
+ * @param listType The type of list (Connect Home or CommCare).
+ * @return A combined list of jobs with the specified view type.
+ */
+ private List createCombinedList(List jobList, int listType) {
+ List combinedList = new ArrayList<>();
+ for (ConnectLoginJobListModel job : jobList) {
+ combinedList.add(new ConnectCombineJobListModel(job, listType));
+ }
+ return combinedList;
+ }
+
+ private boolean isAppsSeparated() {
+ return jobList.size() > 2 && traditionalJobList.size() > 2;
+ }
+
+ private void configureRecyclerViewScrolling() {
+ for (int i = 0; i < binding.viewPager.getChildCount(); i++) {
+ View child = binding.viewPager.getChildAt(i);
+ if (child instanceof RecyclerView) {
+ child.setNestedScrollingEnabled(false);
+ break;
+ }
+ }
+ }
+
+ private void customizeBottomSheetBackground(View view) {
+ view.getViewTreeObserver().addOnPreDrawListener(() -> {
+ BottomSheetDialog dialog = (BottomSheetDialog) getDialog();
+ if (dialog != null) {
+ View bottomSheet = dialog.findViewById(com.google.android.material.R.id.design_bottom_sheet);
+ if (bottomSheet != null) {
+ bottomSheet.setBackground(new ColorDrawable(
+ ContextCompat.getColor(requireContext(), R.color.transparent)
+ ));
+ }
+ }
+ return true;
+ });
+ }
+
+ public void setOnJobListClickedListener(JobListCallBack mCallback) {
+ this.mCallback = mCallback;
+ }
+}
diff --git a/app/src/org/commcare/interfaces/JobListCallBack.java b/app/src/org/commcare/interfaces/JobListCallBack.java
new file mode 100644
index 0000000000..b50205ee2d
--- /dev/null
+++ b/app/src/org/commcare/interfaces/JobListCallBack.java
@@ -0,0 +1,5 @@
+package org.commcare.interfaces;
+
+public interface JobListCallBack {
+ void onClick(String jobId,String appId,String jobName,String appType);
+}
diff --git a/app/src/org/commcare/models/connect/ConnectCombineJobListModel.java b/app/src/org/commcare/models/connect/ConnectCombineJobListModel.java
new file mode 100644
index 0000000000..07a6187268
--- /dev/null
+++ b/app/src/org/commcare/models/connect/ConnectCombineJobListModel.java
@@ -0,0 +1,62 @@
+package org.commcare.models.connect;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class ConnectCombineJobListModel implements Parcelable {
+ ConnectLoginJobListModel combineAppsList;
+ int listType;
+
+ public ConnectCombineJobListModel() {
+
+ }
+
+ public ConnectCombineJobListModel(ConnectLoginJobListModel combineAppsList, int listType) {
+ this.combineAppsList = combineAppsList;
+ this.listType = listType;
+ }
+
+ protected ConnectCombineJobListModel(Parcel in) {
+ combineAppsList = in.readParcelable(ConnectLoginJobListModel.class.getClassLoader());
+ listType = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(combineAppsList, flags);
+ dest.writeInt(listType);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public ConnectCombineJobListModel createFromParcel(Parcel in) {
+ return new ConnectCombineJobListModel(in);
+ }
+
+ @Override
+ public ConnectCombineJobListModel[] newArray(int size) {
+ return new ConnectCombineJobListModel[size];
+ }
+ };
+
+ public ConnectLoginJobListModel getConnectLoginJobListModel() {
+ return combineAppsList;
+ }
+
+ public void setConnectLoginJobListModel(ConnectLoginJobListModel combineAppsList) {
+ this.combineAppsList = combineAppsList;
+ }
+
+ public int getListType() {
+ return listType;
+ }
+
+ public void setListType(int listType) {
+ this.listType = listType;
+ }
+}
diff --git a/app/src/org/commcare/models/connect/ConnectLoginJobListModel.java b/app/src/org/commcare/models/connect/ConnectLoginJobListModel.java
new file mode 100644
index 0000000000..021be9004e
--- /dev/null
+++ b/app/src/org/commcare/models/connect/ConnectLoginJobListModel.java
@@ -0,0 +1,231 @@
+package org.commcare.models.connect;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Date;
+
+/**
+ * Model class for storing list item data
+ */
+public class ConnectLoginJobListModel implements Parcelable {
+ private String name;
+ private String id;
+ private String appId;
+ private Date date;
+ private String description;
+ private String organization;
+ private boolean isAppInstalled;
+ private boolean isNew;
+ private boolean isLeaningApp;
+ private boolean isDeliveryApp;
+ private Date lastAccessed;
+ private int learningProgress;
+ private int deliveryProgress;
+ private String jobType;
+ private String appType;
+
+ // Constructor
+ public ConnectLoginJobListModel(
+ String name,
+ String id,
+ String appId,
+ Date date,
+ String description,
+ String organization,
+ boolean isAppInstalled,
+ boolean isNew,
+ boolean isLeaningApp,
+ boolean isDeliveryApp,
+ Date lastAccessed,
+ int learningProgress,
+ int deliveryProgress,
+ String jobType,
+ String appType
+ ) {
+ this.name = name;
+ this.id = id;
+ this.appId = appId;
+ this.date = date;
+ this.description = description;
+ this.organization = organization;
+ this.isAppInstalled = isAppInstalled;
+ this.isNew = isNew;
+ this.isLeaningApp = isLeaningApp;
+ this.isDeliveryApp = isDeliveryApp;
+ this.lastAccessed = lastAccessed;
+ this.learningProgress = learningProgress;
+ this.deliveryProgress = deliveryProgress;
+ this.jobType = jobType;
+ this.appType = appType;
+ }
+
+ // Default constructor
+ public ConnectLoginJobListModel() {
+ }
+
+ protected ConnectLoginJobListModel(Parcel in) {
+ name = in.readString();
+ id = in.readString();
+ description = in.readString();
+ organization = in.readString();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(name);
+ dest.writeString(id);
+ dest.writeString(description);
+ dest.writeString(organization);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator CREATOR = new Creator<>() {
+ @Override
+ public ConnectLoginJobListModel createFromParcel(Parcel in) {
+ return new ConnectLoginJobListModel(in);
+ }
+
+ @Override
+ public ConnectLoginJobListModel[] newArray(int size) {
+ return new ConnectLoginJobListModel[size];
+ }
+ };
+
+ // Getters and Setters
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getAppId() {
+ return appId;
+ }
+
+ public void setAppId(String appId) {
+ this.appId = appId;
+ }
+
+ public Date getDate() {
+ return date;
+ }
+
+ public void setDate(Date date) {
+ this.date = date;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getOrganization() {
+ return organization;
+ }
+
+ public void setOrganization(String organization) {
+ this.organization = organization;
+ }
+
+ public boolean isAppInstalled() {
+ return isAppInstalled;
+ }
+
+ public void setAppInstalled(boolean appInstalled) {
+ isAppInstalled = appInstalled;
+ }
+
+ public boolean isNew() {
+ return isNew;
+ }
+
+ public void setNew(boolean aNew) {
+ isNew = aNew;
+ }
+
+ public boolean isLeaningApp() {
+ return isLeaningApp;
+ }
+
+ public void setLeaningApp(boolean leaningApp) {
+ isLeaningApp = leaningApp;
+ }
+
+ public boolean isDeliveryApp() {
+ return isDeliveryApp;
+ }
+
+ public void setDeliveryApp(boolean deliveryApp) {
+ isDeliveryApp = deliveryApp;
+ }
+
+ public Date getLastAccessed() {
+ return lastAccessed;
+ }
+
+ public void setLastAccessed(Date lastAccessed) {
+ this.lastAccessed = lastAccessed;
+ }
+
+ public int getLearningProgress() {
+ return learningProgress;
+ }
+
+ public void setLearningProgress(int learningProgress) {
+ this.learningProgress = learningProgress;
+ }
+
+ public int getDeliveryProgress() {
+ return deliveryProgress;
+ }
+
+ public void setDeliveryProgress(int deliveryProgress) {
+ this.deliveryProgress = deliveryProgress;
+ }
+
+ public String getJobType() {
+ return jobType;
+ }
+
+ public void setJobType(String jobType) {
+ this.jobType = jobType;
+ }
+
+ public String getAppType() {
+ return appType;
+ }
+
+ public void setAppType(String appType) {
+ this.appType = appType;
+ }
+
+ // Optionally, override toString for easy logging or display
+ @Override
+ public String toString() {
+ return "AddListItem{" +
+ "name='" + name + '\'' +
+ ", id=" + id +
+ ", date=" + date +
+ ", description='" + description + '\'' +
+ ", organization='" + organization + '\'' +
+ '}';
+ }
+}
diff --git a/app/src/org/commcare/testingModel/CommCareItem.java b/app/src/org/commcare/testingModel/CommCareItem.java
new file mode 100644
index 0000000000..62f34a6c96
--- /dev/null
+++ b/app/src/org/commcare/testingModel/CommCareItem.java
@@ -0,0 +1,22 @@
+package org.commcare.testingModel;
+
+// Model class for CommCare items
+public class CommCareItem implements JobListItem {
+ private String title;
+ private String description;
+
+ // Constructor
+ public CommCareItem(String title, String description) {
+ this.title = title;
+ this.description = description;
+ }
+
+ // Getters
+ public String getTitle() {
+ return title;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+}
\ No newline at end of file
diff --git a/app/src/org/commcare/testingModel/ConnectHomeItem.java b/app/src/org/commcare/testingModel/ConnectHomeItem.java
new file mode 100644
index 0000000000..f29678dbc9
--- /dev/null
+++ b/app/src/org/commcare/testingModel/ConnectHomeItem.java
@@ -0,0 +1,21 @@
+package org.commcare.testingModel;
+
+public class ConnectHomeItem implements JobListItem {
+ private String name;
+ private String address;
+
+ // Constructor
+ public ConnectHomeItem(String name, String address) {
+ this.name = name;
+ this.address = address;
+ }
+
+ // Getters
+ public String getName() {
+ return name;
+ }
+
+ public String getAddress() {
+ return address;
+ }
+}
\ No newline at end of file
diff --git a/app/src/org/commcare/testingModel/JobListItem.java b/app/src/org/commcare/testingModel/JobListItem.java
new file mode 100644
index 0000000000..7894dafd58
--- /dev/null
+++ b/app/src/org/commcare/testingModel/JobListItem.java
@@ -0,0 +1,5 @@
+package org.commcare.testingModel;
+
+public interface JobListItem {
+ // Common methods (if needed)
+}
\ No newline at end of file
diff --git a/app/src/org/commcare/utils/SnackBarUtils.java b/app/src/org/commcare/utils/SnackBarUtils.java
new file mode 100644
index 0000000000..3077c25e9f
--- /dev/null
+++ b/app/src/org/commcare/utils/SnackBarUtils.java
@@ -0,0 +1,53 @@
+package org.commcare.utils;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.Toast;
+
+import androidx.core.content.ContextCompat;
+
+import org.commcare.dalvik.R;
+import org.commcare.views.connect.CustomSnackBar;
+
+public class SnackBarUtils {
+
+ public static void showErrorSnackBar(Activity activity, View anchorView) {
+ // Create CustomSnackBar instance
+ CustomSnackBar customSnackBar = new CustomSnackBar(activity, anchorView);
+
+ // Set the text, icons, and other customizations
+ customSnackBar.setText("This is a custom Snackbar");
+ customSnackBar.setTextColor(ContextCompat.getColor(activity, android.R.color.white));
+ customSnackBar.setTextSize(20f);
+
+ // Set left icon
+ Drawable leftIcon = ContextCompat.getDrawable(activity, R.drawable.ic_snackbar_done); // Replace with your drawable resource
+ customSnackBar.setLeftIcon(leftIcon);
+// customSnackBar.setLeftIconColor(ContextCompat.getColor(activity, android.R.color.white));
+
+ // Set right icon
+ Drawable rightIcon = ContextCompat.getDrawable(activity, R.drawable.ic_snackbar_done); // Replace with your drawable resource
+ customSnackBar.setRightIcon(rightIcon);
+// customSnackBar.setRightIconColor(ContextCompat.getColor(activity, android.R.color.white));
+
+ // Set background color and corner radius
+// customSnackBar.setBackgroundColor(R.color.connect_blue_color);
+ customSnackBar.setCornerRadius(16);
+
+ // Set click listeners for icons
+ customSnackBar.setLeftIconClickListener(view -> {
+ // Handle left icon click event
+ });
+
+ customSnackBar.setRightIconClickListener(view -> {
+ // Handle right icon click event
+ });
+
+ // Show the Snackbar
+ customSnackBar.show();
+ }
+}
diff --git a/app/src/org/commcare/views/connect/CircleProgressBar.java b/app/src/org/commcare/views/connect/CircleProgressBar.java
new file mode 100644
index 0000000000..703de9fd61
--- /dev/null
+++ b/app/src/org/commcare/views/connect/CircleProgressBar.java
@@ -0,0 +1,110 @@
+package org.commcare.views.connect;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.graphics.Shader;
+import android.graphics.SweepGradient;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+public class CircleProgressBar extends View {
+
+ private Paint backgroundPaint;
+ private Paint progressPaint;
+ private float progress = 0f;
+ private float strokeWidth = 10f;
+ private int startAngle = 270;
+ private int[] gradientColors = {Color.BLUE, Color.GREEN};
+ private boolean isGradient = false;
+ private int progressColor = Color.BLUE;
+ private int backgroundColor = Color.LTGRAY;
+
+ public CircleProgressBar(Context context) {
+ super(context);
+ init();
+ }
+
+ public CircleProgressBar(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init();
+ }
+
+ public CircleProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init();
+ }
+
+ private void init() {
+ backgroundPaint = new Paint();
+ backgroundPaint.setColor(backgroundColor);
+ backgroundPaint.setStyle(Paint.Style.STROKE);
+ backgroundPaint.setStrokeWidth(strokeWidth);
+ backgroundPaint.setAntiAlias(true);
+
+ progressPaint = new Paint();
+ progressPaint.setColor(progressColor);
+ progressPaint.setStyle(Paint.Style.STROKE);
+ progressPaint.setStrokeWidth(strokeWidth);
+ progressPaint.setAntiAlias(true);
+ progressPaint.setStrokeCap(Paint.Cap.ROUND);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ int width = getWidth();
+ int height = getHeight();
+ int radius = Math.min(width, height) / 2 - (int) strokeWidth;
+ int centerX = width / 2;
+ int centerY = height / 2;
+
+ canvas.drawCircle(centerX, centerY, radius, backgroundPaint);
+ RectF rectF = new RectF(centerX - radius, centerY - radius, centerX + radius, centerY + radius);
+ float sweepAngle = progress * 360f / 100f;
+ if (isGradient) {
+ Shader shader = new SweepGradient(centerX, centerY, gradientColors, null);
+ progressPaint.setShader(shader);
+ } else {
+ progressPaint.setShader(null);
+ progressPaint.setColor(progressColor);
+ }
+ canvas.drawArc(rectF, startAngle, sweepAngle, false, progressPaint);
+ }
+
+ public void setProgress(float progress) {
+ this.progress = progress;
+ invalidate();
+ }
+
+ public void setGradientColors(int[] colors) {
+ this.gradientColors = colors;
+ this.isGradient = true;
+ invalidate();
+ }
+
+ public void setStrokeWidth(float width) {
+ this.strokeWidth = width;
+ backgroundPaint.setStrokeWidth(width);
+ progressPaint.setStrokeWidth(width);
+ invalidate();
+ }
+
+ public void setProgressColor(int color) {
+ Log.e("CircleProgressBar", "Setting progress color to: " + color);
+ this.progressColor = color;
+ this.isGradient = false;
+ progressPaint.setColor(color);
+ invalidate();
+ }
+
+ public void setBackgroundColor(int color) {
+ this.backgroundColor = color;
+ backgroundPaint.setColor(color);
+ invalidate();
+ }
+}
diff --git a/app/src/org/commcare/views/connect/ConnectEditText.java b/app/src/org/commcare/views/connect/ConnectEditText.java
index 691fb7822f..fe84177090 100644
--- a/app/src/org/commcare/views/connect/ConnectEditText.java
+++ b/app/src/org/commcare/views/connect/ConnectEditText.java
@@ -10,6 +10,7 @@
import android.text.InputType;
import android.util.AttributeSet;
import android.view.MotionEvent;
+import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -21,15 +22,35 @@
public class ConnectEditText extends AppCompatEditText {
+ private static final int DEFAULT_BACKGROUND_COLOR = R.color.white;
private static final double DEFAULT_BORDER_WIDTH = 1.0;
private static final double DEFAULT_CORNER_RADIUS = 5.0;
private static final int DEFAULT_BORDER_COLOR = R.color.connect_light_grey;
private static final int DEFAULT_HINT_COLOR = Color.BLACK;
private static final int DEFAULT_TINT_COLOR = R.color.connect_light_grey;
- private static final float DEFAULT_HINT_SIZE = 7f;
- private static final float DEFAULT_TEXT_SIZE = 7f;
+ private static final int DEFAULT_ERROR_COLOR = R.color.connect_error_color;
+ private static final float DEFAULT_HINT_SIZE = 6f;
+ private static final float DEFAULT_TEXT_SIZE = 6f;
private static final int DEFAULT_FONT_RES_ID = R.font.roboto_regular;
+ // Global variables to store attribute values
private boolean drawableStartVisible = false, drawableEndVisible = false;
+ private int borderWidth;
+ private int cornerRadius;
+ private int drawableTintColor;
+ private int drawableStartPaddingLeft;
+ private int drawableEndPaddingRight;
+ private int drawableEndPadding;
+
+ private Drawable drawableStart;
+ private Drawable drawableEnd;
+
+ private int paddingTop;
+ private int paddingBottom;
+ private int paddingStart;
+ private int paddingEnd;
+
+ private GradientDrawable backgroundDrawable;
+
private OnDrawableStartClickListener onDrawableStartClickListener;
private OnDrawableEndClickListener onDrawableEndClickListener;
@@ -49,6 +70,8 @@ public ConnectEditText(@NonNull Context context, @Nullable AttributeSet attrs, i
}
private void init(Context context, @Nullable AttributeSet attrs) {
+ backgroundDrawable = new GradientDrawable();
+
if (attrs != null) {
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
@@ -56,27 +79,28 @@ private void init(Context context, @Nullable AttributeSet attrs) {
0, 0);
try {
- int borderWidth = a.getDimensionPixelSize(R.styleable.CustomEditText_editTextBorderWidth, dpToPx(DEFAULT_BORDER_WIDTH));
- int cornerRadius = a.getDimensionPixelSize(R.styleable.CustomEditText_editTextCornerRadius, dpToPx(DEFAULT_CORNER_RADIUS));
+ // Initialize global variables with attribute values
+ borderWidth = a.getDimensionPixelSize(R.styleable.CustomEditText_editTextBorderWidth, dpToPx(DEFAULT_BORDER_WIDTH));
+ cornerRadius = a.getDimensionPixelSize(R.styleable.CustomEditText_editTextCornerRadius, dpToPx(DEFAULT_CORNER_RADIUS));
int borderColor = a.getColor(R.styleable.CustomEditText_editTextBorderColor, ContextCompat.getColor(getContext(), DEFAULT_BORDER_COLOR));
int hintColor = a.getColor(R.styleable.CustomEditText_editTextHintColor, DEFAULT_HINT_COLOR);
float hintSize = a.getDimension(R.styleable.CustomEditText_editTextHintSize, spToPx(DEFAULT_HINT_SIZE));
boolean isEditable = a.getBoolean(R.styleable.CustomEditText_editTextEditable, true);
- Drawable drawableStart = a.getDrawable(R.styleable.CustomEditText_editTextDrawableStart);
- Drawable drawableEnd = a.getDrawable(R.styleable.CustomEditText_editTextDrawableEnd);
+ drawableStart = a.getDrawable(R.styleable.CustomEditText_editTextDrawableStart);
+ drawableEnd = a.getDrawable(R.styleable.CustomEditText_editTextDrawableEnd);
drawableStartVisible = a.getBoolean(R.styleable.CustomEditText_editTextDrawableStartVisible, false);
drawableEndVisible = a.getBoolean(R.styleable.CustomEditText_editTextDrawableEndVisible, false);
- int drawableTintColor = a.getColor(R.styleable.CustomEditText_editTextDrawableTint, ContextCompat.getColor(getContext(), DEFAULT_TINT_COLOR));
+ drawableTintColor = a.getColor(R.styleable.CustomEditText_editTextDrawableTint, ContextCompat.getColor(getContext(), DEFAULT_TINT_COLOR));
- int drawableStartPaddingLeft = a.getDimensionPixelSize(R.styleable.CustomEditText_editTextDrawableStartPaddingLeft, dpToPx(15));
- int drawableEndPaddingRight = a.getDimensionPixelSize(R.styleable.CustomEditText_editTextDrawableEndPaddingRight, dpToPx(15));
- int drawableEndPadding = a.getDimensionPixelSize(R.styleable.CustomEditText_editTextDrawablePadding, dpToPx(12));
+ drawableStartPaddingLeft = a.getDimensionPixelSize(R.styleable.CustomEditText_editTextDrawableStartPaddingLeft, dpToPx(8));
+ drawableEndPaddingRight = a.getDimensionPixelSize(R.styleable.CustomEditText_editTextDrawableEndPaddingRight, dpToPx(14));
+ drawableEndPadding = a.getDimensionPixelSize(R.styleable.CustomEditText_editTextDrawablePadding, dpToPx(8));
- // New padding attribute handling
- int paddingTop = a.getDimensionPixelSize(R.styleable.CustomEditText_editTextPaddingTop, dpToPx(20));
- int paddingBottom = a.getDimensionPixelSize(R.styleable.CustomEditText_editTextPaddingBottom, dpToPx(20));
- int paddingStart = a.getDimensionPixelSize(R.styleable.CustomEditText_editTextPaddingStart, dpToPx(10));
- int paddingEnd = a.getDimensionPixelSize(R.styleable.CustomEditText_editTextPaddingEnd, dpToPx(0));
+ // Padding attributes
+ paddingTop = a.getDimensionPixelSize(R.styleable.CustomEditText_editTextPaddingTop, dpToPx(20));
+ paddingBottom = a.getDimensionPixelSize(R.styleable.CustomEditText_editTextPaddingBottom, dpToPx(20));
+ paddingStart = a.getDimensionPixelSize(R.styleable.CustomEditText_editTextPaddingStart, dpToPx(10));
+ paddingEnd = a.getDimensionPixelSize(R.styleable.CustomEditText_editTextPaddingEnd, dpToPx(0));
int fontFamily = a.getResourceId(R.styleable.CustomEditText_editTextFontFamily, DEFAULT_FONT_RES_ID);
// New attributes for text, textSize, hint, and hintSize
@@ -105,8 +129,8 @@ private void init(Context context, @Nullable AttributeSet attrs) {
drawableEnd,
drawableStartVisible,
drawableEndVisible,
- dpToPx(25),
- dpToPx(25),
+ dpToPx(20),
+ dpToPx(20),
drawableTintColor,
drawableStartPaddingLeft,
drawableEndPaddingRight,
@@ -139,10 +163,9 @@ public void setHintTextSize(float size) {
}
private void setBorder(int borderWidth, int cornerRadius, int borderColor) {
- GradientDrawable drawable = new GradientDrawable();
- drawable.setCornerRadius(cornerRadius);
- drawable.setStroke(borderWidth, borderColor);
- setBackground(drawable);
+ backgroundDrawable.setCornerRadius(cornerRadius);
+ backgroundDrawable.setStroke(borderWidth, borderColor);
+ setBackground(backgroundDrawable);
}
private void setHintAttributes(int color, float size) {
@@ -150,6 +173,69 @@ private void setHintAttributes(int color, float size) {
setTextSize(size);
}
+ public void showErrorState() {
+ int errorColor = ContextCompat.getColor(getContext(), DEFAULT_ERROR_COLOR);
+ backgroundDrawable.setStroke(borderWidth, errorColor);
+ backgroundDrawable.setColor(ContextCompat.getColor(getContext(), DEFAULT_BACKGROUND_COLOR));
+ setTextColor(errorColor);
+ setHintTextColor(errorColor);
+ setDrawables(
+ drawableStart,
+ drawableEnd,
+ drawableStartVisible,
+ drawableEndVisible,
+ dpToPx(20),
+ dpToPx(20),
+ errorColor,
+ drawableStartPaddingLeft,
+ drawableEndPaddingRight,
+ drawableEndPadding
+ );
+ setBackground(backgroundDrawable);
+ }
+
+ public void setNormalBorder(){
+ int borderColor = ContextCompat.getColor(getContext(), DEFAULT_BORDER_COLOR);
+ backgroundDrawable.setStroke(borderWidth, borderColor);
+ backgroundDrawable.setColor(ContextCompat.getColor(getContext(), DEFAULT_BACKGROUND_COLOR));
+ setTextColor(DEFAULT_HINT_COLOR);
+ setHintTextColor(DEFAULT_HINT_COLOR);
+ setDrawables(
+ drawableStart,
+ drawableEnd,
+ drawableStartVisible,
+ drawableEndVisible,
+ dpToPx(20),
+ dpToPx(20),
+ borderColor,
+ drawableStartPaddingLeft,
+ drawableEndPaddingRight,
+ drawableEndPadding
+ );
+ setBackground(backgroundDrawable);
+ }
+
+ public void setGreyBackground(){
+ int borderColor = ContextCompat.getColor(getContext(), R.color.connect_light_grey);
+ backgroundDrawable.setStroke(borderWidth, borderColor);
+ backgroundDrawable.setColor(borderColor);
+ setTextColor(DEFAULT_HINT_COLOR);
+ setHintTextColor(DEFAULT_HINT_COLOR);
+ setDrawables(
+ drawableStart,
+ drawableEnd,
+ drawableStartVisible,
+ drawableEndVisible,
+ dpToPx(20),
+ dpToPx(20),
+ DEFAULT_HINT_COLOR,
+ drawableStartPaddingLeft,
+ drawableEndPaddingRight,
+ drawableEndPadding
+ );
+ setBackground(backgroundDrawable);
+ }
+
public void setEditable(boolean isEditable) {
setFocusable(isEditable);
setFocusableInTouchMode(isEditable);
@@ -187,8 +273,12 @@ private void setDrawables(
@SuppressLint("ClickableViewAccessibility")
private void setupDrawableClickListeners() {
setOnTouchListener((v, event) -> {
- if (drawableStartVisible && event.getAction() == MotionEvent.ACTION_UP) {
- if (event.getX() <= getCompoundDrawables()[0].getBounds().width()) {
+ Drawable[] drawables = getCompoundDrawables();
+ Drawable drawableStart = drawables[0];
+ Drawable drawableEnd = drawables[2];
+
+ if (drawableStartVisible && drawableStart != null && event.getAction() == MotionEvent.ACTION_UP) {
+ if (event.getX() <= drawableStart.getBounds().width()) {
if (onDrawableStartClickListener != null) {
onDrawableStartClickListener.onDrawableStartClick();
}
@@ -196,8 +286,8 @@ private void setupDrawableClickListeners() {
}
}
- if (drawableEndVisible && event.getAction() == MotionEvent.ACTION_UP) {
- if (event.getX() >= (getWidth() - getPaddingRight() - getCompoundDrawables()[2].getBounds().width())) {
+ if (drawableEndVisible && drawableEnd != null && event.getAction() == MotionEvent.ACTION_UP) {
+ if (event.getX() >= (getWidth() - getPaddingRight() - drawableEnd.getBounds().width())) {
if (onDrawableEndClickListener != null) {
onDrawableEndClickListener.onDrawableEndClick();
}
@@ -209,6 +299,7 @@ private void setupDrawableClickListeners() {
});
}
+
public interface OnDrawableStartClickListener {
void onDrawableStartClick();
}
diff --git a/app/src/org/commcare/views/connect/CustomSnackBar.java b/app/src/org/commcare/views/connect/CustomSnackBar.java
new file mode 100644
index 0000000000..fa33d995ae
--- /dev/null
+++ b/app/src/org/commcare/views/connect/CustomSnackBar.java
@@ -0,0 +1,206 @@
+package org.commcare.views.connect;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.os.Build;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.constraintlayout.widget.ConstraintLayout;
+
+import com.google.android.material.snackbar.Snackbar;
+
+import org.commcare.dalvik.R;
+
+public class CustomSnackBar {
+
+ private final Snackbar snackbar;
+ private final Context context;
+ private final View customView;
+ Snackbar.SnackbarLayout snackbarLayout;
+
+ @SuppressLint("InflateParams")
+ public CustomSnackBar(Context context, View anchorView) {
+ this.context = context;
+ snackbar = Snackbar.make(anchorView, "", Snackbar.LENGTH_LONG);
+ customView = LayoutInflater.from(context).inflate(R.layout.custom_snack_bar, null);
+ setupSnackBarView();
+ }
+
+ @SuppressLint("RestrictedApi")
+ private void setupSnackBarView() {
+ snackbar.getView().setBackgroundColor(Color.TRANSPARENT);
+ snackbarLayout = (Snackbar.SnackbarLayout) snackbar.getView();
+ snackbarLayout.setPadding(10, 10, 10, 10);
+ snackbarLayout.addView(customView, 0);
+ }
+
+ // Helper method to update TextView properties
+ private void updateTextView(TextUpdater updater) {
+ if (customView != null) {
+ TextView textView = customView.findViewById(R.id.tvMessage);
+ if (textView != null) {
+ updater.update(textView);
+ }
+ }
+ }
+
+ @FunctionalInterface
+ private interface TextUpdater {
+ void update(TextView textView);
+ }
+
+ // Set text on a TextView
+ public void setText(CharSequence text) {
+ updateTextView(textView -> textView.setText(text));
+ }
+
+ // Set text color on a TextView
+ public void setTextColor(int color) {
+ updateTextView(textView -> textView.setTextColor(color));
+ }
+
+ // Set text size on a TextView
+ public void setTextSize(float size) {
+ updateTextView(textView -> textView.setTextSize(size));
+ }
+
+ // Set text font on a TextView
+ public void setTextFont(int fontId) {
+ if (customView != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ TextView textView = customView.findViewById(R.id.tvMessage);
+ if (textView != null) {
+ textView.setTypeface(context.getResources().getFont(fontId));
+ }
+ }
+ }
+
+ // Helper method to update ImageView properties
+ private void updateImageView(int iconId, ImageViewUpdater updater) {
+ if (customView != null) {
+ ImageView icon = customView.findViewById(iconId);
+ if (icon != null) {
+ updater.update(icon);
+ }
+ }
+ }
+
+ @FunctionalInterface
+ private interface ImageViewUpdater {
+ void update(ImageView imageView);
+ }
+
+ // Set left icon properties
+ public void setLeftIcon(Drawable drawable) {
+ ImageView icon = customView.findViewById(R.id.imgLeftIcon);
+ if (icon != null) {
+ icon.setImageDrawable(drawable);
+ }
+ }
+
+ public void setLeftIconSize(int width, int height) {
+ if (customView != null) {
+ ImageView icon = customView.findViewById(R.id.imgLeftIcon);
+ if (icon != null) {
+ ConstraintLayout.LayoutParams params = new ConstraintLayout.LayoutParams(width, height);
+ icon.setLayoutParams(params);
+ }
+ }
+ }
+
+ public void setLeftIconColor(int color) {
+ if (customView != null) {
+ ImageView icon = customView.findViewById(R.id.imgLeftIcon);
+
+ if (icon != null) {
+ icon.setColorFilter(color);
+ }
+ }
+ }
+
+ public void setLeftIconClickListener(View.OnClickListener listener) {
+ if (customView != null) {
+ ImageView icon = customView.findViewById(R.id.imgLeftIcon);
+
+ if (icon != null) {
+ icon.setOnClickListener(listener);
+ }
+ }
+ }
+
+ // Set right icon properties
+ public void setRightIcon(Drawable drawable) {
+ ImageView icon = customView.findViewById(R.id.imgRightIcon);
+ if (icon != null) {
+ icon.setImageDrawable(drawable);
+ }
+ }
+
+ public void setRightIconSize(int width, int height) {
+ if (customView != null) {
+ ImageView icon = customView.findViewById(R.id.imgRightIcon);
+ if (icon != null) {
+ ConstraintLayout.LayoutParams params = new ConstraintLayout.LayoutParams(width, height);
+ icon.setLayoutParams(params);
+ }
+ }
+ }
+
+ public void setRightIconColor(int color) {
+ if (customView != null) {
+ ImageView icon = customView.findViewById(R.id.imgRightIcon);
+ if (icon != null) {
+ icon.setColorFilter(color);
+ }
+ }
+ }
+
+ public void setRightIconClickListener(View.OnClickListener listener) {
+ if (customView != null) {
+ ImageView icon = customView.findViewById(R.id.imgRightIcon);
+
+ if (icon != null) {
+ icon.setOnClickListener(listener);
+ }
+ }
+ }
+
+ // Set the background color of the custom view
+ @SuppressLint("RestrictedApi")
+ public void setBackgroundColor(Drawable color) {
+ if (customView != null) {
+// GradientDrawable background = new GradientDrawable();
+// background.setColor(color); // Set background color
+// customView.setBackground(background);
+ snackbarLayout.setBackground(color);
+ }
+ }
+
+ // Set the corner radius of the background
+ public void setCornerRadius(int radius) {
+ if (customView != null) {
+ GradientDrawable background = new GradientDrawable();
+ background.setCornerRadius(radius);
+ customView.setBackground(background);
+ }
+ }
+
+ // Set layout parameters for the custom view
+ public void setCustomViewLayoutParams(int width, int height, int marginLeft, int marginTop, int marginRight, int marginBottom) {
+ if (customView != null) {
+ ConstraintLayout.LayoutParams layoutParams = new ConstraintLayout.LayoutParams(width, height);
+ layoutParams.setMargins(marginLeft, marginTop, marginRight, marginBottom);
+ customView.setLayoutParams(layoutParams);
+ }
+ }
+
+ // Show the Snackbar
+ public void show() {
+ snackbar.show();
+ }
+}