From 8e8b31a71aa896cfb5e63dafe4946799695c13ef Mon Sep 17 00:00:00 2001 From: Idelcano Date: Tue, 2 May 2017 18:50:09 +0200 Subject: [PATCH 01/69] Added inteceptor and cache config to okhttpclient instance to save and be able to display the cached images --- .../dashboard/api/utils/PicassoProvider.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/PicassoProvider.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/PicassoProvider.java index 03fb8567..574f008f 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/PicassoProvider.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/PicassoProvider.java @@ -30,13 +30,18 @@ import android.content.Context; +import com.squareup.okhttp.Cache; +import com.squareup.okhttp.Interceptor; import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Response; import com.squareup.picasso.OkHttpDownloader; import com.squareup.picasso.Picasso; import org.hisp.dhis.android.dashboard.api.controllers.DhisController; import org.hisp.dhis.android.dashboard.api.network.RepoManager; +import java.io.IOException; + public final class PicassoProvider { private static Picasso mPicasso; @@ -49,6 +54,16 @@ public static Picasso getInstance(Context context) { OkHttpClient client = RepoManager.provideOkHttpClient( DhisController.getInstance().getUserCredentials()); + client.networkInterceptors().add(new Interceptor() { + @Override + public Response intercept(Chain chain) throws IOException { + Response originalResponse = chain.proceed(chain.request()); + return originalResponse.newBuilder().header("Cache-Control", "max-age=" + (60 * 60 * 24 * 365)).build(); + } + }); + + client.setCache(new Cache(context.getCacheDir(), Integer.MAX_VALUE)); + mPicasso = new Picasso.Builder(context) .downloader(new OkHttpDownloader(client)) .build(); From 787d4ff9dc8c295eaefc363dc85345a65a887d98 Mon Sep 17 00:00:00 2001 From: Idelcano Date: Thu, 4 May 2017 09:42:04 +0200 Subject: [PATCH 02/69] fix typo --- .../org/hisp/dhis/android/dashboard/api/models/UserAccount.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/UserAccount.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/UserAccount.java index 182185cf..6e4f4a2a 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/UserAccount.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/UserAccount.java @@ -151,7 +151,7 @@ public static User toUser(UserAccount userAccount) { User user = new User(); user.setUId(userAccount.getUId()); user.setAccess(userAccount.getAccess()); - user.setCreated(user.getCreated()); + user.setCreated(userAccount.getCreated()); user.setLastUpdated(userAccount.getLastUpdated()); user.setName(userAccount.getName()); user.setDisplayName(userAccount.getDisplayName()); From 50973afec0fb0a40edd779e31c83d98c58c97ae2 Mon Sep 17 00:00:00 2001 From: idelcano Date: Fri, 5 May 2017 02:41:41 +0200 Subject: [PATCH 03/69] Added TextUtil test and gradle dependencies --- app/build.gradle | 3 +++ app/src/test/java/TextUtilsTest.java | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 app/src/test/java/TextUtilsTest.java diff --git a/app/build.gradle b/app/build.gradle index 59656a3e..3058b6e9 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -43,4 +43,7 @@ dependencies { // Other compile 'com.jakewharton:butterknife:7.0.1' compile 'com.github.chrisbanes.photoview:library:1.2.4' + + // Java test dependencies + testCompile "junit:junit:4.10" } diff --git a/app/src/test/java/TextUtilsTest.java b/app/src/test/java/TextUtilsTest.java new file mode 100644 index 00000000..8fff2c09 --- /dev/null +++ b/app/src/test/java/TextUtilsTest.java @@ -0,0 +1,17 @@ +import static org.junit.Assert.assertTrue; + +import org.hisp.dhis.android.dashboard.utils.TextUtils; +import org.junit.Test; + +public class TextUtilsTest { + + @Test + public void test_empty_string_is_empty() { + assertTrue(TextUtils.isEmpty("")); + } + + @Test + public void test_null_string_is_empty() { + assertTrue(TextUtils.isEmpty(null)); + } +} From b5a8335ebf45e489e06a7c770754615e11376692 Mon Sep 17 00:00:00 2001 From: idelcano Date: Fri, 5 May 2017 02:42:05 +0200 Subject: [PATCH 04/69] Added test dependencies into api gradle --- api/build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api/build.gradle b/api/build.gradle index 14b5bbb3..158c896d 100755 --- a/api/build.gradle +++ b/api/build.gradle @@ -47,4 +47,7 @@ dependencies { // Other compile 'joda-time:joda-time:2.9.2' + + // Java test dependencies + testCompile "junit:junit:4.12" } From 365ab57448e362c7748f8223676e77eda88832d3 Mon Sep 17 00:00:00 2001 From: idelcano Date: Fri, 5 May 2017 02:50:15 +0200 Subject: [PATCH 05/69] Added api tests --- .../dhis/android/core/PreconditionsTests.java | 18 ++ .../android/core/commons/DateTestUtils.java | 27 ++ .../dhis/android/core/commons/JsonParser.java | 15 + .../core/converters/AccessConverterTests.java | 55 ++++ .../converters/DateTimeConverterTests.java | 51 ++++ .../core/converters/StateConverterTests.java | 56 ++++ .../core/dashboard/DashboardTests.java | 258 ++++++++++++++++++ .../interpretation/InterpretationTests.java | 238 ++++++++++++++++ .../android/core/meta/CredentialsTests.java | 27 ++ .../core/network/ApiExceptionTests.java | 75 +++++ .../core/systeminfo/SystemInfoTests.java | 60 ++++ .../dhis/android/core/user/UserTests.java | 81 ++++++ 12 files changed, 961 insertions(+) create mode 100644 api/src/test/java/org/hisp/dhis/android/core/PreconditionsTests.java create mode 100644 api/src/test/java/org/hisp/dhis/android/core/commons/DateTestUtils.java create mode 100644 api/src/test/java/org/hisp/dhis/android/core/commons/JsonParser.java create mode 100644 api/src/test/java/org/hisp/dhis/android/core/converters/AccessConverterTests.java create mode 100644 api/src/test/java/org/hisp/dhis/android/core/converters/DateTimeConverterTests.java create mode 100644 api/src/test/java/org/hisp/dhis/android/core/converters/StateConverterTests.java create mode 100644 api/src/test/java/org/hisp/dhis/android/core/dashboard/DashboardTests.java create mode 100644 api/src/test/java/org/hisp/dhis/android/core/interpretation/InterpretationTests.java create mode 100644 api/src/test/java/org/hisp/dhis/android/core/meta/CredentialsTests.java create mode 100644 api/src/test/java/org/hisp/dhis/android/core/network/ApiExceptionTests.java create mode 100644 api/src/test/java/org/hisp/dhis/android/core/systeminfo/SystemInfoTests.java create mode 100644 api/src/test/java/org/hisp/dhis/android/core/user/UserTests.java diff --git a/api/src/test/java/org/hisp/dhis/android/core/PreconditionsTests.java b/api/src/test/java/org/hisp/dhis/android/core/PreconditionsTests.java new file mode 100644 index 00000000..d6e851a6 --- /dev/null +++ b/api/src/test/java/org/hisp/dhis/android/core/PreconditionsTests.java @@ -0,0 +1,18 @@ +package org.hisp.dhis.android.core; + +import org.hisp.dhis.android.dashboard.api.utils.Preconditions; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class PreconditionsTests { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void precondition_null_exception() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("test_message"); + Preconditions.isNull(null, "test_message"); + } +} diff --git a/api/src/test/java/org/hisp/dhis/android/core/commons/DateTestUtils.java b/api/src/test/java/org/hisp/dhis/android/core/commons/DateTestUtils.java new file mode 100644 index 00000000..f71391da --- /dev/null +++ b/api/src/test/java/org/hisp/dhis/android/core/commons/DateTestUtils.java @@ -0,0 +1,27 @@ +package org.hisp.dhis.android.core.commons; + +import org.joda.time.DateTime; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +public class DateTestUtils { + public final static String DHIS2_GMT_NEW_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS"; + + public static Date parseDate(String date, String format) { + try { + SimpleDateFormat sdf = new SimpleDateFormat(format); + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); + return sdf.parse(date); + } catch (ParseException e) { + e.printStackTrace(); + return null; + } + } + + public static boolean compareParsedDateWithStringDate(DateTime date, String isDate) { + return date.toDate().getTime() == (parseDate(isDate, DHIS2_GMT_NEW_DATE_FORMAT).getTime()); + } +} diff --git a/api/src/test/java/org/hisp/dhis/android/core/commons/JsonParser.java b/api/src/test/java/org/hisp/dhis/android/core/commons/JsonParser.java new file mode 100644 index 00000000..7a586257 --- /dev/null +++ b/api/src/test/java/org/hisp/dhis/android/core/commons/JsonParser.java @@ -0,0 +1,15 @@ +package org.hisp.dhis.android.core.commons; + +import org.hisp.dhis.android.dashboard.api.utils.ObjectMapperProvider; + +import java.io.IOException; + +public class JsonParser { + + + public static Object getModelFromJson(Class modelClass, String json) throws IOException { + return ObjectMapperProvider.getInstance() + .readValue(json, modelClass); + } + +} \ No newline at end of file diff --git a/api/src/test/java/org/hisp/dhis/android/core/converters/AccessConverterTests.java b/api/src/test/java/org/hisp/dhis/android/core/converters/AccessConverterTests.java new file mode 100644 index 00000000..a7476282 --- /dev/null +++ b/api/src/test/java/org/hisp/dhis/android/core/converters/AccessConverterTests.java @@ -0,0 +1,55 @@ +package org.hisp.dhis.android.core.converters; + +import static junit.framework.Assert.assertTrue; + +import org.hisp.dhis.android.dashboard.api.models.Access; +import org.hisp.dhis.android.dashboard.api.persistence.converters.AccessConverter; +import org.junit.Before; +import org.junit.Test; + +public class AccessConverterTests { + public static final String ACCESS_ALL_TRUE_AS_DATABASE_STRING = "{\"manage\":true," + + "\"externalize\":true,\"write\":true,\"read\":true,\"update\":true,\"delete\":true}"; + public static final String ACCESS_ALL_FALSE_AS_DATABASE_STRING = "{\"manage\":false," + + "\"externalize\":false,\"write\":false,\"read\":false,\"update\":false," + + "\"delete\":false}"; + public static Access accessObject; + AccessConverter accessConverter = new AccessConverter(); + + @Before + public void setUp() throws Exception { + accessObject = new Access(); + accessObject.setDelete(true); + accessObject.setWrite(true); + accessObject.setUpdate(true); + accessObject.setRead(true); + accessObject.setManage(true); + accessObject.setExternalize(true); + } + + @Test + public void convert_access_object_to_database_string() throws Exception { + assertTrue(accessConverter.getDBValue(accessObject).equals( + ACCESS_ALL_TRUE_AS_DATABASE_STRING)); + } + + @Test + public void convert_access_all_true_database_string_to_model() { + Access access = accessConverter.getModelValue(ACCESS_ALL_TRUE_AS_DATABASE_STRING); + assertTrue(access.isDelete()); + assertTrue(access.isRead()); + assertTrue(access.isWrite()); + assertTrue(access.isManage()); + assertTrue(access.isExternalize()); + } + + @Test + public void convert_access_all_false_database_string_to_model() { + Access access = accessConverter.getModelValue(ACCESS_ALL_FALSE_AS_DATABASE_STRING); + assertTrue(!access.isDelete()); + assertTrue(!access.isRead()); + assertTrue(!access.isWrite()); + assertTrue(!access.isManage()); + assertTrue(!access.isExternalize()); + } +} diff --git a/api/src/test/java/org/hisp/dhis/android/core/converters/DateTimeConverterTests.java b/api/src/test/java/org/hisp/dhis/android/core/converters/DateTimeConverterTests.java new file mode 100644 index 00000000..09050c6d --- /dev/null +++ b/api/src/test/java/org/hisp/dhis/android/core/converters/DateTimeConverterTests.java @@ -0,0 +1,51 @@ +package org.hisp.dhis.android.core.converters; + +import static junit.framework.Assert.assertTrue; + +import org.hisp.dhis.android.core.commons.DateTestUtils; +import org.hisp.dhis.android.dashboard.api.persistence.converters.DateTimeConverter; +import org.joda.time.DateTime; +import org.junit.Before; +import org.junit.Test; + +import java.util.Calendar; +import java.util.TimeZone; + +public class DateTimeConverterTests { + + public static final String DATETIME_AS_STRING = "2016-04-21T15:37:07.740Z"; + public static final String STATE_TO_POST = "TO_POST"; + + DateTimeConverter dateTimeConverter = new DateTimeConverter(); + + DateTime dateTime; + + @Before + public void setUp() throws Exception { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeZone(TimeZone.getTimeZone("UTC")); + calendar.set(Calendar.YEAR, 2016); + calendar.set(Calendar.MONTH, 04); + calendar.set(Calendar.DAY_OF_MONTH, 21); + calendar.set(Calendar.HOUR_OF_DAY, 15); + calendar.set(Calendar.MINUTE, 37); + calendar.set(Calendar.SECOND, 07); + calendar.set(Calendar.MILLISECOND, 740); + dateTime = new DateTime(calendar); + } + + @Test + public void convert_datetime_string_to_object() throws Exception { + DateTime convertedDate = dateTimeConverter.getModelValue(DATETIME_AS_STRING); + + assertTrue(DateTestUtils.compareParsedDateWithStringDate(convertedDate, DATETIME_AS_STRING)); + } + + @Test + public void convert_datetime_object_to_string() throws Exception { + String converterDate = dateTimeConverter.getDBValue(dateTime); + + assertTrue(DateTestUtils.compareParsedDateWithStringDate(dateTime, converterDate)); + } + +} diff --git a/api/src/test/java/org/hisp/dhis/android/core/converters/StateConverterTests.java b/api/src/test/java/org/hisp/dhis/android/core/converters/StateConverterTests.java new file mode 100644 index 00000000..7368763f --- /dev/null +++ b/api/src/test/java/org/hisp/dhis/android/core/converters/StateConverterTests.java @@ -0,0 +1,56 @@ +package org.hisp.dhis.android.core.converters; + +import static junit.framework.Assert.assertTrue; + +import org.hisp.dhis.android.dashboard.api.models.meta.State; +import org.hisp.dhis.android.dashboard.api.persistence.converters.StateConverter; +import org.junit.Test; + +public class StateConverterTests { + public static final String STATE_SYNCED = "SYNCED"; + public static final String STATE_TO_POST = "TO_POST"; + public static final String STATE_TO_UPDATE = "TO_UPDATE"; + public static final String STATE_TO_DELETE = "TO_DELETE"; + + StateConverter stateConverter = new StateConverter(); + + @Test + public void convert_state_to_post_string_to_object() throws Exception { + assertTrue(stateConverter.getModelValue(STATE_TO_POST).equals(State.TO_POST)); + } + + @Test + public void convert_state_to_post_object_to_string() throws Exception { + assertTrue(stateConverter.getDBValue(State.TO_POST).equals(STATE_TO_POST)); + } + + @Test + public void convert_state_to_delete_string_to_object() throws Exception { + assertTrue(stateConverter.getModelValue(STATE_TO_DELETE).equals(State.TO_DELETE)); + } + + @Test + public void convert_state_to_delete_object_to_string() throws Exception { + assertTrue(stateConverter.getDBValue(State.TO_DELETE).equals(STATE_TO_DELETE)); + } + + @Test + public void convert_state_to_update_string_to_object() throws Exception { + assertTrue(stateConverter.getModelValue(STATE_TO_UPDATE).equals(State.TO_UPDATE)); + } + + @Test + public void convert_state_to_update_object_to_string() throws Exception { + assertTrue(stateConverter.getDBValue(State.TO_UPDATE).equals(STATE_TO_UPDATE)); + } + + @Test + public void convert_state_synced_string_to_object() throws Exception { + assertTrue(stateConverter.getModelValue(STATE_SYNCED).equals(State.SYNCED)); + } + + @Test + public void convert_state_synced_object_to_string() throws Exception { + assertTrue(stateConverter.getDBValue(State.SYNCED).equals(STATE_SYNCED)); + } +} diff --git a/api/src/test/java/org/hisp/dhis/android/core/dashboard/DashboardTests.java b/api/src/test/java/org/hisp/dhis/android/core/dashboard/DashboardTests.java new file mode 100644 index 00000000..821f950e --- /dev/null +++ b/api/src/test/java/org/hisp/dhis/android/core/dashboard/DashboardTests.java @@ -0,0 +1,258 @@ +package org.hisp.dhis.android.core.dashboard; + +import static org.junit.Assert.assertTrue; + +import org.hisp.dhis.android.core.commons.DateTestUtils; +import org.hisp.dhis.android.core.commons.JsonParser; +import org.hisp.dhis.android.dashboard.api.models.Access; +import org.hisp.dhis.android.dashboard.api.models.Dashboard; +import org.hisp.dhis.android.dashboard.api.models.DashboardElement; +import org.hisp.dhis.android.dashboard.api.models.DashboardItem; +import org.hisp.dhis.android.dashboard.api.models.meta.State; +import org.junit.Test; + +import java.io.IOException; +import java.util.List; + +public class DashboardTests { + private final String DASHBOARD_JSON = "[{\n" + + "\t\t\"lastUpdated\": \"2016-10-11T19:24:33.599\",\n" + + "\t\t\"created\": \"2013-09-08T21:47:17.960\",\n" + + "\t\t\"name\": \"Antenatal Care\",\n" + + "\t\t\"id\": \"nghVC4wtyzi\",\n" + + "\t\t\"displayName\": \"Antenatal Care\",\n" + + "\t\t\"access\": {\n" + + "\t\t\t\"read\": true,\n" + + "\t\t\t\"update\": true,\n" + + "\t\t\t\"externalize\": true,\n" + + "\t\t\t\"delete\": true,\n" + + "\t\t\t\"write\": true,\n" + + "\t\t\t\"manage\": true\n" + + "\t\t},\n" + + "\t\t\"dashboardItems\": [{\n" + + "\t\t\t\"created\": \"2016-10-10T17:24:30.487\",\n" + + "\t\t\t\"lastUpdated\": \"2016-10-10T17:24:30.487\",\n" + + "\t\t\t\"id\": \"cX2przhv9UC\",\n" + + "\t\t\t\"shape\": \"NORMAL\",\n" + + "\t\t\t\"type\": \"CHART\",\n" + + "\t\t\t\"chart\": {\n" + + "\t\t\t\t\"lastUpdated\": \"2016-10-10T17:24:49.196\",\n" + + "\t\t\t\t\"created\": \"2016-10-10T17:24:49.196\",\n" + + "\t\t\t\t\"name\": \"ANC: ANC IPT 1 Coverage last 12 months districts\",\n" + + "\t\t\t\t\"id\": \"VffWmdKFHSq\",\n" + + "\t\t\t\t\"displayName\": \"ANC: ANC IPT 1 Coverage last 12 months districts\"\n" + + "\t\t\t}\n" + + "\t\t}, {\n" + + "\t\t\t\"created\": \"2016-08-02T11:57:55.229\",\n" + + "\t\t\t\"lastUpdated\": \"2016-08-02T11:57:55.229\",\n" + + "\t\t\t\"id\": \"JcO7yJlKIa3\",\n" + + "\t\t\t\"shape\": \"NORMAL\",\n" + + "\t\t\t\"type\": \"CHART\",\n" + + "\t\t\t\"chart\": {\n" + + "\t\t\t\t\"lastUpdated\": \"2016-10-10T17:08:15.277\",\n" + + "\t\t\t\t\"created\": \"2016-10-10T17:08:15.277\",\n" + + "\t\t\t\t\"name\": \"ANC: ANC 3 coverage by districts last 4 quarters\",\n" + + "\t\t\t\t\"id\": \"CNkMibmx1Zr\",\n" + + "\t\t\t\t\"displayName\": \"ANC: ANC 3 coverage by districts last 4 quarters\"\n" + + "\t\t\t}\n" + + "\t\t}, {\n" + + "\t\t\t\"created\": \"2015-01-16T11:52:44.928\",\n" + + "\t\t\t\"lastUpdated\": \"2015-01-16T11:52:44.928\",\n" + + "\t\t\t\"id\": \"OiyMNoXzSdY\",\n" + + "\t\t\t\"shape\": \"NORMAL\",\n" + + "\t\t\t\"type\": \"MAP\",\n" + + "\t\t\t\"map\": {\n" + + "\t\t\t\t\"lastUpdated\": \"2013-09-09T16:35:12.062\",\n" + + "\t\t\t\t\"created\": \"2012-11-14T12:56:59.322\",\n" + + "\t\t\t\t\"name\": \"ANC: LLITN coverage district and facility\",\n" + + "\t\t\t\t\"id\": \"ZBjCfSaLSqD\",\n" + + "\t\t\t\t\"displayName\": \"ANC: LLITN coverage district and facility\"\n" + + "\t\t\t}\n" + + "\t\t}, {\n" + + "\t\t\t\"created\": \"2016-04-21T15:37:07.740\",\n" + + "\t\t\t\"lastUpdated\": \"2016-04-21T15:37:07.740\",\n" + + "\t\t\t\"id\": \"i6NTSuDsk6l\",\n" + + "\t\t\t\"shape\": \"NORMAL\",\n" + + "\t\t\t\"type\": \"MAP\",\n" + + "\t\t\t\"map\": {\n" + + "\t\t\t\t\"lastUpdated\": \"2016-10-10T18:16:43.265\",\n" + + "\t\t\t\t\"created\": \"2016-10-10T18:16:43.261\",\n" + + "\t\t\t\t\"name\": \"ANC: IPT 2 Coverage this year\",\n" + + "\t\t\t\t\"id\": \"voX07ulo2Bq\",\n" + + "\t\t\t\t\"displayName\": \"ANC: IPT 2 Coverage this year\"\n" + + "\t\t\t}\n" + + "\t\t}, {\n" + + "\t\t\t\"created\": \"2015-01-15T16:50:51.427\",\n" + + "\t\t\t\"lastUpdated\": \"2015-08-09T22:10:20.307\",\n" + + "\t\t\t\"id\": \"YZ7U25Japom\",\n" + + "\t\t\t\"shape\": \"DOUBLE_WIDTH\",\n" + + "\t\t\t\"type\": \"CHART\",\n" + + "\t\t\t\"chart\": {\n" + + "\t\t\t\t\"lastUpdated\": \"2015-07-15T15:25:20.004\",\n" + + "\t\t\t\t\"created\": \"2015-01-15T16:50:34.302\",\n" + + "\t\t\t\t\"name\": \"ANC: ANC 1 coverage western chiefdoms this year\",\n" + + "\t\t\t\t\"id\": \"zKl0LcQyxPl\",\n" + + "\t\t\t\t\"displayName\": \"ANC: ANC 1 coverage western chiefdoms this year\"\n" + + "\t\t\t}\n" + + "\t\t}, {\n" + + "\t\t\t\"created\": \"2016-08-02T11:57:59.474\",\n" + + "\t\t\t\"lastUpdated\": \"2016-10-10T17:11:06.823\",\n" + + "\t\t\t\"id\": \"UQeYhQOJ2f1\",\n" + + "\t\t\t\"shape\": \"DOUBLE_WIDTH\",\n" + + "\t\t\t\"type\": \"CHART\",\n" + + "\t\t\t\"chart\": {\n" + + "\t\t\t\t\"lastUpdated\": \"2016-08-02T11:58:28.517\",\n" + + "\t\t\t\t\"created\": \"2016-08-02T11:53:53.607\",\n" + + "\t\t\t\t\"name\": \"ANC: IPT 1 Coverage by districts last 4 quarters\",\n" + + "\t\t\t\t\"id\": \"DHPu0vtZ2mW\",\n" + + "\t\t\t\t\"displayName\": \"ANC: IPT 1 Coverage by districts last 4 quarters\"\n" + + "\t\t\t}\n" + + "\t\t}, {\n" + + "\t\t\t\"created\": \"2014-04-03T14:11:10.942\",\n" + + "\t\t\t\"lastUpdated\": \"2016-10-11T09:29:40.874\",\n" + + "\t\t\t\"id\": \"xS4X0ZL6GCI\",\n" + + "\t\t\t\"shape\": \"DOUBLE_WIDTH\",\n" + + "\t\t\t\"type\": \"CHART\",\n" + + "\t\t\t\"chart\": {\n" + + "\t\t\t\t\"lastUpdated\": \"2015-07-15T15:25:20.312\",\n" + + "\t\t\t\t\"created\": \"2014-04-03T14:07:29.442\",\n" + + "\t\t\t\t\"name\": \"ANC: Fixed vs Outreach last year\",\n" + + "\t\t\t\t\"id\": \"AVZpYsdG44G\",\n" + + "\t\t\t\t\"displayName\": \"ANC: Fixed vs Outreach last year\"\n" + + "\t\t\t}\n" + + "\t\t}, {\n" + + "\t\t\t\"created\": \"2016-10-11T19:24:21.931\",\n" + + "\t\t\t\"lastUpdated\": \"2016-10-11T19:24:21.931\",\n" + + "\t\t\t\"id\": \"ZF9vWMXob7N\",\n" + + "\t\t\t\"shape\": \"NORMAL\",\n" + + "\t\t\t\"type\": \"MAP\",\n" + + "\t\t\t\"map\": {\n" + + "\t\t\t\t\"lastUpdated\": \"2016-10-11T19:23:49.953\",\n" + + "\t\t\t\t\"created\": \"2016-10-11T19:23:49.952\",\n" + + "\t\t\t\t\"name\": \"ANC: ANC 1 coverage Sierra Leone dark basemap\",\n" + + "\t\t\t\t\"id\": \"qTfO4YkQ9xW\",\n" + + "\t\t\t\t\"displayName\": \"ANC: ANC 1 coverage Sierra Leone dark basemap\"\n" + + "\t\t\t}\n" + + "\t\t}, {\n" + + "\t\t\t\"created\": \"2014-04-03T14:09:47.075\",\n" + + "\t\t\t\"lastUpdated\": \"2016-10-11T09:19:35.442\",\n" + + "\t\t\t\"id\": \"kHRSFUr3dYe\",\n" + + "\t\t\t\"shape\": \"NORMAL\",\n" + + "\t\t\t\"type\": \"CHART\",\n" + + "\t\t\t\"chart\": {\n" + + "\t\t\t\t\"lastUpdated\": \"2015-07-15T15:25:20.315\",\n" + + "\t\t\t\t\"created\": \"2014-04-03T14:09:05.734\",\n" + + "\t\t\t\t\"name\": \"ANC: 4+ visits by Facility Type last year\",\n" + + "\t\t\t\t\"id\": \"ZfQMIA4o2s3\",\n" + + "\t\t\t\t\"displayName\": \"ANC: 4+ visits by Facility Type last year\"\n" + + "\t\t\t}\n" + + "\t\t}]\n" + + "\t}]"; + + @Test + public void dashboard_shouldMapFromJsonString() throws IOException { + Dashboard dashboard = getDashboardsFromJson(); + assertTrue(dashboard.getState().equals(State.SYNCED)); + assertTrue(dashboard.getUId().equals("nghVC4wtyzi")); + assertTrue(dashboard.getName().equals("Antenatal Care")); + assertTrue(dashboard.getDisplayName().equals("Antenatal Care")); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(dashboard.getCreated(), + "2013-09-08T21:47:17.960")); + assertTrue(dashboard.getDashboardItems().size() == 9); + } + + @Test + public void dashboard_access_shouldMapFromJsonString() throws IOException { + Dashboard dashboard = getDashboardsFromJson(); + Access access = dashboard.getAccess(); + assertTrue(access.isManage() == true); + assertTrue(access.isExternalize() == true); + assertTrue(access.isWrite() == true); + assertTrue(access.isRead() == true); + assertTrue(access.isUpdate() == true); + assertTrue(access.isDelete() == true); + } + + @Test + public void dashboard_item_type_chart_shouldMapFromJsonString() throws IOException { + DashboardItem dashboardItem = getDashboardItemWithChart(); + assertTrue(dashboardItem.getName() == null); + assertTrue(dashboardItem.getDisplayName() == null); + assertTrue(dashboardItem.getState() == State.SYNCED); + assertTrue(dashboardItem.getEventChart() == null); + assertTrue(dashboardItem.getEventReport() == null); + assertTrue(dashboardItem.getUsers() == null); + assertTrue(dashboardItem.getReports() == null); + assertTrue(dashboardItem.getResources() == null); + assertTrue(dashboardItem.isMessages() == false); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(dashboardItem.getCreated(), + "2016-10-10T17:24:30.487")); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(dashboardItem.getLastUpdated(), + "2016-10-10T17:24:30.487")); + assertTrue(dashboardItem.getUId().equals("cX2przhv9UC")); + assertTrue(dashboardItem.getShape().equals("NORMAL")); + assertTrue(dashboardItem.getType().equals("CHART")); + assertTrue(dashboardItem.getAccess() == null); + + DashboardElement dashboardElement = dashboardItem.getChart(); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(dashboardElement.getLastUpdated(), + "2016-10-10T17:24:49.196")); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(dashboardElement.getCreated(), + "2016-10-10T17:24:49.196")); + assertTrue(dashboardElement.getName().equals( + "ANC: ANC IPT 1 Coverage last 12 months districts")); + assertTrue(dashboardElement.getUId().equals("VffWmdKFHSq")); + assertTrue(dashboardElement.getDisplayName().equals( + "ANC: ANC IPT 1 Coverage last 12 months districts")); + } + + @Test + public void dashboard_item_type_map_shouldMapFromJsonString() throws IOException { + DashboardItem dashboardItem = getDashboardItemWithMap(); + assertTrue(dashboardItem.getName() == null); + assertTrue(dashboardItem.getDisplayName() == null); + assertTrue(dashboardItem.getState() == State.SYNCED); + assertTrue(dashboardItem.getEventChart() == null); + assertTrue(dashboardItem.getEventReport() == null); + assertTrue(dashboardItem.getUsers() == null); + assertTrue(dashboardItem.getReports() == null); + assertTrue(dashboardItem.getResources() == null); + assertTrue(dashboardItem.isMessages() == false); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(dashboardItem.getCreated(), + "2015-01-16T11:52:44.928")); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(dashboardItem.getLastUpdated(), + "2015-01-16T11:52:44.928")); + assertTrue(dashboardItem.getUId().equals("OiyMNoXzSdY")); + assertTrue(dashboardItem.getShape().equals("NORMAL")); + assertTrue(dashboardItem.getType().equals("MAP")); + assertTrue(dashboardItem.getAccess() == null); + DashboardElement dashboardElement = dashboardItem.getMap(); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(dashboardElement.getLastUpdated(), + "2013-09-09T16:35:12.062")); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(dashboardElement.getCreated(), + "2012-11-14T12:56:59.322")); + assertTrue(dashboardElement.getName().equals("ANC: LLITN coverage district and facility")); + assertTrue(dashboardElement.getUId().equals("ZBjCfSaLSqD")); + assertTrue(dashboardElement.getDisplayName().equals( + "ANC: LLITN coverage district and facility")); + } + + private DashboardItem getDashboardItemWithChart() throws IOException { + Dashboard dashboard = getDashboardsFromJson(); + List dashboardItems = dashboard.getDashboardItems(); + return dashboardItems.get(0); + } + + private DashboardItem getDashboardItemWithMap() throws IOException { + Dashboard dashboard = getDashboardsFromJson(); + List dashboardItems = dashboard.getDashboardItems(); + return dashboardItems.get(2); + } + + private Dashboard getDashboardsFromJson() throws IOException { + Dashboard[] dashboards = (Dashboard[]) JsonParser.getModelFromJson(Dashboard[].class, + DASHBOARD_JSON); + return dashboards[0]; + } + +} diff --git a/api/src/test/java/org/hisp/dhis/android/core/interpretation/InterpretationTests.java b/api/src/test/java/org/hisp/dhis/android/core/interpretation/InterpretationTests.java new file mode 100644 index 00000000..2b4e25c1 --- /dev/null +++ b/api/src/test/java/org/hisp/dhis/android/core/interpretation/InterpretationTests.java @@ -0,0 +1,238 @@ +package org.hisp.dhis.android.core.interpretation; + +import static org.junit.Assert.assertTrue; + +import org.hisp.dhis.android.core.commons.DateTestUtils; +import org.hisp.dhis.android.core.commons.JsonParser; +import org.hisp.dhis.android.dashboard.api.models.Access; +import org.hisp.dhis.android.dashboard.api.models.Interpretation; +import org.hisp.dhis.android.dashboard.api.models.InterpretationComment; +import org.hisp.dhis.android.dashboard.api.models.InterpretationElement; +import org.hisp.dhis.android.dashboard.api.models.User; +import org.junit.Test; + +import java.io.IOException; + +public class InterpretationTests { + + private static final String INTERPRETATION_JSON = "{\n" + + "\n" + + " \"created\": \"2017-10-21T10:10:43.451\",\n" + + " \"lastUpdated\": \"2017-10-21T10:10:43.451\",\n" + + " \"name\": \"BR11Oy1Q4yR\",\n" + + " \"id\": \"BR11Oy1Q4yR\",\n" + + " \"displayName\": \"BR11Oy1Q4yR\",\n" + + " \"type\": \"CHART\",\n" + + " \"text\": \"This chart shows that BCG doses is low for 2014, why is that?\",\n" + + " \"access\": {\n" + + " \"read\": true,\n" + + " \"update\": true,\n" + + " \"externalize\": false,\n" + + " \"delete\": true,\n" + + " \"write\": true,\n" + + " \"manage\": true\n" + + " },\n" + + " \"chart\": {\n" + + " \"lastUpdated\": \"2015-07-15T15:25:20.264\",\n" + + " \"created\": \"2013-05-29T12:52:54.560\",\n" + + " \"name\": \"Immunization: BCG, Measles, YF doses comparison\",\n" + + " \"id\": \"R9A0rvAydpn\",\n" + + " \"displayName\": \"Immunization: BCG, Measles, YF doses comparison\"\n" + + " },\n" + + " \"user\": {\n" + + " \"lastUpdated\": \"2017-01-19T14:24:04.447\",\n" + + " \"created\": \"2013-04-18T17:15:08.407\",\n" + + " \"name\": \"John Traore\",\n" + + " \"id\": \"xE7jOejl9FI\",\n" + + " \"displayName\": \"John Traore\"\n" + + " },\n" + + " \"comments\": [\n" + + " {\n" + + " \"lastUpdated\": \"2014-10-21T10:11:19.537\",\n" + + " \"created\": \"2014-10-21T10:11:19.537\",\n" + + " \"id\": \"Eg7x5Kt2XgV\",\n" + + " \"text\": \"It might be caused by a stock-out of vaccines.\",\n" + + " \"user\": {\n" + + " \"lastUpdated\": \"2017-01-19T14:24:04.447\",\n" + + " \"created\": \"2013-04-18T17:15:08.407\",\n" + + " \"name\": \"John Traore\",\n" + + " \"id\": \"xE7jOejl9FI\",\n" + + " \"displayName\": \"John Traore\"\n" + + " }\n" + + " },\n" + + " {\n" + + " \"lastUpdated\": \"2014-10-21T10:11:44.325\",\n" + + " \"created\": \"2014-10-21T10:11:44.325\",\n" + + " \"id\": \"oRmqfmnCLsQ\",\n" + + " \"text\": \"Yes I believe so\",\n" + + " \"user\": {\n" + + " \"lastUpdated\": \"2017-01-19T14:24:04.447\",\n" + + " \"created\": \"2013-04-18T17:15:08.407\",\n" + + " \"name\": \"John Traore\",\n" + + " \"id\": \"xE7jOejl9FI\",\n" + + " \"displayName\": \"John Traore\"\n" + + " }\n" + + " }\n" + + " ]\n" + + "\n" + + "}"; + + @Test + public void interpretation_shouldMapFromJsonString() throws IOException { + Interpretation interpretation = getInterpretationFromJson(); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(interpretation.getCreated(), + "2017-10-21T10:10:43.451")); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(interpretation.getLastUpdated(), + "2017-10-21T10:10:43.451")); + assertTrue(interpretation.getName().equals("BR11Oy1Q4yR")); + assertTrue(interpretation.getUId().equals("BR11Oy1Q4yR")); + assertTrue(interpretation.getDisplayName().equals("BR11Oy1Q4yR")); + assertTrue(interpretation.getType().equals("CHART")); + assertTrue(interpretation.getText().equals( + "This chart shows that BCG doses is low for 2014, why is that?")); + Access access = getAccessObject(); + assertTrue(interpretation.getAccess().isDelete() == access.isDelete()); + assertTrue(interpretation.getAccess().isExternalize() == access.isExternalize()); + assertTrue(interpretation.getAccess().isManage() == access.isManage()); + assertTrue(interpretation.getAccess().isRead() == access.isRead()); + assertTrue(interpretation.getAccess().isWrite() == access.isWrite()); + } + + @Test + public void interpretation_chart_shouldMapFromJsonString() throws IOException { + Interpretation interpretation = getInterpretationFromJson(); + InterpretationElement interpretationElement = interpretation.getChart(); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(interpretationElement + .getCreated(), "2013-05-29T12:52:54.560")); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(interpretationElement + .getLastUpdated(), "2015-07-15T15:25:20.264")); + assertTrue(interpretationElement.getName().equals( + "Immunization: BCG, Measles, YF doses comparison")); + assertTrue(interpretationElement.getUId().equals("R9A0rvAydpn")); + assertTrue(interpretationElement.getDisplayName().equals( + "Immunization: BCG, Measles, YF doses comparison")); + assertTrue(interpretationElement.getType() == null); + assertTrue(interpretationElement.getAccess() == null); + } + + @Test + public void interpretation_user_shouldMapFromJsonString() throws IOException { + Interpretation interpretation = getInterpretationFromJson(); + User user = interpretation.getUser(); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(user + .getCreated(), "2013-04-18T17:15:08.407")); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(user + .getLastUpdated(), "2017-01-19T14:24:04.447")); + assertTrue(user.getName().equals( + "John Traore")); + assertTrue(user.getUId().equals("xE7jOejl9FI")); + assertTrue(user.getDisplayName().equals( + "John Traore")); + } + + @Test + public void interpretation_first_comment_shouldMapFromJsonString() throws IOException { + InterpretationComment interpretationComment = getFirstCommentFromInterpretationJson(); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(interpretationComment.getCreated(), + "2014-10-21T10:11:19.537")); + assertTrue(DateTestUtils.compareParsedDateWithStringDate( + interpretationComment.getLastUpdated(), + "2014-10-21T10:11:19.537")); + assertTrue(interpretationComment.getUId().equals("Eg7x5Kt2XgV")); + assertTrue(interpretationComment.getText().equals( + "It might be caused by a stock-out of vaccines.")); + } + + @Test + public void interpretation_second_comment_shouldMapFromJsonString() throws IOException { + InterpretationComment interpretationComment = getSecondCommentFromInterpretationJson(); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(interpretationComment.getCreated(), + "2014-10-21T10:11:44.325")); + assertTrue(DateTestUtils.compareParsedDateWithStringDate( + interpretationComment.getLastUpdated(), + "2014-10-21T10:11:44.325")); + assertTrue(interpretationComment.getUId().equals("oRmqfmnCLsQ")); + assertTrue(interpretationComment.getText().equals("Yes I believe so")); + } + + @Test + public void interpretation_comments_first_user_shouldMapFromJsonString() throws IOException { + User user = getUserFromInterpretationJsonFirstComment(); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(user.getCreated(), + "2013-04-18T17:15:08.407")); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(user.getLastUpdated(), + "2017-01-19T14:24:04.447")); + assertTrue(user.getName().equals("John Traore")); + assertTrue(user.getUId().equals("xE7jOejl9FI")); + assertTrue(user.getDisplayName().equals("John Traore")); + + assertTrue(DateTestUtils.compareParsedDateWithStringDate(user.getCreated(), + "2013-04-18T17:15:08.407")); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(user.getLastUpdated(), + "2017-01-19T14:24:04.447")); + assertTrue(user.getName().equals("John Traore")); + assertTrue(user.getUId().equals("xE7jOejl9FI")); + assertTrue(user.getDisplayName().equals("John Traore")); + } + + @Test + public void interpretation_comments_second_user_shouldMapFromJsonString() throws IOException { + User user = getUserFromInterpretationJsonSecondComment(); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(user.getCreated(), + "2013-04-18T17:15:08.407")); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(user.getLastUpdated(), + "2017-01-19T14:24:04.447")); + assertTrue(user.getName().equals("John Traore")); + assertTrue(user.getUId().equals("xE7jOejl9FI")); + assertTrue(user.getDisplayName().equals("John Traore")); + + assertTrue(DateTestUtils.compareParsedDateWithStringDate(user.getCreated(), + "2013-04-18T17:15:08.407")); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(user.getLastUpdated(), + "2017-01-19T14:24:04.447")); + assertTrue(user.getName().equals("John Traore")); + assertTrue(user.getUId().equals("xE7jOejl9FI")); + assertTrue(user.getDisplayName().equals("John Traore")); + } + + private User getUserFromInterpretationJsonSecondComment() throws IOException { + Interpretation interpretation = getInterpretationFromJson(); + InterpretationComment interpretationComment = interpretation.getComments().get(1); + return interpretationComment.getUser(); + } + + private InterpretationComment getFirstCommentFromInterpretationJson() throws IOException { + return getInterpretationComment(0); + } + + private InterpretationComment getSecondCommentFromInterpretationJson() throws IOException { + return getInterpretationComment(1); + } + + private InterpretationComment getInterpretationComment(int index) throws IOException { + Interpretation interpretation = getInterpretationFromJson(); + return interpretation.getComments().get(index); + } + + private User getUserFromInterpretationJsonFirstComment() throws IOException { + InterpretationComment interpretationComment = + getFirstCommentFromInterpretationJson(); + return interpretationComment.getUser(); + } + + private Interpretation getInterpretationFromJson() throws IOException { + return (Interpretation) JsonParser.getModelFromJson(Interpretation.class, + INTERPRETATION_JSON); + } + + private Access getAccessObject() { + Access access = new Access(); + access.setDelete(true); + access.setExternalize(false); + access.setManage(true); + access.setRead(true); + access.setUpdate(true); + access.setWrite(true); + return access; + } +} diff --git a/api/src/test/java/org/hisp/dhis/android/core/meta/CredentialsTests.java b/api/src/test/java/org/hisp/dhis/android/core/meta/CredentialsTests.java new file mode 100644 index 00000000..5622f79a --- /dev/null +++ b/api/src/test/java/org/hisp/dhis/android/core/meta/CredentialsTests.java @@ -0,0 +1,27 @@ +package org.hisp.dhis.android.core.meta; + +import org.hisp.dhis.android.dashboard.api.models.meta.Credentials; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +public class CredentialsTests { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void throw_exception_if_username_is_not_provided() throws Exception { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Username must not be null"); + + new Credentials(null, "pwd"); + } + + @Test + public void throw_exception_if_password_is_not_provided() throws Exception { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("Password must not be null"); + + new Credentials("user", null); + } +} diff --git a/api/src/test/java/org/hisp/dhis/android/core/network/ApiExceptionTests.java b/api/src/test/java/org/hisp/dhis/android/core/network/ApiExceptionTests.java new file mode 100644 index 00000000..21ae331f --- /dev/null +++ b/api/src/test/java/org/hisp/dhis/android/core/network/ApiExceptionTests.java @@ -0,0 +1,75 @@ +package org.hisp.dhis.android.core.network; + +import static org.junit.Assert.assertTrue; + +import org.hisp.dhis.android.dashboard.api.network.APIException; +import org.junit.Test; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import retrofit.RetrofitError; +import retrofit.client.Header; +import retrofit.client.Response; +import retrofit.converter.ConversionException; +import retrofit.converter.Converter; + +public class ApiExceptionTests { + + Response response; + Converter converter; + java.lang.reflect.Type type; + + @Test + public void retrofit_network_exception_map_to_api_exception() { + APIException apiException = getNetworkExceptionFromRetrofit(); + assertTrue(apiException.getKind().equals(APIException.Kind.NETWORK)); + assertTrue(apiException.getUrl().equals("test_message")); + } + + @Test + public void retrofit_conversion_exception_map_to_api_exception() { + APIException apiException = APIException.fromRetrofitError( + getConversionExceptionFromRetrofit()); + assertTrue(apiException.getKind().equals(APIException.Kind.CONVERSION)); + assertTrue(apiException.getUrl().equals("test_message")); + } + + @Test + public void retrofit_unexpected_exception_map_to_api_exception() { + APIException apiException = getUnexpectedExceptionFromRetrofit(); + assertTrue(apiException.getKind().equals(APIException.Kind.UNEXPECTED)); + assertTrue(apiException.getUrl().equals("test_message")); + } + + @Test + public void retrofit_http_error_exception_map_to_api_exception() { + APIException apiException = getHttpErrorExceptionFromRetrofit(); + assertTrue(apiException.getKind().equals(APIException.Kind.HTTP)); + assertTrue(apiException.getUrl().equals("test_message")); + } + + + private APIException getNetworkExceptionFromRetrofit() { + return APIException.fromRetrofitError( + RetrofitError.networkError("test_message", new IOException())); + } + + private RetrofitError getConversionExceptionFromRetrofit() { + return RetrofitError.conversionError("test_message", response, converter, + type, new ConversionException("test_message")); + } + + private APIException getUnexpectedExceptionFromRetrofit() { + return APIException.fromRetrofitError( + RetrofitError.unexpectedError("test_message", new IOException())); + } + + private APIException getHttpErrorExceptionFromRetrofit() { + List
headerList = new ArrayList<>(); + Response response = new Response("test_url", 404, "Not found", headerList, null); + return APIException.fromRetrofitError( + RetrofitError.httpError("test_message", response, converter, type)); + } +} \ No newline at end of file diff --git a/api/src/test/java/org/hisp/dhis/android/core/systeminfo/SystemInfoTests.java b/api/src/test/java/org/hisp/dhis/android/core/systeminfo/SystemInfoTests.java new file mode 100644 index 00000000..909d520c --- /dev/null +++ b/api/src/test/java/org/hisp/dhis/android/core/systeminfo/SystemInfoTests.java @@ -0,0 +1,60 @@ +package org.hisp.dhis.android.core.systeminfo; + +import static org.junit.Assert.assertTrue; + +import org.hisp.dhis.android.core.commons.DateTestUtils; +import org.hisp.dhis.android.core.commons.JsonParser; +import org.hisp.dhis.android.dashboard.api.models.SystemInfo; +import org.junit.Test; + +import java.io.IOException; +import java.text.ParseException; + +public class SystemInfoTests { + private static final String SYSTEM_INFO_JSON = "{\n" + + "\n" + + " \"contextPath\": \"https://play.dhis2.org/demo\",\n" + + " \"userAgent\": \"Mozilla/5.0 (Windows NT 10.0; WOW64; rv:53.0) Gecko/20100101 " + + "Firefox/53.0\",\n" + + " \"calendar\": \"iso8601\",\n" + + " \"dateFormat\": \"yyyy-mm-dd\",\n" + + " \"serverDate\": \"2017-05-04T16:34:45.957\",\n" + + " \"lastAnalyticsTableSuccess\": \"2017-01-26T23:19:34.009\",\n" + + " \"intervalSinceLastAnalyticsTableSuccess\": \"2345 h, 15 m, 11 s\",\n" + + " \"lastAnalyticsTableRuntime\": \"5 m, 17 s\",\n" + + " \"version\": \"2.26\",\n" + + " \"revision\": \"f297d4c\",\n" + + " \"buildTime\": \"2017-05-04T06:37:32.000\",\n" + + " \"jasperReportsVersion\": \"6.3.1\",\n" + + " \"environmentVariable\": \"DHIS2_HOME\",\n" + + " \"readOnlyMode\": \"off\",\n" + + " \"databaseInfo\": {\n" + + " \"type\": \"PostgreSQL\",\n" + + " \"spatialSupport\": true\n" + + " },\n" + + " \"encryption\": false,\n" + + " \"isMetadataVersionEnabled\": true,\n" + + " \"isMetadataSyncEnabled\": false\n" + + "\n" + + "}"; + + @Test + public void systemInfo_should_map_from_json_String() throws IOException, ParseException { + + SystemInfo systemInfo = (SystemInfo) JsonParser.getModelFromJson(SystemInfo.class, + SYSTEM_INFO_JSON); + + assertTrue(systemInfo.getVersion().equals("2.26")); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(systemInfo.getBuildTime(), + "2017-05-04T06:37:32.000")); + assertTrue(systemInfo.getCalendar().equals("iso8601")); + assertTrue(systemInfo.getIntervalSinceLastAnalyticsTableSuccess().equals("2345 h, 15 m, 11 s")); + assertTrue(systemInfo.getLastAnalyticsTableSuccess().equals("2017-01-26T23:19:34.009")); + assertTrue(systemInfo.getRevision().equals("f297d4c")); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(systemInfo.getServerDate(), + "2017-05-04T16:34:45.957")); + assertTrue(systemInfo.getDateFormat().equals("yyyy-mm-dd")); + } + + +} diff --git a/api/src/test/java/org/hisp/dhis/android/core/user/UserTests.java b/api/src/test/java/org/hisp/dhis/android/core/user/UserTests.java new file mode 100644 index 00000000..be904603 --- /dev/null +++ b/api/src/test/java/org/hisp/dhis/android/core/user/UserTests.java @@ -0,0 +1,81 @@ +package org.hisp.dhis.android.core.user; + +import static org.junit.Assert.assertTrue; + +import org.hisp.dhis.android.core.commons.DateTestUtils; +import org.hisp.dhis.android.core.commons.JsonParser; +import org.hisp.dhis.android.dashboard.api.models.User; +import org.hisp.dhis.android.dashboard.api.models.UserAccount; +import org.hisp.dhis.android.dashboard.api.models.meta.State; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.io.IOException; + +public class UserTests { + + public static final String USER_ACCOUNT_JSON = "{\"created\":\"2013-04-18T17:15:08.407\"," + + "\"lastUpdated\":\"2017-05-02T17:02:37.817\"," + + "\"name\":\"John Traore\"," + + "\"id\":\"xE7jOejl9FI\"," + + "\"birthday\":\"1971-04-08T00:00:00.000\"," + + "\"education\":\"Master of super using\"," + + "\"gender\":\"gender_male\"," + + "\"languages\":\"English\"," + + "\"displayName\":\"John Traore\"," + + "\"jobTitle\":\"Super user\"," + + "\"firstName\":\"John\"," + + "\"surname\":\"Traore\"," + + "\"employer\":\"DHIS\"," + + "\"interests\":\"Football, swimming, singing, dancing\"," + + "\"introduction\":\"I am the super user of DHIS 2\"," + + "\"email\":\"someone@dhis2.org\"," + + "\"organisationUnits\":[{\"id\":\"ImspTQPwCqd\"}]}"; + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void userAccount_should_map_to_user() throws IOException { + UserAccount userAccount = getUserAccountFromJson(); + User user = UserAccount.toUser(userAccount); + assertTrue(user.getUId().equals("xE7jOejl9FI")); + assertTrue(user.getName().equals("John Traore")); + assertTrue(user.getDisplayName().equals("John Traore")); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(user.getCreated(), + "2013-04-18T17:15:08.407")); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(user.getLastUpdated(), + "2017-05-02T17:02:37.817")); + assertTrue(user.getAccess() == null); + } + + @Test + public void user_should_map_from_json_string() throws IOException { + UserAccount userAccount = getUserAccountFromJson(); + assertTrue(userAccount.getUId().equals("xE7jOejl9FI")); + assertTrue(userAccount.getName().equals("John Traore")); + assertTrue(userAccount.getDisplayName().equals("John Traore")); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(userAccount.getCreated(), + "2013-04-18T17:15:08.407")); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(userAccount.getLastUpdated(), + "2017-05-02T17:02:37.817")); + assertTrue(userAccount.getAccess() == null); + assertTrue(userAccount.getState().equals(State.SYNCED)); + assertTrue(userAccount.getFirstName().equals("John")); + assertTrue(userAccount.getSurname().equals("Traore")); + assertTrue(userAccount.getGender().equals("gender_male")); + assertTrue(userAccount.getBirthday().equals("1971-04-08T00:00:00.000")); + assertTrue(userAccount.getIntroduction().equals("I am the super user of DHIS 2")); + assertTrue(userAccount.getEducation().equals("Master of super using")); + assertTrue(userAccount.getEmployer().equals("DHIS")); + assertTrue(userAccount.getInterests().equals("Football, swimming, singing, dancing")); + assertTrue(userAccount.getJobTitle().equals("Super user")); + assertTrue(userAccount.getLanguages().equals("English")); + assertTrue(userAccount.getEmail().equals("someone@dhis2.org")); + assertTrue(userAccount.getPhoneNumber() == null); + } + + private UserAccount getUserAccountFromJson() throws IOException { + return (UserAccount) JsonParser.getModelFromJson(UserAccount.class, USER_ACCOUNT_JSON); + } +} \ No newline at end of file From 07c25aab02d3eb107b0e9c8bcc6b712539d97ff5 Mon Sep 17 00:00:00 2001 From: idelcano Date: Fri, 5 May 2017 12:25:34 +0200 Subject: [PATCH 06/69] Move json from String to resources file --- .../core/api/BasicAuthenticatorTests.java | 117 +++++++++++++++ .../dhis/android/core/commons/FileReader.java | 28 ++++ .../core/converters/AccessConverterTests.java | 22 +-- .../converters/DateTimeConverterTests.java | 1 - .../core/dashboard/DashboardTests.java | 136 +----------------- .../interpretation/InterpretationTests.java | 65 +-------- .../core/systeminfo/SystemInfoTests.java | 30 +--- .../dhis/android/core/user/UserTests.java | 21 +-- .../resources/access_all_false_string.txt | 8 ++ .../test/resources/access_all_true_string.txt | 1 + api/src/test/resources/dashboard.json | 133 +++++++++++++++++ api/src/test/resources/interpretation.json | 59 ++++++++ api/src/test/resources/systeminfo.json | 23 +++ api/src/test/resources/user.json | 21 +++ 14 files changed, 409 insertions(+), 256 deletions(-) create mode 100644 api/src/test/java/org/hisp/dhis/android/core/api/BasicAuthenticatorTests.java create mode 100644 api/src/test/java/org/hisp/dhis/android/core/commons/FileReader.java create mode 100644 api/src/test/resources/access_all_false_string.txt create mode 100644 api/src/test/resources/access_all_true_string.txt create mode 100644 api/src/test/resources/dashboard.json create mode 100644 api/src/test/resources/interpretation.json create mode 100644 api/src/test/resources/systeminfo.json create mode 100644 api/src/test/resources/user.json diff --git a/api/src/test/java/org/hisp/dhis/android/core/api/BasicAuthenticatorTests.java b/api/src/test/java/org/hisp/dhis/android/core/api/BasicAuthenticatorTests.java new file mode 100644 index 00000000..cd7678be --- /dev/null +++ b/api/src/test/java/org/hisp/dhis/android/core/api/BasicAuthenticatorTests.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2017, University of Oslo + * + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.hisp.dhis.android.core.api; + +import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.mockito.Mockito.when; + +import static okhttp3.Credentials.basic; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; + +// ToDo: Solve problem with INFO logs from MockWebServer being interpreted as errors in gradle +@RunWith(JUnit4.class) +public class BasicAuthenticatorTests { +/* + @Mock + private AuthenticatedUserStore authenticatedUserStore; + + private MockWebServer mockWebServer; + private OkHttpClient okHttpClient; + + @Before + public void setUp() throws IOException { + MockitoAnnotations.initMocks(this); + + mockWebServer = new MockWebServer(); + mockWebServer.enqueue(new MockResponse()); + mockWebServer.start(); + + okHttpClient = new OkHttpClient.Builder() + .addInterceptor(new BasicAuthenticator(authenticatedUserStore)) + .build(); + } + + @Test + public void authenticator_shouldAddAuthorizationHeader() throws IOException, InterruptedException { + AuthenticatedUserModel authenticatedUserModel = + AuthenticatedUserModel.builder() + .user("test_user") + .credentials(base64("test_user", "test_password")) + .build(); + + when(authenticatedUserStore.query()).thenReturn(Arrays.asList(authenticatedUserModel)); + + okHttpClient.newCall( + new Request.Builder() + .url(mockWebServer.url("/api/me/")) + .build()) + .execute(); + + RecordedRequest recordedRequest = mockWebServer.takeRequest(); + assertThat(recordedRequest.getHeader("Authorization")) + .isEqualTo(basic("test_user", "test_password")); + } + + @Test + public void authenticator_shouldNotModifyRequestIfNoUsers() throws IOException, InterruptedException { + when(authenticatedUserStore.query()).thenReturn(new ArrayList()); + + okHttpClient.newCall( + new Request.Builder() + .url(mockWebServer.url("/api/me/")) + .build()) + .execute(); + + RecordedRequest recordedRequest = mockWebServer.takeRequest(); + assertThat(recordedRequest.getHeader("Authorization")).isNull(); + } + + @After + public void tearDown() throws IOException { + mockWebServer.shutdown(); + } + */ +} diff --git a/api/src/test/java/org/hisp/dhis/android/core/commons/FileReader.java b/api/src/test/java/org/hisp/dhis/android/core/commons/FileReader.java new file mode 100644 index 00000000..5b53187f --- /dev/null +++ b/api/src/test/java/org/hisp/dhis/android/core/commons/FileReader.java @@ -0,0 +1,28 @@ +package org.hisp.dhis.android.core.commons; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URL; + +public class FileReader { + public File getFile(String filename) { + ClassLoader classLoader = getClass().getClassLoader(); + URL resource = classLoader.getResource(filename); + return new File(resource.getPath()); + } + + public String getStringFromFile(String filename) throws IOException { + FileInputStream inputStream = new FileInputStream(getFile(filename)); + InputStreamReader isr = new InputStreamReader(inputStream); + BufferedReader bufferedReader = new BufferedReader(isr); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = bufferedReader.readLine()) != null) { + sb.append(line); + } + return sb.toString(); + } +} diff --git a/api/src/test/java/org/hisp/dhis/android/core/converters/AccessConverterTests.java b/api/src/test/java/org/hisp/dhis/android/core/converters/AccessConverterTests.java index a7476282..b0a9e7f6 100644 --- a/api/src/test/java/org/hisp/dhis/android/core/converters/AccessConverterTests.java +++ b/api/src/test/java/org/hisp/dhis/android/core/converters/AccessConverterTests.java @@ -2,17 +2,15 @@ import static junit.framework.Assert.assertTrue; +import org.hisp.dhis.android.core.commons.FileReader; import org.hisp.dhis.android.dashboard.api.models.Access; import org.hisp.dhis.android.dashboard.api.persistence.converters.AccessConverter; import org.junit.Before; import org.junit.Test; +import java.io.IOException; + public class AccessConverterTests { - public static final String ACCESS_ALL_TRUE_AS_DATABASE_STRING = "{\"manage\":true," - + "\"externalize\":true,\"write\":true,\"read\":true,\"update\":true,\"delete\":true}"; - public static final String ACCESS_ALL_FALSE_AS_DATABASE_STRING = "{\"manage\":false," - + "\"externalize\":false,\"write\":false,\"read\":false,\"update\":false," - + "\"delete\":false}"; public static Access accessObject; AccessConverter accessConverter = new AccessConverter(); @@ -29,13 +27,14 @@ public void setUp() throws Exception { @Test public void convert_access_object_to_database_string() throws Exception { - assertTrue(accessConverter.getDBValue(accessObject).equals( - ACCESS_ALL_TRUE_AS_DATABASE_STRING)); + String access = new FileReader().getStringFromFile("access_all_true_string.txt"); + assertTrue(accessConverter.getDBValue(accessObject).equals(access)); } @Test - public void convert_access_all_true_database_string_to_model() { - Access access = accessConverter.getModelValue(ACCESS_ALL_TRUE_AS_DATABASE_STRING); + public void convert_access_all_true_database_string_to_model() throws IOException { + Access access = accessConverter.getModelValue( new FileReader().getStringFromFile( + "access_all_true_string.txt")); assertTrue(access.isDelete()); assertTrue(access.isRead()); assertTrue(access.isWrite()); @@ -44,8 +43,9 @@ public void convert_access_all_true_database_string_to_model() { } @Test - public void convert_access_all_false_database_string_to_model() { - Access access = accessConverter.getModelValue(ACCESS_ALL_FALSE_AS_DATABASE_STRING); + public void convert_access_all_false_database_string_to_model() throws IOException { + Access access = accessConverter.getModelValue( new FileReader().getStringFromFile( + "access_all_false_string.txt")); assertTrue(!access.isDelete()); assertTrue(!access.isRead()); assertTrue(!access.isWrite()); diff --git a/api/src/test/java/org/hisp/dhis/android/core/converters/DateTimeConverterTests.java b/api/src/test/java/org/hisp/dhis/android/core/converters/DateTimeConverterTests.java index 09050c6d..89b74161 100644 --- a/api/src/test/java/org/hisp/dhis/android/core/converters/DateTimeConverterTests.java +++ b/api/src/test/java/org/hisp/dhis/android/core/converters/DateTimeConverterTests.java @@ -14,7 +14,6 @@ public class DateTimeConverterTests { public static final String DATETIME_AS_STRING = "2016-04-21T15:37:07.740Z"; - public static final String STATE_TO_POST = "TO_POST"; DateTimeConverter dateTimeConverter = new DateTimeConverter(); diff --git a/api/src/test/java/org/hisp/dhis/android/core/dashboard/DashboardTests.java b/api/src/test/java/org/hisp/dhis/android/core/dashboard/DashboardTests.java index 821f950e..c53c8ffb 100644 --- a/api/src/test/java/org/hisp/dhis/android/core/dashboard/DashboardTests.java +++ b/api/src/test/java/org/hisp/dhis/android/core/dashboard/DashboardTests.java @@ -3,6 +3,7 @@ import static org.junit.Assert.assertTrue; import org.hisp.dhis.android.core.commons.DateTestUtils; +import org.hisp.dhis.android.core.commons.FileReader; import org.hisp.dhis.android.core.commons.JsonParser; import org.hisp.dhis.android.dashboard.api.models.Access; import org.hisp.dhis.android.dashboard.api.models.Dashboard; @@ -15,139 +16,6 @@ import java.util.List; public class DashboardTests { - private final String DASHBOARD_JSON = "[{\n" - + "\t\t\"lastUpdated\": \"2016-10-11T19:24:33.599\",\n" - + "\t\t\"created\": \"2013-09-08T21:47:17.960\",\n" - + "\t\t\"name\": \"Antenatal Care\",\n" - + "\t\t\"id\": \"nghVC4wtyzi\",\n" - + "\t\t\"displayName\": \"Antenatal Care\",\n" - + "\t\t\"access\": {\n" - + "\t\t\t\"read\": true,\n" - + "\t\t\t\"update\": true,\n" - + "\t\t\t\"externalize\": true,\n" - + "\t\t\t\"delete\": true,\n" - + "\t\t\t\"write\": true,\n" - + "\t\t\t\"manage\": true\n" - + "\t\t},\n" - + "\t\t\"dashboardItems\": [{\n" - + "\t\t\t\"created\": \"2016-10-10T17:24:30.487\",\n" - + "\t\t\t\"lastUpdated\": \"2016-10-10T17:24:30.487\",\n" - + "\t\t\t\"id\": \"cX2przhv9UC\",\n" - + "\t\t\t\"shape\": \"NORMAL\",\n" - + "\t\t\t\"type\": \"CHART\",\n" - + "\t\t\t\"chart\": {\n" - + "\t\t\t\t\"lastUpdated\": \"2016-10-10T17:24:49.196\",\n" - + "\t\t\t\t\"created\": \"2016-10-10T17:24:49.196\",\n" - + "\t\t\t\t\"name\": \"ANC: ANC IPT 1 Coverage last 12 months districts\",\n" - + "\t\t\t\t\"id\": \"VffWmdKFHSq\",\n" - + "\t\t\t\t\"displayName\": \"ANC: ANC IPT 1 Coverage last 12 months districts\"\n" - + "\t\t\t}\n" - + "\t\t}, {\n" - + "\t\t\t\"created\": \"2016-08-02T11:57:55.229\",\n" - + "\t\t\t\"lastUpdated\": \"2016-08-02T11:57:55.229\",\n" - + "\t\t\t\"id\": \"JcO7yJlKIa3\",\n" - + "\t\t\t\"shape\": \"NORMAL\",\n" - + "\t\t\t\"type\": \"CHART\",\n" - + "\t\t\t\"chart\": {\n" - + "\t\t\t\t\"lastUpdated\": \"2016-10-10T17:08:15.277\",\n" - + "\t\t\t\t\"created\": \"2016-10-10T17:08:15.277\",\n" - + "\t\t\t\t\"name\": \"ANC: ANC 3 coverage by districts last 4 quarters\",\n" - + "\t\t\t\t\"id\": \"CNkMibmx1Zr\",\n" - + "\t\t\t\t\"displayName\": \"ANC: ANC 3 coverage by districts last 4 quarters\"\n" - + "\t\t\t}\n" - + "\t\t}, {\n" - + "\t\t\t\"created\": \"2015-01-16T11:52:44.928\",\n" - + "\t\t\t\"lastUpdated\": \"2015-01-16T11:52:44.928\",\n" - + "\t\t\t\"id\": \"OiyMNoXzSdY\",\n" - + "\t\t\t\"shape\": \"NORMAL\",\n" - + "\t\t\t\"type\": \"MAP\",\n" - + "\t\t\t\"map\": {\n" - + "\t\t\t\t\"lastUpdated\": \"2013-09-09T16:35:12.062\",\n" - + "\t\t\t\t\"created\": \"2012-11-14T12:56:59.322\",\n" - + "\t\t\t\t\"name\": \"ANC: LLITN coverage district and facility\",\n" - + "\t\t\t\t\"id\": \"ZBjCfSaLSqD\",\n" - + "\t\t\t\t\"displayName\": \"ANC: LLITN coverage district and facility\"\n" - + "\t\t\t}\n" - + "\t\t}, {\n" - + "\t\t\t\"created\": \"2016-04-21T15:37:07.740\",\n" - + "\t\t\t\"lastUpdated\": \"2016-04-21T15:37:07.740\",\n" - + "\t\t\t\"id\": \"i6NTSuDsk6l\",\n" - + "\t\t\t\"shape\": \"NORMAL\",\n" - + "\t\t\t\"type\": \"MAP\",\n" - + "\t\t\t\"map\": {\n" - + "\t\t\t\t\"lastUpdated\": \"2016-10-10T18:16:43.265\",\n" - + "\t\t\t\t\"created\": \"2016-10-10T18:16:43.261\",\n" - + "\t\t\t\t\"name\": \"ANC: IPT 2 Coverage this year\",\n" - + "\t\t\t\t\"id\": \"voX07ulo2Bq\",\n" - + "\t\t\t\t\"displayName\": \"ANC: IPT 2 Coverage this year\"\n" - + "\t\t\t}\n" - + "\t\t}, {\n" - + "\t\t\t\"created\": \"2015-01-15T16:50:51.427\",\n" - + "\t\t\t\"lastUpdated\": \"2015-08-09T22:10:20.307\",\n" - + "\t\t\t\"id\": \"YZ7U25Japom\",\n" - + "\t\t\t\"shape\": \"DOUBLE_WIDTH\",\n" - + "\t\t\t\"type\": \"CHART\",\n" - + "\t\t\t\"chart\": {\n" - + "\t\t\t\t\"lastUpdated\": \"2015-07-15T15:25:20.004\",\n" - + "\t\t\t\t\"created\": \"2015-01-15T16:50:34.302\",\n" - + "\t\t\t\t\"name\": \"ANC: ANC 1 coverage western chiefdoms this year\",\n" - + "\t\t\t\t\"id\": \"zKl0LcQyxPl\",\n" - + "\t\t\t\t\"displayName\": \"ANC: ANC 1 coverage western chiefdoms this year\"\n" - + "\t\t\t}\n" - + "\t\t}, {\n" - + "\t\t\t\"created\": \"2016-08-02T11:57:59.474\",\n" - + "\t\t\t\"lastUpdated\": \"2016-10-10T17:11:06.823\",\n" - + "\t\t\t\"id\": \"UQeYhQOJ2f1\",\n" - + "\t\t\t\"shape\": \"DOUBLE_WIDTH\",\n" - + "\t\t\t\"type\": \"CHART\",\n" - + "\t\t\t\"chart\": {\n" - + "\t\t\t\t\"lastUpdated\": \"2016-08-02T11:58:28.517\",\n" - + "\t\t\t\t\"created\": \"2016-08-02T11:53:53.607\",\n" - + "\t\t\t\t\"name\": \"ANC: IPT 1 Coverage by districts last 4 quarters\",\n" - + "\t\t\t\t\"id\": \"DHPu0vtZ2mW\",\n" - + "\t\t\t\t\"displayName\": \"ANC: IPT 1 Coverage by districts last 4 quarters\"\n" - + "\t\t\t}\n" - + "\t\t}, {\n" - + "\t\t\t\"created\": \"2014-04-03T14:11:10.942\",\n" - + "\t\t\t\"lastUpdated\": \"2016-10-11T09:29:40.874\",\n" - + "\t\t\t\"id\": \"xS4X0ZL6GCI\",\n" - + "\t\t\t\"shape\": \"DOUBLE_WIDTH\",\n" - + "\t\t\t\"type\": \"CHART\",\n" - + "\t\t\t\"chart\": {\n" - + "\t\t\t\t\"lastUpdated\": \"2015-07-15T15:25:20.312\",\n" - + "\t\t\t\t\"created\": \"2014-04-03T14:07:29.442\",\n" - + "\t\t\t\t\"name\": \"ANC: Fixed vs Outreach last year\",\n" - + "\t\t\t\t\"id\": \"AVZpYsdG44G\",\n" - + "\t\t\t\t\"displayName\": \"ANC: Fixed vs Outreach last year\"\n" - + "\t\t\t}\n" - + "\t\t}, {\n" - + "\t\t\t\"created\": \"2016-10-11T19:24:21.931\",\n" - + "\t\t\t\"lastUpdated\": \"2016-10-11T19:24:21.931\",\n" - + "\t\t\t\"id\": \"ZF9vWMXob7N\",\n" - + "\t\t\t\"shape\": \"NORMAL\",\n" - + "\t\t\t\"type\": \"MAP\",\n" - + "\t\t\t\"map\": {\n" - + "\t\t\t\t\"lastUpdated\": \"2016-10-11T19:23:49.953\",\n" - + "\t\t\t\t\"created\": \"2016-10-11T19:23:49.952\",\n" - + "\t\t\t\t\"name\": \"ANC: ANC 1 coverage Sierra Leone dark basemap\",\n" - + "\t\t\t\t\"id\": \"qTfO4YkQ9xW\",\n" - + "\t\t\t\t\"displayName\": \"ANC: ANC 1 coverage Sierra Leone dark basemap\"\n" - + "\t\t\t}\n" - + "\t\t}, {\n" - + "\t\t\t\"created\": \"2014-04-03T14:09:47.075\",\n" - + "\t\t\t\"lastUpdated\": \"2016-10-11T09:19:35.442\",\n" - + "\t\t\t\"id\": \"kHRSFUr3dYe\",\n" - + "\t\t\t\"shape\": \"NORMAL\",\n" - + "\t\t\t\"type\": \"CHART\",\n" - + "\t\t\t\"chart\": {\n" - + "\t\t\t\t\"lastUpdated\": \"2015-07-15T15:25:20.315\",\n" - + "\t\t\t\t\"created\": \"2014-04-03T14:09:05.734\",\n" - + "\t\t\t\t\"name\": \"ANC: 4+ visits by Facility Type last year\",\n" - + "\t\t\t\t\"id\": \"ZfQMIA4o2s3\",\n" - + "\t\t\t\t\"displayName\": \"ANC: 4+ visits by Facility Type last year\"\n" - + "\t\t\t}\n" - + "\t\t}]\n" - + "\t}]"; @Test public void dashboard_shouldMapFromJsonString() throws IOException { @@ -251,7 +119,7 @@ private DashboardItem getDashboardItemWithMap() throws IOException { private Dashboard getDashboardsFromJson() throws IOException { Dashboard[] dashboards = (Dashboard[]) JsonParser.getModelFromJson(Dashboard[].class, - DASHBOARD_JSON); + new FileReader().getStringFromFile("dashboard.json")); return dashboards[0]; } diff --git a/api/src/test/java/org/hisp/dhis/android/core/interpretation/InterpretationTests.java b/api/src/test/java/org/hisp/dhis/android/core/interpretation/InterpretationTests.java index 2b4e25c1..5b8446c6 100644 --- a/api/src/test/java/org/hisp/dhis/android/core/interpretation/InterpretationTests.java +++ b/api/src/test/java/org/hisp/dhis/android/core/interpretation/InterpretationTests.java @@ -3,6 +3,7 @@ import static org.junit.Assert.assertTrue; import org.hisp.dhis.android.core.commons.DateTestUtils; +import org.hisp.dhis.android.core.commons.FileReader; import org.hisp.dhis.android.core.commons.JsonParser; import org.hisp.dhis.android.dashboard.api.models.Access; import org.hisp.dhis.android.dashboard.api.models.Interpretation; @@ -15,68 +16,6 @@ public class InterpretationTests { - private static final String INTERPRETATION_JSON = "{\n" - + "\n" - + " \"created\": \"2017-10-21T10:10:43.451\",\n" - + " \"lastUpdated\": \"2017-10-21T10:10:43.451\",\n" - + " \"name\": \"BR11Oy1Q4yR\",\n" - + " \"id\": \"BR11Oy1Q4yR\",\n" - + " \"displayName\": \"BR11Oy1Q4yR\",\n" - + " \"type\": \"CHART\",\n" - + " \"text\": \"This chart shows that BCG doses is low for 2014, why is that?\",\n" - + " \"access\": {\n" - + " \"read\": true,\n" - + " \"update\": true,\n" - + " \"externalize\": false,\n" - + " \"delete\": true,\n" - + " \"write\": true,\n" - + " \"manage\": true\n" - + " },\n" - + " \"chart\": {\n" - + " \"lastUpdated\": \"2015-07-15T15:25:20.264\",\n" - + " \"created\": \"2013-05-29T12:52:54.560\",\n" - + " \"name\": \"Immunization: BCG, Measles, YF doses comparison\",\n" - + " \"id\": \"R9A0rvAydpn\",\n" - + " \"displayName\": \"Immunization: BCG, Measles, YF doses comparison\"\n" - + " },\n" - + " \"user\": {\n" - + " \"lastUpdated\": \"2017-01-19T14:24:04.447\",\n" - + " \"created\": \"2013-04-18T17:15:08.407\",\n" - + " \"name\": \"John Traore\",\n" - + " \"id\": \"xE7jOejl9FI\",\n" - + " \"displayName\": \"John Traore\"\n" - + " },\n" - + " \"comments\": [\n" - + " {\n" - + " \"lastUpdated\": \"2014-10-21T10:11:19.537\",\n" - + " \"created\": \"2014-10-21T10:11:19.537\",\n" - + " \"id\": \"Eg7x5Kt2XgV\",\n" - + " \"text\": \"It might be caused by a stock-out of vaccines.\",\n" - + " \"user\": {\n" - + " \"lastUpdated\": \"2017-01-19T14:24:04.447\",\n" - + " \"created\": \"2013-04-18T17:15:08.407\",\n" - + " \"name\": \"John Traore\",\n" - + " \"id\": \"xE7jOejl9FI\",\n" - + " \"displayName\": \"John Traore\"\n" - + " }\n" - + " },\n" - + " {\n" - + " \"lastUpdated\": \"2014-10-21T10:11:44.325\",\n" - + " \"created\": \"2014-10-21T10:11:44.325\",\n" - + " \"id\": \"oRmqfmnCLsQ\",\n" - + " \"text\": \"Yes I believe so\",\n" - + " \"user\": {\n" - + " \"lastUpdated\": \"2017-01-19T14:24:04.447\",\n" - + " \"created\": \"2013-04-18T17:15:08.407\",\n" - + " \"name\": \"John Traore\",\n" - + " \"id\": \"xE7jOejl9FI\",\n" - + " \"displayName\": \"John Traore\"\n" - + " }\n" - + " }\n" - + " ]\n" - + "\n" - + "}"; - @Test public void interpretation_shouldMapFromJsonString() throws IOException { Interpretation interpretation = getInterpretationFromJson(); @@ -222,7 +161,7 @@ private User getUserFromInterpretationJsonFirstComment() throws IOException { private Interpretation getInterpretationFromJson() throws IOException { return (Interpretation) JsonParser.getModelFromJson(Interpretation.class, - INTERPRETATION_JSON); + new FileReader().getStringFromFile("interpretation.json")); } private Access getAccessObject() { diff --git a/api/src/test/java/org/hisp/dhis/android/core/systeminfo/SystemInfoTests.java b/api/src/test/java/org/hisp/dhis/android/core/systeminfo/SystemInfoTests.java index 909d520c..ed19ac70 100644 --- a/api/src/test/java/org/hisp/dhis/android/core/systeminfo/SystemInfoTests.java +++ b/api/src/test/java/org/hisp/dhis/android/core/systeminfo/SystemInfoTests.java @@ -3,6 +3,7 @@ import static org.junit.Assert.assertTrue; import org.hisp.dhis.android.core.commons.DateTestUtils; +import org.hisp.dhis.android.core.commons.FileReader; import org.hisp.dhis.android.core.commons.JsonParser; import org.hisp.dhis.android.dashboard.api.models.SystemInfo; import org.junit.Test; @@ -11,38 +12,11 @@ import java.text.ParseException; public class SystemInfoTests { - private static final String SYSTEM_INFO_JSON = "{\n" - + "\n" - + " \"contextPath\": \"https://play.dhis2.org/demo\",\n" - + " \"userAgent\": \"Mozilla/5.0 (Windows NT 10.0; WOW64; rv:53.0) Gecko/20100101 " - + "Firefox/53.0\",\n" - + " \"calendar\": \"iso8601\",\n" - + " \"dateFormat\": \"yyyy-mm-dd\",\n" - + " \"serverDate\": \"2017-05-04T16:34:45.957\",\n" - + " \"lastAnalyticsTableSuccess\": \"2017-01-26T23:19:34.009\",\n" - + " \"intervalSinceLastAnalyticsTableSuccess\": \"2345 h, 15 m, 11 s\",\n" - + " \"lastAnalyticsTableRuntime\": \"5 m, 17 s\",\n" - + " \"version\": \"2.26\",\n" - + " \"revision\": \"f297d4c\",\n" - + " \"buildTime\": \"2017-05-04T06:37:32.000\",\n" - + " \"jasperReportsVersion\": \"6.3.1\",\n" - + " \"environmentVariable\": \"DHIS2_HOME\",\n" - + " \"readOnlyMode\": \"off\",\n" - + " \"databaseInfo\": {\n" - + " \"type\": \"PostgreSQL\",\n" - + " \"spatialSupport\": true\n" - + " },\n" - + " \"encryption\": false,\n" - + " \"isMetadataVersionEnabled\": true,\n" - + " \"isMetadataSyncEnabled\": false\n" - + "\n" - + "}"; @Test public void systemInfo_should_map_from_json_String() throws IOException, ParseException { - SystemInfo systemInfo = (SystemInfo) JsonParser.getModelFromJson(SystemInfo.class, - SYSTEM_INFO_JSON); + new FileReader().getStringFromFile("systeminfo.json")); assertTrue(systemInfo.getVersion().equals("2.26")); assertTrue(DateTestUtils.compareParsedDateWithStringDate(systemInfo.getBuildTime(), diff --git a/api/src/test/java/org/hisp/dhis/android/core/user/UserTests.java b/api/src/test/java/org/hisp/dhis/android/core/user/UserTests.java index be904603..3e4a14c0 100644 --- a/api/src/test/java/org/hisp/dhis/android/core/user/UserTests.java +++ b/api/src/test/java/org/hisp/dhis/android/core/user/UserTests.java @@ -3,6 +3,7 @@ import static org.junit.Assert.assertTrue; import org.hisp.dhis.android.core.commons.DateTestUtils; +import org.hisp.dhis.android.core.commons.FileReader; import org.hisp.dhis.android.core.commons.JsonParser; import org.hisp.dhis.android.dashboard.api.models.User; import org.hisp.dhis.android.dashboard.api.models.UserAccount; @@ -14,24 +15,6 @@ import java.io.IOException; public class UserTests { - - public static final String USER_ACCOUNT_JSON = "{\"created\":\"2013-04-18T17:15:08.407\"," - + "\"lastUpdated\":\"2017-05-02T17:02:37.817\"," - + "\"name\":\"John Traore\"," - + "\"id\":\"xE7jOejl9FI\"," - + "\"birthday\":\"1971-04-08T00:00:00.000\"," - + "\"education\":\"Master of super using\"," - + "\"gender\":\"gender_male\"," - + "\"languages\":\"English\"," - + "\"displayName\":\"John Traore\"," - + "\"jobTitle\":\"Super user\"," - + "\"firstName\":\"John\"," - + "\"surname\":\"Traore\"," - + "\"employer\":\"DHIS\"," - + "\"interests\":\"Football, swimming, singing, dancing\"," - + "\"introduction\":\"I am the super user of DHIS 2\"," - + "\"email\":\"someone@dhis2.org\"," - + "\"organisationUnits\":[{\"id\":\"ImspTQPwCqd\"}]}"; @Rule public ExpectedException thrown = ExpectedException.none(); @@ -76,6 +59,6 @@ public void user_should_map_from_json_string() throws IOException { } private UserAccount getUserAccountFromJson() throws IOException { - return (UserAccount) JsonParser.getModelFromJson(UserAccount.class, USER_ACCOUNT_JSON); + return (UserAccount) JsonParser.getModelFromJson(UserAccount.class, new FileReader().getStringFromFile("user.json")); } } \ No newline at end of file diff --git a/api/src/test/resources/access_all_false_string.txt b/api/src/test/resources/access_all_false_string.txt new file mode 100644 index 00000000..1cd2bc71 --- /dev/null +++ b/api/src/test/resources/access_all_false_string.txt @@ -0,0 +1,8 @@ +{ + "manage": false, + "externalize": false, + "write": false, + "read": false, + "update": false, + "delete": false +} \ No newline at end of file diff --git a/api/src/test/resources/access_all_true_string.txt b/api/src/test/resources/access_all_true_string.txt new file mode 100644 index 00000000..86766b27 --- /dev/null +++ b/api/src/test/resources/access_all_true_string.txt @@ -0,0 +1 @@ +{"manage":true,"externalize":true,"write":true,"read":true,"update":true,"delete":true} \ No newline at end of file diff --git a/api/src/test/resources/dashboard.json b/api/src/test/resources/dashboard.json new file mode 100644 index 00000000..2214b078 --- /dev/null +++ b/api/src/test/resources/dashboard.json @@ -0,0 +1,133 @@ +[{ + "lastUpdated": "2016-10-11T19:24:33.599", + "created": "2013-09-08T21:47:17.960", + "name": "Antenatal Care", + "id": "nghVC4wtyzi", + "displayName": "Antenatal Care", + "access": { + "read": true, + "update": true, + "externalize": true, + "delete": true, + "write": true, + "manage": true + }, + "dashboardItems": [{ + "created": "2016-10-10T17:24:30.487", + "lastUpdated": "2016-10-10T17:24:30.487", + "id": "cX2przhv9UC", + "shape": "NORMAL", + "type": "CHART", + "chart": { + "lastUpdated": "2016-10-10T17:24:49.196", + "created": "2016-10-10T17:24:49.196", + "name": "ANC: ANC IPT 1 Coverage last 12 months districts", + "id": "VffWmdKFHSq", + "displayName": "ANC: ANC IPT 1 Coverage last 12 months districts" + } + }, { + "created": "2016-08-02T11:57:55.229", + "lastUpdated": "2016-08-02T11:57:55.229", + "id": "JcO7yJlKIa3", + "shape": "NORMAL", + "type": "CHART", + "chart": { + "lastUpdated": "2016-10-10T17:08:15.277", + "created": "2016-10-10T17:08:15.277", + "name": "ANC: ANC 3 coverage by districts last 4 quarters", + "id": "CNkMibmx1Zr", + "displayName": "ANC: ANC 3 coverage by districts last 4 quarters" + } + }, { + "created": "2015-01-16T11:52:44.928", + "lastUpdated": "2015-01-16T11:52:44.928", + "id": "OiyMNoXzSdY", + "shape": "NORMAL", + "type": "MAP", + "map": { + "lastUpdated": "2013-09-09T16:35:12.062", + "created": "2012-11-14T12:56:59.322", + "name": "ANC: LLITN coverage district and facility", + "id": "ZBjCfSaLSqD", + "displayName": "ANC: LLITN coverage district and facility" + } + }, { + "created": "2016-04-21T15:37:07.740", + "lastUpdated": "2016-04-21T15:37:07.740", + "id": "i6NTSuDsk6l", + "shape": "NORMAL", + "type": "MAP", + "map": { + "lastUpdated": "2016-10-10T18:16:43.265", + "created": "2016-10-10T18:16:43.261", + "name": "ANC: IPT 2 Coverage this year", + "id": "voX07ulo2Bq", + "displayName": "ANC: IPT 2 Coverage this year" + } + }, { + "created": "2015-01-15T16:50:51.427", + "lastUpdated": "2015-08-09T22:10:20.307", + "id": "YZ7U25Japom", + "shape": "DOUBLE_WIDTH", + "type": "CHART", + "chart": { + "lastUpdated": "2015-07-15T15:25:20.004", + "created": "2015-01-15T16:50:34.302", + "name": "ANC: ANC 1 coverage western chiefdoms this year", + "id": "zKl0LcQyxPl", + "displayName": "ANC: ANC 1 coverage western chiefdoms this year" + } + }, { + "created": "2016-08-02T11:57:59.474", + "lastUpdated": "2016-10-10T17:11:06.823", + "id": "UQeYhQOJ2f1", + "shape": "DOUBLE_WIDTH", + "type": "CHART", + "chart": { + "lastUpdated": "2016-08-02T11:58:28.517", + "created": "2016-08-02T11:53:53.607", + "name": "ANC: IPT 1 Coverage by districts last 4 quarters", + "id": "DHPu0vtZ2mW", + "displayName": "ANC: IPT 1 Coverage by districts last 4 quarters" + } + }, { + "created": "2014-04-03T14:11:10.942", + "lastUpdated": "2016-10-11T09:29:40.874", + "id": "xS4X0ZL6GCI", + "shape": "DOUBLE_WIDTH", + "type": "CHART", + "chart": { + "lastUpdated": "2015-07-15T15:25:20.312", + "created": "2014-04-03T14:07:29.442", + "name": "ANC: Fixed vs Outreach last year", + "id": "AVZpYsdG44G", + "displayName": "ANC: Fixed vs Outreach last year" + } + }, { + "created": "2016-10-11T19:24:21.931", + "lastUpdated": "2016-10-11T19:24:21.931", + "id": "ZF9vWMXob7N", + "shape": "NORMAL", + "type": "MAP", + "map": { + "lastUpdated": "2016-10-11T19:23:49.953", + "created": "2016-10-11T19:23:49.952", + "name": "ANC: ANC 1 coverage Sierra Leone dark basemap", + "id": "qTfO4YkQ9xW", + "displayName": "ANC: ANC 1 coverage Sierra Leone dark basemap" + } + }, { + "created": "2014-04-03T14:09:47.075", + "lastUpdated": "2016-10-11T09:19:35.442", + "id": "kHRSFUr3dYe", + "shape": "NORMAL", + "type": "CHART", + "chart": { + "lastUpdated": "2015-07-15T15:25:20.315", + "created": "2014-04-03T14:09:05.734", + "name": "ANC: 4+ visits by Facility Type last year", + "id": "ZfQMIA4o2s3", + "displayName": "ANC: 4+ visits by Facility Type last year" + } + }] +}] \ No newline at end of file diff --git a/api/src/test/resources/interpretation.json b/api/src/test/resources/interpretation.json new file mode 100644 index 00000000..38c5f367 --- /dev/null +++ b/api/src/test/resources/interpretation.json @@ -0,0 +1,59 @@ +{ + "created": "2017-10-21T10:10:43.451", + "lastUpdated": "2017-10-21T10:10:43.451", + "name": "BR11Oy1Q4yR", + "id": "BR11Oy1Q4yR", + "displayName": "BR11Oy1Q4yR", + "type": "CHART", + "text": "This chart shows that BCG doses is low for 2014, why is that?", + "access": { + "read": true, + "update": true, + "externalize": false, + "delete": true, + "write": true, + "manage": true + }, + "chart": { + "lastUpdated": "2015-07-15T15:25:20.264", + "created": "2013-05-29T12:52:54.560", + "name": "Immunization: BCG, Measles, YF doses comparison", + "id": "R9A0rvAydpn", + "displayName": "Immunization: BCG, Measles, YF doses comparison" + }, + "user": { + "lastUpdated": "2017-01-19T14:24:04.447", + "created": "2013-04-18T17:15:08.407", + "name": "John Traore", + "id": "xE7jOejl9FI", + "displayName": "John Traore" + }, + "comments": [ + { + "lastUpdated": "2014-10-21T10:11:19.537", + "created": "2014-10-21T10:11:19.537", + "id": "Eg7x5Kt2XgV", + "text": "It might be caused by a stock-out of vaccines.", + "user": { + "lastUpdated": "2017-01-19T14:24:04.447", + "created": "2013-04-18T17:15:08.407", + "name": "John Traore", + "id": "xE7jOejl9FI", + "displayName": "John Traore" + } + }, + { + "lastUpdated": "2014-10-21T10:11:44.325", + "created": "2014-10-21T10:11:44.325", + "id": "oRmqfmnCLsQ", + "text": "Yes I believe so", + "user": { + "lastUpdated": "2017-01-19T14:24:04.447", + "created": "2013-04-18T17:15:08.407", + "name": "John Traore", + "id": "xE7jOejl9FI", + "displayName": "John Traore" + } + } + ] +} \ No newline at end of file diff --git a/api/src/test/resources/systeminfo.json b/api/src/test/resources/systeminfo.json new file mode 100644 index 00000000..436de5b4 --- /dev/null +++ b/api/src/test/resources/systeminfo.json @@ -0,0 +1,23 @@ +{ + "contextPath": "https://play.dhis2.org/demo", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0", + "calendar": "iso8601", + "dateFormat": "yyyy-mm-dd", + "serverDate": "2017-05-04T16:34:45.957", + "lastAnalyticsTableSuccess": "2017-01-26T23:19:34.009", + "intervalSinceLastAnalyticsTableSuccess": "2345 h, 15 m, 11 s", + "lastAnalyticsTableRuntime": "5 m, 17 s", + "version": "2.26", + "revision": "f297d4c", + "buildTime": "2017-05-04T06:37:32.000", + "jasperReportsVersion": "6.3.1", + "environmentVariable": "DHIS2_HOME", + "readOnlyMode": "off", + "databaseInfo": { + "type": "PostgreSQL", + "spatialSupport": true + }, + "encryption": false, + "isMetadataVersionEnabled": true, + "isMetadataSyncEnabled": false +} \ No newline at end of file diff --git a/api/src/test/resources/user.json b/api/src/test/resources/user.json new file mode 100644 index 00000000..0fbfc01e --- /dev/null +++ b/api/src/test/resources/user.json @@ -0,0 +1,21 @@ +{ + "created": "2013-04-18T17:15:08.407", + "lastUpdated": "2017-05-02T17:02:37.817", + "name": "John Traore", + "id": "xE7jOejl9FI", + "birthday": "1971-04-08T00:00:00.000", + "education": "Master of super using", + "gender": "gender_male", + "languages": "English", + "displayName": "John Traore", + "jobTitle": "Super user", + "firstName": "John", + "surname": "Traore", + "employer": "DHIS", + "interests": "Football, swimming, singing, dancing", + "introduction": "I am the super user of DHIS 2", + "email": "someone@dhis2.org", + "organisationUnits": [{ + "id": "ImspTQPwCqd" + }] +} \ No newline at end of file From 1b5fe911fdd82ee3ba0daffec455a2086d498681 Mon Sep 17 00:00:00 2001 From: idelcano Date: Mon, 8 May 2017 16:34:46 +0200 Subject: [PATCH 07/69] rename package and remove tests --- .../core/dashboard/DashboardTests.java | 126 ------------- .../interpretation/InterpretationTests.java | 177 ------------------ .../core/systeminfo/SystemInfoTests.java | 34 ---- .../api}/PreconditionsTests.java | 0 .../api}/api/BasicAuthenticatorTests.java | 63 +++---- .../api}/commons/DateTestUtils.java | 0 .../api}/commons/FileReader.java | 0 .../api}/commons/JsonParser.java | 0 .../api}/converters/AccessConverterTests.java | 0 .../converters/DateTimeConverterTests.java | 0 .../api}/converters/StateConverterTests.java | 0 .../api}/meta/CredentialsTests.java | 0 .../api}/network/ApiExceptionTests.java | 0 .../api}/user/UserTests.java | 0 ...tring.txt => access_all_false_string.json} | 0 .../resources/access_all_true_string.json | 8 + .../test/resources/access_all_true_string.txt | 1 - api/src/test/resources/dashboard.json | 133 ------------- api/src/test/resources/interpretation.json | 59 ------ api/src/test/resources/systeminfo.json | 23 --- .../resources/{user.json => userAccount.json} | 0 21 files changed, 39 insertions(+), 585 deletions(-) delete mode 100644 api/src/test/java/org/hisp/dhis/android/core/dashboard/DashboardTests.java delete mode 100644 api/src/test/java/org/hisp/dhis/android/core/interpretation/InterpretationTests.java delete mode 100644 api/src/test/java/org/hisp/dhis/android/core/systeminfo/SystemInfoTests.java rename api/src/test/java/org/hisp/dhis/android/{core => dashboard/api}/PreconditionsTests.java (100%) rename api/src/test/java/org/hisp/dhis/android/{core => dashboard/api}/api/BasicAuthenticatorTests.java (67%) rename api/src/test/java/org/hisp/dhis/android/{core => dashboard/api}/commons/DateTestUtils.java (100%) rename api/src/test/java/org/hisp/dhis/android/{core => dashboard/api}/commons/FileReader.java (100%) rename api/src/test/java/org/hisp/dhis/android/{core => dashboard/api}/commons/JsonParser.java (100%) rename api/src/test/java/org/hisp/dhis/android/{core => dashboard/api}/converters/AccessConverterTests.java (100%) rename api/src/test/java/org/hisp/dhis/android/{core => dashboard/api}/converters/DateTimeConverterTests.java (100%) rename api/src/test/java/org/hisp/dhis/android/{core => dashboard/api}/converters/StateConverterTests.java (100%) rename api/src/test/java/org/hisp/dhis/android/{core => dashboard/api}/meta/CredentialsTests.java (100%) rename api/src/test/java/org/hisp/dhis/android/{core => dashboard/api}/network/ApiExceptionTests.java (100%) rename api/src/test/java/org/hisp/dhis/android/{core => dashboard/api}/user/UserTests.java (100%) rename api/src/test/resources/{access_all_false_string.txt => access_all_false_string.json} (100%) create mode 100644 api/src/test/resources/access_all_true_string.json delete mode 100644 api/src/test/resources/access_all_true_string.txt delete mode 100644 api/src/test/resources/dashboard.json delete mode 100644 api/src/test/resources/interpretation.json delete mode 100644 api/src/test/resources/systeminfo.json rename api/src/test/resources/{user.json => userAccount.json} (100%) diff --git a/api/src/test/java/org/hisp/dhis/android/core/dashboard/DashboardTests.java b/api/src/test/java/org/hisp/dhis/android/core/dashboard/DashboardTests.java deleted file mode 100644 index c53c8ffb..00000000 --- a/api/src/test/java/org/hisp/dhis/android/core/dashboard/DashboardTests.java +++ /dev/null @@ -1,126 +0,0 @@ -package org.hisp.dhis.android.core.dashboard; - -import static org.junit.Assert.assertTrue; - -import org.hisp.dhis.android.core.commons.DateTestUtils; -import org.hisp.dhis.android.core.commons.FileReader; -import org.hisp.dhis.android.core.commons.JsonParser; -import org.hisp.dhis.android.dashboard.api.models.Access; -import org.hisp.dhis.android.dashboard.api.models.Dashboard; -import org.hisp.dhis.android.dashboard.api.models.DashboardElement; -import org.hisp.dhis.android.dashboard.api.models.DashboardItem; -import org.hisp.dhis.android.dashboard.api.models.meta.State; -import org.junit.Test; - -import java.io.IOException; -import java.util.List; - -public class DashboardTests { - - @Test - public void dashboard_shouldMapFromJsonString() throws IOException { - Dashboard dashboard = getDashboardsFromJson(); - assertTrue(dashboard.getState().equals(State.SYNCED)); - assertTrue(dashboard.getUId().equals("nghVC4wtyzi")); - assertTrue(dashboard.getName().equals("Antenatal Care")); - assertTrue(dashboard.getDisplayName().equals("Antenatal Care")); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(dashboard.getCreated(), - "2013-09-08T21:47:17.960")); - assertTrue(dashboard.getDashboardItems().size() == 9); - } - - @Test - public void dashboard_access_shouldMapFromJsonString() throws IOException { - Dashboard dashboard = getDashboardsFromJson(); - Access access = dashboard.getAccess(); - assertTrue(access.isManage() == true); - assertTrue(access.isExternalize() == true); - assertTrue(access.isWrite() == true); - assertTrue(access.isRead() == true); - assertTrue(access.isUpdate() == true); - assertTrue(access.isDelete() == true); - } - - @Test - public void dashboard_item_type_chart_shouldMapFromJsonString() throws IOException { - DashboardItem dashboardItem = getDashboardItemWithChart(); - assertTrue(dashboardItem.getName() == null); - assertTrue(dashboardItem.getDisplayName() == null); - assertTrue(dashboardItem.getState() == State.SYNCED); - assertTrue(dashboardItem.getEventChart() == null); - assertTrue(dashboardItem.getEventReport() == null); - assertTrue(dashboardItem.getUsers() == null); - assertTrue(dashboardItem.getReports() == null); - assertTrue(dashboardItem.getResources() == null); - assertTrue(dashboardItem.isMessages() == false); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(dashboardItem.getCreated(), - "2016-10-10T17:24:30.487")); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(dashboardItem.getLastUpdated(), - "2016-10-10T17:24:30.487")); - assertTrue(dashboardItem.getUId().equals("cX2przhv9UC")); - assertTrue(dashboardItem.getShape().equals("NORMAL")); - assertTrue(dashboardItem.getType().equals("CHART")); - assertTrue(dashboardItem.getAccess() == null); - - DashboardElement dashboardElement = dashboardItem.getChart(); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(dashboardElement.getLastUpdated(), - "2016-10-10T17:24:49.196")); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(dashboardElement.getCreated(), - "2016-10-10T17:24:49.196")); - assertTrue(dashboardElement.getName().equals( - "ANC: ANC IPT 1 Coverage last 12 months districts")); - assertTrue(dashboardElement.getUId().equals("VffWmdKFHSq")); - assertTrue(dashboardElement.getDisplayName().equals( - "ANC: ANC IPT 1 Coverage last 12 months districts")); - } - - @Test - public void dashboard_item_type_map_shouldMapFromJsonString() throws IOException { - DashboardItem dashboardItem = getDashboardItemWithMap(); - assertTrue(dashboardItem.getName() == null); - assertTrue(dashboardItem.getDisplayName() == null); - assertTrue(dashboardItem.getState() == State.SYNCED); - assertTrue(dashboardItem.getEventChart() == null); - assertTrue(dashboardItem.getEventReport() == null); - assertTrue(dashboardItem.getUsers() == null); - assertTrue(dashboardItem.getReports() == null); - assertTrue(dashboardItem.getResources() == null); - assertTrue(dashboardItem.isMessages() == false); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(dashboardItem.getCreated(), - "2015-01-16T11:52:44.928")); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(dashboardItem.getLastUpdated(), - "2015-01-16T11:52:44.928")); - assertTrue(dashboardItem.getUId().equals("OiyMNoXzSdY")); - assertTrue(dashboardItem.getShape().equals("NORMAL")); - assertTrue(dashboardItem.getType().equals("MAP")); - assertTrue(dashboardItem.getAccess() == null); - DashboardElement dashboardElement = dashboardItem.getMap(); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(dashboardElement.getLastUpdated(), - "2013-09-09T16:35:12.062")); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(dashboardElement.getCreated(), - "2012-11-14T12:56:59.322")); - assertTrue(dashboardElement.getName().equals("ANC: LLITN coverage district and facility")); - assertTrue(dashboardElement.getUId().equals("ZBjCfSaLSqD")); - assertTrue(dashboardElement.getDisplayName().equals( - "ANC: LLITN coverage district and facility")); - } - - private DashboardItem getDashboardItemWithChart() throws IOException { - Dashboard dashboard = getDashboardsFromJson(); - List dashboardItems = dashboard.getDashboardItems(); - return dashboardItems.get(0); - } - - private DashboardItem getDashboardItemWithMap() throws IOException { - Dashboard dashboard = getDashboardsFromJson(); - List dashboardItems = dashboard.getDashboardItems(); - return dashboardItems.get(2); - } - - private Dashboard getDashboardsFromJson() throws IOException { - Dashboard[] dashboards = (Dashboard[]) JsonParser.getModelFromJson(Dashboard[].class, - new FileReader().getStringFromFile("dashboard.json")); - return dashboards[0]; - } - -} diff --git a/api/src/test/java/org/hisp/dhis/android/core/interpretation/InterpretationTests.java b/api/src/test/java/org/hisp/dhis/android/core/interpretation/InterpretationTests.java deleted file mode 100644 index 5b8446c6..00000000 --- a/api/src/test/java/org/hisp/dhis/android/core/interpretation/InterpretationTests.java +++ /dev/null @@ -1,177 +0,0 @@ -package org.hisp.dhis.android.core.interpretation; - -import static org.junit.Assert.assertTrue; - -import org.hisp.dhis.android.core.commons.DateTestUtils; -import org.hisp.dhis.android.core.commons.FileReader; -import org.hisp.dhis.android.core.commons.JsonParser; -import org.hisp.dhis.android.dashboard.api.models.Access; -import org.hisp.dhis.android.dashboard.api.models.Interpretation; -import org.hisp.dhis.android.dashboard.api.models.InterpretationComment; -import org.hisp.dhis.android.dashboard.api.models.InterpretationElement; -import org.hisp.dhis.android.dashboard.api.models.User; -import org.junit.Test; - -import java.io.IOException; - -public class InterpretationTests { - - @Test - public void interpretation_shouldMapFromJsonString() throws IOException { - Interpretation interpretation = getInterpretationFromJson(); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(interpretation.getCreated(), - "2017-10-21T10:10:43.451")); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(interpretation.getLastUpdated(), - "2017-10-21T10:10:43.451")); - assertTrue(interpretation.getName().equals("BR11Oy1Q4yR")); - assertTrue(interpretation.getUId().equals("BR11Oy1Q4yR")); - assertTrue(interpretation.getDisplayName().equals("BR11Oy1Q4yR")); - assertTrue(interpretation.getType().equals("CHART")); - assertTrue(interpretation.getText().equals( - "This chart shows that BCG doses is low for 2014, why is that?")); - Access access = getAccessObject(); - assertTrue(interpretation.getAccess().isDelete() == access.isDelete()); - assertTrue(interpretation.getAccess().isExternalize() == access.isExternalize()); - assertTrue(interpretation.getAccess().isManage() == access.isManage()); - assertTrue(interpretation.getAccess().isRead() == access.isRead()); - assertTrue(interpretation.getAccess().isWrite() == access.isWrite()); - } - - @Test - public void interpretation_chart_shouldMapFromJsonString() throws IOException { - Interpretation interpretation = getInterpretationFromJson(); - InterpretationElement interpretationElement = interpretation.getChart(); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(interpretationElement - .getCreated(), "2013-05-29T12:52:54.560")); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(interpretationElement - .getLastUpdated(), "2015-07-15T15:25:20.264")); - assertTrue(interpretationElement.getName().equals( - "Immunization: BCG, Measles, YF doses comparison")); - assertTrue(interpretationElement.getUId().equals("R9A0rvAydpn")); - assertTrue(interpretationElement.getDisplayName().equals( - "Immunization: BCG, Measles, YF doses comparison")); - assertTrue(interpretationElement.getType() == null); - assertTrue(interpretationElement.getAccess() == null); - } - - @Test - public void interpretation_user_shouldMapFromJsonString() throws IOException { - Interpretation interpretation = getInterpretationFromJson(); - User user = interpretation.getUser(); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(user - .getCreated(), "2013-04-18T17:15:08.407")); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(user - .getLastUpdated(), "2017-01-19T14:24:04.447")); - assertTrue(user.getName().equals( - "John Traore")); - assertTrue(user.getUId().equals("xE7jOejl9FI")); - assertTrue(user.getDisplayName().equals( - "John Traore")); - } - - @Test - public void interpretation_first_comment_shouldMapFromJsonString() throws IOException { - InterpretationComment interpretationComment = getFirstCommentFromInterpretationJson(); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(interpretationComment.getCreated(), - "2014-10-21T10:11:19.537")); - assertTrue(DateTestUtils.compareParsedDateWithStringDate( - interpretationComment.getLastUpdated(), - "2014-10-21T10:11:19.537")); - assertTrue(interpretationComment.getUId().equals("Eg7x5Kt2XgV")); - assertTrue(interpretationComment.getText().equals( - "It might be caused by a stock-out of vaccines.")); - } - - @Test - public void interpretation_second_comment_shouldMapFromJsonString() throws IOException { - InterpretationComment interpretationComment = getSecondCommentFromInterpretationJson(); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(interpretationComment.getCreated(), - "2014-10-21T10:11:44.325")); - assertTrue(DateTestUtils.compareParsedDateWithStringDate( - interpretationComment.getLastUpdated(), - "2014-10-21T10:11:44.325")); - assertTrue(interpretationComment.getUId().equals("oRmqfmnCLsQ")); - assertTrue(interpretationComment.getText().equals("Yes I believe so")); - } - - @Test - public void interpretation_comments_first_user_shouldMapFromJsonString() throws IOException { - User user = getUserFromInterpretationJsonFirstComment(); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(user.getCreated(), - "2013-04-18T17:15:08.407")); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(user.getLastUpdated(), - "2017-01-19T14:24:04.447")); - assertTrue(user.getName().equals("John Traore")); - assertTrue(user.getUId().equals("xE7jOejl9FI")); - assertTrue(user.getDisplayName().equals("John Traore")); - - assertTrue(DateTestUtils.compareParsedDateWithStringDate(user.getCreated(), - "2013-04-18T17:15:08.407")); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(user.getLastUpdated(), - "2017-01-19T14:24:04.447")); - assertTrue(user.getName().equals("John Traore")); - assertTrue(user.getUId().equals("xE7jOejl9FI")); - assertTrue(user.getDisplayName().equals("John Traore")); - } - - @Test - public void interpretation_comments_second_user_shouldMapFromJsonString() throws IOException { - User user = getUserFromInterpretationJsonSecondComment(); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(user.getCreated(), - "2013-04-18T17:15:08.407")); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(user.getLastUpdated(), - "2017-01-19T14:24:04.447")); - assertTrue(user.getName().equals("John Traore")); - assertTrue(user.getUId().equals("xE7jOejl9FI")); - assertTrue(user.getDisplayName().equals("John Traore")); - - assertTrue(DateTestUtils.compareParsedDateWithStringDate(user.getCreated(), - "2013-04-18T17:15:08.407")); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(user.getLastUpdated(), - "2017-01-19T14:24:04.447")); - assertTrue(user.getName().equals("John Traore")); - assertTrue(user.getUId().equals("xE7jOejl9FI")); - assertTrue(user.getDisplayName().equals("John Traore")); - } - - private User getUserFromInterpretationJsonSecondComment() throws IOException { - Interpretation interpretation = getInterpretationFromJson(); - InterpretationComment interpretationComment = interpretation.getComments().get(1); - return interpretationComment.getUser(); - } - - private InterpretationComment getFirstCommentFromInterpretationJson() throws IOException { - return getInterpretationComment(0); - } - - private InterpretationComment getSecondCommentFromInterpretationJson() throws IOException { - return getInterpretationComment(1); - } - - private InterpretationComment getInterpretationComment(int index) throws IOException { - Interpretation interpretation = getInterpretationFromJson(); - return interpretation.getComments().get(index); - } - - private User getUserFromInterpretationJsonFirstComment() throws IOException { - InterpretationComment interpretationComment = - getFirstCommentFromInterpretationJson(); - return interpretationComment.getUser(); - } - - private Interpretation getInterpretationFromJson() throws IOException { - return (Interpretation) JsonParser.getModelFromJson(Interpretation.class, - new FileReader().getStringFromFile("interpretation.json")); - } - - private Access getAccessObject() { - Access access = new Access(); - access.setDelete(true); - access.setExternalize(false); - access.setManage(true); - access.setRead(true); - access.setUpdate(true); - access.setWrite(true); - return access; - } -} diff --git a/api/src/test/java/org/hisp/dhis/android/core/systeminfo/SystemInfoTests.java b/api/src/test/java/org/hisp/dhis/android/core/systeminfo/SystemInfoTests.java deleted file mode 100644 index ed19ac70..00000000 --- a/api/src/test/java/org/hisp/dhis/android/core/systeminfo/SystemInfoTests.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.hisp.dhis.android.core.systeminfo; - -import static org.junit.Assert.assertTrue; - -import org.hisp.dhis.android.core.commons.DateTestUtils; -import org.hisp.dhis.android.core.commons.FileReader; -import org.hisp.dhis.android.core.commons.JsonParser; -import org.hisp.dhis.android.dashboard.api.models.SystemInfo; -import org.junit.Test; - -import java.io.IOException; -import java.text.ParseException; - -public class SystemInfoTests { - - @Test - public void systemInfo_should_map_from_json_String() throws IOException, ParseException { - SystemInfo systemInfo = (SystemInfo) JsonParser.getModelFromJson(SystemInfo.class, - new FileReader().getStringFromFile("systeminfo.json")); - - assertTrue(systemInfo.getVersion().equals("2.26")); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(systemInfo.getBuildTime(), - "2017-05-04T06:37:32.000")); - assertTrue(systemInfo.getCalendar().equals("iso8601")); - assertTrue(systemInfo.getIntervalSinceLastAnalyticsTableSuccess().equals("2345 h, 15 m, 11 s")); - assertTrue(systemInfo.getLastAnalyticsTableSuccess().equals("2017-01-26T23:19:34.009")); - assertTrue(systemInfo.getRevision().equals("f297d4c")); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(systemInfo.getServerDate(), - "2017-05-04T16:34:45.957")); - assertTrue(systemInfo.getDateFormat().equals("yyyy-mm-dd")); - } - - -} diff --git a/api/src/test/java/org/hisp/dhis/android/core/PreconditionsTests.java b/api/src/test/java/org/hisp/dhis/android/dashboard/api/PreconditionsTests.java similarity index 100% rename from api/src/test/java/org/hisp/dhis/android/core/PreconditionsTests.java rename to api/src/test/java/org/hisp/dhis/android/dashboard/api/PreconditionsTests.java diff --git a/api/src/test/java/org/hisp/dhis/android/core/api/BasicAuthenticatorTests.java b/api/src/test/java/org/hisp/dhis/android/dashboard/api/api/BasicAuthenticatorTests.java similarity index 67% rename from api/src/test/java/org/hisp/dhis/android/core/api/BasicAuthenticatorTests.java rename to api/src/test/java/org/hisp/dhis/android/dashboard/api/api/BasicAuthenticatorTests.java index cd7678be..9255e9a6 100644 --- a/api/src/test/java/org/hisp/dhis/android/core/api/BasicAuthenticatorTests.java +++ b/api/src/test/java/org/hisp/dhis/android/dashboard/api/api/BasicAuthenticatorTests.java @@ -28,38 +28,41 @@ package org.hisp.dhis.android.core.api; +import static com.squareup.okhttp.Credentials.basic; + import static org.assertj.core.api.Java6Assertions.assertThat; import static org.mockito.Mockito.when; -import static okhttp3.Credentials.basic; - +import org.hisp.dhis.android.dashboard.api.models.meta.Credentials; +import org.hisp.dhis.android.dashboard.api.network.DhisApi; +import org.hisp.dhis.android.dashboard.api.network.RepoManager; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; +import java.util.concurrent.TimeUnit; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import okhttp3.mockwebserver.RecordedRequest; +import com.squareup.okhttp.HttpUrl; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.Response; +import com.squareup.okhttp.mockwebserver.MockResponse; +import com.squareup.okhttp.mockwebserver.MockWebServer; +import com.squareup.okhttp.mockwebserver.RecordedRequest; // ToDo: Solve problem with INFO logs from MockWebServer being interpreted as errors in gradle @RunWith(JUnit4.class) public class BasicAuthenticatorTests { -/* - @Mock - private AuthenticatedUserStore authenticatedUserStore; + + Credentials credentials = new Credentials("test_user","test_password"); private MockWebServer mockWebServer; private OkHttpClient okHttpClient; + private DhisApi mDhisApi; @Before public void setUp() throws IOException { @@ -68,43 +71,39 @@ public void setUp() throws IOException { mockWebServer = new MockWebServer(); mockWebServer.enqueue(new MockResponse()); mockWebServer.start(); + okHttpClient=RepoManager.provideOkHttpClient(credentials); - okHttpClient = new OkHttpClient.Builder() - .addInterceptor(new BasicAuthenticator(authenticatedUserStore)) - .build(); } @Test public void authenticator_shouldAddAuthorizationHeader() throws IOException, InterruptedException { - AuthenticatedUserModel authenticatedUserModel = - AuthenticatedUserModel.builder() - .user("test_user") - .credentials(base64("test_user", "test_password")) - .build(); - - when(authenticatedUserStore.query()).thenReturn(Arrays.asList(authenticatedUserModel)); okHttpClient.newCall( new Request.Builder() - .url(mockWebServer.url("/api/me/")) + .url(mockWebServer.url("/")) .build()) .execute(); - RecordedRequest recordedRequest = mockWebServer.takeRequest(); - assertThat(recordedRequest.getHeader("Authorization")) - .isEqualTo(basic("test_user", "test_password")); + RecordedRequest recordedRequest = null; + try { + recordedRequest = mockWebServer.takeRequest(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + mDhisApi = RepoManager.createService(mockWebServer.url("/system/info/"), credentials); + assertThat(recordedRequest.getHeader("Authorization")).isNull(); } @Test public void authenticator_shouldNotModifyRequestIfNoUsers() throws IOException, InterruptedException { - when(authenticatedUserStore.query()).thenReturn(new ArrayList()); - + assertThat(okHttpClient.getAuthenticator()).isNull(); + String base64Credentials = basic(credentials.getUsername(), credentials.getPassword()); okHttpClient.newCall( new Request.Builder() - .url(mockWebServer.url("/api/me/")) + .url(mockWebServer.url("/system/info/")) + .addHeader("Authorization", base64Credentials) .build()) .execute(); - RecordedRequest recordedRequest = mockWebServer.takeRequest(); assertThat(recordedRequest.getHeader("Authorization")).isNull(); } @@ -113,5 +112,5 @@ public void authenticator_shouldNotModifyRequestIfNoUsers() throws IOException, public void tearDown() throws IOException { mockWebServer.shutdown(); } - */ + } diff --git a/api/src/test/java/org/hisp/dhis/android/core/commons/DateTestUtils.java b/api/src/test/java/org/hisp/dhis/android/dashboard/api/commons/DateTestUtils.java similarity index 100% rename from api/src/test/java/org/hisp/dhis/android/core/commons/DateTestUtils.java rename to api/src/test/java/org/hisp/dhis/android/dashboard/api/commons/DateTestUtils.java diff --git a/api/src/test/java/org/hisp/dhis/android/core/commons/FileReader.java b/api/src/test/java/org/hisp/dhis/android/dashboard/api/commons/FileReader.java similarity index 100% rename from api/src/test/java/org/hisp/dhis/android/core/commons/FileReader.java rename to api/src/test/java/org/hisp/dhis/android/dashboard/api/commons/FileReader.java diff --git a/api/src/test/java/org/hisp/dhis/android/core/commons/JsonParser.java b/api/src/test/java/org/hisp/dhis/android/dashboard/api/commons/JsonParser.java similarity index 100% rename from api/src/test/java/org/hisp/dhis/android/core/commons/JsonParser.java rename to api/src/test/java/org/hisp/dhis/android/dashboard/api/commons/JsonParser.java diff --git a/api/src/test/java/org/hisp/dhis/android/core/converters/AccessConverterTests.java b/api/src/test/java/org/hisp/dhis/android/dashboard/api/converters/AccessConverterTests.java similarity index 100% rename from api/src/test/java/org/hisp/dhis/android/core/converters/AccessConverterTests.java rename to api/src/test/java/org/hisp/dhis/android/dashboard/api/converters/AccessConverterTests.java diff --git a/api/src/test/java/org/hisp/dhis/android/core/converters/DateTimeConverterTests.java b/api/src/test/java/org/hisp/dhis/android/dashboard/api/converters/DateTimeConverterTests.java similarity index 100% rename from api/src/test/java/org/hisp/dhis/android/core/converters/DateTimeConverterTests.java rename to api/src/test/java/org/hisp/dhis/android/dashboard/api/converters/DateTimeConverterTests.java diff --git a/api/src/test/java/org/hisp/dhis/android/core/converters/StateConverterTests.java b/api/src/test/java/org/hisp/dhis/android/dashboard/api/converters/StateConverterTests.java similarity index 100% rename from api/src/test/java/org/hisp/dhis/android/core/converters/StateConverterTests.java rename to api/src/test/java/org/hisp/dhis/android/dashboard/api/converters/StateConverterTests.java diff --git a/api/src/test/java/org/hisp/dhis/android/core/meta/CredentialsTests.java b/api/src/test/java/org/hisp/dhis/android/dashboard/api/meta/CredentialsTests.java similarity index 100% rename from api/src/test/java/org/hisp/dhis/android/core/meta/CredentialsTests.java rename to api/src/test/java/org/hisp/dhis/android/dashboard/api/meta/CredentialsTests.java diff --git a/api/src/test/java/org/hisp/dhis/android/core/network/ApiExceptionTests.java b/api/src/test/java/org/hisp/dhis/android/dashboard/api/network/ApiExceptionTests.java similarity index 100% rename from api/src/test/java/org/hisp/dhis/android/core/network/ApiExceptionTests.java rename to api/src/test/java/org/hisp/dhis/android/dashboard/api/network/ApiExceptionTests.java diff --git a/api/src/test/java/org/hisp/dhis/android/core/user/UserTests.java b/api/src/test/java/org/hisp/dhis/android/dashboard/api/user/UserTests.java similarity index 100% rename from api/src/test/java/org/hisp/dhis/android/core/user/UserTests.java rename to api/src/test/java/org/hisp/dhis/android/dashboard/api/user/UserTests.java diff --git a/api/src/test/resources/access_all_false_string.txt b/api/src/test/resources/access_all_false_string.json similarity index 100% rename from api/src/test/resources/access_all_false_string.txt rename to api/src/test/resources/access_all_false_string.json diff --git a/api/src/test/resources/access_all_true_string.json b/api/src/test/resources/access_all_true_string.json new file mode 100644 index 00000000..75d73647 --- /dev/null +++ b/api/src/test/resources/access_all_true_string.json @@ -0,0 +1,8 @@ +{ + "manage": true, + "externalize": true, + "write": true, + "read": true, + "update": true, + "delete": true +} \ No newline at end of file diff --git a/api/src/test/resources/access_all_true_string.txt b/api/src/test/resources/access_all_true_string.txt deleted file mode 100644 index 86766b27..00000000 --- a/api/src/test/resources/access_all_true_string.txt +++ /dev/null @@ -1 +0,0 @@ -{"manage":true,"externalize":true,"write":true,"read":true,"update":true,"delete":true} \ No newline at end of file diff --git a/api/src/test/resources/dashboard.json b/api/src/test/resources/dashboard.json deleted file mode 100644 index 2214b078..00000000 --- a/api/src/test/resources/dashboard.json +++ /dev/null @@ -1,133 +0,0 @@ -[{ - "lastUpdated": "2016-10-11T19:24:33.599", - "created": "2013-09-08T21:47:17.960", - "name": "Antenatal Care", - "id": "nghVC4wtyzi", - "displayName": "Antenatal Care", - "access": { - "read": true, - "update": true, - "externalize": true, - "delete": true, - "write": true, - "manage": true - }, - "dashboardItems": [{ - "created": "2016-10-10T17:24:30.487", - "lastUpdated": "2016-10-10T17:24:30.487", - "id": "cX2przhv9UC", - "shape": "NORMAL", - "type": "CHART", - "chart": { - "lastUpdated": "2016-10-10T17:24:49.196", - "created": "2016-10-10T17:24:49.196", - "name": "ANC: ANC IPT 1 Coverage last 12 months districts", - "id": "VffWmdKFHSq", - "displayName": "ANC: ANC IPT 1 Coverage last 12 months districts" - } - }, { - "created": "2016-08-02T11:57:55.229", - "lastUpdated": "2016-08-02T11:57:55.229", - "id": "JcO7yJlKIa3", - "shape": "NORMAL", - "type": "CHART", - "chart": { - "lastUpdated": "2016-10-10T17:08:15.277", - "created": "2016-10-10T17:08:15.277", - "name": "ANC: ANC 3 coverage by districts last 4 quarters", - "id": "CNkMibmx1Zr", - "displayName": "ANC: ANC 3 coverage by districts last 4 quarters" - } - }, { - "created": "2015-01-16T11:52:44.928", - "lastUpdated": "2015-01-16T11:52:44.928", - "id": "OiyMNoXzSdY", - "shape": "NORMAL", - "type": "MAP", - "map": { - "lastUpdated": "2013-09-09T16:35:12.062", - "created": "2012-11-14T12:56:59.322", - "name": "ANC: LLITN coverage district and facility", - "id": "ZBjCfSaLSqD", - "displayName": "ANC: LLITN coverage district and facility" - } - }, { - "created": "2016-04-21T15:37:07.740", - "lastUpdated": "2016-04-21T15:37:07.740", - "id": "i6NTSuDsk6l", - "shape": "NORMAL", - "type": "MAP", - "map": { - "lastUpdated": "2016-10-10T18:16:43.265", - "created": "2016-10-10T18:16:43.261", - "name": "ANC: IPT 2 Coverage this year", - "id": "voX07ulo2Bq", - "displayName": "ANC: IPT 2 Coverage this year" - } - }, { - "created": "2015-01-15T16:50:51.427", - "lastUpdated": "2015-08-09T22:10:20.307", - "id": "YZ7U25Japom", - "shape": "DOUBLE_WIDTH", - "type": "CHART", - "chart": { - "lastUpdated": "2015-07-15T15:25:20.004", - "created": "2015-01-15T16:50:34.302", - "name": "ANC: ANC 1 coverage western chiefdoms this year", - "id": "zKl0LcQyxPl", - "displayName": "ANC: ANC 1 coverage western chiefdoms this year" - } - }, { - "created": "2016-08-02T11:57:59.474", - "lastUpdated": "2016-10-10T17:11:06.823", - "id": "UQeYhQOJ2f1", - "shape": "DOUBLE_WIDTH", - "type": "CHART", - "chart": { - "lastUpdated": "2016-08-02T11:58:28.517", - "created": "2016-08-02T11:53:53.607", - "name": "ANC: IPT 1 Coverage by districts last 4 quarters", - "id": "DHPu0vtZ2mW", - "displayName": "ANC: IPT 1 Coverage by districts last 4 quarters" - } - }, { - "created": "2014-04-03T14:11:10.942", - "lastUpdated": "2016-10-11T09:29:40.874", - "id": "xS4X0ZL6GCI", - "shape": "DOUBLE_WIDTH", - "type": "CHART", - "chart": { - "lastUpdated": "2015-07-15T15:25:20.312", - "created": "2014-04-03T14:07:29.442", - "name": "ANC: Fixed vs Outreach last year", - "id": "AVZpYsdG44G", - "displayName": "ANC: Fixed vs Outreach last year" - } - }, { - "created": "2016-10-11T19:24:21.931", - "lastUpdated": "2016-10-11T19:24:21.931", - "id": "ZF9vWMXob7N", - "shape": "NORMAL", - "type": "MAP", - "map": { - "lastUpdated": "2016-10-11T19:23:49.953", - "created": "2016-10-11T19:23:49.952", - "name": "ANC: ANC 1 coverage Sierra Leone dark basemap", - "id": "qTfO4YkQ9xW", - "displayName": "ANC: ANC 1 coverage Sierra Leone dark basemap" - } - }, { - "created": "2014-04-03T14:09:47.075", - "lastUpdated": "2016-10-11T09:19:35.442", - "id": "kHRSFUr3dYe", - "shape": "NORMAL", - "type": "CHART", - "chart": { - "lastUpdated": "2015-07-15T15:25:20.315", - "created": "2014-04-03T14:09:05.734", - "name": "ANC: 4+ visits by Facility Type last year", - "id": "ZfQMIA4o2s3", - "displayName": "ANC: 4+ visits by Facility Type last year" - } - }] -}] \ No newline at end of file diff --git a/api/src/test/resources/interpretation.json b/api/src/test/resources/interpretation.json deleted file mode 100644 index 38c5f367..00000000 --- a/api/src/test/resources/interpretation.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "created": "2017-10-21T10:10:43.451", - "lastUpdated": "2017-10-21T10:10:43.451", - "name": "BR11Oy1Q4yR", - "id": "BR11Oy1Q4yR", - "displayName": "BR11Oy1Q4yR", - "type": "CHART", - "text": "This chart shows that BCG doses is low for 2014, why is that?", - "access": { - "read": true, - "update": true, - "externalize": false, - "delete": true, - "write": true, - "manage": true - }, - "chart": { - "lastUpdated": "2015-07-15T15:25:20.264", - "created": "2013-05-29T12:52:54.560", - "name": "Immunization: BCG, Measles, YF doses comparison", - "id": "R9A0rvAydpn", - "displayName": "Immunization: BCG, Measles, YF doses comparison" - }, - "user": { - "lastUpdated": "2017-01-19T14:24:04.447", - "created": "2013-04-18T17:15:08.407", - "name": "John Traore", - "id": "xE7jOejl9FI", - "displayName": "John Traore" - }, - "comments": [ - { - "lastUpdated": "2014-10-21T10:11:19.537", - "created": "2014-10-21T10:11:19.537", - "id": "Eg7x5Kt2XgV", - "text": "It might be caused by a stock-out of vaccines.", - "user": { - "lastUpdated": "2017-01-19T14:24:04.447", - "created": "2013-04-18T17:15:08.407", - "name": "John Traore", - "id": "xE7jOejl9FI", - "displayName": "John Traore" - } - }, - { - "lastUpdated": "2014-10-21T10:11:44.325", - "created": "2014-10-21T10:11:44.325", - "id": "oRmqfmnCLsQ", - "text": "Yes I believe so", - "user": { - "lastUpdated": "2017-01-19T14:24:04.447", - "created": "2013-04-18T17:15:08.407", - "name": "John Traore", - "id": "xE7jOejl9FI", - "displayName": "John Traore" - } - } - ] -} \ No newline at end of file diff --git a/api/src/test/resources/systeminfo.json b/api/src/test/resources/systeminfo.json deleted file mode 100644 index 436de5b4..00000000 --- a/api/src/test/resources/systeminfo.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "contextPath": "https://play.dhis2.org/demo", - "userAgent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0", - "calendar": "iso8601", - "dateFormat": "yyyy-mm-dd", - "serverDate": "2017-05-04T16:34:45.957", - "lastAnalyticsTableSuccess": "2017-01-26T23:19:34.009", - "intervalSinceLastAnalyticsTableSuccess": "2345 h, 15 m, 11 s", - "lastAnalyticsTableRuntime": "5 m, 17 s", - "version": "2.26", - "revision": "f297d4c", - "buildTime": "2017-05-04T06:37:32.000", - "jasperReportsVersion": "6.3.1", - "environmentVariable": "DHIS2_HOME", - "readOnlyMode": "off", - "databaseInfo": { - "type": "PostgreSQL", - "spatialSupport": true - }, - "encryption": false, - "isMetadataVersionEnabled": true, - "isMetadataSyncEnabled": false -} \ No newline at end of file diff --git a/api/src/test/resources/user.json b/api/src/test/resources/userAccount.json similarity index 100% rename from api/src/test/resources/user.json rename to api/src/test/resources/userAccount.json From 44e0af87c1cc3616d23a5e80b3fe52618690bcd0 Mon Sep 17 00:00:00 2001 From: idelcano Date: Mon, 8 May 2017 17:01:38 +0200 Subject: [PATCH 08/69] Make public the provideDefaultAccess to access it during test --- .../java/org/hisp/dhis/android/dashboard/api/models/Access.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/Access.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/Access.java index bc2c182f..8fc263f9 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/Access.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/Access.java @@ -58,7 +58,7 @@ public final class Access { * * @return new Access object. */ - static Access provideDefaultAccess() { + public static Access provideDefaultAccess() { Access access = new Access(); access.setManage(true); access.setExternalize(true); From ec19cd2d304b24f6b538b60b9c5a44ce1c54e00e Mon Sep 17 00:00:00 2001 From: idelcano Date: Mon, 8 May 2017 17:01:50 +0200 Subject: [PATCH 09/69] Refactor tests --- .../dashboard/api/PreconditionsTests.java | 2 +- .../api/api/BasicAuthenticatorTests.java | 116 ------------------ .../dashboard/api/commons/DateTestUtils.java | 2 +- .../dashboard/api/commons/FileReader.java | 2 +- .../dashboard/api/commons/JsonParser.java | 2 +- .../api/converters/AccessConverterTests.java | 35 +++--- .../converters/DateTimeConverterTests.java | 38 +++--- .../api/converters/StateConverterTests.java | 2 +- .../dashboard/api/meta/CredentialsTests.java | 2 +- .../api/network/ApiExceptionTests.java | 13 +- .../android/dashboard/api/user/UserTests.java | 42 ++----- 11 files changed, 52 insertions(+), 204 deletions(-) delete mode 100644 api/src/test/java/org/hisp/dhis/android/dashboard/api/api/BasicAuthenticatorTests.java diff --git a/api/src/test/java/org/hisp/dhis/android/dashboard/api/PreconditionsTests.java b/api/src/test/java/org/hisp/dhis/android/dashboard/api/PreconditionsTests.java index d6e851a6..deaf35cc 100644 --- a/api/src/test/java/org/hisp/dhis/android/dashboard/api/PreconditionsTests.java +++ b/api/src/test/java/org/hisp/dhis/android/dashboard/api/PreconditionsTests.java @@ -1,4 +1,4 @@ -package org.hisp.dhis.android.core; +package org.hisp.dhis.android.dashboard.api; import org.hisp.dhis.android.dashboard.api.utils.Preconditions; import org.junit.Rule; diff --git a/api/src/test/java/org/hisp/dhis/android/dashboard/api/api/BasicAuthenticatorTests.java b/api/src/test/java/org/hisp/dhis/android/dashboard/api/api/BasicAuthenticatorTests.java deleted file mode 100644 index 9255e9a6..00000000 --- a/api/src/test/java/org/hisp/dhis/android/dashboard/api/api/BasicAuthenticatorTests.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (c) 2017, University of Oslo - * - * All rights reserved. - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * Neither the name of the HISP project nor the names of its contributors may - * be used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package org.hisp.dhis.android.core.api; - -import static com.squareup.okhttp.Credentials.basic; - -import static org.assertj.core.api.Java6Assertions.assertThat; -import static org.mockito.Mockito.when; - -import org.hisp.dhis.android.dashboard.api.models.meta.Credentials; -import org.hisp.dhis.android.dashboard.api.network.DhisApi; -import org.hisp.dhis.android.dashboard.api.network.RepoManager; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -import org.mockito.MockitoAnnotations; - -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -import com.squareup.okhttp.HttpUrl; -import com.squareup.okhttp.OkHttpClient; -import com.squareup.okhttp.Request; -import com.squareup.okhttp.Response; -import com.squareup.okhttp.mockwebserver.MockResponse; -import com.squareup.okhttp.mockwebserver.MockWebServer; -import com.squareup.okhttp.mockwebserver.RecordedRequest; - -// ToDo: Solve problem with INFO logs from MockWebServer being interpreted as errors in gradle -@RunWith(JUnit4.class) -public class BasicAuthenticatorTests { - - Credentials credentials = new Credentials("test_user","test_password"); - - private MockWebServer mockWebServer; - private OkHttpClient okHttpClient; - private DhisApi mDhisApi; - - @Before - public void setUp() throws IOException { - MockitoAnnotations.initMocks(this); - - mockWebServer = new MockWebServer(); - mockWebServer.enqueue(new MockResponse()); - mockWebServer.start(); - okHttpClient=RepoManager.provideOkHttpClient(credentials); - - } - - @Test - public void authenticator_shouldAddAuthorizationHeader() throws IOException, InterruptedException { - - okHttpClient.newCall( - new Request.Builder() - .url(mockWebServer.url("/")) - .build()) - .execute(); - - RecordedRequest recordedRequest = null; - try { - recordedRequest = mockWebServer.takeRequest(); - } catch (InterruptedException e) { - e.printStackTrace(); - } - mDhisApi = RepoManager.createService(mockWebServer.url("/system/info/"), credentials); - assertThat(recordedRequest.getHeader("Authorization")).isNull(); - } - - @Test - public void authenticator_shouldNotModifyRequestIfNoUsers() throws IOException, InterruptedException { - assertThat(okHttpClient.getAuthenticator()).isNull(); - String base64Credentials = basic(credentials.getUsername(), credentials.getPassword()); - okHttpClient.newCall( - new Request.Builder() - .url(mockWebServer.url("/system/info/")) - .addHeader("Authorization", base64Credentials) - .build()) - .execute(); - RecordedRequest recordedRequest = mockWebServer.takeRequest(); - assertThat(recordedRequest.getHeader("Authorization")).isNull(); - } - - @After - public void tearDown() throws IOException { - mockWebServer.shutdown(); - } - -} diff --git a/api/src/test/java/org/hisp/dhis/android/dashboard/api/commons/DateTestUtils.java b/api/src/test/java/org/hisp/dhis/android/dashboard/api/commons/DateTestUtils.java index f71391da..e1087ca4 100644 --- a/api/src/test/java/org/hisp/dhis/android/dashboard/api/commons/DateTestUtils.java +++ b/api/src/test/java/org/hisp/dhis/android/dashboard/api/commons/DateTestUtils.java @@ -1,4 +1,4 @@ -package org.hisp.dhis.android.core.commons; +package org.hisp.dhis.android.dashboard.api.commons; import org.joda.time.DateTime; diff --git a/api/src/test/java/org/hisp/dhis/android/dashboard/api/commons/FileReader.java b/api/src/test/java/org/hisp/dhis/android/dashboard/api/commons/FileReader.java index 5b53187f..2bfed03c 100644 --- a/api/src/test/java/org/hisp/dhis/android/dashboard/api/commons/FileReader.java +++ b/api/src/test/java/org/hisp/dhis/android/dashboard/api/commons/FileReader.java @@ -1,4 +1,4 @@ -package org.hisp.dhis.android.core.commons; +package org.hisp.dhis.android.dashboard.api.commons; import java.io.BufferedReader; import java.io.File; diff --git a/api/src/test/java/org/hisp/dhis/android/dashboard/api/commons/JsonParser.java b/api/src/test/java/org/hisp/dhis/android/dashboard/api/commons/JsonParser.java index 7a586257..3512c207 100644 --- a/api/src/test/java/org/hisp/dhis/android/dashboard/api/commons/JsonParser.java +++ b/api/src/test/java/org/hisp/dhis/android/dashboard/api/commons/JsonParser.java @@ -1,4 +1,4 @@ -package org.hisp.dhis.android.core.commons; +package org.hisp.dhis.android.dashboard.api.commons; import org.hisp.dhis.android.dashboard.api.utils.ObjectMapperProvider; diff --git a/api/src/test/java/org/hisp/dhis/android/dashboard/api/converters/AccessConverterTests.java b/api/src/test/java/org/hisp/dhis/android/dashboard/api/converters/AccessConverterTests.java index b0a9e7f6..b64e79d4 100644 --- a/api/src/test/java/org/hisp/dhis/android/dashboard/api/converters/AccessConverterTests.java +++ b/api/src/test/java/org/hisp/dhis/android/dashboard/api/converters/AccessConverterTests.java @@ -1,40 +1,29 @@ -package org.hisp.dhis.android.core.converters; +package org.hisp.dhis.android.dashboard.api.converters; import static junit.framework.Assert.assertTrue; -import org.hisp.dhis.android.core.commons.FileReader; +import org.hisp.dhis.android.dashboard.api.commons.FileReader; import org.hisp.dhis.android.dashboard.api.models.Access; import org.hisp.dhis.android.dashboard.api.persistence.converters.AccessConverter; -import org.junit.Before; import org.junit.Test; import java.io.IOException; public class AccessConverterTests { - public static Access accessObject; + public static final String ACCESS_ALL_FALSE_STRING_TXT = "access_all_false_string.json"; + public static final String ACCESS_ALL_TRUE_STRING_TXT = "access_all_true_string.json"; AccessConverter accessConverter = new AccessConverter(); - @Before - public void setUp() throws Exception { - accessObject = new Access(); - accessObject.setDelete(true); - accessObject.setWrite(true); - accessObject.setUpdate(true); - accessObject.setRead(true); - accessObject.setManage(true); - accessObject.setExternalize(true); - } - @Test public void convert_access_object_to_database_string() throws Exception { - String access = new FileReader().getStringFromFile("access_all_true_string.txt"); - assertTrue(accessConverter.getDBValue(accessObject).equals(access)); + String access = getAccessFromJson(ACCESS_ALL_TRUE_STRING_TXT); + assertTrue(accessConverter.getDBValue(Access.provideDefaultAccess()).equals(access)); } @Test public void convert_access_all_true_database_string_to_model() throws IOException { - Access access = accessConverter.getModelValue( new FileReader().getStringFromFile( - "access_all_true_string.txt")); + Access access = accessConverter.getModelValue( + getAccessFromJson(ACCESS_ALL_TRUE_STRING_TXT)); assertTrue(access.isDelete()); assertTrue(access.isRead()); assertTrue(access.isWrite()); @@ -44,12 +33,16 @@ public void convert_access_all_true_database_string_to_model() throws IOExceptio @Test public void convert_access_all_false_database_string_to_model() throws IOException { - Access access = accessConverter.getModelValue( new FileReader().getStringFromFile( - "access_all_false_string.txt")); + Access access = accessConverter.getModelValue( + getAccessFromJson(ACCESS_ALL_FALSE_STRING_TXT)); assertTrue(!access.isDelete()); assertTrue(!access.isRead()); assertTrue(!access.isWrite()); assertTrue(!access.isManage()); assertTrue(!access.isExternalize()); } + + private String getAccessFromJson(String json) throws IOException { + return new FileReader().getStringFromFile(json).replaceAll(" ", "").replace("\t", ""); + } } diff --git a/api/src/test/java/org/hisp/dhis/android/dashboard/api/converters/DateTimeConverterTests.java b/api/src/test/java/org/hisp/dhis/android/dashboard/api/converters/DateTimeConverterTests.java index 89b74161..4820d504 100644 --- a/api/src/test/java/org/hisp/dhis/android/dashboard/api/converters/DateTimeConverterTests.java +++ b/api/src/test/java/org/hisp/dhis/android/dashboard/api/converters/DateTimeConverterTests.java @@ -1,11 +1,10 @@ -package org.hisp.dhis.android.core.converters; +package org.hisp.dhis.android.dashboard.api.converters; import static junit.framework.Assert.assertTrue; -import org.hisp.dhis.android.core.commons.DateTestUtils; +import org.hisp.dhis.android.dashboard.api.commons.DateTestUtils; import org.hisp.dhis.android.dashboard.api.persistence.converters.DateTimeConverter; import org.joda.time.DateTime; -import org.junit.Before; import org.junit.Test; import java.util.Calendar; @@ -17,22 +16,6 @@ public class DateTimeConverterTests { DateTimeConverter dateTimeConverter = new DateTimeConverter(); - DateTime dateTime; - - @Before - public void setUp() throws Exception { - Calendar calendar = Calendar.getInstance(); - calendar.setTimeZone(TimeZone.getTimeZone("UTC")); - calendar.set(Calendar.YEAR, 2016); - calendar.set(Calendar.MONTH, 04); - calendar.set(Calendar.DAY_OF_MONTH, 21); - calendar.set(Calendar.HOUR_OF_DAY, 15); - calendar.set(Calendar.MINUTE, 37); - calendar.set(Calendar.SECOND, 07); - calendar.set(Calendar.MILLISECOND, 740); - dateTime = new DateTime(calendar); - } - @Test public void convert_datetime_string_to_object() throws Exception { DateTime convertedDate = dateTimeConverter.getModelValue(DATETIME_AS_STRING); @@ -42,9 +25,22 @@ public void convert_datetime_string_to_object() throws Exception { @Test public void convert_datetime_object_to_string() throws Exception { - String converterDate = dateTimeConverter.getDBValue(dateTime); + String converterDate = dateTimeConverter.getDBValue(createStubDateTime()); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(dateTime, converterDate)); + assertTrue(DateTestUtils.compareParsedDateWithStringDate(createStubDateTime(), converterDate)); } + + private DateTime createStubDateTime(){ + Calendar calendar = Calendar.getInstance(); + calendar.setTimeZone(TimeZone.getTimeZone("UTC")); + calendar.set(Calendar.YEAR, 2016); + calendar.set(Calendar.MONTH, 04); + calendar.set(Calendar.DAY_OF_MONTH, 21); + calendar.set(Calendar.HOUR_OF_DAY, 15); + calendar.set(Calendar.MINUTE, 37); + calendar.set(Calendar.SECOND, 07); + calendar.set(Calendar.MILLISECOND, 740); + return new DateTime(calendar); + } } diff --git a/api/src/test/java/org/hisp/dhis/android/dashboard/api/converters/StateConverterTests.java b/api/src/test/java/org/hisp/dhis/android/dashboard/api/converters/StateConverterTests.java index 7368763f..fbe2fa59 100644 --- a/api/src/test/java/org/hisp/dhis/android/dashboard/api/converters/StateConverterTests.java +++ b/api/src/test/java/org/hisp/dhis/android/dashboard/api/converters/StateConverterTests.java @@ -1,4 +1,4 @@ -package org.hisp.dhis.android.core.converters; +package org.hisp.dhis.android.dashboard.api.converters; import static junit.framework.Assert.assertTrue; diff --git a/api/src/test/java/org/hisp/dhis/android/dashboard/api/meta/CredentialsTests.java b/api/src/test/java/org/hisp/dhis/android/dashboard/api/meta/CredentialsTests.java index 5622f79a..f35115eb 100644 --- a/api/src/test/java/org/hisp/dhis/android/dashboard/api/meta/CredentialsTests.java +++ b/api/src/test/java/org/hisp/dhis/android/dashboard/api/meta/CredentialsTests.java @@ -1,4 +1,4 @@ -package org.hisp.dhis.android.core.meta; +package org.hisp.dhis.android.dashboard.api.meta; import org.hisp.dhis.android.dashboard.api.models.meta.Credentials; import org.junit.Rule; diff --git a/api/src/test/java/org/hisp/dhis/android/dashboard/api/network/ApiExceptionTests.java b/api/src/test/java/org/hisp/dhis/android/dashboard/api/network/ApiExceptionTests.java index 21ae331f..7451f712 100644 --- a/api/src/test/java/org/hisp/dhis/android/dashboard/api/network/ApiExceptionTests.java +++ b/api/src/test/java/org/hisp/dhis/android/dashboard/api/network/ApiExceptionTests.java @@ -1,8 +1,7 @@ -package org.hisp.dhis.android.core.network; +package org.hisp.dhis.android.dashboard.api.network; import static org.junit.Assert.assertTrue; -import org.hisp.dhis.android.dashboard.api.network.APIException; import org.junit.Test; import java.io.IOException; @@ -30,8 +29,7 @@ public void retrofit_network_exception_map_to_api_exception() { @Test public void retrofit_conversion_exception_map_to_api_exception() { - APIException apiException = APIException.fromRetrofitError( - getConversionExceptionFromRetrofit()); + APIException apiException = getConversionExceptionFromRetrofit(); assertTrue(apiException.getKind().equals(APIException.Kind.CONVERSION)); assertTrue(apiException.getUrl().equals("test_message")); } @@ -56,9 +54,10 @@ private APIException getNetworkExceptionFromRetrofit() { RetrofitError.networkError("test_message", new IOException())); } - private RetrofitError getConversionExceptionFromRetrofit() { - return RetrofitError.conversionError("test_message", response, converter, - type, new ConversionException("test_message")); + private APIException getConversionExceptionFromRetrofit() { + return APIException.fromRetrofitError( + RetrofitError.conversionError("test_message", response, converter, + type, new ConversionException("test_message"))); } private APIException getUnexpectedExceptionFromRetrofit() { diff --git a/api/src/test/java/org/hisp/dhis/android/dashboard/api/user/UserTests.java b/api/src/test/java/org/hisp/dhis/android/dashboard/api/user/UserTests.java index 3e4a14c0..205cdee9 100644 --- a/api/src/test/java/org/hisp/dhis/android/dashboard/api/user/UserTests.java +++ b/api/src/test/java/org/hisp/dhis/android/dashboard/api/user/UserTests.java @@ -1,13 +1,12 @@ -package org.hisp.dhis.android.core.user; +package org.hisp.dhis.android.dashboard.api.user; import static org.junit.Assert.assertTrue; -import org.hisp.dhis.android.core.commons.DateTestUtils; -import org.hisp.dhis.android.core.commons.FileReader; -import org.hisp.dhis.android.core.commons.JsonParser; +import org.hisp.dhis.android.dashboard.api.commons.DateTestUtils; +import org.hisp.dhis.android.dashboard.api.commons.FileReader; +import org.hisp.dhis.android.dashboard.api.commons.JsonParser; import org.hisp.dhis.android.dashboard.api.models.User; import org.hisp.dhis.android.dashboard.api.models.UserAccount; -import org.hisp.dhis.android.dashboard.api.models.meta.State; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -19,7 +18,7 @@ public class UserTests { public ExpectedException thrown = ExpectedException.none(); @Test - public void userAccount_should_map_to_user() throws IOException { + public void userAccount_conversion_to_user() throws IOException { UserAccount userAccount = getUserAccountFromJson(); User user = UserAccount.toUser(userAccount); assertTrue(user.getUId().equals("xE7jOejl9FI")); @@ -32,33 +31,10 @@ public void userAccount_should_map_to_user() throws IOException { assertTrue(user.getAccess() == null); } - @Test - public void user_should_map_from_json_string() throws IOException { - UserAccount userAccount = getUserAccountFromJson(); - assertTrue(userAccount.getUId().equals("xE7jOejl9FI")); - assertTrue(userAccount.getName().equals("John Traore")); - assertTrue(userAccount.getDisplayName().equals("John Traore")); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(userAccount.getCreated(), - "2013-04-18T17:15:08.407")); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(userAccount.getLastUpdated(), - "2017-05-02T17:02:37.817")); - assertTrue(userAccount.getAccess() == null); - assertTrue(userAccount.getState().equals(State.SYNCED)); - assertTrue(userAccount.getFirstName().equals("John")); - assertTrue(userAccount.getSurname().equals("Traore")); - assertTrue(userAccount.getGender().equals("gender_male")); - assertTrue(userAccount.getBirthday().equals("1971-04-08T00:00:00.000")); - assertTrue(userAccount.getIntroduction().equals("I am the super user of DHIS 2")); - assertTrue(userAccount.getEducation().equals("Master of super using")); - assertTrue(userAccount.getEmployer().equals("DHIS")); - assertTrue(userAccount.getInterests().equals("Football, swimming, singing, dancing")); - assertTrue(userAccount.getJobTitle().equals("Super user")); - assertTrue(userAccount.getLanguages().equals("English")); - assertTrue(userAccount.getEmail().equals("someone@dhis2.org")); - assertTrue(userAccount.getPhoneNumber() == null); - } - private UserAccount getUserAccountFromJson() throws IOException { - return (UserAccount) JsonParser.getModelFromJson(UserAccount.class, new FileReader().getStringFromFile("user.json")); + return (UserAccount) JsonParser.getModelFromJson(UserAccount.class, new FileReader().getStringFromFile( + + + "userAccount.json")); } } \ No newline at end of file From 8825ffa3c724e26c1438864fddd80540761703ae Mon Sep 17 00:00:00 2001 From: idelcano Date: Mon, 8 May 2017 17:07:10 +0200 Subject: [PATCH 10/69] make private some public methods --- .../hisp/dhis/android/dashboard/api/commons/DateTestUtils.java | 2 +- .../org/hisp/dhis/android/dashboard/api/commons/FileReader.java | 2 +- .../org/hisp/dhis/android/dashboard/api/user/UserTests.java | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/api/src/test/java/org/hisp/dhis/android/dashboard/api/commons/DateTestUtils.java b/api/src/test/java/org/hisp/dhis/android/dashboard/api/commons/DateTestUtils.java index e1087ca4..d69740ca 100644 --- a/api/src/test/java/org/hisp/dhis/android/dashboard/api/commons/DateTestUtils.java +++ b/api/src/test/java/org/hisp/dhis/android/dashboard/api/commons/DateTestUtils.java @@ -10,7 +10,7 @@ public class DateTestUtils { public final static String DHIS2_GMT_NEW_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS"; - public static Date parseDate(String date, String format) { + private static Date parseDate(String date, String format) { try { SimpleDateFormat sdf = new SimpleDateFormat(format); sdf.setTimeZone(TimeZone.getTimeZone("UTC")); diff --git a/api/src/test/java/org/hisp/dhis/android/dashboard/api/commons/FileReader.java b/api/src/test/java/org/hisp/dhis/android/dashboard/api/commons/FileReader.java index 2bfed03c..90dc5bcc 100644 --- a/api/src/test/java/org/hisp/dhis/android/dashboard/api/commons/FileReader.java +++ b/api/src/test/java/org/hisp/dhis/android/dashboard/api/commons/FileReader.java @@ -8,7 +8,7 @@ import java.net.URL; public class FileReader { - public File getFile(String filename) { + private File getFile(String filename) { ClassLoader classLoader = getClass().getClassLoader(); URL resource = classLoader.getResource(filename); return new File(resource.getPath()); diff --git a/api/src/test/java/org/hisp/dhis/android/dashboard/api/user/UserTests.java b/api/src/test/java/org/hisp/dhis/android/dashboard/api/user/UserTests.java index 205cdee9..c642a2f7 100644 --- a/api/src/test/java/org/hisp/dhis/android/dashboard/api/user/UserTests.java +++ b/api/src/test/java/org/hisp/dhis/android/dashboard/api/user/UserTests.java @@ -33,8 +33,6 @@ public void userAccount_conversion_to_user() throws IOException { private UserAccount getUserAccountFromJson() throws IOException { return (UserAccount) JsonParser.getModelFromJson(UserAccount.class, new FileReader().getStringFromFile( - - "userAccount.json")); } } \ No newline at end of file From 0548c9f0a75eaddeadea15b02aab5e579d34aef3 Mon Sep 17 00:00:00 2001 From: idelcano Date: Wed, 10 May 2017 21:53:15 +0200 Subject: [PATCH 11/69] refactor repeated image url builder --- .../api/controllers/DhisController.java | 8 ++++++++ .../DashboardElementDetailActivity.java | 17 +++++------------ .../ui/adapters/DashboardItemAdapter.java | 13 +++---------- .../ui/adapters/InterpretationAdapter.java | 11 ++--------- 4 files changed, 18 insertions(+), 31 deletions(-) diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DhisController.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DhisController.java index b0fcd96e..9b30c272 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DhisController.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DhisController.java @@ -73,6 +73,14 @@ public static DhisController getInstance() { return mDhisController; } + public static String buildImageUrl(String resource, String id) { + return getInstance().getServerUrl().newBuilder() + .addPathSegment("api").addPathSegment(resource).addPathSegment(id).addPathSegment( + "data.png") + .addQueryParameter("width", "480").addQueryParameter("height", "320") + .toString(); + } + public UserAccount logInUser(HttpUrl serverUrl, Credentials credentials) throws APIException { return signInUser(serverUrl, credentials); } diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/activities/DashboardElementDetailActivity.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/activities/DashboardElementDetailActivity.java index 8ad0094c..82c72e1b 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/activities/DashboardElementDetailActivity.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/activities/DashboardElementDetailActivity.java @@ -72,13 +72,6 @@ public static Intent newIntentForInterpretationElement(Activity activity, long i return intent; } - private static String buildImageUrl(String resource, String id) { - return DhisController.getInstance().getServerUrl().newBuilder() - .addPathSegment("api").addPathSegment(resource).addPathSegment(id).addPathSegment("data.png") - .addQueryParameter("width", "480").addQueryParameter("height", "320") - .toString(); - } - private long getDashboardElementId() { return getIntent().getLongExtra(DASHBOARD_ELEMENT_ID, -1); } @@ -136,17 +129,17 @@ private void handleDashboardElement(DashboardElement element) { mToolbar.setTitle(element.getDisplayName()); switch (element.getDashboardItem().getType()) { case DashboardItemContent.TYPE_CHART: { - String request = buildImageUrl("charts", element.getUId()); + String request = DhisController.getInstance().buildImageUrl("charts", element.getUId()); attachFragment(ImageViewFragment.newInstance(request)); break; } case DashboardItemContent.TYPE_EVENT_CHART: { - String request = buildImageUrl("eventCharts", element.getUId()); + String request = DhisController.getInstance().buildImageUrl("eventCharts", element.getUId()); attachFragment(ImageViewFragment.newInstance(request)); break; } case DashboardItemContent.TYPE_MAP: { - String request = buildImageUrl("maps", element.getUId()); + String request = DhisController.getInstance().buildImageUrl("maps", element.getUId()); attachFragment(ImageViewFragment.newInstance(request)); break; } @@ -166,12 +159,12 @@ private void handleInterpretationElement(InterpretationElement element) { mToolbar.setTitle(element.getDisplayName()); switch (element.getInterpretation().getType()) { case Interpretation.TYPE_CHART: { - String request = buildImageUrl("charts", element.getUId()); + String request = DhisController.getInstance().buildImageUrl("charts", element.getUId()); attachFragment(ImageViewFragment.newInstance(request)); break; } case Interpretation.TYPE_MAP: { - String request = buildImageUrl("maps", element.getUId()); + String request = DhisController.getInstance().buildImageUrl("maps", element.getUId()); attachFragment(ImageViewFragment.newInstance(request)); break; } diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java index d1c47311..9f1859b7 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java @@ -111,13 +111,6 @@ public DashboardItemAdapter(Context context, Access dashboardAccess, mImageLoader = PicassoProvider.getInstance(context); } - private static String buildImageUrl(String resource, String id) { - return DhisController.getInstance().getServerUrl().newBuilder() - .addPathSegment("api").addPathSegment(resource).addPathSegment(id).addPathSegment("data.png") - .addQueryParameter("width", "480").addQueryParameter("height", "320") - .toString(); - } - /* returns type of row depending on item content type. */ @Override public int getItemViewType(int position) { @@ -312,13 +305,13 @@ private void handleItemsWithImages(ImageItemViewHolder holder, DashboardItem ite String request = null; if (DashboardItemContent.TYPE_CHART.equals(item.getType()) && item.getChart() != null) { element = item.getChart(); - request = buildImageUrl("charts", element.getUId()); + request = DhisController.getInstance().buildImageUrl("charts", element.getUId()); } else if (DashboardItemContent.TYPE_MAP.equals(item.getType()) && item.getMap() != null) { element = item.getMap(); - request = buildImageUrl("maps", element.getUId()); + request = DhisController.getInstance().buildImageUrl("maps", element.getUId()); } else if (DashboardItemContent.TYPE_EVENT_CHART.equals(item.getType()) && item.getEventChart() != null) { element = item.getEventChart(); - request = buildImageUrl("eventCharts", element.getUId()); + request = DhisController.getInstance().buildImageUrl("eventCharts", element.getUId()); } holder.listener.setDashboardElement(element); diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationAdapter.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationAdapter.java index 0fe188b3..b4eda684 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationAdapter.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationAdapter.java @@ -74,13 +74,6 @@ public InterpretationAdapter(Context context, LayoutInflater inflater, mImageLoader = PicassoProvider.getInstance(context); } - private static String buildImageUrl(String resource, String id) { - return DhisController.getInstance().getServerUrl().newBuilder() - .addPathSegment("api").addPathSegment(resource).addPathSegment(id).addPathSegment("data.png") - .addQueryParameter("width", "480").addQueryParameter("height", "320") - .toString(); - } - /* returns type of row depending on item content type. */ @Override public int getItemViewType(int position) { @@ -223,10 +216,10 @@ private void handleItemsWithImages(ImageItemViewHolder holder, Interpretation it String request = null; if (Interpretation.TYPE_CHART.equals(item.getType()) && item.getChart() != null) { InterpretationElement element = item.getChart(); - request = buildImageUrl("charts", element.getUId()); + request = DhisController.getInstance().buildImageUrl("charts", element.getUId()); } else if (Interpretation.TYPE_MAP.equals(item.getType()) && item.getMap() != null) { InterpretationElement element = item.getMap(); - request = buildImageUrl("maps", element.getUId()); + request = DhisController.getInstance().buildImageUrl("maps", element.getUId()); } holder.listener.setInterpretation(item); From 786a89f45ec2ecaf0a139515a13281b953912c48 Mon Sep 17 00:00:00 2001 From: idelcano Date: Wed, 10 May 2017 22:02:40 +0200 Subject: [PATCH 12/69] Added pull of images service --- .../api/controllers/DashboardController.java | 5 ++ .../api/controllers/DhisController.java | 7 ++ .../controllers/InterpretationController.java | 5 ++ .../api/controllers/PullImageController.java | 82 +++++++++++++++++++ .../persistence/preferences/ResourceType.java | 2 +- .../dhis/android/dashboard/DhisService.java | 25 ++++++ 6 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/PullImageController.java diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DashboardController.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DashboardController.java index a27634b4..8936d276 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DashboardController.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DashboardController.java @@ -96,6 +96,11 @@ private static List queryDashboards() { .queryList(); } + public static List queryAllDashboardElement() { + return new Select().from(DashboardElement.class) + .queryList(); + } + private static List queryDashboardItems(Dashboard dashboard) { Where where = new Select().from(DashboardItem.class) .where(Condition.column(DashboardItem$Table diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DhisController.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DhisController.java index 9b30c272..7e7ae850 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DhisController.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DhisController.java @@ -155,4 +155,11 @@ public void syncDashboards() throws APIException { public void syncInterpretations() throws APIException { (new InterpretationController(mDhisApi)).syncInterpretations(); } + + public void pullDashboardImages(Context context) { + (new PullImageController(context)).pullDashboardImages(); + } + public void pullInterpretationImages(Context context) { + (new PullImageController(context)).pullInterpretationImages(); + } } diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/InterpretationController.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/InterpretationController.java index de78a2c8..787dbfd6 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/InterpretationController.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/InterpretationController.java @@ -573,6 +573,11 @@ private static List queryInterpretations() { .queryList(); } + public static List queryAllInterpretationElements() { + return new Select().from(InterpretationElement.class) + .queryList(); + } + private static List queryInterpretationUsers() { return new Select().from(User.class).queryList(); } diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/PullImageController.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/PullImageController.java new file mode 100644 index 00000000..bc9357aa --- /dev/null +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/PullImageController.java @@ -0,0 +1,82 @@ +package org.hisp.dhis.android.dashboard.api.controllers; + +import android.content.Context; + +import com.squareup.picasso.NetworkPolicy; + +import org.hisp.dhis.android.dashboard.api.models.DashboardElement; +import org.hisp.dhis.android.dashboard.api.models.DashboardItemContent; +import org.hisp.dhis.android.dashboard.api.models.Interpretation; +import org.hisp.dhis.android.dashboard.api.models.InterpretationElement; +import org.hisp.dhis.android.dashboard.api.network.APIException; +import org.hisp.dhis.android.dashboard.api.utils.PicassoProvider; + +import java.util.ArrayList; +import java.util.List; + +final class PullImageController { + Context mContext; + + public PullImageController(Context context) { + mContext = context; + } + + public void pullDashboardImages() throws APIException { + List requestList = new ArrayList<>(); + requestList = downloadDashboardImages(requestList); + downloadImages(requestList, mContext); + } + + public void pullInterpretationImages() throws APIException { + List requestList = new ArrayList<>(); + requestList = downloadInterpretationImages(requestList); + downloadImages(requestList, mContext); + } + + public static List downloadInterpretationImages(List requestList) { + for (InterpretationElement interpretationElement : InterpretationController + .queryAllInterpretationElements()) { + if (interpretationElement == null || interpretationElement.getType() == null) { + continue; + } + if (Interpretation.TYPE_CHART.equals(interpretationElement.getType())) { + requestList.add(DhisController.buildImageUrl("charts", interpretationElement.getUId())); + } else if (Interpretation.TYPE_MAP.equals(interpretationElement.getType())) { + requestList.add(DhisController.buildImageUrl("maps", interpretationElement.getUId())); + } + } + return requestList; + } + + public static List downloadDashboardImages(List requestList) { + for (DashboardElement element : DashboardController.queryAllDashboardElement()) { + if (element.getDashboardItem().getType() == null) { + continue; + } + + switch (element.getDashboardItem().getType()) { + case DashboardItemContent.TYPE_CHART: { + requestList.add(DhisController.buildImageUrl("charts", element.getUId())); + break; + } + case DashboardItemContent.TYPE_EVENT_CHART: { + requestList.add(DhisController.buildImageUrl("eventCharts", element.getUId())); + break; + } + case DashboardItemContent.TYPE_MAP: { + requestList.add(DhisController.buildImageUrl("maps", element.getUId())); + break; + } + } + } + return requestList; + } + + private static void downloadImages(final List requestUrlList, final Context context) { + for (int i = 0; i < requestUrlList.size(); i++) { + final String request = requestUrlList.get(i); + PicassoProvider.getInstance(context) + .load(request).networkPolicy(NetworkPolicy.NO_CACHE).fetch(); + } + } +} diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/persistence/preferences/ResourceType.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/persistence/preferences/ResourceType.java index 943a094a..aefdc8d4 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/persistence/preferences/ResourceType.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/persistence/preferences/ResourceType.java @@ -4,5 +4,5 @@ * @author Araz Abishov . */ public enum ResourceType { - DASHBOARDS_CONTENT, DASHBOARDS, INTERPRETATIONS, USERS, + DASHBOARDS_CONTENT, DASHBOARDS, INTERPRETATIONS, USERS, INTERPRETATION_IMAGES, DASHBOARD_IMAGES } diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/DhisService.java b/app/src/main/java/org/hisp/dhis/android/dashboard/DhisService.java index 60e6bb55..6213182e 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/DhisService.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/DhisService.java @@ -27,6 +27,7 @@ package org.hisp.dhis.android.dashboard; import android.app.Service; +import android.content.Context; import android.content.Intent; import android.os.Binder; import android.os.IBinder; @@ -54,6 +55,8 @@ public final class DhisService extends Service { public static final int SYNC_DASHBOARDS = 5; public static final int SYNC_DASHBOARD_CONTENT = 6; public static final int SYNC_INTERPRETATIONS = 7; + public static final int PULL_INTERPRETATION_IMAGES = 8; + public static final int PULL_DASHBOARD_IMAGES = 9; private final IBinder mBinder = new ServiceBinder(); private DhisController mDhisController; @@ -165,6 +168,28 @@ public Object execute() throws APIException { }); } + public void pullInterpretationImages(final Context context) { + JobExecutor.enqueueJob(new NetworkJob(PULL_INTERPRETATION_IMAGES, + ResourceType.INTERPRETATION_IMAGES) { + @Override + public Object execute() throws APIException { + mDhisController.pullInterpretationImages(context); + return new Object(); + } + }); + } + + public void pullDashboardImages(final Context context) { + JobExecutor.enqueueJob(new NetworkJob(PULL_DASHBOARD_IMAGES, + ResourceType.DASHBOARD_IMAGES) { + @Override + public Object execute() throws APIException { + mDhisController.pullDashboardImages(context); + return new Object(); + } + }); + } + public boolean isJobRunning(int jobId) { return JobExecutor.isJobRunning(jobId); } From a4186cb6797b2a15b4ce1d87a6254e219bc1a680 Mon Sep 17 00:00:00 2001 From: idelcano Date: Wed, 10 May 2017 22:34:17 +0200 Subject: [PATCH 13/69] Modify initial sync workflow to download all the data to allow offline mode --- .../dashboard/DashboardEmptyFragment.java | 14 +++++++++++++- .../fragments/dashboard/DashboardFragment.java | 10 ++++++++++ .../dashboard/DashboardViewPagerFragment.java | 18 +++++++++++++++++- .../InterpretationEmptyFragment.java | 4 ++++ .../interpretation/InterpretationFragment.java | 7 ++++++- 5 files changed, 50 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardEmptyFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardEmptyFragment.java index d12a13a2..608251dd 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardEmptyFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardEmptyFragment.java @@ -29,6 +29,7 @@ import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.widget.Toolbar; +import android.util.Log; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; @@ -133,7 +134,7 @@ public boolean onMenuItemClicked(MenuItem item) { private void syncDashboards() { if (isDhisServiceBound()) { - getDhisService().syncDashboardsAndContent(); + getDhisService().syncDashboards(); mProgressBar.setVisibility(View.VISIBLE); } } @@ -141,8 +142,19 @@ private void syncDashboards() { @Subscribe @SuppressWarnings("unused") public void onResponseReceived(NetworkJob.NetworkJobResult result) { + Log.d(TAG, "Received" + result.getResourceType()); if (result.getResourceType() == ResourceType.DASHBOARDS) { + getDhisService().syncDashboardContents(); + } + if (result.getResourceType() == ResourceType.DASHBOARDS_CONTENT) { + getDhisService().pullDashboardImages(getContext()); + } + if (result.getResourceType() == ResourceType.INTERPRETATIONS) { + getDhisService().pullInterpretationImages(getContext()); + } + if (result.getResourceType() == ResourceType.DASHBOARD_IMAGES) { mProgressBar.setVisibility(View.INVISIBLE); + getDhisService().syncInterpretations(); } } } diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardFragment.java index 680dec39..ac88db14 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardFragment.java @@ -36,6 +36,7 @@ import android.support.v7.widget.DefaultItemAnimator; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -44,8 +45,10 @@ import com.raizlabs.android.dbflow.sql.builder.Condition; import com.raizlabs.android.dbflow.sql.language.Select; +import com.squareup.otto.Subscribe; import org.hisp.dhis.android.dashboard.R; +import org.hisp.dhis.android.dashboard.api.job.NetworkJob; import org.hisp.dhis.android.dashboard.api.models.Access; import org.hisp.dhis.android.dashboard.api.models.Dashboard; import org.hisp.dhis.android.dashboard.api.models.DashboardElement; @@ -55,6 +58,7 @@ import org.hisp.dhis.android.dashboard.api.models.meta.State; import org.hisp.dhis.android.dashboard.api.persistence.loaders.DbLoader; import org.hisp.dhis.android.dashboard.api.persistence.loaders.Query; +import org.hisp.dhis.android.dashboard.api.persistence.preferences.ResourceType; import org.hisp.dhis.android.dashboard.api.utils.EventBusProvider; import org.hisp.dhis.android.dashboard.ui.activities.DashboardElementDetailActivity; import org.hisp.dhis.android.dashboard.ui.adapters.DashboardItemAdapter; @@ -76,6 +80,7 @@ public class DashboardFragment extends BaseFragment private static final String WRITE = "arg:write"; private static final String MANAGE = "arg:manage"; private static final String EXTERNALIZE = "arg:externalize"; + public static final String TAG = DashboardFragment.class.getSimpleName(); ViewSwitcher mViewSwitcher; @@ -168,6 +173,11 @@ public Loader> onCreateLoader(int id, Bundle args) { return null; } + @Subscribe + @SuppressWarnings("unused") + public void onResponseReceived(NetworkJob.NetworkJobResult result) { + Log.d(TAG, "Received " + result.getResourceType()); + } @Override public void onLoadFinished(Loader> loader, List dashboardItems) { diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardViewPagerFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardViewPagerFragment.java index 1b8e5815..da427918 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardViewPagerFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardViewPagerFragment.java @@ -35,6 +35,7 @@ import android.support.v4.content.Loader; import android.support.v4.view.ViewPager; import android.support.v7.widget.Toolbar; +import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; @@ -121,7 +122,11 @@ public boolean onMenuItemClick(MenuItem item) { } boolean isLoading = isDhisServiceBound() && - getDhisService().isJobRunning(DhisService.SYNC_DASHBOARDS); + getDhisService().isJobRunning(DhisService.SYNC_DASHBOARDS) + || + getDhisService().isJobRunning(DhisService.SYNC_DASHBOARD_CONTENT) + || + getDhisService().isJobRunning(DhisService.PULL_DASHBOARD_IMAGES); if ((savedInstanceState != null && savedInstanceState.getBoolean(IS_LOADING)) || isLoading) { mProgressBar.setVisibility(View.VISIBLE); @@ -260,8 +265,19 @@ private void syncDashboards() { @Subscribe @SuppressWarnings("unused") public void onResponseReceived(NetworkJob.NetworkJobResult result) { + Log.d(TAG, "Received " + result.getResourceType()); if (result.getResourceType() == ResourceType.DASHBOARDS) { + getDhisService().syncDashboardContents(); + } + if (result.getResourceType() == ResourceType.DASHBOARDS_CONTENT) { + getDhisService().pullDashboardImages(getContext()); + } + if (result.getResourceType() == ResourceType.INTERPRETATIONS) { + getDhisService().pullInterpretationImages(getContext()); + } + if (result.getResourceType() == ResourceType.DASHBOARD_IMAGES) { mProgressBar.setVisibility(View.INVISIBLE); + getDhisService().syncInterpretations(); } } diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationEmptyFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationEmptyFragment.java index d690e21d..37ef2c5f 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationEmptyFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationEmptyFragment.java @@ -3,6 +3,7 @@ import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.widget.Toolbar; +import android.util.Log; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; @@ -106,7 +107,10 @@ private void syncInterpretations() { @Subscribe @SuppressWarnings("unused") public void onResponseReceived(NetworkJob.NetworkJobResult result) { + Log.d(TAG, "Received " + result.getResourceType()); if (result.getResourceType() == ResourceType.INTERPRETATIONS) { + getDhisService().pullInterpretationImages(getContext()); + } else if (result.getResourceType() == ResourceType.INTERPRETATION_IMAGES) { mProgressBar.setVisibility(View.INVISIBLE); } } diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationFragment.java index 2bc6f0f4..70633d16 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationFragment.java @@ -36,6 +36,7 @@ import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; +import android.util.Log; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; @@ -271,7 +272,10 @@ public void onInterpretationCommentsClick(Interpretation interpretation) { @Subscribe @SuppressWarnings("unused") public void onResponseReceived(NetworkJob.NetworkJobResult result) { + Log.d(TAG, "Received " + result.getResourceType()); if (result.getResourceType() == ResourceType.INTERPRETATIONS) { + getDhisService().pullInterpretationImages(getContext()); + } else if (result.getResourceType() == ResourceType.INTERPRETATION_IMAGES) { mProgressBar.setVisibility(View.INVISIBLE); } } @@ -281,7 +285,8 @@ public void onResponseReceived(NetworkJob.NetworkJobResult result) { public void onUiEventReceived(UiEvent uiEvent) { if (uiEvent.getEventType() == UiEvent.UiEventType.SYNC_INTERPRETATIONS) { boolean isLoading = isDhisServiceBound() && - getDhisService().isJobRunning(DhisService.SYNC_INTERPRETATIONS); + getDhisService().isJobRunning(DhisService.SYNC_INTERPRETATIONS) + || getDhisService().isJobRunning(DhisService.PULL_INTERPRETATION_IMAGES); if (isLoading) { mProgressBar.setVisibility(View.VISIBLE); } else { From ed022c3add27947744a49b552855094c22f22eb9 Mon Sep 17 00:00:00 2001 From: idelcano Date: Tue, 16 May 2017 14:30:54 +0200 Subject: [PATCH 14/69] Added about us fragment with commit hash --- .gitignore | 3 + .../android/dashboard/AboutUsFragment.java | 117 ++++++++++++++++++ .../dashboard/ui/activities/MenuActivity.java | 5 + app/src/main/res/layout/fragment_about_us.xml | 112 +++++++++++++++++ app/src/main/res/menu/menu_drawer.xml | 4 + app/src/main/res/raw/description | 4 + app/src/main/res/values/dimens.xml | 2 + app/src/main/res/values/strings.xml | 19 +++ generate_last_commit.sh | 8 ++ 9 files changed, 274 insertions(+) create mode 100644 app/src/main/java/org/hisp/dhis/android/dashboard/AboutUsFragment.java create mode 100644 app/src/main/res/layout/fragment_about_us.xml create mode 100644 app/src/main/res/raw/description create mode 100644 generate_last_commit.sh diff --git a/.gitignore b/.gitignore index 23ee352e..ff7f2830 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ build #Windows *.db + +# Last commit file +app/src/main/res/raw/lastcommit.txt \ No newline at end of file diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/AboutUsFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/AboutUsFragment.java new file mode 100644 index 00000000..cf05dded --- /dev/null +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/AboutUsFragment.java @@ -0,0 +1,117 @@ +package org.hisp.dhis.android.dashboard; + +import android.app.AlertDialog; +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.text.Html; +import android.text.SpannableString; +import android.text.method.LinkMovementMethod; +import android.text.util.Linkify; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.hisp.dhis.android.dashboard.ui.fragments.BaseFragment; +import org.hisp.dhis.android.dashboard.ui.views.FontTextView; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import butterknife.Bind; +import butterknife.ButterKnife; +import android.support.v7.widget.Toolbar; + +public class AboutUsFragment extends BaseFragment { + + @Bind(R.id.toolbar) + Toolbar mToolbar; + + public static String getAppVersion() { + return String.valueOf(BuildConfig.VERSION_NAME); + } + + public static String getCommitHash(Context context) { + String stringCommit; + //Check if lastcommit.txt file exist, and if not exist show as unavailable. + int layoutId = context.getResources().getIdentifier("lastcommit", "raw", + context.getPackageName()); + if (layoutId == 0) { + stringCommit = context.getString(R.string.unavailable); + } else { + InputStream commit = context.getResources().openRawResource(layoutId); + stringCommit = convertFromInputStreamToString(commit).toString(); + } + return stringCommit; + } + + private SpannableString getDescriptionMessage(Context context) { + InputStream message = context.getResources().openRawResource(R.raw.description); + String stringMessage = convertFromInputStreamToString(message).toString(); + final SpannableString linkedMessage = new SpannableString(Html.fromHtml(stringMessage)); + Linkify.addLinks(linkedMessage, Linkify.EMAIL_ADDRESSES | Linkify.WEB_URLS); + return linkedMessage; + } + + private static StringBuilder convertFromInputStreamToString(InputStream inputStream) { + StringBuilder stringBuilder = new StringBuilder(); + + try { + BufferedReader r = new BufferedReader(new InputStreamReader(inputStream, "UTF-8")); + String line; + while ((line = r.readLine()) != null) { + stringBuilder.append(line + "\n"); + } + } catch (IOException e) { + Log.d("AUtils", String.format("Error reading inputStream [%s]", inputStream)); + e.printStackTrace(); + } + + return stringBuilder; + } + + public static String getCommitMessage(Context context) { + String stringCommit = getCommitHash(context); + + if (stringCommit.contains(context.getString(R.string.unavailable))) { + stringCommit = String.format(context.getString(R.string.last_commit), stringCommit); + stringCommit = stringCommit + " " + context.getText(R.string.lastcommit_unavailable); + } else { + stringCommit = String.format(context.getString(R.string.last_commit), stringCommit); + } + return stringCommit; + } + + public static String getVersionMessage(Context context) { + String version = getAppVersion(); + return String.format(context.getString(R.string.app_version), version); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_about_us, container, false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + ButterKnife.bind(this, view); + + ((FontTextView) view.findViewById(R.id.app_version)).setText(getVersionMessage(getContext())); + ((FontTextView) view.findViewById(R.id.commit_hash)).setText(getCommitMessage(getContext())); + ((FontTextView) view.findViewById(R.id.description)).setText(getDescriptionMessage(getContext())); + mToolbar.setNavigationIcon(R.mipmap.ic_menu); + mToolbar.setTitle(R.string.about_this_app); + mToolbar.setNavigationOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + toggleNavigationDrawer(); + } + }); + } +} diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/activities/MenuActivity.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/activities/MenuActivity.java index e1d56975..3986dcdd 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/activities/MenuActivity.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/activities/MenuActivity.java @@ -45,6 +45,7 @@ import android.view.View; import android.widget.TextView; +import org.hisp.dhis.android.dashboard.AboutUsFragment; import org.hisp.dhis.android.dashboard.R; import org.hisp.dhis.android.dashboard.api.models.UserAccount; import org.hisp.dhis.android.dashboard.api.persistence.loaders.DbLoader; @@ -131,6 +132,10 @@ public boolean onNavigationItemSelected(MenuItem menuItem) { attachFragmentDelayed(new SettingsFragment()); break; } + case R.id.menu_about_app: { + attachFragmentDelayed(new AboutUsFragment()); + break; + } /* case R.id.menu_about_item: { getDhisController().invalidateSession(); startActivity(new Intent(this, LauncherActivity.class)); diff --git a/app/src/main/res/layout/fragment_about_us.xml b/app/src/main/res/layout/fragment_about_us.xml new file mode 100644 index 00000000..b0a2e7e3 --- /dev/null +++ b/app/src/main/res/layout/fragment_about_us.xml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_drawer.xml b/app/src/main/res/menu/menu_drawer.xml index 4d8276ae..9d86ec69 100755 --- a/app/src/main/res/menu/menu_drawer.xml +++ b/app/src/main/res/menu/menu_drawer.xml @@ -46,6 +46,10 @@ android:id="@+id/menu_settings_item" android:icon="@mipmap/ic_settings" android:title="@string/settings"/> + + + + "About this app" + App version: %s + "Commit hash: %s" + "Unavailable" + This option requires some changes in the git repository.

+

Steps:

+

1. Please open or create the .git/hooks/post-commit and .git/hooks/post-checkout files.

+

2. If the file does not exist, add the following lines:

+

#!/bin/sh

+

gitPath=$(git rev-parse --show-toplevel)

+

sh ${gitPath}/generate_last_commit.sh

+

2. If the file already exists, you only need add the following lines at the end:

+

gitPath=$(git rev-parse --show-toplevel)

+

sh ${gitPath}/generate_last_commit.sh

+

3. Run "git checkout".

]]> +
diff --git a/generate_last_commit.sh b/generate_last_commit.sh new file mode 100644 index 00000000..08506669 --- /dev/null +++ b/generate_last_commit.sh @@ -0,0 +1,8 @@ +#!/bin/sh +gitPath=$(git rev-parse --show-toplevel) +filePath="${gitPath}/app/src/main/res/raw/lastcommit.txt" +echo "Last commit path $filePath" +commit=$(git log -1 HEAD --format=%H) +echo "Saving last commit: ${commit}" +echo ${commit} > $filePath +echo "Done." From 2bc28f1fc8d1f014a1eeeaa0b5adbcb098f3c118 Mon Sep 17 00:00:00 2001 From: idelcano Date: Tue, 16 May 2017 14:39:47 +0200 Subject: [PATCH 15/69] Added scroll in about fragment --- .../org/hisp/dhis/android/dashboard/AboutUsFragment.java | 6 ++++-- app/src/main/res/layout/fragment_about_us.xml | 6 +++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/AboutUsFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/AboutUsFragment.java index cf05dded..ff3b7c3a 100644 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/AboutUsFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/AboutUsFragment.java @@ -6,6 +6,7 @@ import android.support.annotation.Nullable; import android.text.Html; import android.text.SpannableString; +import android.text.Spanned; import android.text.method.LinkMovementMethod; import android.text.util.Linkify; import android.util.Log; @@ -74,7 +75,7 @@ private static StringBuilder convertFromInputStreamToString(InputStream inputStr return stringBuilder; } - public static String getCommitMessage(Context context) { + public static Spanned getCommitMessage(Context context) { String stringCommit = getCommitHash(context); if (stringCommit.contains(context.getString(R.string.unavailable))) { @@ -83,7 +84,8 @@ public static String getCommitMessage(Context context) { } else { stringCommit = String.format(context.getString(R.string.last_commit), stringCommit); } - return stringCommit; + + return Html.fromHtml(stringCommit); } public static String getVersionMessage(Context context) { diff --git a/app/src/main/res/layout/fragment_about_us.xml b/app/src/main/res/layout/fragment_about_us.xml index b0a2e7e3..be1dab8f 100644 --- a/app/src/main/res/layout/fragment_about_us.xml +++ b/app/src/main/res/layout/fragment_about_us.xml @@ -48,6 +48,10 @@ android:layout_margin="8dp" app:cardCornerRadius="2dp"> + + - + \ No newline at end of file From 5dfe1b4127f10e9734bbd74ae77744fef05552b7 Mon Sep 17 00:00:00 2001 From: idelcano Date: Tue, 16 May 2017 14:41:58 +0200 Subject: [PATCH 16/69] Added buddybuild last commit script --- buddybuild_postclone.sh | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 buddybuild_postclone.sh diff --git a/buddybuild_postclone.sh b/buddybuild_postclone.sh new file mode 100644 index 00000000..8eb58cb5 --- /dev/null +++ b/buddybuild_postclone.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +# Definitions +gitPath=$(git rev-parse --show-toplevel) + +# Generate last commit +sh ${gitPath}/generate_last_commit.sh \ No newline at end of file From 9282e5dad6818d5475b39fae6b2219e62c92d397 Mon Sep 17 00:00:00 2001 From: idelcano Date: Tue, 16 May 2017 16:33:09 +0200 Subject: [PATCH 17/69] refactor package of AboutUsFragment --- .../android/dashboard/ui/activities/MenuActivity.java | 2 +- .../dashboard/{ => ui/fragments}/AboutUsFragment.java | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) rename app/src/main/java/org/hisp/dhis/android/dashboard/{ => ui/fragments}/AboutUsFragment.java (96%) diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/activities/MenuActivity.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/activities/MenuActivity.java index 3986dcdd..c9c477b4 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/activities/MenuActivity.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/activities/MenuActivity.java @@ -45,7 +45,7 @@ import android.view.View; import android.widget.TextView; -import org.hisp.dhis.android.dashboard.AboutUsFragment; +import org.hisp.dhis.android.dashboard.ui.fragments.AboutUsFragment; import org.hisp.dhis.android.dashboard.R; import org.hisp.dhis.android.dashboard.api.models.UserAccount; import org.hisp.dhis.android.dashboard.api.persistence.loaders.DbLoader; diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/AboutUsFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/AboutUsFragment.java similarity index 96% rename from app/src/main/java/org/hisp/dhis/android/dashboard/AboutUsFragment.java rename to app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/AboutUsFragment.java index ff3b7c3a..e2535469 100644 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/AboutUsFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/AboutUsFragment.java @@ -1,20 +1,20 @@ -package org.hisp.dhis.android.dashboard; +package org.hisp.dhis.android.dashboard.ui.fragments; -import android.app.AlertDialog; import android.content.Context; import android.os.Bundle; import android.support.annotation.Nullable; +import android.support.v7.widget.Toolbar; import android.text.Html; import android.text.SpannableString; import android.text.Spanned; -import android.text.method.LinkMovementMethod; import android.text.util.Linkify; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.TextView; +import org.hisp.dhis.android.dashboard.BuildConfig; +import org.hisp.dhis.android.dashboard.R; import org.hisp.dhis.android.dashboard.ui.fragments.BaseFragment; import org.hisp.dhis.android.dashboard.ui.views.FontTextView; @@ -25,7 +25,6 @@ import butterknife.Bind; import butterknife.ButterKnife; -import android.support.v7.widget.Toolbar; public class AboutUsFragment extends BaseFragment { From e1e08b683a6849e08550c30ac8a243f6ffc992d9 Mon Sep 17 00:00:00 2001 From: idelcano Date: Tue, 16 May 2017 19:17:29 +0200 Subject: [PATCH 18/69] Remove log --- .../dhis/android/dashboard/ui/fragments/AboutUsFragment.java | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/AboutUsFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/AboutUsFragment.java index e2535469..e88fb2e7 100644 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/AboutUsFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/AboutUsFragment.java @@ -67,7 +67,6 @@ private static StringBuilder convertFromInputStreamToString(InputStream inputStr stringBuilder.append(line + "\n"); } } catch (IOException e) { - Log.d("AUtils", String.format("Error reading inputStream [%s]", inputStream)); e.printStackTrace(); } From 3e4d5a6591a94ae7d0102680571c2abcee7771d0 Mon Sep 17 00:00:00 2001 From: idelcano Date: Tue, 16 May 2017 19:18:13 +0200 Subject: [PATCH 19/69] Remove unused import --- .../dhis/android/dashboard/ui/fragments/AboutUsFragment.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/AboutUsFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/AboutUsFragment.java index e88fb2e7..46832f16 100644 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/AboutUsFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/AboutUsFragment.java @@ -8,14 +8,12 @@ import android.text.SpannableString; import android.text.Spanned; import android.text.util.Linkify; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import org.hisp.dhis.android.dashboard.BuildConfig; import org.hisp.dhis.android.dashboard.R; -import org.hisp.dhis.android.dashboard.ui.fragments.BaseFragment; import org.hisp.dhis.android.dashboard.ui.views.FontTextView; import java.io.BufferedReader; From be3f1e910c5dda528579384d4a8066527c1a7aba Mon Sep 17 00:00:00 2001 From: ifoche Date: Thu, 18 May 2017 19:43:11 +0200 Subject: [PATCH 20/69] added links to about us description --- app/src/main/res/raw/description | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/raw/description b/app/src/main/res/raw/description index 2cd9575a..c0db1c11 100644 --- a/app/src/main/res/raw/description +++ b/app/src/main/res/raw/description @@ -1,4 +1,4 @@ Android application for DHIS2 which provides basic dashboard functionality.
-Supports DHIS 2 version 2.25, 2.26 at the moment of release.
-Developed by University of Oslo(UiO).
-Maintained by EyeSeeTea.
+Supports DHIS 2 version 2.25, 2.26 at the moment of release.

+Developed by University of Oslo(UiO) (http://www.uio.no).

+Maintained by EyeSeeTea Ltd (http://eyeseetea.com).

From df468b03aa9f2351fe2a509bae6153630e6fcbdc Mon Sep 17 00:00:00 2001 From: ifoche Date: Thu, 18 May 2017 20:07:49 +0200 Subject: [PATCH 21/69] make links work on about us text --- app/src/main/res/layout/fragment_about_us.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/layout/fragment_about_us.xml b/app/src/main/res/layout/fragment_about_us.xml index be1dab8f..6ce814c7 100644 --- a/app/src/main/res/layout/fragment_about_us.xml +++ b/app/src/main/res/layout/fragment_about_us.xml @@ -107,6 +107,7 @@ android:text="@string/unavailable" android:textSize="15sp" android:textColor="@color/dark_grey_text" + android:autoLink="web" app:font="@string/medium_font_name" /> From dd9788ff86b3c99293d867a818a1426ccd05dbbd Mon Sep 17 00:00:00 2001 From: mplazaspalacio Date: Wed, 21 Jun 2017 10:12:29 +0200 Subject: [PATCH 22/69] Creating model class EventReport for make url of event reports tables html. --- .../dashboard/api/models/EventReport.java | 28 +++++++ .../dashboard/api/models/RelativePeriod.java | 81 +++++++++++++++++++ .../dashboard/api/models/UIDObject.java | 42 ++++++++++ 3 files changed, 151 insertions(+) create mode 100644 api/src/main/java/org/hisp/dhis/android/dashboard/api/models/EventReport.java create mode 100644 api/src/main/java/org/hisp/dhis/android/dashboard/api/models/RelativePeriod.java create mode 100644 api/src/main/java/org/hisp/dhis/android/dashboard/api/models/UIDObject.java diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/EventReport.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/EventReport.java new file mode 100644 index 00000000..5bb62694 --- /dev/null +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/EventReport.java @@ -0,0 +1,28 @@ +package org.hisp.dhis.android.dashboard.api.models; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.raizlabs.android.dbflow.annotation.Column; +import com.raizlabs.android.dbflow.annotation.PrimaryKey; +import com.raizlabs.android.dbflow.annotation.Table; + +import org.hisp.dhis.android.dashboard.api.models.meta.DbDhis; + +import java.util.List; + +@Table(databaseName = DbDhis.NAME) +public final class EventReport extends BaseIdentifiableObject { + @JsonIgnore + @Column(name = "id") + @PrimaryKey(autoincrement = true) + long id; + + @JsonProperty("id") + @Column(name = "uId") + String uId; + + UIDObject programStage; + List organisationUnits; + RelativePeriod relativePeriods; + +} diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/RelativePeriod.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/RelativePeriod.java new file mode 100644 index 00000000..bd377962 --- /dev/null +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/RelativePeriod.java @@ -0,0 +1,81 @@ +package org.hisp.dhis.android.dashboard.api.models; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.raizlabs.android.dbflow.annotation.Column; +import com.raizlabs.android.dbflow.annotation.PrimaryKey; +import com.raizlabs.android.dbflow.annotation.Table; + +import org.hisp.dhis.android.dashboard.api.models.meta.DbDhis; + +@Table(databaseName = DbDhis.NAME) +public final class RelativePeriod { + + @JsonIgnore + @Column(name = "id") + @PrimaryKey(autoincrement = true) + long id; + + boolean thisYear; + boolean quartersLastYear; + boolean last52Weeks; + boolean thisWeek; + boolean lastMonth; + boolean last14Days; + boolean monthsThisYear; + boolean last2SixMonths; + boolean yesterday; + boolean thisQuarter; + boolean last12Months; + boolean last5FinancialYears; + boolean thisSixMonth; + boolean lastQuarter; + boolean thisFinancialYear; + boolean last4Weeks; + boolean last3Months; + boolean thisDay; + boolean thisMonth; + boolean last5Years; + boolean last6BiMonths; + boolean lastFinancialYear; + boolean last6Months; + boolean last3Days; + boolean quartersThisYear; + boolean monthsLastYear; + boolean lastWeek; + boolean last7Days; + boolean thisBimonth; + boolean lastBimonth; + boolean lastSixMonth; + boolean lastYear; + boolean last12Weeks; + boolean last4Quarters; + + @JsonIgnore + private boolean[] periodsList; + + + public RelativePeriod() { + periodsList = new boolean[]{thisYear, quartersLastYear, last52Weeks, thisWeek, lastMonth, + last14Days, monthsThisYear, last2SixMonths, yesterday, thisQuarter, last12Months, + last5FinancialYears, thisSixMonth, lastQuarter, thisFinancialYear, last4Weeks, + last3Months, thisDay, thisMonth, last5Years, last6BiMonths, lastFinancialYear, + last6Months, last3Days, quartersThisYear, monthsLastYear, lastWeek, last7Days, + thisBimonth, lastBimonth, lastSixMonth, lastYear, last12Weeks, last4Quarters}; + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public boolean[] getPeriodsList() { + return periodsList; + } + + public void setPeriodsList(boolean[] periodsList) { + this.periodsList = periodsList; + } +} diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/UIDObject.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/UIDObject.java new file mode 100644 index 00000000..cfc66054 --- /dev/null +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/UIDObject.java @@ -0,0 +1,42 @@ +package org.hisp.dhis.android.dashboard.api.models; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.raizlabs.android.dbflow.annotation.Column; +import com.raizlabs.android.dbflow.annotation.PrimaryKey; +import com.raizlabs.android.dbflow.annotation.Table; + +import org.hisp.dhis.android.dashboard.api.models.meta.DbDhis; + +@Table(databaseName = DbDhis.NAME) +public class UIDObject { + @JsonIgnore + @Column(name = "id") + @PrimaryKey(autoincrement = true) + long id; + + @JsonProperty("id") + @Column(name = "uId") + String uId; + + public UIDObject(String uId) { + this.uId = uId; + } + + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getuId() { + return uId; + } + + public void setuId(String uId) { + this.uId = uId; + } +} From 002e31c2bac1b88200ce657f1e9a3056020bb700 Mon Sep 17 00:00:00 2001 From: Ignacio Foche Date: Wed, 21 Jun 2017 11:31:31 +0200 Subject: [PATCH 23/69] Feature codecov coverage integration (#17) * Add CodeCov integrated with Jacoco and Buddybuild * fix lint errors and gradlew problems * add traces to script * change inflater call to pass lint check * removing lint failures only for api module * Introduce abortOnError false --- api/build.gradle | 15 ++++++++++++++- app/build.gradle | 10 +++++++++- .../dashboard/ui/fragments/AccountFragment.java | 2 +- .../interpretation/InterpretationFragment.java | 2 +- .../android/dashboard/ui/views/FontButton.java | 2 +- .../android/dashboard/ui/views/FontCheckBox.java | 3 +-- .../android/dashboard/ui/views/FontEditText.java | 2 +- .../android/dashboard/ui/views/FontTextView.java | 2 +- buddybuild_postbuild.sh | 9 +++++++++ buddybuild_postclone.sh | 7 +++++-- build.gradle | 1 + 11 files changed, 44 insertions(+), 11 deletions(-) create mode 100644 buddybuild_postbuild.sh diff --git a/api/build.gradle b/api/build.gradle index 158c896d..4c76294e 100755 --- a/api/build.gradle +++ b/api/build.gradle @@ -1,5 +1,6 @@ apply plugin: 'com.android.library' apply plugin: 'com.neenbedankt.android-apt' +apply plugin: 'jacoco-android' android { compileSdkVersion 25 @@ -17,8 +18,20 @@ android { targetCompatibility JavaVersion.VERSION_1_7 } + packagingOptions { + exclude 'META-INF/LICENSE' + exclude 'META-INF/NOTICE' + } + lintOptions { - disable 'RtlSymmetry', 'RtlHardcoded' + disable 'RtlSymmetry', 'RtlHardcoded', 'RestrictedApi' + abortOnError false + } + + buildTypes { + debug { + testCoverageEnabled true + } } } diff --git a/app/build.gradle b/app/build.gradle index 3058b6e9..37ff3bd4 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'com.android.application' +apply plugin: 'jacoco-android' android { compileSdkVersion 25 @@ -23,7 +24,14 @@ android { } lintOptions { - disable 'RtlSymmetry', 'RtlHardcoded', 'ContentDescription' + disable 'RtlSymmetry', 'RtlHardcoded', 'ContentDescription', 'RestrictedApi' + abortOnError false + } + + buildTypes { + debug { + testCoverageEnabled true + } } } diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/AccountFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/AccountFragment.java index 784b9476..0fae6a12 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/AccountFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/AccountFragment.java @@ -88,7 +88,7 @@ public void onClick(View v) { }); mAdapter = new AccountFieldAdapter(getActivity().getApplicationContext(), - getLayoutInflater(savedInstanceState)); + (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE)); LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity()); diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationFragment.java index 70633d16..1708c5a0 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationFragment.java @@ -109,7 +109,7 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { ButterKnife.bind(this, view); mAdapter = new InterpretationAdapter(getActivity(), - getLayoutInflater(savedInstanceState), this); + (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE), this); final int spanCount = getResources().getInteger(R.integer.column_nums); diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/views/FontButton.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/views/FontButton.java index 48d0e288..be33d74e 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/views/FontButton.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/views/FontButton.java @@ -38,7 +38,7 @@ import org.hisp.dhis.android.dashboard.R; import org.hisp.dhis.android.dashboard.utils.TypefaceManager; -public class FontButton extends Button { +public class FontButton extends android.support.v7.widget.AppCompatButton { public FontButton(Context context) { super(context); diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/views/FontCheckBox.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/views/FontCheckBox.java index 4cff7b05..55a46e87 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/views/FontCheckBox.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/views/FontCheckBox.java @@ -33,12 +33,11 @@ import android.graphics.Paint; import android.graphics.Typeface; import android.util.AttributeSet; -import android.widget.CheckBox; import org.hisp.dhis.android.dashboard.R; import org.hisp.dhis.android.dashboard.utils.TypefaceManager; -public class FontCheckBox extends CheckBox { +public class FontCheckBox extends android.support.v7.widget.AppCompatCheckBox { public FontCheckBox(Context context) { super(context); } diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/views/FontEditText.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/views/FontEditText.java index ebb5dec3..090980f5 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/views/FontEditText.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/views/FontEditText.java @@ -38,7 +38,7 @@ import org.hisp.dhis.android.dashboard.R; import org.hisp.dhis.android.dashboard.utils.TypefaceManager; -public class FontEditText extends EditText { +public class FontEditText extends android.support.v7.widget.AppCompatEditText { public FontEditText(Context context) { super(context); diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/views/FontTextView.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/views/FontTextView.java index 1e1054a4..5a82ce9a 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/views/FontTextView.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/views/FontTextView.java @@ -38,7 +38,7 @@ import org.hisp.dhis.android.dashboard.R; import org.hisp.dhis.android.dashboard.utils.TypefaceManager; -public class FontTextView extends TextView { +public class FontTextView extends android.support.v7.widget.AppCompatTextView { public FontTextView(Context context) { super(context); diff --git a/buddybuild_postbuild.sh b/buddybuild_postbuild.sh new file mode 100644 index 00000000..507c8e47 --- /dev/null +++ b/buddybuild_postbuild.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +# Upload CodeCov report +echo "ConnectedCheck: " +./gradlew connectedCheck + +echo "Send report to CodeCov" +bash <(curl -s https://codecov.io/bash) -t ${CODECOV_TOKEN} + diff --git a/buddybuild_postclone.sh b/buddybuild_postclone.sh index 8eb58cb5..83e8e1f0 100644 --- a/buddybuild_postclone.sh +++ b/buddybuild_postclone.sh @@ -1,7 +1,10 @@ -#!/bin/sh +#!/usr/bin/env bash # Definitions gitPath=$(git rev-parse --show-toplevel) # Generate last commit -sh ${gitPath}/generate_last_commit.sh \ No newline at end of file +sh ${gitPath}/generate_last_commit.sh + +echo "Generate Test Coverage Report:" +./gradlew build jacocoTestReport assembleAndroidTest diff --git a/build.gradle b/build.gradle index b1ecb178..9bc13480 100755 --- a/build.gradle +++ b/build.gradle @@ -6,6 +6,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:2.3.0' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4' + classpath 'com.dicedmelon.gradle:jacoco-android:0.1.1' } } From 5dfe1bceb9d30bcfe3273672957e0239004a6f14 Mon Sep 17 00:00:00 2001 From: mplazaspalacio Date: Thu, 22 Jun 2017 15:13:54 +0200 Subject: [PATCH 24/69] En model class EventReport --- .../api/models/DataElementDimension.java | 23 ++++++ .../dashboard/api/models/EventReport.java | 74 ++++++++++++++++--- .../dashboard/api/models/RelativePeriod.java | 18 +++++ 3 files changed, 103 insertions(+), 12 deletions(-) create mode 100644 api/src/main/java/org/hisp/dhis/android/dashboard/api/models/DataElementDimension.java diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/DataElementDimension.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/DataElementDimension.java new file mode 100644 index 00000000..2a14f797 --- /dev/null +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/DataElementDimension.java @@ -0,0 +1,23 @@ +package org.hisp.dhis.android.dashboard.api.models; + +import com.raizlabs.android.dbflow.annotation.Table; + +import org.hisp.dhis.android.dashboard.api.models.meta.DbDhis; + +@Table(databaseName = DbDhis.NAME) +public class DataElementDimension { + + UIDObject dataElement; + + public DataElementDimension(UIDObject dataElement) { + this.dataElement = dataElement; + } + + public UIDObject getDataElement() { + return dataElement; + } + + public void setDataElement(UIDObject dataElement) { + this.dataElement = dataElement; + } +} diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/EventReport.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/EventReport.java index 5bb62694..d0140055 100644 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/EventReport.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/EventReport.java @@ -1,9 +1,5 @@ package org.hisp.dhis.android.dashboard.api.models; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.raizlabs.android.dbflow.annotation.Column; -import com.raizlabs.android.dbflow.annotation.PrimaryKey; import com.raizlabs.android.dbflow.annotation.Table; import org.hisp.dhis.android.dashboard.api.models.meta.DbDhis; @@ -12,17 +8,71 @@ @Table(databaseName = DbDhis.NAME) public final class EventReport extends BaseIdentifiableObject { - @JsonIgnore - @Column(name = "id") - @PrimaryKey(autoincrement = true) - long id; - - @JsonProperty("id") - @Column(name = "uId") - String uId; UIDObject programStage; List organisationUnits; RelativePeriod relativePeriods; + List dataElementDimensions; + UIDObject dataElementValueDimension; + String aggregationType; + String outputType; + + public UIDObject getProgramStage() { + return programStage; + } + + public void setProgramStage(UIDObject programStage) { + this.programStage = programStage; + } + + public List getOrganisationUnits() { + return organisationUnits; + } + + public void setOrganisationUnits( + List organisationUnits) { + this.organisationUnits = organisationUnits; + } + + public RelativePeriod getRelativePeriods() { + return relativePeriods; + } + + public void setRelativePeriods(RelativePeriod relativePeriods) { + this.relativePeriods = relativePeriods; + } + + public List getDataElementDimensions() { + return dataElementDimensions; + } + + public void setDataElementDimensions( + List dataElementDimensions) { + this.dataElementDimensions = dataElementDimensions; + } + + public UIDObject getDataElementValueDimension() { + return dataElementValueDimension; + } + + public void setDataElementValueDimension( + UIDObject dataElementValueDimension) { + this.dataElementValueDimension = dataElementValueDimension; + } + + public String getAggregationType() { + return aggregationType; + } + + public void setAggregationType(String aggregationType) { + this.aggregationType = aggregationType; + } + + public String getOutputType() { + return outputType; + } + public void setOutputType(String outputType) { + this.outputType = outputType; + } } diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/RelativePeriod.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/RelativePeriod.java index bd377962..eecbcf72 100644 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/RelativePeriod.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/RelativePeriod.java @@ -10,6 +10,17 @@ @Table(databaseName = DbDhis.NAME) public final class RelativePeriod { + @JsonIgnore + private static final String[] periodsStrings = + {"THIS_YEAR", "QUARTERS_LAST_YEAR", "LAST_52_WEEKS", "THIS_WEEK", "LAST_MONTH", + "LAST_14_DAYS", "MONTHS_THIS_YEAR", "LAST_2_SIXMONTHS", "YESTERDAY", + "THIS_QUARTER", "LAST_12_MONTHS", "LAST_5_FINANCIAL_YEARS", "THIS_SIX_MONTH", + "LAST_QUARTER", "THIS_FINANCIAL_YEAR", "LAST_4_WEEKS", "LAST_3_MONTHS", + "THIS_DAY", "THIS_MONTH", "LAST_5_YEARS", "LAST_6_BIMONTHS", + "LAST_FINANCIAL_YEAR", "LAST_6_MONTHS", "LAST_3_DAYS", "QUARTERS_THIS_YEAR", + "MONTHS_LAST_YEAR", "LAST_WEEK", "LAST_7_DAYS", "THIS_BIMONTH", "LAST_BIMONTH", + "LAST_SIX_MONTH", "LAST_YEAR", "LAST_12_WEEKS", "LAST_4_QUARTERS"}; + @JsonIgnore @Column(name = "id") @PrimaryKey(autoincrement = true) @@ -78,4 +89,11 @@ public boolean[] getPeriodsList() { public void setPeriodsList(boolean[] periodsList) { this.periodsList = periodsList; } + + public String getRelativePeriodString() { + for (int i = 0; i < periodsList.length; i++) { + if (periodsList[i]) return periodsStrings[i]; + } + return ""; + } } From d56ee2339f36e4fae68a74e1c2ce740153794faf Mon Sep 17 00:00:00 2001 From: mplazaspalacio Date: Fri, 23 Jun 2017 13:15:39 +0200 Subject: [PATCH 25/69] Solving model DBFlow bugs --- .../dashboard/api/models/DataElementDimension.java | 13 ++++++++++++- .../dashboard/api/models/RelativePeriod.java | 3 ++- .../android/dashboard/api/models/UIDObject.java | 6 +++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/DataElementDimension.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/DataElementDimension.java index 2a14f797..98a84a1e 100644 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/DataElementDimension.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/DataElementDimension.java @@ -1,14 +1,25 @@ package org.hisp.dhis.android.dashboard.api.models; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.raizlabs.android.dbflow.annotation.Column; +import com.raizlabs.android.dbflow.annotation.PrimaryKey; import com.raizlabs.android.dbflow.annotation.Table; +import com.raizlabs.android.dbflow.structure.BaseModel; import org.hisp.dhis.android.dashboard.api.models.meta.DbDhis; @Table(databaseName = DbDhis.NAME) -public class DataElementDimension { +public class DataElementDimension extends BaseModel { + @JsonIgnore + @Column + @PrimaryKey(autoincrement = true) + long id; UIDObject dataElement; + public DataElementDimension() { + } + public DataElementDimension(UIDObject dataElement) { this.dataElement = dataElement; } diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/RelativePeriod.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/RelativePeriod.java index eecbcf72..71dbda78 100644 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/RelativePeriod.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/RelativePeriod.java @@ -4,11 +4,12 @@ import com.raizlabs.android.dbflow.annotation.Column; import com.raizlabs.android.dbflow.annotation.PrimaryKey; import com.raizlabs.android.dbflow.annotation.Table; +import com.raizlabs.android.dbflow.structure.BaseModel; import org.hisp.dhis.android.dashboard.api.models.meta.DbDhis; @Table(databaseName = DbDhis.NAME) -public final class RelativePeriod { +public final class RelativePeriod extends BaseModel { @JsonIgnore private static final String[] periodsStrings = diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/UIDObject.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/UIDObject.java index cfc66054..d4cfc892 100644 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/UIDObject.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/UIDObject.java @@ -5,11 +5,12 @@ import com.raizlabs.android.dbflow.annotation.Column; import com.raizlabs.android.dbflow.annotation.PrimaryKey; import com.raizlabs.android.dbflow.annotation.Table; +import com.raizlabs.android.dbflow.structure.BaseModel; import org.hisp.dhis.android.dashboard.api.models.meta.DbDhis; @Table(databaseName = DbDhis.NAME) -public class UIDObject { +public class UIDObject extends BaseModel { @JsonIgnore @Column(name = "id") @PrimaryKey(autoincrement = true) @@ -19,6 +20,9 @@ public class UIDObject { @Column(name = "uId") String uId; + public UIDObject() { + } + public UIDObject(String uId) { this.uId = uId; } From 19527d7da4e80b43de64bc60fa945a74a346bcbc Mon Sep 17 00:00:00 2001 From: mplazaspalacio Date: Fri, 23 Jun 2017 18:56:36 +0200 Subject: [PATCH 26/69] Showing event report table in the detail. --- .../api/models/DataElementDimension.java | 10 + .../dashboard/api/models/EventReport.java | 11 + .../dashboard/api/models/RelativePeriod.java | 284 +++++++++++++++++- .../dashboard/api/network/DhisApi.java | 13 + .../DashboardElementDetailActivity.java | 6 + .../fragments/WebViewEventReportFragment.java | 162 ++++++++++ .../dashboard/DashboardFragment.java | 4 +- 7 files changed, 482 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewEventReportFragment.java diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/DataElementDimension.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/DataElementDimension.java index 98a84a1e..17acb1bb 100644 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/DataElementDimension.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/DataElementDimension.java @@ -15,6 +15,8 @@ public class DataElementDimension extends BaseModel { @PrimaryKey(autoincrement = true) long id; + String filter; + UIDObject dataElement; public DataElementDimension() { @@ -31,4 +33,12 @@ public UIDObject getDataElement() { public void setDataElement(UIDObject dataElement) { this.dataElement = dataElement; } + + public String getFilter() { + return filter; + } + + public void setFilter(String filter) { + this.filter = filter; + } } diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/EventReport.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/EventReport.java index d0140055..bdb3da82 100644 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/EventReport.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/EventReport.java @@ -9,6 +9,7 @@ @Table(databaseName = DbDhis.NAME) public final class EventReport extends BaseIdentifiableObject { + UIDObject program; UIDObject programStage; List organisationUnits; RelativePeriod relativePeriods; @@ -17,6 +18,14 @@ public final class EventReport extends BaseIdentifiableObject { String aggregationType; String outputType; + public UIDObject getProgram() { + return program; + } + + public void setProgram(UIDObject program) { + this.program = program; + } + public UIDObject getProgramStage() { return programStage; } @@ -75,4 +84,6 @@ public String getOutputType() { public void setOutputType(String outputType) { this.outputType = outputType; } + + } diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/RelativePeriod.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/RelativePeriod.java index 71dbda78..81b76ee8 100644 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/RelativePeriod.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/RelativePeriod.java @@ -67,12 +67,6 @@ public final class RelativePeriod extends BaseModel { public RelativePeriod() { - periodsList = new boolean[]{thisYear, quartersLastYear, last52Weeks, thisWeek, lastMonth, - last14Days, monthsThisYear, last2SixMonths, yesterday, thisQuarter, last12Months, - last5FinancialYears, thisSixMonth, lastQuarter, thisFinancialYear, last4Weeks, - last3Months, thisDay, thisMonth, last5Years, last6BiMonths, lastFinancialYear, - last6Months, last3Days, quartersThisYear, monthsLastYear, lastWeek, last7Days, - thisBimonth, lastBimonth, lastSixMonth, lastYear, last12Weeks, last4Quarters}; } public long getId() { @@ -92,9 +86,287 @@ public void setPeriodsList(boolean[] periodsList) { } public String getRelativePeriodString() { + periodsList = new boolean[]{thisYear, quartersLastYear, last52Weeks, thisWeek, lastMonth, + last14Days, monthsThisYear, last2SixMonths, yesterday, thisQuarter, last12Months, + last5FinancialYears, thisSixMonth, lastQuarter, thisFinancialYear, last4Weeks, + last3Months, thisDay, thisMonth, last5Years, last6BiMonths, lastFinancialYear, + last6Months, last3Days, quartersThisYear, monthsLastYear, lastWeek, last7Days, + thisBimonth, lastBimonth, lastSixMonth, lastYear, last12Weeks, last4Quarters}; for (int i = 0; i < periodsList.length; i++) { if (periodsList[i]) return periodsStrings[i]; } return ""; } + + public boolean isThisYear() { + return thisYear; + } + + public void setThisYear(boolean thisYear) { + this.thisYear = thisYear; + } + + public boolean isQuartersLastYear() { + return quartersLastYear; + } + + public void setQuartersLastYear(boolean quartersLastYear) { + this.quartersLastYear = quartersLastYear; + } + + public boolean isLast52Weeks() { + return last52Weeks; + } + + public void setLast52Weeks(boolean last52Weeks) { + this.last52Weeks = last52Weeks; + } + + public boolean isThisWeek() { + return thisWeek; + } + + public void setThisWeek(boolean thisWeek) { + this.thisWeek = thisWeek; + } + + public boolean isLastMonth() { + return lastMonth; + } + + public void setLastMonth(boolean lastMonth) { + this.lastMonth = lastMonth; + } + + public boolean isLast14Days() { + return last14Days; + } + + public void setLast14Days(boolean last14Days) { + this.last14Days = last14Days; + } + + public boolean isMonthsThisYear() { + return monthsThisYear; + } + + public void setMonthsThisYear(boolean monthsThisYear) { + this.monthsThisYear = monthsThisYear; + } + + public boolean isLast2SixMonths() { + return last2SixMonths; + } + + public void setLast2SixMonths(boolean last2SixMonths) { + this.last2SixMonths = last2SixMonths; + } + + public boolean isYesterday() { + return yesterday; + } + + public void setYesterday(boolean yesterday) { + this.yesterday = yesterday; + } + + public boolean isThisQuarter() { + return thisQuarter; + } + + public void setThisQuarter(boolean thisQuarter) { + this.thisQuarter = thisQuarter; + } + + public boolean isLast12Months() { + return last12Months; + } + + public void setLast12Months(boolean last12Months) { + this.last12Months = last12Months; + } + + public boolean isLast5FinancialYears() { + return last5FinancialYears; + } + + public void setLast5FinancialYears(boolean last5FinancialYears) { + this.last5FinancialYears = last5FinancialYears; + } + + public boolean isThisSixMonth() { + return thisSixMonth; + } + + public void setThisSixMonth(boolean thisSixMonth) { + this.thisSixMonth = thisSixMonth; + } + + public boolean isLastQuarter() { + return lastQuarter; + } + + public void setLastQuarter(boolean lastQuarter) { + this.lastQuarter = lastQuarter; + } + + public boolean isThisFinancialYear() { + return thisFinancialYear; + } + + public void setThisFinancialYear(boolean thisFinancialYear) { + this.thisFinancialYear = thisFinancialYear; + } + + public boolean isLast4Weeks() { + return last4Weeks; + } + + public void setLast4Weeks(boolean last4Weeks) { + this.last4Weeks = last4Weeks; + } + + public boolean isLast3Months() { + return last3Months; + } + + public void setLast3Months(boolean last3Months) { + this.last3Months = last3Months; + } + + public boolean isThisDay() { + return thisDay; + } + + public void setThisDay(boolean thisDay) { + this.thisDay = thisDay; + } + + public boolean isThisMonth() { + return thisMonth; + } + + public void setThisMonth(boolean thisMonth) { + this.thisMonth = thisMonth; + } + + public boolean isLast5Years() { + return last5Years; + } + + public void setLast5Years(boolean last5Years) { + this.last5Years = last5Years; + } + + public boolean isLast6BiMonths() { + return last6BiMonths; + } + + public void setLast6BiMonths(boolean last6BiMonths) { + this.last6BiMonths = last6BiMonths; + } + + public boolean isLastFinancialYear() { + return lastFinancialYear; + } + + public void setLastFinancialYear(boolean lastFinancialYear) { + this.lastFinancialYear = lastFinancialYear; + } + + public boolean isLast6Months() { + return last6Months; + } + + public void setLast6Months(boolean last6Months) { + this.last6Months = last6Months; + } + + public boolean isLast3Days() { + return last3Days; + } + + public void setLast3Days(boolean last3Days) { + this.last3Days = last3Days; + } + + public boolean isQuartersThisYear() { + return quartersThisYear; + } + + public void setQuartersThisYear(boolean quartersThisYear) { + this.quartersThisYear = quartersThisYear; + } + + public boolean isMonthsLastYear() { + return monthsLastYear; + } + + public void setMonthsLastYear(boolean monthsLastYear) { + this.monthsLastYear = monthsLastYear; + } + + public boolean isLastWeek() { + return lastWeek; + } + + public void setLastWeek(boolean lastWeek) { + this.lastWeek = lastWeek; + } + + public boolean isLast7Days() { + return last7Days; + } + + public void setLast7Days(boolean last7Days) { + this.last7Days = last7Days; + } + + public boolean isThisBimonth() { + return thisBimonth; + } + + public void setThisBimonth(boolean thisBimonth) { + this.thisBimonth = thisBimonth; + } + + public boolean isLastBimonth() { + return lastBimonth; + } + + public void setLastBimonth(boolean lastBimonth) { + this.lastBimonth = lastBimonth; + } + + public boolean isLastSixMonth() { + return lastSixMonth; + } + + public void setLastSixMonth(boolean lastSixMonth) { + this.lastSixMonth = lastSixMonth; + } + + public boolean isLastYear() { + return lastYear; + } + + public void setLastYear(boolean lastYear) { + this.lastYear = lastYear; + } + + public boolean isLast12Weeks() { + return last12Weeks; + } + + public void setLast12Weeks(boolean last12Weeks) { + this.last12Weeks = last12Weeks; + } + + public boolean isLast4Quarters() { + return last4Quarters; + } + + public void setLast4Quarters(boolean last4Quarters) { + this.last4Quarters = last4Quarters; + } } diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/network/DhisApi.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/network/DhisApi.java index c7a77540..82201bd9 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/network/DhisApi.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/network/DhisApi.java @@ -31,6 +31,7 @@ import org.hisp.dhis.android.dashboard.api.models.Dashboard; import org.hisp.dhis.android.dashboard.api.models.DashboardItem; import org.hisp.dhis.android.dashboard.api.models.DashboardItemContent; +import org.hisp.dhis.android.dashboard.api.models.EventReport; import org.hisp.dhis.android.dashboard.api.models.Interpretation; import org.hisp.dhis.android.dashboard.api.models.SystemInfo; import org.hisp.dhis.android.dashboard.api.models.UserAccount; @@ -141,6 +142,17 @@ Response deleteDashboardItemContent(@Path("dashboardUid") String dashboardUid, @GET("/reportTables/{id}/data.html") Response getReportTableData(@Path("id") String id); + @Headers("Accept: application/json") + @GET("/eventReports/{id}") + EventReport getEventReport(@Path("id") String id); + + @Headers("Accept: application/text") + @GET("/25/analytics/events/query/{program}" + + ".html+css?displayProperty=NAME") + Response getEventReportTableData(@Path("program") String program, + @Query("stage") String programStage, + @Query("dimension") List dimensions); + @GET("/eventReports?paging=false") @Headers("Accept: application/json") Map> getEventReports(@QueryMap Map queryParams); @@ -207,4 +219,5 @@ Response putInterpretationComment(@Path("interpretationUid") String interpretati @DELETE("/interpretations/{interpretationUid}/comments/{commentUid}") Response deleteInterpretationComment(@Path("interpretationUid") String interpretationUid, @Path("commentUid") String commentUid); + } \ No newline at end of file diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/activities/DashboardElementDetailActivity.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/activities/DashboardElementDetailActivity.java index 82c72e1b..82e9ee5b 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/activities/DashboardElementDetailActivity.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/activities/DashboardElementDetailActivity.java @@ -47,6 +47,7 @@ import org.hisp.dhis.android.dashboard.api.models.InterpretationElement; import org.hisp.dhis.android.dashboard.api.models.InterpretationElement$Table; import org.hisp.dhis.android.dashboard.ui.fragments.ImageViewFragment; +import org.hisp.dhis.android.dashboard.ui.fragments.WebViewEventReportFragment; import org.hisp.dhis.android.dashboard.ui.fragments.WebViewFragment; import butterknife.Bind; @@ -148,6 +149,11 @@ private void handleDashboardElement(DashboardElement element) { attachFragment(WebViewFragment.newInstance(elementId)); break; } + case DashboardItemContent.TYPE_EVENT_REPORT: { + String elementId = element.getUId(); + attachFragment(WebViewEventReportFragment.newInstance(elementId)); + break; + } } } diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewEventReportFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewEventReportFragment.java new file mode 100644 index 00000000..8da56a4a --- /dev/null +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewEventReportFragment.java @@ -0,0 +1,162 @@ +package org.hisp.dhis.android.dashboard.ui.fragments; + +import static android.text.TextUtils.isEmpty; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.WebView; + +import org.hisp.dhis.android.dashboard.DhisApplication; +import org.hisp.dhis.android.dashboard.R; +import org.hisp.dhis.android.dashboard.api.controllers.DhisController; +import org.hisp.dhis.android.dashboard.api.job.Job; +import org.hisp.dhis.android.dashboard.api.job.JobExecutor; +import org.hisp.dhis.android.dashboard.api.models.DataElementDimension; +import org.hisp.dhis.android.dashboard.api.models.EventReport; +import org.hisp.dhis.android.dashboard.api.models.meta.ResponseHolder; +import org.hisp.dhis.android.dashboard.api.network.APIException; +import org.hisp.dhis.android.dashboard.api.network.DhisApi; +import org.hisp.dhis.android.dashboard.api.network.RepoManager; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + +import butterknife.Bind; +import butterknife.ButterKnife; +import retrofit.mime.TypedInput; + +public class WebViewEventReportFragment extends BaseFragment { + private static final String DASHBOARD_ELEMENT_ID = "arg:dashboardElementId"; + + @Bind(R.id.web_view_content) + WebView mWebView; + + @Bind(R.id.container_layout_progress_bar) + View mProgressBarContainer; + + public static WebViewEventReportFragment newInstance(String id) { + Bundle args = new Bundle(); + args.putString(DASHBOARD_ELEMENT_ID, id); + + WebViewEventReportFragment fragment = new WebViewEventReportFragment(); + fragment.setArguments(args); + + return fragment; + } + + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_web_view, container, false); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + ButterKnife.bind(this, view); + + mWebView.getSettings().setBuiltInZoomControls(true); + if (getArguments() != null && !isEmpty(getArguments() + .getString(DASHBOARD_ELEMENT_ID))) { + JobExecutor.enqueueJob(new GetEventReportTableJob(this, getArguments() + .getString(DASHBOARD_ELEMENT_ID))); + } + } + + public void onDataDownloaded(ResponseHolder data) { + mProgressBarContainer.setVisibility(View.GONE); + + if (data.getApiException() == null) { + mWebView.loadData(data.getItem(), "text/html", "UTF-8"); + } else { + if (isAdded()) { + ((DhisApplication) (getActivity().getApplication())) + .showApiExceptionMessage(data.getApiException()); + } + } + } + + static class GetEventReportTableJob extends Job> { + static final int JOB_ID = 1546489; + + final WeakReference mFragmentRef; + final String mDashboardElementId; + + public GetEventReportTableJob(WebViewEventReportFragment fragment, + String dashboardElementId) { + super(JOB_ID); + + mFragmentRef = new WeakReference<>(fragment); + mDashboardElementId = dashboardElementId; + } + + + static String readInputStream(TypedInput in) { + StringBuilder builder = new StringBuilder(); + try { + BufferedReader bufferedStream + = new BufferedReader(new InputStreamReader(in.in())); + try { + String line; + while ((line = bufferedStream.readLine()) != null) { + builder.append(line); + builder.append('\n'); + } + return builder.toString(); + } finally { + bufferedStream.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + return builder.toString(); + } + + @Override + public ResponseHolder inBackground() { + ResponseHolder responseHolder = new ResponseHolder<>(); + EventReport eventReport; + + try { + DhisApi dhisApi = RepoManager.createService( + DhisController.getInstance().getServerUrl(), + DhisController.getInstance().getUserCredentials()); + eventReport = dhisApi.getEventReport(mDashboardElementId); + responseHolder.setItem(readInputStream( + dhisApi.getEventReportTableData(eventReport.getProgram().getuId(), + eventReport.getProgramStage().getuId(), + getDimensions(eventReport)).getBody())); + } catch (APIException exception) { + responseHolder.setApiException(exception); + } + + return responseHolder; + } + + private List getDimensions(EventReport eventReport) { + List dimensions = new ArrayList<>(); + dimensions.add("pe:" + eventReport.getRelativePeriods().getRelativePeriodString()); + dimensions.add("ou:" + eventReport.getOrganisationUnits().get(0).getuId()); + for (DataElementDimension dimension : eventReport.getDataElementDimensions()) { + String dimensionUID = ""; + dimensionUID += dimension.getDataElement().getuId(); + if (dimension.getFilter() != null && !dimension.getFilter().isEmpty()) { + dimensionUID += ":" + dimension.getFilter(); + } + dimensions.add(dimensionUID); + } + return dimensions; + } + + @Override + public void onFinish(ResponseHolder result) { + if (mFragmentRef.get() != null) { + mFragmentRef.get().onDataDownloaded(result); + } + } + } +} diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardFragment.java index ac88db14..fa1a8aa7 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardFragment.java @@ -58,7 +58,6 @@ import org.hisp.dhis.android.dashboard.api.models.meta.State; import org.hisp.dhis.android.dashboard.api.persistence.loaders.DbLoader; import org.hisp.dhis.android.dashboard.api.persistence.loaders.Query; -import org.hisp.dhis.android.dashboard.api.persistence.preferences.ResourceType; import org.hisp.dhis.android.dashboard.api.utils.EventBusProvider; import org.hisp.dhis.android.dashboard.ui.activities.DashboardElementDetailActivity; import org.hisp.dhis.android.dashboard.ui.adapters.DashboardItemAdapter; @@ -209,7 +208,8 @@ public void onContentClick(DashboardElement element) { case DashboardItemContent.TYPE_CHART: case DashboardItemContent.TYPE_EVENT_CHART: case DashboardItemContent.TYPE_MAP: - case DashboardItemContent.TYPE_REPORT_TABLE: { + case DashboardItemContent.TYPE_REPORT_TABLE: + case DashboardItemContent.TYPE_EVENT_REPORT: { Intent intent = DashboardElementDetailActivity .newIntentForDashboardElement(getActivity(), element.getId()); startActivity(intent); From bbd320481f2188aadaed60689114e04d60f56be2 Mon Sep 17 00:00:00 2001 From: idelcano Date: Mon, 26 Jun 2017 14:42:37 +0200 Subject: [PATCH 27/69] Added width/height image size preference --- .../api/controllers/DhisController.java | 24 +++++-- .../api/controllers/PullImageController.java | 12 ++-- .../preferences/SettingsManager.java | 34 +++++++++ .../DashboardElementDetailActivity.java | 19 ++--- .../ui/adapters/DashboardItemAdapter.java | 10 +-- .../ui/adapters/InterpretationAdapter.java | 4 +- .../ui/fragments/ImageViewFragment.java | 4 +- .../ui/fragments/SettingsFragment.java | 44 +++++++++++- app/src/main/res/layout/fragment_settings.xml | 69 ++++++++++++++++++- app/src/main/res/values/colors.xml | 2 + app/src/main/res/values/dimens.xml | 1 + app/src/main/res/values/strings.xml | 6 ++ 12 files changed, 199 insertions(+), 30 deletions(-) create mode 100644 api/src/main/java/org/hisp/dhis/android/dashboard/api/persistence/preferences/SettingsManager.java diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DhisController.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DhisController.java index 7e7ae850..5b19cff9 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DhisController.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DhisController.java @@ -28,6 +28,8 @@ package org.hisp.dhis.android.dashboard.api.controllers; +import static org.hisp.dhis.android.dashboard.api.utils.Preconditions.isNull; + import android.content.Context; import com.raizlabs.android.dbflow.config.FlowManager; @@ -41,8 +43,7 @@ import org.hisp.dhis.android.dashboard.api.network.RepoManager; import org.hisp.dhis.android.dashboard.api.persistence.preferences.DateTimeManager; import org.hisp.dhis.android.dashboard.api.persistence.preferences.LastUpdatedManager; - -import static org.hisp.dhis.android.dashboard.api.utils.Preconditions.isNull; +import org.hisp.dhis.android.dashboard.api.persistence.preferences.SettingsManager; public class DhisController { private static DhisController mDhisController; @@ -73,11 +74,26 @@ public static DhisController getInstance() { return mDhisController; } - public static String buildImageUrl(String resource, String id) { + public static String buildImageUrl(String resource, String id, Context context) { + String height = "320"; + String width = "480"; + + String widthUserPreference = SettingsManager.getInstance(context).getPreference( + (SettingsManager.CHART_WIDTH), "0"); + String heightUserPreference = SettingsManager.getInstance(context).getPreference( + (SettingsManager.CHART_WIDTH), "0"); + if (widthUserPreference != null && !widthUserPreference.equals("") && Integer.parseInt( + widthUserPreference) > 480) { + width = widthUserPreference; + } + if (heightUserPreference != null && !heightUserPreference.equals("") && Integer.parseInt( + heightUserPreference) > 320) { + height = heightUserPreference; + } return getInstance().getServerUrl().newBuilder() .addPathSegment("api").addPathSegment(resource).addPathSegment(id).addPathSegment( "data.png") - .addQueryParameter("width", "480").addQueryParameter("height", "320") + .addQueryParameter("width", width).addQueryParameter("height", height) .toString(); } diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/PullImageController.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/PullImageController.java index bc9357aa..41b6ca7b 100644 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/PullImageController.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/PullImageController.java @@ -15,7 +15,7 @@ import java.util.List; final class PullImageController { - Context mContext; + static Context mContext; public PullImageController(Context context) { mContext = context; @@ -40,9 +40,9 @@ public static List downloadInterpretationImages(List requestList continue; } if (Interpretation.TYPE_CHART.equals(interpretationElement.getType())) { - requestList.add(DhisController.buildImageUrl("charts", interpretationElement.getUId())); + requestList.add(DhisController.buildImageUrl("charts", interpretationElement.getUId(), mContext)); } else if (Interpretation.TYPE_MAP.equals(interpretationElement.getType())) { - requestList.add(DhisController.buildImageUrl("maps", interpretationElement.getUId())); + requestList.add(DhisController.buildImageUrl("maps", interpretationElement.getUId(), mContext)); } } return requestList; @@ -56,15 +56,15 @@ public static List downloadDashboardImages(List requestList) { switch (element.getDashboardItem().getType()) { case DashboardItemContent.TYPE_CHART: { - requestList.add(DhisController.buildImageUrl("charts", element.getUId())); + requestList.add(DhisController.buildImageUrl("charts", element.getUId(), mContext)); break; } case DashboardItemContent.TYPE_EVENT_CHART: { - requestList.add(DhisController.buildImageUrl("eventCharts", element.getUId())); + requestList.add(DhisController.buildImageUrl("eventCharts", element.getUId(), mContext)); break; } case DashboardItemContent.TYPE_MAP: { - requestList.add(DhisController.buildImageUrl("maps", element.getUId())); + requestList.add(DhisController.buildImageUrl("maps", element.getUId(), mContext)); break; } } diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/persistence/preferences/SettingsManager.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/persistence/preferences/SettingsManager.java new file mode 100644 index 00000000..a8793791 --- /dev/null +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/persistence/preferences/SettingsManager.java @@ -0,0 +1,34 @@ +package org.hisp.dhis.android.dashboard.api.persistence.preferences; + +import android.content.Context; +import android.content.SharedPreferences; + +public class SettingsManager { + public static final String CHART_WIDTH = "key:chart_width"; + public static final String CHART_HEIGHT = "key:chart_height"; + private static final String PREFERENCES = "preferences:settings"; + private static SettingsManager mSettingsManager = null; + private SharedPreferences mPrefs; + + public SettingsManager(Context context) { + mPrefs = context.getSharedPreferences(SettingsManager.PREFERENCES, + Context.MODE_PRIVATE); + } + + public static SettingsManager getInstance(Context context) { + if (mSettingsManager == null) { + mSettingsManager = new SettingsManager(context); + } + return mSettingsManager; + } + + public void setPreference(String key, String value) { + mPrefs.edit().putString(key, value).commit(); + } + + public String getPreference(String key, String defaultValue) { + return mPrefs.getString(key, defaultValue); + } + + +} diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/activities/DashboardElementDetailActivity.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/activities/DashboardElementDetailActivity.java index 82c72e1b..85f8332c 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/activities/DashboardElementDetailActivity.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/activities/DashboardElementDetailActivity.java @@ -29,6 +29,7 @@ package org.hisp.dhis.android.dashboard.ui.activities; import android.app.Activity; +import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.Fragment; @@ -107,7 +108,7 @@ protected void onPostCreate(Bundle savedInstanceState) { .where(Condition.column(DashboardElement$Table.ID) .is(getDashboardElementId())) .querySingle(); - handleDashboardElement(element); + handleDashboardElement(element, getApplicationContext()); } if (interpretationElementId > 0) { @@ -116,11 +117,11 @@ protected void onPostCreate(Bundle savedInstanceState) { .where(Condition.column(InterpretationElement$Table .ID).is(interpretationElementId)) .querySingle(); - handleInterpretationElement(element); + handleInterpretationElement(element, getApplicationContext()); } } - private void handleDashboardElement(DashboardElement element) { + private void handleDashboardElement(DashboardElement element, Context context) { if (element == null || element.getDashboardItem() == null) { return; @@ -129,17 +130,17 @@ private void handleDashboardElement(DashboardElement element) { mToolbar.setTitle(element.getDisplayName()); switch (element.getDashboardItem().getType()) { case DashboardItemContent.TYPE_CHART: { - String request = DhisController.getInstance().buildImageUrl("charts", element.getUId()); + String request = DhisController.getInstance().buildImageUrl("charts", element.getUId(), context); attachFragment(ImageViewFragment.newInstance(request)); break; } case DashboardItemContent.TYPE_EVENT_CHART: { - String request = DhisController.getInstance().buildImageUrl("eventCharts", element.getUId()); + String request = DhisController.getInstance().buildImageUrl("eventCharts", element.getUId(), context); attachFragment(ImageViewFragment.newInstance(request)); break; } case DashboardItemContent.TYPE_MAP: { - String request = DhisController.getInstance().buildImageUrl("maps", element.getUId()); + String request = DhisController.getInstance().buildImageUrl("maps", element.getUId(), context); attachFragment(ImageViewFragment.newInstance(request)); break; } @@ -151,7 +152,7 @@ private void handleDashboardElement(DashboardElement element) { } } - private void handleInterpretationElement(InterpretationElement element) { + private void handleInterpretationElement(InterpretationElement element, Context context) { if (element == null || element.getInterpretation() == null) { return; } @@ -159,12 +160,12 @@ private void handleInterpretationElement(InterpretationElement element) { mToolbar.setTitle(element.getDisplayName()); switch (element.getInterpretation().getType()) { case Interpretation.TYPE_CHART: { - String request = DhisController.getInstance().buildImageUrl("charts", element.getUId()); + String request = DhisController.getInstance().buildImageUrl("charts", element.getUId(), context); attachFragment(ImageViewFragment.newInstance(request)); break; } case Interpretation.TYPE_MAP: { - String request = DhisController.getInstance().buildImageUrl("maps", element.getUId()); + String request = DhisController.getInstance().buildImageUrl("maps", element.getUId(), context); attachFragment(ImageViewFragment.newInstance(request)); break; } diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java index 9f1859b7..812e9d74 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java @@ -285,7 +285,7 @@ private IElementContentViewHolder onCreateElementContentViewHolder(ViewGroup par private void onBindElementContentViewHolder(IElementContentViewHolder holder, int viewType, int position) { switch (viewType) { case ITEM_WITH_IMAGE_TYPE: { - handleItemsWithImages((ImageItemViewHolder) holder, getItem(position)); + handleItemsWithImages((ImageItemViewHolder) holder, getItem(position), getContext()); break; } case ITEM_WITH_TABLE_TYPE: { @@ -300,18 +300,18 @@ private void onBindElementContentViewHolder(IElementContentViewHolder holder, in } /* builds the URL to image data and loads it by means of Picasso. */ - private void handleItemsWithImages(ImageItemViewHolder holder, DashboardItem item) { + private void handleItemsWithImages(ImageItemViewHolder holder, DashboardItem item, Context context) { DashboardElement element = null; String request = null; if (DashboardItemContent.TYPE_CHART.equals(item.getType()) && item.getChart() != null) { element = item.getChart(); - request = DhisController.getInstance().buildImageUrl("charts", element.getUId()); + request = DhisController.getInstance().buildImageUrl("charts", element.getUId(), context); } else if (DashboardItemContent.TYPE_MAP.equals(item.getType()) && item.getMap() != null) { element = item.getMap(); - request = DhisController.getInstance().buildImageUrl("maps", element.getUId()); + request = DhisController.getInstance().buildImageUrl("maps", element.getUId(), context); } else if (DashboardItemContent.TYPE_EVENT_CHART.equals(item.getType()) && item.getEventChart() != null) { element = item.getEventChart(); - request = DhisController.getInstance().buildImageUrl("eventCharts", element.getUId()); + request = DhisController.getInstance().buildImageUrl("eventCharts", element.getUId(), context); } holder.listener.setDashboardElement(element); diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationAdapter.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationAdapter.java index b4eda684..4718ac82 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationAdapter.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationAdapter.java @@ -216,10 +216,10 @@ private void handleItemsWithImages(ImageItemViewHolder holder, Interpretation it String request = null; if (Interpretation.TYPE_CHART.equals(item.getType()) && item.getChart() != null) { InterpretationElement element = item.getChart(); - request = DhisController.getInstance().buildImageUrl("charts", element.getUId()); + request = DhisController.getInstance().buildImageUrl("charts", element.getUId(), getContext()); } else if (Interpretation.TYPE_MAP.equals(item.getType()) && item.getMap() != null) { InterpretationElement element = item.getMap(); - request = DhisController.getInstance().buildImageUrl("maps", element.getUId()); + request = DhisController.getInstance().buildImageUrl("maps", element.getUId(), getContext()); } holder.listener.setInterpretation(item); diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/ImageViewFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/ImageViewFragment.java index 68d54850..70bfa160 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/ImageViewFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/ImageViewFragment.java @@ -36,6 +36,7 @@ import android.widget.ImageView; import org.hisp.dhis.android.dashboard.R; +import org.hisp.dhis.android.dashboard.api.persistence.preferences.SettingsManager; import org.hisp.dhis.android.dashboard.api.utils.PicassoProvider; import uk.co.senab.photoview.PhotoViewAttacher; @@ -60,7 +61,8 @@ public static ImageViewFragment newInstance(String imageUrl) { } private String getImageUrl() { - return getArguments().getString(IMAGE_URL); + String imageSimplePath = getArguments().getString(IMAGE_URL); + return imageSimplePath; } @Nullable @Override diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/SettingsFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/SettingsFragment.java index 24ef3431..48f17500 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/SettingsFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/SettingsFragment.java @@ -1,9 +1,13 @@ package org.hisp.dhis.android.dashboard.ui.fragments; +import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.widget.Toolbar; +import android.text.Editable; +import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -11,8 +15,11 @@ import com.squareup.otto.Subscribe; import org.hisp.dhis.android.dashboard.R; +import org.hisp.dhis.android.dashboard.api.persistence.preferences.DateTimeManager; +import org.hisp.dhis.android.dashboard.api.persistence.preferences.SettingsManager; import org.hisp.dhis.android.dashboard.ui.activities.LauncherActivity; import org.hisp.dhis.android.dashboard.ui.events.UiEvent; +import org.hisp.dhis.android.dashboard.ui.views.FontEditText; import butterknife.Bind; import butterknife.ButterKnife; @@ -22,14 +29,20 @@ * Created by arazabishov on 7/27/15. */ public final class SettingsFragment extends BaseFragment { - @Bind(R.id.toolbar) Toolbar mToolbar; + FontEditText widthEditText; + FontEditText heightEditText; @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_settings, container, false); + View view = inflater.inflate(R.layout.fragment_settings, container, false); + widthEditText = (FontEditText) view.findViewById(R.id.update_width_edit); + heightEditText =(FontEditText) view.findViewById(R.id.update_height_edit); + widthEditText.addTextChangedListener(new CustomTextWatcher(SettingsManager.CHART_WIDTH)); + widthEditText.addTextChangedListener(new CustomTextWatcher(SettingsManager.CHART_HEIGHT)); + return view; } @Override @@ -44,6 +57,11 @@ public void onClick(View v) { toggleNavigationDrawer(); } }); + Integer width = Integer.parseInt(SettingsManager.getInstance(getContext()).getPreference((SettingsManager.CHART_WIDTH), "0")); + Integer height = Integer.parseInt(SettingsManager.getInstance(getContext()).getPreference((SettingsManager.CHART_HEIGHT), "0")); + widthEditText.setText(width+""); + heightEditText.setText(height+""); + } @OnClick(R.id.delete_and_log_out_button) @@ -62,4 +80,26 @@ public void onLogOut(UiEvent event) { getActivity().finish(); } } + + private class CustomTextWatcher implements TextWatcher{ + + final String preference; + public CustomTextWatcher(String preference){ + this.preference = preference; + } + @Override + public void afterTextChanged(Editable s) {} + + @Override + public void beforeTextChanged(CharSequence s, int start, + int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, + int before, int count) { + SettingsManager.getInstance(getContext()).setPreference(preference, s.toString()); + } + } + } diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index df6b7975..fbb562a3 100755 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -53,13 +53,80 @@ + + + + + + + + + + + + + + + + #FF223F5C #FF113151 #FF0C243C + + #9f9f9f \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 921db573..59aed7c9 100755 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -6,6 +6,7 @@ 3dp 6dp 13dp + 22sp 1 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5bb935c7..e6c4b583 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -132,4 +132,10 @@

sh ${gitPath}/generate_last_commit.sh

3. Run "git checkout".

]]>
+ + + + "Images size (Default=0)" + "Width:" + "Height:" From 96cc40f89c1b6bd15c5174fe7308d1d96e9ff0ac Mon Sep 17 00:00:00 2001 From: mplazaspalacio Date: Mon, 26 Jun 2017 14:56:31 +0200 Subject: [PATCH 28/69] Refactor remove WebViewEventReportFragment. --- .../DashboardElementDetailActivity.java | 12 +- .../fragments/WebViewEventReportFragment.java | 162 ------------------ .../ui/fragments/WebViewFragment.java | 69 +++++++- 3 files changed, 67 insertions(+), 176 deletions(-) delete mode 100644 app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewEventReportFragment.java diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/activities/DashboardElementDetailActivity.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/activities/DashboardElementDetailActivity.java index 82e9ee5b..1522a513 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/activities/DashboardElementDetailActivity.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/activities/DashboardElementDetailActivity.java @@ -47,7 +47,6 @@ import org.hisp.dhis.android.dashboard.api.models.InterpretationElement; import org.hisp.dhis.android.dashboard.api.models.InterpretationElement$Table; import org.hisp.dhis.android.dashboard.ui.fragments.ImageViewFragment; -import org.hisp.dhis.android.dashboard.ui.fragments.WebViewEventReportFragment; import org.hisp.dhis.android.dashboard.ui.fragments.WebViewFragment; import butterknife.Bind; @@ -144,14 +143,11 @@ private void handleDashboardElement(DashboardElement element) { attachFragment(ImageViewFragment.newInstance(request)); break; } - case DashboardItemContent.TYPE_REPORT_TABLE: { - String elementId = element.getUId(); - attachFragment(WebViewFragment.newInstance(elementId)); - break; - } + case DashboardItemContent.TYPE_REPORT_TABLE: case DashboardItemContent.TYPE_EVENT_REPORT: { String elementId = element.getUId(); - attachFragment(WebViewEventReportFragment.newInstance(elementId)); + attachFragment(WebViewFragment.newInstance(elementId, + element.getDashboardItem().getType())); break; } } @@ -176,7 +172,7 @@ private void handleInterpretationElement(InterpretationElement element) { } case Interpretation.TYPE_REPORT_TABLE: { String elementId = element.getUId(); - attachFragment(WebViewFragment.newInstance(elementId)); + attachFragment(WebViewFragment.newInstance(elementId, element.getType())); break; } case Interpretation.TYPE_DATA_SET_REPORT: { diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewEventReportFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewEventReportFragment.java deleted file mode 100644 index 8da56a4a..00000000 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewEventReportFragment.java +++ /dev/null @@ -1,162 +0,0 @@ -package org.hisp.dhis.android.dashboard.ui.fragments; - -import static android.text.TextUtils.isEmpty; - -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.webkit.WebView; - -import org.hisp.dhis.android.dashboard.DhisApplication; -import org.hisp.dhis.android.dashboard.R; -import org.hisp.dhis.android.dashboard.api.controllers.DhisController; -import org.hisp.dhis.android.dashboard.api.job.Job; -import org.hisp.dhis.android.dashboard.api.job.JobExecutor; -import org.hisp.dhis.android.dashboard.api.models.DataElementDimension; -import org.hisp.dhis.android.dashboard.api.models.EventReport; -import org.hisp.dhis.android.dashboard.api.models.meta.ResponseHolder; -import org.hisp.dhis.android.dashboard.api.network.APIException; -import org.hisp.dhis.android.dashboard.api.network.DhisApi; -import org.hisp.dhis.android.dashboard.api.network.RepoManager; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.List; - -import butterknife.Bind; -import butterknife.ButterKnife; -import retrofit.mime.TypedInput; - -public class WebViewEventReportFragment extends BaseFragment { - private static final String DASHBOARD_ELEMENT_ID = "arg:dashboardElementId"; - - @Bind(R.id.web_view_content) - WebView mWebView; - - @Bind(R.id.container_layout_progress_bar) - View mProgressBarContainer; - - public static WebViewEventReportFragment newInstance(String id) { - Bundle args = new Bundle(); - args.putString(DASHBOARD_ELEMENT_ID, id); - - WebViewEventReportFragment fragment = new WebViewEventReportFragment(); - fragment.setArguments(args); - - return fragment; - } - - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_web_view, container, false); - } - - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - ButterKnife.bind(this, view); - - mWebView.getSettings().setBuiltInZoomControls(true); - if (getArguments() != null && !isEmpty(getArguments() - .getString(DASHBOARD_ELEMENT_ID))) { - JobExecutor.enqueueJob(new GetEventReportTableJob(this, getArguments() - .getString(DASHBOARD_ELEMENT_ID))); - } - } - - public void onDataDownloaded(ResponseHolder data) { - mProgressBarContainer.setVisibility(View.GONE); - - if (data.getApiException() == null) { - mWebView.loadData(data.getItem(), "text/html", "UTF-8"); - } else { - if (isAdded()) { - ((DhisApplication) (getActivity().getApplication())) - .showApiExceptionMessage(data.getApiException()); - } - } - } - - static class GetEventReportTableJob extends Job> { - static final int JOB_ID = 1546489; - - final WeakReference mFragmentRef; - final String mDashboardElementId; - - public GetEventReportTableJob(WebViewEventReportFragment fragment, - String dashboardElementId) { - super(JOB_ID); - - mFragmentRef = new WeakReference<>(fragment); - mDashboardElementId = dashboardElementId; - } - - - static String readInputStream(TypedInput in) { - StringBuilder builder = new StringBuilder(); - try { - BufferedReader bufferedStream - = new BufferedReader(new InputStreamReader(in.in())); - try { - String line; - while ((line = bufferedStream.readLine()) != null) { - builder.append(line); - builder.append('\n'); - } - return builder.toString(); - } finally { - bufferedStream.close(); - } - } catch (IOException e) { - e.printStackTrace(); - } - return builder.toString(); - } - - @Override - public ResponseHolder inBackground() { - ResponseHolder responseHolder = new ResponseHolder<>(); - EventReport eventReport; - - try { - DhisApi dhisApi = RepoManager.createService( - DhisController.getInstance().getServerUrl(), - DhisController.getInstance().getUserCredentials()); - eventReport = dhisApi.getEventReport(mDashboardElementId); - responseHolder.setItem(readInputStream( - dhisApi.getEventReportTableData(eventReport.getProgram().getuId(), - eventReport.getProgramStage().getuId(), - getDimensions(eventReport)).getBody())); - } catch (APIException exception) { - responseHolder.setApiException(exception); - } - - return responseHolder; - } - - private List getDimensions(EventReport eventReport) { - List dimensions = new ArrayList<>(); - dimensions.add("pe:" + eventReport.getRelativePeriods().getRelativePeriodString()); - dimensions.add("ou:" + eventReport.getOrganisationUnits().get(0).getuId()); - for (DataElementDimension dimension : eventReport.getDataElementDimensions()) { - String dimensionUID = ""; - dimensionUID += dimension.getDataElement().getuId(); - if (dimension.getFilter() != null && !dimension.getFilter().isEmpty()) { - dimensionUID += ":" + dimension.getFilter(); - } - dimensions.add(dimensionUID); - } - return dimensions; - } - - @Override - public void onFinish(ResponseHolder result) { - if (mFragmentRef.get() != null) { - mFragmentRef.get().onDataDownloaded(result); - } - } - } -} diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewFragment.java index 52e7f30f..9bb2e628 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewFragment.java @@ -28,6 +28,10 @@ package org.hisp.dhis.android.dashboard.ui.fragments; +import static android.text.TextUtils.isEmpty; + +import static org.hisp.dhis.android.dashboard.api.models.DashboardItemContent.TYPE_REPORT_TABLE; + import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -39,6 +43,8 @@ import org.hisp.dhis.android.dashboard.api.controllers.DhisController; import org.hisp.dhis.android.dashboard.api.job.Job; import org.hisp.dhis.android.dashboard.api.job.JobExecutor; +import org.hisp.dhis.android.dashboard.api.models.DataElementDimension; +import org.hisp.dhis.android.dashboard.api.models.EventReport; import org.hisp.dhis.android.dashboard.api.models.meta.ResponseHolder; import org.hisp.dhis.android.dashboard.api.network.APIException; import org.hisp.dhis.android.dashboard.api.network.DhisApi; @@ -48,15 +54,16 @@ import java.io.IOException; import java.io.InputStreamReader; import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; import butterknife.Bind; import butterknife.ButterKnife; import retrofit.mime.TypedInput; -import static android.text.TextUtils.isEmpty; - public class WebViewFragment extends BaseFragment { private static final String DASHBOARD_ELEMENT_ID = "arg:dashboardElementId"; + private static final String DASHBOARD_TYPE = "dashboardType"; @Bind(R.id.web_view_content) WebView mWebView; @@ -64,9 +71,10 @@ public class WebViewFragment extends BaseFragment { @Bind(R.id.container_layout_progress_bar) View mProgressBarContainer; - public static WebViewFragment newInstance(String id) { + public static WebViewFragment newInstance(String id, String dashboardType) { Bundle args = new Bundle(); args.putString(DASHBOARD_ELEMENT_ID, id); + args.putString(DASHBOARD_TYPE, dashboardType); WebViewFragment fragment = new WebViewFragment(); fragment.setArguments(args); @@ -84,9 +92,16 @@ public void onViewCreated(View view, Bundle savedInstanceState) { mWebView.getSettings().setBuiltInZoomControls(true); if (getArguments() != null && !isEmpty(getArguments() - .getString(DASHBOARD_ELEMENT_ID))) { - JobExecutor.enqueueJob(new GetReportTableJob(this, getArguments() - .getString(DASHBOARD_ELEMENT_ID))); + .getString(DASHBOARD_ELEMENT_ID)) && !isEmpty(getArguments() + .getString(DASHBOARD_TYPE))) { + if (getArguments() + .getString(DASHBOARD_TYPE).equals(TYPE_REPORT_TABLE)) { + JobExecutor.enqueueJob(new GetReportTableJob(this, getArguments() + .getString(DASHBOARD_ELEMENT_ID))); + } else { + JobExecutor.enqueueJob(new GetEventReportTableJob(this, getArguments() + .getString(DASHBOARD_ELEMENT_ID))); + } } } @@ -159,4 +174,46 @@ public void onFinish(ResponseHolder result) { } } } + + static class GetEventReportTableJob extends GetReportTableJob { + public GetEventReportTableJob(WebViewFragment fragment, String dashboardElementId) { + super(fragment, dashboardElementId); + } + + @Override + public ResponseHolder inBackground() { + ResponseHolder responseHolder = new ResponseHolder<>(); + EventReport eventReport; + + try { + DhisApi dhisApi = RepoManager.createService( + DhisController.getInstance().getServerUrl(), + DhisController.getInstance().getUserCredentials()); + eventReport = dhisApi.getEventReport(mDashboardElementId); + responseHolder.setItem(readInputStream( + dhisApi.getEventReportTableData(eventReport.getProgram().getuId(), + eventReport.getProgramStage().getuId(), + getDimensions(eventReport)).getBody())); + } catch (APIException exception) { + responseHolder.setApiException(exception); + } + + return responseHolder; + } + + private List getDimensions(EventReport eventReport) { + List dimensions = new ArrayList<>(); + dimensions.add("pe:" + eventReport.getRelativePeriods().getRelativePeriodString()); + dimensions.add("ou:" + eventReport.getOrganisationUnits().get(0).getuId()); + for (DataElementDimension dimension : eventReport.getDataElementDimensions()) { + String dimensionUID = ""; + dimensionUID += dimension.getDataElement().getuId(); + if (dimension.getFilter() != null && !dimension.getFilter().isEmpty()) { + dimensionUID += ":" + dimension.getFilter(); + } + dimensions.add(dimensionUID); + } + return dimensions; + } + } } \ No newline at end of file From c60bc54862cf63b45c4a64756d0d7f384ca85dd3 Mon Sep 17 00:00:00 2001 From: Idelcano Date: Mon, 26 Jun 2017 19:04:42 +0200 Subject: [PATCH 29/69] Check if a dashboard is null before loop it --- .../android/dashboard/api/controllers/PullImageController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/PullImageController.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/PullImageController.java index bc9357aa..939220ae 100644 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/PullImageController.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/PullImageController.java @@ -50,7 +50,7 @@ public static List downloadInterpretationImages(List requestList public static List downloadDashboardImages(List requestList) { for (DashboardElement element : DashboardController.queryAllDashboardElement()) { - if (element.getDashboardItem().getType() == null) { + if (element.getDashboardItem() == null || element.getDashboardItem().getType() == null) { continue; } From 686371dc6341e78804e135d8bb75bb93591a7f13 Mon Sep 17 00:00:00 2001 From: mplazaspalacio Date: Tue, 27 Jun 2017 11:07:06 +0200 Subject: [PATCH 30/69] Add order to dashboard items --- .../api/controllers/DashboardController.java | 3 +++ .../android/dashboard/api/models/DashboardItem.java | 12 ++++++++++++ .../ui/fragments/dashboard/DashboardFragment.java | 3 ++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DashboardController.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DashboardController.java index 8936d276..2b9fc053 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DashboardController.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DashboardController.java @@ -188,8 +188,11 @@ private List updateDashboards(DateTime lastUpdated) throws APIExcepti continue; } + int i=0; for (DashboardItem item : dashboard.getDashboardItems()) { item.setDashboard(dashboard); + item.setOrderPosition(i); + i++; } } } diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/DashboardItem.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/DashboardItem.java index 8749f701..f83af67f 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/DashboardItem.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/DashboardItem.java @@ -108,6 +108,10 @@ public final class DashboardItem extends BaseIdentifiableObject { @JsonProperty("messages") boolean messages; + @JsonIgnore + @Column + int orderPosition; + public DashboardItem() { state = State.SYNCED; shape = SHAPE_NORMAL; @@ -425,4 +429,12 @@ public State getState() { public void setState(State state) { this.state = state; } + + public int getOrderPosition() { + return orderPosition; + } + + public void setOrderPosition(int orderPosition) { + this.orderPosition = orderPosition; + } } \ No newline at end of file diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardFragment.java index ac88db14..f7ab9f50 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardFragment.java @@ -277,7 +277,8 @@ public List query(Context context) { DashboardItemContent.TYPE_EVENT_REPORT, DashboardItemContent.TYPE_USERS, DashboardItemContent.TYPE_REPORTS, - DashboardItemContent.TYPE_RESOURCES)) + DashboardItemContent.TYPE_RESOURCES) + ).orderBy(DashboardItem$Table.ORDERPOSITION) .queryList(); if (dashboardItems != null && !dashboardItems.isEmpty()) { for (DashboardItem dashboardItem : dashboardItems) { From 673609bff6f15ed621412514e7cbeb729433575d Mon Sep 17 00:00:00 2001 From: mplazaspalacio Date: Tue, 4 Jul 2017 11:57:50 +0200 Subject: [PATCH 31/69] Solved bug not updating dashboard items --- .../api/controllers/DashboardController.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DashboardController.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DashboardController.java index 8936d276..c0fb9d28 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DashboardController.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DashboardController.java @@ -28,6 +28,13 @@ package org.hisp.dhis.android.dashboard.api.controllers; +import static org.hisp.dhis.android.dashboard.api.models.BaseIdentifiableObject.merge; +import static org.hisp.dhis.android.dashboard.api.models.BaseIdentifiableObject.toListIds; +import static org.hisp.dhis.android.dashboard.api.models.BaseIdentifiableObject.toMap; +import static org.hisp.dhis.android.dashboard.api.utils.NetworkUtils.findLocationHeader; +import static org.hisp.dhis.android.dashboard.api.utils.NetworkUtils.handleApiException; +import static org.hisp.dhis.android.dashboard.api.utils.NetworkUtils.unwrapResponse; + import android.net.Uri; import com.raizlabs.android.dbflow.sql.builder.Condition; @@ -61,13 +68,6 @@ import retrofit.client.Header; import retrofit.client.Response; -import static org.hisp.dhis.android.dashboard.api.models.BaseIdentifiableObject.merge; -import static org.hisp.dhis.android.dashboard.api.models.BaseIdentifiableObject.toListIds; -import static org.hisp.dhis.android.dashboard.api.models.BaseIdentifiableObject.toMap; -import static org.hisp.dhis.android.dashboard.api.utils.NetworkUtils.findLocationHeader; -import static org.hisp.dhis.android.dashboard.api.utils.NetworkUtils.handleApiException; -import static org.hisp.dhis.android.dashboard.api.utils.NetworkUtils.unwrapResponse; - final class DashboardController { final DhisApi mDhisApi; @@ -169,7 +169,8 @@ private List updateDashboards(DateTime lastUpdated) throws APIExcepti "]"); if (lastUpdated != null) { - QUERY_MAP_FULL.put("filter", "lastUpdated:gt:" + lastUpdated.toString()); + QUERY_MAP_FULL.put("filter", + "lastUpdated:gt:" + lastUpdated.toLocalDateTime().toString()); } // List of dashboards with UUIDs (without content). This list is used From 074f910dc580f19bc3179f39a463e255263f0e21 Mon Sep 17 00:00:00 2001 From: mplazaspalacio Date: Wed, 5 Jul 2017 11:51:29 +0200 Subject: [PATCH 32/69] Saving always the dashboard items in the db to save the order of it. Making the migration to add orderPos column to dashboardItem. --- .../dashboard/api/models/meta/DbDhis.java | 2 +- .../MigrationAddOrderPosToDashboardItem.java | 20 +++++++++++++++++++ .../android/dashboard/api/utils/DbUtils.java | 5 +---- 3 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 api/src/main/java/org/hisp/dhis/android/dashboard/api/models/meta/migrations/MigrationAddOrderPosToDashboardItem.java diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/meta/DbDhis.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/meta/DbDhis.java index 0d8492e9..bfc838c6 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/meta/DbDhis.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/meta/DbDhis.java @@ -34,5 +34,5 @@ @Database(name = DbDhis.NAME, version = DbDhis.VERSION) public final class DbDhis { public static final String NAME = "dhis"; - public static final int VERSION = 3; + public static final int VERSION = 4; } diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/meta/migrations/MigrationAddOrderPosToDashboardItem.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/meta/migrations/MigrationAddOrderPosToDashboardItem.java new file mode 100644 index 00000000..2dbabb11 --- /dev/null +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/meta/migrations/MigrationAddOrderPosToDashboardItem.java @@ -0,0 +1,20 @@ +package org.hisp.dhis.android.dashboard.api.models.meta.migrations; + +import com.raizlabs.android.dbflow.annotation.Migration; +import com.raizlabs.android.dbflow.sql.migration.AlterTableMigration; + +import org.hisp.dhis.android.dashboard.api.models.DashboardItem; +import org.hisp.dhis.android.dashboard.api.models.meta.DbDhis; + +@Migration(version = 4, databaseName = DbDhis.NAME) +public class MigrationAddOrderPosToDashboardItem extends AlterTableMigration { + public MigrationAddOrderPosToDashboardItem( + Class table) { + super(table); + } + + @Override + public void onPreMigrate() { + addColumn(Integer.class, "orderPosition"); + } +} diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/DbUtils.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/DbUtils.java index 6d830bd2..6235b397 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/DbUtils.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/DbUtils.java @@ -94,14 +94,11 @@ public static List createOperati continue; } - // if the last updated field in up to date model is after the same - // field in persisted model, it means we need to update it. - if (newModel.getLastUpdated().isAfter(oldModel.getLastUpdated())) { + //always updating to save changes not from the server // note, we need to pass database primary id to updated model // in order to avoid creation of new object. newModel.setId(oldModel.getId()); ops.add(DbOperation.update(newModel)); - } // as we have processed given old (persisted) model, // we can remove it from map of new models. From a07cb373dd9da6f622d57f27a1ed1756f542a911 Mon Sep 17 00:00:00 2001 From: mplazaspalacio Date: Wed, 5 Jul 2017 12:09:59 +0200 Subject: [PATCH 33/69] Solved migration bug --- .../migrations/MigrationAddOrderPosToDashboardItem.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/meta/migrations/MigrationAddOrderPosToDashboardItem.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/meta/migrations/MigrationAddOrderPosToDashboardItem.java index 2dbabb11..ba34e06c 100644 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/meta/migrations/MigrationAddOrderPosToDashboardItem.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/meta/migrations/MigrationAddOrderPosToDashboardItem.java @@ -8,9 +8,10 @@ @Migration(version = 4, databaseName = DbDhis.NAME) public class MigrationAddOrderPosToDashboardItem extends AlterTableMigration { - public MigrationAddOrderPosToDashboardItem( - Class table) { - super(table); + + + public MigrationAddOrderPosToDashboardItem() { + super(DashboardItem.class); } @Override From 3a56cc3520ab47a12ac6227044de6b2fd96e7825 Mon Sep 17 00:00:00 2001 From: mplazaspalacio Date: Fri, 7 Jul 2017 13:49:43 +0200 Subject: [PATCH 34/69] Solved bug crashing app when rotating screen and service is null. --- .../ui/fragments/dashboard/DashboardViewPagerFragment.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardViewPagerFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardViewPagerFragment.java index da427918..fa06be94 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardViewPagerFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardViewPagerFragment.java @@ -122,11 +122,11 @@ public boolean onMenuItemClick(MenuItem item) { } boolean isLoading = isDhisServiceBound() && - getDhisService().isJobRunning(DhisService.SYNC_DASHBOARDS) + (getDhisService().isJobRunning(DhisService.SYNC_DASHBOARDS) || getDhisService().isJobRunning(DhisService.SYNC_DASHBOARD_CONTENT) || - getDhisService().isJobRunning(DhisService.PULL_DASHBOARD_IMAGES); + getDhisService().isJobRunning(DhisService.PULL_DASHBOARD_IMAGES)); if ((savedInstanceState != null && savedInstanceState.getBoolean(IS_LOADING)) || isLoading) { mProgressBar.setVisibility(View.VISIBLE); From ea52df65ac37fb5b697637c69cda407842ebc7b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Sa=CC=81nchez?= Date: Mon, 10 Jul 2017 10:28:49 +0200 Subject: [PATCH 35/69] set expected format date also to DashboardItems request --- .../android/dashboard/api/controllers/DashboardController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DashboardController.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DashboardController.java index c0fb9d28..2828a8ec 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DashboardController.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DashboardController.java @@ -219,7 +219,7 @@ private List updateDashboardItems(List dashboards, Dat QUERY_MAP_BASIC.put("fields", "id,created,lastUpdated,shape"); if (lastUpdated != null) { - QUERY_MAP_BASIC.put("filter", "lastUpdated:gt:" + lastUpdated.toString()); + QUERY_MAP_BASIC.put("filter", "lastUpdated:gt:" + lastUpdated.toLocalDateTime().toString()); } // List of actual dashboard items. From 6a89572cef44acfb19842dd6b12d7b554ffb54db Mon Sep 17 00:00:00 2001 From: idelcano Date: Mon, 10 Jul 2017 11:41:32 +0200 Subject: [PATCH 36/69] fix height and width preference values, added validation --- .../api/controllers/DhisController.java | 17 ++-------- .../preferences/SettingsManager.java | 2 ++ .../ui/fragments/SettingsFragment.java | 31 ++++++++++++++----- app/src/main/res/values/strings.xml | 3 +- 4 files changed, 30 insertions(+), 23 deletions(-) diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DhisController.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DhisController.java index 5b19cff9..5f1f718b 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DhisController.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DhisController.java @@ -75,25 +75,14 @@ public static DhisController getInstance() { } public static String buildImageUrl(String resource, String id, Context context) { - String height = "320"; - String width = "480"; - String widthUserPreference = SettingsManager.getInstance(context).getPreference( - (SettingsManager.CHART_WIDTH), "0"); + (SettingsManager.CHART_WIDTH), SettingsManager.DEFAULT_WIDTH); String heightUserPreference = SettingsManager.getInstance(context).getPreference( - (SettingsManager.CHART_WIDTH), "0"); - if (widthUserPreference != null && !widthUserPreference.equals("") && Integer.parseInt( - widthUserPreference) > 480) { - width = widthUserPreference; - } - if (heightUserPreference != null && !heightUserPreference.equals("") && Integer.parseInt( - heightUserPreference) > 320) { - height = heightUserPreference; - } + (SettingsManager.CHART_HEIGHT), SettingsManager.DEFAULT_HEIGHT); return getInstance().getServerUrl().newBuilder() .addPathSegment("api").addPathSegment(resource).addPathSegment(id).addPathSegment( "data.png") - .addQueryParameter("width", width).addQueryParameter("height", height) + .addQueryParameter("width", widthUserPreference).addQueryParameter("height", heightUserPreference) .toString(); } diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/persistence/preferences/SettingsManager.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/persistence/preferences/SettingsManager.java index a8793791..24200850 100644 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/persistence/preferences/SettingsManager.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/persistence/preferences/SettingsManager.java @@ -6,6 +6,8 @@ public class SettingsManager { public static final String CHART_WIDTH = "key:chart_width"; public static final String CHART_HEIGHT = "key:chart_height"; + public static final String DEFAULT_WIDTH = "480"; + public static final String DEFAULT_HEIGHT = "320"; private static final String PREFERENCES = "preferences:settings"; private static SettingsManager mSettingsManager = null; private SharedPreferences mPrefs; diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/SettingsFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/SettingsFragment.java index 48f17500..915ce135 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/SettingsFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/SettingsFragment.java @@ -11,6 +11,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.EditText; import com.squareup.otto.Subscribe; @@ -33,6 +34,8 @@ public final class SettingsFragment extends BaseFragment { Toolbar mToolbar; FontEditText widthEditText; FontEditText heightEditText; + public static final String MINIMUM_WIDTH = "480"; + public static final String MINIMUM_HEIGHT = "320"; @Nullable @Override @@ -40,8 +43,8 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa View view = inflater.inflate(R.layout.fragment_settings, container, false); widthEditText = (FontEditText) view.findViewById(R.id.update_width_edit); heightEditText =(FontEditText) view.findViewById(R.id.update_height_edit); - widthEditText.addTextChangedListener(new CustomTextWatcher(SettingsManager.CHART_WIDTH)); - widthEditText.addTextChangedListener(new CustomTextWatcher(SettingsManager.CHART_HEIGHT)); + widthEditText.addTextChangedListener(new CustomTextWatcher(SettingsManager.CHART_WIDTH, SettingsManager.DEFAULT_WIDTH, widthEditText)); + heightEditText.addTextChangedListener(new CustomTextWatcher(SettingsManager.CHART_HEIGHT, SettingsManager.DEFAULT_HEIGHT, heightEditText)); return view; } @@ -57,10 +60,10 @@ public void onClick(View v) { toggleNavigationDrawer(); } }); - Integer width = Integer.parseInt(SettingsManager.getInstance(getContext()).getPreference((SettingsManager.CHART_WIDTH), "0")); - Integer height = Integer.parseInt(SettingsManager.getInstance(getContext()).getPreference((SettingsManager.CHART_HEIGHT), "0")); - widthEditText.setText(width+""); - heightEditText.setText(height+""); + String width = SettingsManager.getInstance(getContext()).getPreference((SettingsManager.CHART_WIDTH), SettingsManager.DEFAULT_WIDTH); + String height = SettingsManager.getInstance(getContext()).getPreference((SettingsManager.CHART_HEIGHT), SettingsManager.DEFAULT_HEIGHT); + widthEditText.setText(width); + heightEditText.setText(height); } @@ -84,8 +87,12 @@ public void onLogOut(UiEvent event) { private class CustomTextWatcher implements TextWatcher{ final String preference; - public CustomTextWatcher(String preference){ + final int minimumValue; + final EditText mEditText; + public CustomTextWatcher(String preference, String minimumValue, EditText editText){ this.preference = preference; + this.minimumValue = Integer.parseInt(minimumValue); + this.mEditText = editText; } @Override public void afterTextChanged(Editable s) {} @@ -98,7 +105,15 @@ public void beforeTextChanged(CharSequence s, int start, @Override public void onTextChanged(CharSequence s, int start, int before, int count) { - SettingsManager.getInstance(getContext()).setPreference(preference, s.toString()); + if(s.toString().length()>0) { + if (Integer.parseInt(s.toString())>= minimumValue) { + mEditText.setError(null); + SettingsManager.getInstance(getContext()).setPreference(preference, + s.toString()); + }else{ + mEditText.setError(getContext().getString(R.string.invalid_value)+" "+minimumValue); + } + } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e6c4b583..cfab2668 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -135,7 +135,8 @@ - "Images size (Default=0)" + "Images size" "Width:" "Height:" + The value is not valid, please enter a value greater than From 586e92333d2b89c14647e97d5499c6773c475716 Mon Sep 17 00:00:00 2001 From: idelcano Date: Mon, 10 Jul 2017 11:50:00 +0200 Subject: [PATCH 37/69] remove imports --- .../dhis/android/dashboard/ui/fragments/SettingsFragment.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/SettingsFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/SettingsFragment.java index 915ce135..1abc6970 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/SettingsFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/SettingsFragment.java @@ -1,8 +1,6 @@ package org.hisp.dhis.android.dashboard.ui.fragments; -import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.widget.Toolbar; @@ -16,7 +14,6 @@ import com.squareup.otto.Subscribe; import org.hisp.dhis.android.dashboard.R; -import org.hisp.dhis.android.dashboard.api.persistence.preferences.DateTimeManager; import org.hisp.dhis.android.dashboard.api.persistence.preferences.SettingsManager; import org.hisp.dhis.android.dashboard.ui.activities.LauncherActivity; import org.hisp.dhis.android.dashboard.ui.events.UiEvent; From f3663322bbe118ce227180897bba49d65053ab81 Mon Sep 17 00:00:00 2001 From: idelcano Date: Mon, 10 Jul 2017 11:50:13 +0200 Subject: [PATCH 38/69] modify settings text colors --- app/src/main/res/layout/fragment_settings.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index fbb562a3..4445be8c 100755 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -65,7 +65,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/chart_preferences_title" - android:textColor="@color/darker_grey" + android:textColor="@color/grey" android:textSize="@dimen/medium_text_size" app:font="@string/light_font_name" /> @@ -87,7 +87,7 @@ android:layout_height="wrap_content" android:layout_weight="0.6" android:inputType="number" - android:textColor="@color/darker_grey" + android:textColor="@color/grey" android:textSize="@dimen/medium_text_size" app:font="@string/light_font_name" /> @@ -102,7 +102,7 @@ android:layout_height="wrap_content" android:layout_weight="0.4" android:text="@string/height_title" - android:textColor="@color/darker_grey" + android:textColor="@color/dark_grey_text" android:textSize="@dimen/medium_text_size" app:font="@string/light_font_name" /> @@ -111,7 +111,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="0.6" - android:textColor="@color/darker_grey" + android:textColor="@color/grey" android:textSize="@dimen/medium_text_size" android:inputType="number" app:font="@string/light_font_name" /> From dfffd3773034135ea28780c82487861d9899caff Mon Sep 17 00:00:00 2001 From: mplazaspalacio Date: Mon, 10 Jul 2017 13:25:47 +0200 Subject: [PATCH 39/69] Save the request to server in cache if there is no internet connection use the files saved in cache. --- api/src/main/AndroidManifest.xml | 1 + .../api/controllers/DhisController.java | 10 +-- .../dashboard/api/network/RepoManager.java | 61 ++++++++++++++++--- .../dashboard/api/utils/PicassoProvider.java | 2 +- .../ui/fragments/WebViewFragment.java | 22 ++++--- 5 files changed, 77 insertions(+), 19 deletions(-) diff --git a/api/src/main/AndroidManifest.xml b/api/src/main/AndroidManifest.xml index 2dbf51c3..b04cd600 100755 --- a/api/src/main/AndroidManifest.xml +++ b/api/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ package="org.hisp.dhis.android.dashboard.api"> + diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DhisController.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DhisController.java index 7e7ae850..b3a8af63 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DhisController.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DhisController.java @@ -28,6 +28,8 @@ package org.hisp.dhis.android.dashboard.api.controllers; +import static org.hisp.dhis.android.dashboard.api.utils.Preconditions.isNull; + import android.content.Context; import com.raizlabs.android.dbflow.config.FlowManager; @@ -42,14 +44,14 @@ import org.hisp.dhis.android.dashboard.api.persistence.preferences.DateTimeManager; import org.hisp.dhis.android.dashboard.api.persistence.preferences.LastUpdatedManager; -import static org.hisp.dhis.android.dashboard.api.utils.Preconditions.isNull; - public class DhisController { private static DhisController mDhisController; private Session mSession; private DhisApi mDhisApi; + private Context mContext; private DhisController(Context context) { + mContext = context; FlowManager.init(context); LastUpdatedManager.init(context); DateTimeManager.init(context); @@ -98,7 +100,7 @@ public void logOutUser() throws APIException { private UserAccount signInUser(HttpUrl serverUrl, Credentials credentials) throws APIException { DhisApi dhisApi = RepoManager - .createService(serverUrl, credentials); + .createService(serverUrl, credentials, mContext); UserAccount user = (new UserController(dhisApi) .logInUser(serverUrl, credentials)); @@ -132,7 +134,7 @@ private void readSession() { mDhisApi = RepoManager.createService( mSession.getServerUrl(), mSession.getCredentials() - ); + , mContext); } } diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/network/RepoManager.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/network/RepoManager.java index 17cf63af..c06a851c 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/network/RepoManager.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/network/RepoManager.java @@ -28,6 +28,13 @@ package org.hisp.dhis.android.dashboard.api.network; +import static com.squareup.okhttp.Credentials.basic; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; + +import com.squareup.okhttp.Cache; import com.squareup.okhttp.HttpUrl; import com.squareup.okhttp.Interceptor; import com.squareup.okhttp.OkHttpClient; @@ -38,19 +45,19 @@ import org.hisp.dhis.android.dashboard.api.models.meta.Credentials; import org.hisp.dhis.android.dashboard.api.utils.ObjectMapperProvider; +import java.io.File; import java.io.IOException; import java.net.HttpURLConnection; import java.util.concurrent.TimeUnit; import retrofit.ErrorHandler; +import retrofit.RequestInterceptor; import retrofit.RestAdapter; import retrofit.RetrofitError; import retrofit.client.OkClient; import retrofit.converter.Converter; import retrofit.converter.JacksonConverter; -import static com.squareup.okhttp.Credentials.basic; - public final class RepoManager { static final int DEFAULT_CONNECT_TIMEOUT_MILLIS = 15 * 1000; // 15s @@ -61,13 +68,15 @@ private RepoManager() { // no instances } - public static DhisApi createService(HttpUrl serverUrl, Credentials credentials) { + public static DhisApi createService(HttpUrl serverUrl, Credentials credentials, + final Context context) { RestAdapter restAdapter = new RestAdapter.Builder() .setEndpoint(provideServerUrl(serverUrl)) .setConverter(provideJacksonConverter()) - .setClient(provideOkClient(credentials)) + .setClient(provideOkClient(credentials, context)) .setErrorHandler(new RetrofitErrorHandler()) .setLogLevel(RestAdapter.LogLevel.BASIC) + .setRequestInterceptor(new ConnectionInterceptor(context)) .build(); return restAdapter.create(DhisApi.class); } @@ -82,19 +91,28 @@ private static Converter provideJacksonConverter() { return new JacksonConverter(ObjectMapperProvider.getInstance()); } - private static OkClient provideOkClient(Credentials credentials) { - return new OkClient(provideOkHttpClient(credentials)); + private static OkClient provideOkClient(Credentials credentials, Context context) { + return new OkClient(provideOkHttpClient(credentials, context)); } - public static OkHttpClient provideOkHttpClient(Credentials credentials) { + public static OkHttpClient provideOkHttpClient(Credentials credentials, Context context) { + OkHttpClient client = new OkHttpClient(); client.interceptors().add(provideInterceptor(credentials)); client.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); client.setReadTimeout(DEFAULT_READ_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); client.setWriteTimeout(DEFAULT_WRITE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); + client.setCache(provideCache(context)); return client; } + private static Cache provideCache(Context context) { + File httpCacheDirectory = new File(context.getCacheDir(), "responses"); + Cache cache = null; + cache = new Cache(httpCacheDirectory, 10 * 1024 * 1024); + return cache; + } + private static Interceptor provideInterceptor(Credentials credentials) { return new AuthInterceptor(credentials.getUsername(), credentials.getPassword()); } @@ -132,4 +150,33 @@ public Throwable handleError(RetrofitError cause) { return APIException.fromRetrofitError(cause); } } + + private static boolean isOnline(Context context) { + ConnectivityManager cm = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo netInfo = cm.getActiveNetworkInfo(); + return netInfo != null && netInfo.isConnectedOrConnecting(); + } + + private static class ConnectionInterceptor implements RequestInterceptor { + private Context mContext; + + public ConnectionInterceptor(Context context) { + mContext = context; + } + + @Override + public void intercept(RequestFacade request) { + request.addHeader("Accept", "application/json;versions=1"); + if (isOnline(mContext)) { + int maxAge = 0; // no read cache if there is internet + request.addHeader("Cache-Control", "public, max-age=" + maxAge); + } else { + int maxStale = 60 * 60 * 24 * 365; // tolerate 1 year state + request.addHeader("Cache-Control", + "public, only-if-cached, max-stale=" + maxStale); + } + } + } + } diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/PicassoProvider.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/PicassoProvider.java index 574f008f..0d0070e1 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/PicassoProvider.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/PicassoProvider.java @@ -52,7 +52,7 @@ private PicassoProvider() { public static Picasso getInstance(Context context) { if (mPicasso == null) { OkHttpClient client = RepoManager.provideOkHttpClient( - DhisController.getInstance().getUserCredentials()); + DhisController.getInstance().getUserCredentials(), context); client.networkInterceptors().add(new Interceptor() { @Override diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewFragment.java index 52e7f30f..f9104abd 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewFragment.java @@ -28,7 +28,11 @@ package org.hisp.dhis.android.dashboard.ui.fragments; +import static android.text.TextUtils.isEmpty; + +import android.content.Context; import android.os.Bundle; +import android.support.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -53,10 +57,9 @@ import butterknife.ButterKnife; import retrofit.mime.TypedInput; -import static android.text.TextUtils.isEmpty; - public class WebViewFragment extends BaseFragment { private static final String DASHBOARD_ELEMENT_ID = "arg:dashboardElementId"; + private Context mContext; @Bind(R.id.web_view_content) WebView mWebView; @@ -75,6 +78,7 @@ public static WebViewFragment newInstance(String id) { } public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + mContext = getContext(); return inflater.inflate(R.layout.fragment_web_view, container, false); } @@ -86,10 +90,9 @@ public void onViewCreated(View view, Bundle savedInstanceState) { if (getArguments() != null && !isEmpty(getArguments() .getString(DASHBOARD_ELEMENT_ID))) { JobExecutor.enqueueJob(new GetReportTableJob(this, getArguments() - .getString(DASHBOARD_ELEMENT_ID))); + .getString(DASHBOARD_ELEMENT_ID), mContext)); } } - public void onDataDownloaded(ResponseHolder data) { mProgressBarContainer.setVisibility(View.GONE); @@ -108,12 +111,15 @@ static class GetReportTableJob extends Job> { final WeakReference mFragmentRef; final String mDashboardElementId; + Context mContext; - public GetReportTableJob(WebViewFragment fragment, String dashboardElementId) { + public GetReportTableJob(WebViewFragment fragment, String dashboardElementId, + Context context) { super(JOB_ID); mFragmentRef = new WeakReference<>(fragment); mDashboardElementId = dashboardElementId; + mContext = context; } static String readInputStream(TypedInput in) { @@ -142,8 +148,10 @@ public ResponseHolder inBackground() { ResponseHolder responseHolder = new ResponseHolder<>(); try { - DhisApi dhisApi = RepoManager.createService(DhisController.getInstance().getServerUrl(), - DhisController.getInstance().getUserCredentials()); + DhisApi dhisApi = RepoManager.createService(DhisController.getInstance() + .getServerUrl(), + DhisController.getInstance().getUserCredentials(), + mContext); responseHolder.setItem(readInputStream(dhisApi.getReportTableData(mDashboardElementId).getBody())); } catch (APIException exception) { responseHolder.setApiException(exception); From 17d78179a36e77c3a08d178824a568a7e1372d36 Mon Sep 17 00:00:00 2001 From: mplazaspalacio Date: Tue, 11 Jul 2017 13:54:41 +0200 Subject: [PATCH 40/69] Solved bug not showing all types off event report tables --- .../dashboard/api/models/EventReport.java | 21 +++++++++++ .../dashboard/api/models/RelativePeriod.java | 35 ++++++++++++++++--- .../dashboard/api/network/DhisApi.java | 8 +++-- .../ui/fragments/WebViewFragment.java | 9 ++++- 4 files changed, 66 insertions(+), 7 deletions(-) diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/EventReport.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/EventReport.java index bdb3da82..be56fd1b 100644 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/EventReport.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/EventReport.java @@ -1,5 +1,6 @@ package org.hisp.dhis.android.dashboard.api.models; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.raizlabs.android.dbflow.annotation.Table; import org.hisp.dhis.android.dashboard.api.models.meta.DbDhis; @@ -8,6 +9,11 @@ @Table(databaseName = DbDhis.NAME) public final class EventReport extends BaseIdentifiableObject { + @JsonIgnore + static final String AGGREGATED_VALUES_TYPE = "AGGREGATED_VALUES"; + @JsonIgnore + static final String EVENTS_TYPE = "EVENTS"; + UIDObject program; UIDObject programStage; @@ -17,6 +23,7 @@ public final class EventReport extends BaseIdentifiableObject { UIDObject dataElementValueDimension; String aggregationType; String outputType; + String dataType; public UIDObject getProgram() { return program; @@ -85,5 +92,19 @@ public void setOutputType(String outputType) { this.outputType = outputType; } + public String getDataType() { + return dataType; + } + public void setDataType(String dataType) { + this.dataType = dataType; + } + + public String getDataTypeString() { + if (dataType.equals(AGGREGATED_VALUES_TYPE)) { + return "aggregate"; + } else { + return "query"; + } + } } diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/RelativePeriod.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/RelativePeriod.java index 81b76ee8..f8718e40 100644 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/RelativePeriod.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/RelativePeriod.java @@ -20,7 +20,8 @@ public final class RelativePeriod extends BaseModel { "THIS_DAY", "THIS_MONTH", "LAST_5_YEARS", "LAST_6_BIMONTHS", "LAST_FINANCIAL_YEAR", "LAST_6_MONTHS", "LAST_3_DAYS", "QUARTERS_THIS_YEAR", "MONTHS_LAST_YEAR", "LAST_WEEK", "LAST_7_DAYS", "THIS_BIMONTH", "LAST_BIMONTH", - "LAST_SIX_MONTH", "LAST_YEAR", "LAST_12_WEEKS", "LAST_4_QUARTERS"}; + "LAST_SIX_MONTH", "LAST_YEAR", "LAST_12_WEEKS", "LAST_4_QUARTERS", + "BIMONTHS_THIS_YEAR", "WEEKS_THIS_YEAR"}; @JsonIgnore @Column(name = "id") @@ -61,6 +62,8 @@ public final class RelativePeriod extends BaseModel { boolean lastYear; boolean last12Weeks; boolean last4Quarters; + boolean biMonthsThisYear; + boolean weeksThisYear; @JsonIgnore private boolean[] periodsList; @@ -86,16 +89,24 @@ public void setPeriodsList(boolean[] periodsList) { } public String getRelativePeriodString() { + String result = ""; periodsList = new boolean[]{thisYear, quartersLastYear, last52Weeks, thisWeek, lastMonth, last14Days, monthsThisYear, last2SixMonths, yesterday, thisQuarter, last12Months, last5FinancialYears, thisSixMonth, lastQuarter, thisFinancialYear, last4Weeks, last3Months, thisDay, thisMonth, last5Years, last6BiMonths, lastFinancialYear, last6Months, last3Days, quartersThisYear, monthsLastYear, lastWeek, last7Days, - thisBimonth, lastBimonth, lastSixMonth, lastYear, last12Weeks, last4Quarters}; + thisBimonth, lastBimonth, lastSixMonth, lastYear, last12Weeks, last4Quarters, + biMonthsThisYear, weeksThisYear}; for (int i = 0; i < periodsList.length; i++) { - if (periodsList[i]) return periodsStrings[i]; + if (periodsList[i]) { + if (i == 0) { + result += periodsStrings[i]; + } else { + result += ";" + periodsStrings[i]; + } + } } - return ""; + return result; } public boolean isThisYear() { @@ -369,4 +380,20 @@ public boolean isLast4Quarters() { public void setLast4Quarters(boolean last4Quarters) { this.last4Quarters = last4Quarters; } + + public boolean isBiMonthsThisYear() { + return biMonthsThisYear; + } + + public void setBiMonthsThisYear(boolean biMonthsThisYear) { + this.biMonthsThisYear = biMonthsThisYear; + } + + public boolean isWeeksThisYear() { + return weeksThisYear; + } + + public void setWeeksThisYear(boolean weeksThisYear) { + this.weeksThisYear = weeksThisYear; + } } diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/network/DhisApi.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/network/DhisApi.java index 82201bd9..27ff0b79 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/network/DhisApi.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/network/DhisApi.java @@ -147,11 +147,15 @@ Response deleteDashboardItemContent(@Path("dashboardUid") String dashboardUid, EventReport getEventReport(@Path("id") String id); @Headers("Accept: application/text") - @GET("/25/analytics/events/query/{program}" + @GET("/analytics/events/{dataType}/{program}" + ".html+css?displayProperty=NAME") Response getEventReportTableData(@Path("program") String program, @Query("stage") String programStage, - @Query("dimension") List dimensions); + @Query("dimension") List dimensions, + @Query("outputType") String outputType, + @Query("aggregationType") String aggregationType, + @Query("value") String value, + @Path("dataType") String dataType); @GET("/eventReports?paging=false") @Headers("Accept: application/json") diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewFragment.java index 9bb2e628..7fd0f23b 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewFragment.java @@ -193,7 +193,14 @@ public ResponseHolder inBackground() { responseHolder.setItem(readInputStream( dhisApi.getEventReportTableData(eventReport.getProgram().getuId(), eventReport.getProgramStage().getuId(), - getDimensions(eventReport)).getBody())); + getDimensions(eventReport), + eventReport.getOutputType(), + eventReport.getAggregationType(), + eventReport.getDataElementValueDimension() != null + ? eventReport.getDataElementValueDimension().getuId() + : null, + eventReport.getDataTypeString()) + .getBody())); } catch (APIException exception) { responseHolder.setApiException(exception); } From 277372446f661d17b2d4a4493bc3a2ccd8077698 Mon Sep 17 00:00:00 2001 From: mplazaspalacio Date: Tue, 11 Jul 2017 14:15:08 +0200 Subject: [PATCH 41/69] Show table image in dashboard items list if it's a pivot table or an even report table. --- .../ui/adapters/DashboardItemAdapter.java | 19 ++++++++++++++---- .../main/res/drawable-hdpi/ic_table_small.png | Bin 0 -> 1381 bytes .../main/res/drawable-mdpi/ic_table_small.png | Bin 0 -> 787 bytes .../res/drawable-xhdpi/ic_table_small.png | Bin 0 -> 1992 bytes .../res/drawable-xxhdpi/ic_table_small.png | Bin 0 -> 3422 bytes .../res/drawable-xxxhdpi/ic_table_small.png | Bin 0 -> 4677 bytes 6 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 app/src/main/res/drawable-hdpi/ic_table_small.png create mode 100644 app/src/main/res/drawable-mdpi/ic_table_small.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_table_small.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_table_small.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_table_small.png diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java index 812e9d74..f577aba7 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java @@ -122,7 +122,7 @@ public int getItemViewType(int position) { return ITEM_WITH_IMAGE_TYPE; case DashboardItemContent.TYPE_REPORT_TABLE: case DashboardItemContent.TYPE_EVENT_REPORT: - return ITEM_WITH_TABLE_TYPE; + return ITEM_WITH_IMAGE_TYPE; case DashboardItemContent.TYPE_USERS: case DashboardItemContent.TYPE_REPORTS: case DashboardItemContent.TYPE_RESOURCES: @@ -312,12 +312,23 @@ private void handleItemsWithImages(ImageItemViewHolder holder, DashboardItem ite } else if (DashboardItemContent.TYPE_EVENT_CHART.equals(item.getType()) && item.getEventChart() != null) { element = item.getEventChart(); request = DhisController.getInstance().buildImageUrl("eventCharts", element.getUId(), context); + } else if (DashboardItemContent.TYPE_REPORT_TABLE.equals(item.getType()) + && item.getReportTable() != null) { + element = item.getReportTable(); + } else if (DashboardItemContent.TYPE_EVENT_REPORT.equals(item.getType()) + && item.getEventReport() != null) { + element = item.getEventReport(); } holder.listener.setDashboardElement(element); - mImageLoader.load(request) - .placeholder(R.mipmap.ic_stub_dashboard_item) - .into(holder.imageView); + if (request != null) { + mImageLoader.load(request) + .placeholder(R.mipmap.ic_stub_dashboard_item) + .into(holder.imageView); + } else { + holder.imageView.setImageDrawable( + context.getResources().getDrawable(R.drawable.ic_table_small)); + } } ///////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/res/drawable-hdpi/ic_table_small.png b/app/src/main/res/drawable-hdpi/ic_table_small.png new file mode 100644 index 0000000000000000000000000000000000000000..ad3a309f2944fbf9628a3cfd81fada7779d232cc GIT binary patch literal 1381 zcmV-r1)BPaP)4+F6vn@?9ovcRcuVX!@sik1(xy?$QY2JLD1E33e*#E6fe;d)Qc;k|Lsbf5sf3Dn zAWul}XMhT7DQUK5by_EBoW@JMjh%Q+VtZ!Ly{@|;8jYGvrGl?yjeUGP^Sj^u&bikN zp2ERAh0Lf!KdDu#D_vK57X|9}sN3UzZI9FEFA3Xa8L~wsI9>)`*jq_35FnQc&^DH! zQE|9*?zE^Iib|@j)Vo7J2ppqANnrpOIl##FW@CW|1RhF02L)fm@E7lk;t`cpBYWe_ zu%OWBp=xtLu2e!Um+uLh=Xh|H3d*S{xJ(MaTzp3qkEo;?*_&sE1+GDZa#JfHS3oA) zo6b@oSLhgTuX>%l_?VUOo6;%PDriUKt9E&TMg z1WJE+?t8&(wxFkX05X{Zf>3?v;|_)a7=}S`Z5i=c2p5Om6D6Xkq#D$xUw$WaIh{Ch z>}8ZH9CC%N$K|EJ(`g!E&}ndeauPoOEu8;Yf`XL5+A(y*FMFzIzPmMatyj(voV%sc_;SZWjN2^P=y^`PBirhqqB)-dT?#F749 z^m$w~b|(_UmB~3A@^<3c10GrjQ8tb7c|WWs177Iw#cxFE*#+s9jwaIx#aRsWbirye zQo!Y47_7b#+ix!|^1>=Kbh#cHofLMWGYvP0TI^(AHwak-lzRzBY!*xtsX> ztVEu!*@Go2J8ZMiK=56Nl2!x`r zTU#jf?|+Ts#{4bmp#$L<{HsAUsnuvuRCmVhoe~P$+cc(DDQT=!E+b#uf?BCWlUhY% zA^l-vqzNOKqSuMaIc$slk78O z64Ue^ z-42XQ&BM32f(xHWWDhrie&r}tbS;{s9l@ZjdX@CwJL?HJtY%p5s=ATQVT7b2~o>+U3 z6DSvRS^mNT}i23 zh{E^2yqEJR=wxaWo7o(``s^c7`-Z5b2RD~TJ$OIj-wd4*#UmtDmU_qzd5DTABb7?X=n&n4A}T~Q4+Wyu!44r5eL#>x zhE{^~d5F4%7+sR0kV*+;pA985bn25h`_d?LY{i5_#Ct0rXxxDi3_(+-2hS zmV+@`0g{zq8YGE4X1=~fciVA!;MNP5NPSHWbb15Wcrq)p$;iNNF`;j85bez;<%19H zuf=SHMKByiCMlm55M$$TI#$s_<|8o}4Gc^Sg9MkNF<=_OLhRu?L^}b>R z=0g}6{eb$NTPXO+%hy;_L4_{$ERX95K z89S;fK?uR{oA>bKt%fH*7nA-u_=6#AE?bKmefO1v{h=tn1?HgDXdp=hd^!ySqo;~f zd>R^!n(hgr2)i{0Rb?f(**~Nl%yK*odL6t}aTKFt4R2E(3#jlB4Q3 zmK5Xm&{K3O0XNj`MmU~CGL?eEYN5%e=R)wg^Qhtpe-Ku)k>1gG0)il-!dHm?htJS? zMuFl3wbfWi@eoA`PMa06L=s=7{Mg|2P{ki7XJEIO;jo$!VH32#QjZG*kDn_8H`G-_ zAizv8jIdWxb*-)R9qiupO( z5=jn^UJT=W>uLGTmDQYFB&h3blRU5z=u&61``0a%MaKRLScYlxj{JXCe+LEiQ7->! RE$sjR002ovPDHLkV1lyFX7&I8 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_table_small.png b/app/src/main/res/drawable-xhdpi/ic_table_small.png new file mode 100644 index 0000000000000000000000000000000000000000..2ddb4537aba2e8ebdbb6f7cfd257e5958d0069f5 GIT binary patch literal 1992 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 J8Uys>{{SC3*@FN8 literal 0 HcmV?d00001 From a76fe1b07303054ad27782cd281c733d5a44ede7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Sa=CC=81nchez?= Date: Tue, 11 Jul 2017 16:47:15 +0200 Subject: [PATCH 42/69] replace image for event reports and set scale type to CENTER_INSIDE --- app/src/main/ic_event_report-web.png | Bin 0 -> 17839 bytes app/src/main/ic_pivot_table-web.png | Bin 0 -> 27668 bytes .../ui/adapters/DashboardItemAdapter.java | 10 +++++++--- .../main/res/drawable-hdpi/ic_event_report.png | Bin 0 -> 4171 bytes .../main/res/drawable-hdpi/ic_pivot_table.png | Bin 0 -> 7726 bytes .../main/res/drawable-hdpi/ic_table_small.png | Bin 1381 -> 0 bytes .../main/res/drawable-mdpi/ic_event_report.png | Bin 0 -> 2488 bytes .../main/res/drawable-mdpi/ic_pivot_table.png | Bin 0 -> 4098 bytes .../main/res/drawable-mdpi/ic_table_small.png | Bin 787 -> 0 bytes .../main/res/drawable-xhdpi/ic_event_report.png | Bin 0 -> 5917 bytes .../main/res/drawable-xhdpi/ic_pivot_table.png | Bin 0 -> 12193 bytes .../main/res/drawable-xhdpi/ic_table_small.png | Bin 1992 -> 0 bytes .../res/drawable-xxhdpi/ic_event_report.png | Bin 0 -> 8158 bytes .../main/res/drawable-xxhdpi/ic_pivot_table.png | Bin 0 -> 16914 bytes .../main/res/drawable-xxhdpi/ic_table_small.png | Bin 3422 -> 0 bytes .../res/drawable-xxxhdpi/ic_event_report.png | Bin 0 -> 9284 bytes .../res/drawable-xxxhdpi/ic_pivot_table.png | Bin 0 -> 17942 bytes .../res/drawable-xxxhdpi/ic_table_small.png | Bin 4677 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_event_report.png | Bin 0 -> 8158 bytes .../main/res/mipmap-xxhdpi/ic_pivot_table.png | Bin 0 -> 16914 bytes 20 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 app/src/main/ic_event_report-web.png create mode 100644 app/src/main/ic_pivot_table-web.png create mode 100644 app/src/main/res/drawable-hdpi/ic_event_report.png create mode 100644 app/src/main/res/drawable-hdpi/ic_pivot_table.png delete mode 100644 app/src/main/res/drawable-hdpi/ic_table_small.png create mode 100644 app/src/main/res/drawable-mdpi/ic_event_report.png create mode 100644 app/src/main/res/drawable-mdpi/ic_pivot_table.png delete mode 100644 app/src/main/res/drawable-mdpi/ic_table_small.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_event_report.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_pivot_table.png delete mode 100644 app/src/main/res/drawable-xhdpi/ic_table_small.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_event_report.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_pivot_table.png delete mode 100644 app/src/main/res/drawable-xxhdpi/ic_table_small.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_event_report.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_pivot_table.png delete mode 100644 app/src/main/res/drawable-xxxhdpi/ic_table_small.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_event_report.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_pivot_table.png diff --git a/app/src/main/ic_event_report-web.png b/app/src/main/ic_event_report-web.png new file mode 100644 index 0000000000000000000000000000000000000000..243af003da5753d1dafd4825b8e1f9cda755af80 GIT binary patch literal 17839 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 bbczSmf{Y$T(@TH5Dtz+znPd4!U9SBfG-CjU literal 0 HcmV?d00001 diff --git a/app/src/main/ic_pivot_table-web.png b/app/src/main/ic_pivot_table-web.png new file mode 100644 index 0000000000000000000000000000000000000000..42ecc34fe5a3d0ca4ce2ef8361fa2e8ea45dc16a GIT binary patch literal 27668 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 W5EUqY87DXvz|_#fpz7?!=zjyU%Sc`T literal 0 HcmV?d00001 diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java index f577aba7..687a52d7 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java @@ -315,19 +315,23 @@ private void handleItemsWithImages(ImageItemViewHolder holder, DashboardItem ite } else if (DashboardItemContent.TYPE_REPORT_TABLE.equals(item.getType()) && item.getReportTable() != null) { element = item.getReportTable(); + holder.imageView.setImageDrawable( + context.getResources().getDrawable(R.drawable.ic_pivot_table)); + holder.imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); } else if (DashboardItemContent.TYPE_EVENT_REPORT.equals(item.getType()) && item.getEventReport() != null) { element = item.getEventReport(); + holder.imageView.setImageDrawable( + context.getResources().getDrawable(R.drawable.ic_event_report)); + holder.imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); } holder.listener.setDashboardElement(element); if (request != null) { + holder.imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); mImageLoader.load(request) .placeholder(R.mipmap.ic_stub_dashboard_item) .into(holder.imageView); - } else { - holder.imageView.setImageDrawable( - context.getResources().getDrawable(R.drawable.ic_table_small)); } } diff --git a/app/src/main/res/drawable-hdpi/ic_event_report.png b/app/src/main/res/drawable-hdpi/ic_event_report.png new file mode 100644 index 0000000000000000000000000000000000000000..63156d167264998a7a62f2f2578cd27797ac810d GIT binary patch literal 4171 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 Ve|h*dZ`G=I?+w1Z|KGFLde+)|ZGZ8L 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 RE$sjR002ovPDHLkV1lyFX7&I8 diff --git a/app/src/main/res/drawable-xhdpi/ic_event_report.png b/app/src/main/res/drawable-xhdpi/ic_event_report.png new file mode 100644 index 0000000000000000000000000000000000000000..c542b41bb4c4519c7b1ebb42b3d8a43d2a47cab3 GIT binary patch literal 5917 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_table_small.png b/app/src/main/res/drawable-xxhdpi/ic_table_small.png deleted file mode 100644 index 98bd510f456b0ce54a45b6f66f432923d3a7527c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3422 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_pivot_table.png b/app/src/main/res/drawable-xxxhdpi/ic_pivot_table.png new file mode 100644 index 0000000000000000000000000000000000000000..42b4fa5208e385ad6f5ef36a3cb37fc8b4c4f7af GIT binary patch literal 17942 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 SJ)&U%0000(p36D3h^fEAO!zk$st( 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 J8Uys>{{SC3*@FN8 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_event_report.png b/app/src/main/res/mipmap-xxhdpi/ic_event_report.png new file mode 100644 index 0000000000000000000000000000000000000000..fab51321f0aa5b89bff4aab80658d648cba8ac08 GIT binary patch literal 8158 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 literal 0 HcmV?d00001 From 2baef3851f17d7b8431858cb76d4504d9cc96f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Sa=CC=81nchez?= Date: Wed, 12 Jul 2017 06:36:32 +0200 Subject: [PATCH 43/69] fix development compile --- .../ui/fragments/WebViewFragment.java | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewFragment.java index 550eff04..61ff5f0f 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewFragment.java @@ -87,7 +87,8 @@ public static WebViewFragment newInstance(String id, String dashboardType) { return fragment; } - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { mContext = getContext(); return inflater.inflate(R.layout.fragment_web_view, container, false); } @@ -103,13 +104,14 @@ public void onViewCreated(View view, Bundle savedInstanceState) { if (getArguments() .getString(DASHBOARD_TYPE).equals(TYPE_REPORT_TABLE)) { JobExecutor.enqueueJob(new GetReportTableJob(this, getArguments() - .getString(DASHBOARD_ELEMENT_ID))); + .getString(DASHBOARD_ELEMENT_ID), mContext)); } else { JobExecutor.enqueueJob(new GetEventReportTableJob(this, getArguments() - .getString(DASHBOARD_ELEMENT_ID))); + .getString(DASHBOARD_ELEMENT_ID), mContext)); } } } + public void onDataDownloaded(ResponseHolder data) { mProgressBarContainer.setVisibility(View.GONE); @@ -169,7 +171,8 @@ public ResponseHolder inBackground() { .getServerUrl(), DhisController.getInstance().getUserCredentials(), mContext); - responseHolder.setItem(readInputStream(dhisApi.getReportTableData(mDashboardElementId).getBody())); + responseHolder.setItem( + readInputStream(dhisApi.getReportTableData(mDashboardElementId).getBody())); } catch (APIException exception) { responseHolder.setApiException(exception); } @@ -186,8 +189,14 @@ public void onFinish(ResponseHolder result) { } static class GetEventReportTableJob extends GetReportTableJob { - public GetEventReportTableJob(WebViewFragment fragment, String dashboardElementId) { - super(fragment, dashboardElementId); + + Context mContext; + + public GetEventReportTableJob(WebViewFragment fragment, String dashboardElementId, + Context context) { + super(fragment, dashboardElementId, context); + + mContext = context; } @Override @@ -198,7 +207,7 @@ public ResponseHolder inBackground() { try { DhisApi dhisApi = RepoManager.createService( DhisController.getInstance().getServerUrl(), - DhisController.getInstance().getUserCredentials()); + DhisController.getInstance().getUserCredentials(), mContext); eventReport = dhisApi.getEventReport(mDashboardElementId); responseHolder.setItem(readInputStream( dhisApi.getEventReportTableData(eventReport.getProgram().getuId(), From df202a1c7152dcb10528ad1a4c6d7dbca5568efe Mon Sep 17 00:00:00 2001 From: Idelcano Date: Wed, 12 Jul 2017 11:52:54 +0200 Subject: [PATCH 44/69] Added image size upper limit --- .../api/controllers/DhisController.java | 4 ++-- .../persistence/preferences/SettingsManager.java | 6 ++++-- .../dashboard/ui/fragments/SettingsFragment.java | 16 +++++++++------- app/src/main/res/values/strings.xml | 2 +- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DhisController.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DhisController.java index d85ded1b..87965d42 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DhisController.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DhisController.java @@ -79,9 +79,9 @@ public static DhisController getInstance() { public static String buildImageUrl(String resource, String id, Context context) { String widthUserPreference = SettingsManager.getInstance(context).getPreference( - (SettingsManager.CHART_WIDTH), SettingsManager.DEFAULT_WIDTH); + (SettingsManager.CHART_WIDTH), SettingsManager.MINIMUM_WIDTH); String heightUserPreference = SettingsManager.getInstance(context).getPreference( - (SettingsManager.CHART_HEIGHT), SettingsManager.DEFAULT_HEIGHT); + (SettingsManager.CHART_HEIGHT), SettingsManager.MINIMUM_HEIGHT); return getInstance().getServerUrl().newBuilder() .addPathSegment("api").addPathSegment(resource).addPathSegment(id).addPathSegment( "data.png") diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/persistence/preferences/SettingsManager.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/persistence/preferences/SettingsManager.java index 24200850..55c78041 100644 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/persistence/preferences/SettingsManager.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/persistence/preferences/SettingsManager.java @@ -6,9 +6,11 @@ public class SettingsManager { public static final String CHART_WIDTH = "key:chart_width"; public static final String CHART_HEIGHT = "key:chart_height"; - public static final String DEFAULT_WIDTH = "480"; - public static final String DEFAULT_HEIGHT = "320"; + public static final String MINIMUM_WIDTH = "480"; + public static final String MINIMUM_HEIGHT = "320"; private static final String PREFERENCES = "preferences:settings"; + public static final String MAXIMUM_WIDTH = "1920"; + public static final String MAXIMUM_HEIGHT = "1080"; private static SettingsManager mSettingsManager = null; private SharedPreferences mPrefs; diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/SettingsFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/SettingsFragment.java index 1abc6970..c9748dec 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/SettingsFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/SettingsFragment.java @@ -40,8 +40,8 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa View view = inflater.inflate(R.layout.fragment_settings, container, false); widthEditText = (FontEditText) view.findViewById(R.id.update_width_edit); heightEditText =(FontEditText) view.findViewById(R.id.update_height_edit); - widthEditText.addTextChangedListener(new CustomTextWatcher(SettingsManager.CHART_WIDTH, SettingsManager.DEFAULT_WIDTH, widthEditText)); - heightEditText.addTextChangedListener(new CustomTextWatcher(SettingsManager.CHART_HEIGHT, SettingsManager.DEFAULT_HEIGHT, heightEditText)); + widthEditText.addTextChangedListener(new CustomTextWatcher(SettingsManager.CHART_WIDTH, SettingsManager.MINIMUM_WIDTH, SettingsManager.MAXIMUM_WIDTH, widthEditText)); + heightEditText.addTextChangedListener(new CustomTextWatcher(SettingsManager.CHART_HEIGHT, SettingsManager.MINIMUM_HEIGHT, SettingsManager.MAXIMUM_HEIGHT, heightEditText)); return view; } @@ -57,8 +57,8 @@ public void onClick(View v) { toggleNavigationDrawer(); } }); - String width = SettingsManager.getInstance(getContext()).getPreference((SettingsManager.CHART_WIDTH), SettingsManager.DEFAULT_WIDTH); - String height = SettingsManager.getInstance(getContext()).getPreference((SettingsManager.CHART_HEIGHT), SettingsManager.DEFAULT_HEIGHT); + String width = SettingsManager.getInstance(getContext()).getPreference((SettingsManager.CHART_WIDTH), SettingsManager.MINIMUM_WIDTH); + String height = SettingsManager.getInstance(getContext()).getPreference((SettingsManager.CHART_HEIGHT), SettingsManager.MINIMUM_HEIGHT); widthEditText.setText(width); heightEditText.setText(height); @@ -85,10 +85,12 @@ private class CustomTextWatcher implements TextWatcher{ final String preference; final int minimumValue; + final int maximumValue; final EditText mEditText; - public CustomTextWatcher(String preference, String minimumValue, EditText editText){ + public CustomTextWatcher(String preference, String minimumValue, String maximumValue, EditText editText){ this.preference = preference; this.minimumValue = Integer.parseInt(minimumValue); + this.maximumValue = Integer.parseInt(maximumValue); this.mEditText = editText; } @Override @@ -103,12 +105,12 @@ public void beforeTextChanged(CharSequence s, int start, public void onTextChanged(CharSequence s, int start, int before, int count) { if(s.toString().length()>0) { - if (Integer.parseInt(s.toString())>= minimumValue) { + if (Integer.parseInt(s.toString())>= minimumValue && Integer.parseInt(s.toString()) <= maximumValue) { mEditText.setError(null); SettingsManager.getInstance(getContext()).setPreference(preference, s.toString()); }else{ - mEditText.setError(getContext().getString(R.string.invalid_value)+" "+minimumValue); + mEditText.setError(String.format(getContext().getString(R.string.invalid_value), minimumValue, maximumValue)); } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cfab2668..378a838c 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -138,5 +138,5 @@ "Images size" "Width:" "Height:" - The value is not valid, please enter a value greater than + The value is not valid, please enter a value between %1$d and %2$d From 111d1773ae299ef3bc33bfb08a42b5652df04622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Sa=CC=81nchez?= Date: Wed, 12 Jul 2017 12:12:08 +0200 Subject: [PATCH 45/69] change item container layout_height to wrap_content --- app/src/main/res/layout/recycler_view_field_item.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/layout/recycler_view_field_item.xml b/app/src/main/res/layout/recycler_view_field_item.xml index 05dd2e50..0138aefd 100755 --- a/app/src/main/res/layout/recycler_view_field_item.xml +++ b/app/src/main/res/layout/recycler_view_field_item.xml @@ -3,7 +3,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_height="wrap_content" android:orientation="vertical" android:padding="16dp"> From 7078f4cca67ff61186649d4d3cca8cbe9c68edd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Sa=CC=81nchez?= Date: Wed, 12 Jul 2017 12:58:24 +0200 Subject: [PATCH 46/69] change strings according to the server --- app/src/main/res/values/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 378a838c..855ef2d4 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -75,10 +75,10 @@ Charts Event charts Maps - Report tables + Pivot Tables Event reports Users - Reports + Standard Reports Resources Unsupported dashboard item type. From 38be10bc117117d0ff1118bc14d2a3d9a12473a4 Mon Sep 17 00:00:00 2001 From: idelcano Date: Wed, 12 Jul 2017 13:06:13 +0200 Subject: [PATCH 47/69] Added messages not supported message --- .../dashboard/api/controllers/DashboardController.java | 4 ++++ .../hisp/dhis/android/dashboard/api/models/Dashboard.java | 3 ++- .../dhis/android/dashboard/api/models/DashboardItem.java | 3 +++ .../dhis/android/dashboard/api/utils/PicassoProvider.java | 1 + .../android/dashboard/ui/adapters/DashboardItemAdapter.java | 6 +++++- .../dashboard/ui/fragments/dashboard/DashboardFragment.java | 3 ++- app/src/main/res/values/strings.xml | 1 + 7 files changed, 18 insertions(+), 3 deletions(-) diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DashboardController.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DashboardController.java index 543a50e3..76819c47 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DashboardController.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DashboardController.java @@ -637,6 +637,8 @@ private List updateApiResources(DateTime lastUpdated) thro DashboardItemContent.TYPE_REPORTS, lastUpdated)); dashboardItemContent.addAll(updateApiResourceByType( DashboardItemContent.TYPE_RESOURCES, lastUpdated)); + dashboardItemContent.addAll(updateApiResourceByType( + DashboardItemContent.TYPE_MESSAGES, lastUpdated)); return dashboardItemContent; } @@ -691,6 +693,8 @@ private List getApiResourceByType(String type, Map getDashboardElements() { elements.addAll(getResources()); break; } + case DashboardItemContent.TYPE_MESSAGES: { + break; + } } return elements; diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/PicassoProvider.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/PicassoProvider.java index 0d0070e1..57d16ddd 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/PicassoProvider.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/PicassoProvider.java @@ -66,6 +66,7 @@ public Response intercept(Chain chain) throws IOException { mPicasso = new Picasso.Builder(context) .downloader(new OkHttpDownloader(client)) + .loggingEnabled(true) .build(); } diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java index 687a52d7..8cd59b29 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java @@ -90,6 +90,7 @@ public class DashboardItemAdapter extends AbsAdapter query(Context context) { DashboardItemContent.TYPE_EVENT_REPORT, DashboardItemContent.TYPE_USERS, DashboardItemContent.TYPE_REPORTS, - DashboardItemContent.TYPE_RESOURCES) + DashboardItemContent.TYPE_RESOURCES, + DashboardItemContent.TYPE_MESSAGES) ).orderBy(DashboardItem$Table.ORDERPOSITION) .queryList(); if (dashboardItems != null && !dashboardItems.isEmpty()) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 378a838c..312f1800 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -80,6 +80,7 @@ Users Reports Resources + Messages. Unsupported Resource. Unsupported dashboard item type. From 52f09dae1441ea6316497956136d719365d53b39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Sa=CC=81nchez?= Date: Wed, 12 Jul 2017 13:17:40 +0200 Subject: [PATCH 48/69] set search EditText as anchor of the menu with filters --- .../ui/fragments/dashboard/DashboardItemAddFragment.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardItemAddFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardItemAddFragment.java index d97d9bc5..227ec4dd 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardItemAddFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardItemAddFragment.java @@ -36,6 +36,7 @@ import android.support.v4.content.Loader; import android.support.v7.widget.PopupMenu; import android.text.Editable; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; @@ -141,7 +142,7 @@ public void onViewCreated(View view, Bundle savedInstanceState) { mListView.setAdapter(mAdapter); mDialogLabel.setText(getString(R.string.add_dashboard_item)); - mResourcesMenu = new PopupMenu(getActivity(), mFilterResources); + mResourcesMenu = new PopupMenu(getActivity(),mFilter, Gravity.END); mResourcesMenu.inflate(R.menu.menu_filter_resources); mResourcesMenu.setOnMenuItemClickListener(this); } From 2e636a223eb6f24bd4e34109517c022d9228e075 Mon Sep 17 00:00:00 2001 From: idelcano Date: Wed, 12 Jul 2017 13:34:23 +0200 Subject: [PATCH 49/69] remove wrong code --- .../hisp/dhis/android/dashboard/api/models/DashboardItem.java | 3 --- .../hisp/dhis/android/dashboard/api/utils/PicassoProvider.java | 1 - 2 files changed, 4 deletions(-) diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/DashboardItem.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/DashboardItem.java index bec5e517..f83af67f 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/DashboardItem.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/DashboardItem.java @@ -236,9 +236,6 @@ public List getDashboardElements() { elements.addAll(getResources()); break; } - case DashboardItemContent.TYPE_MESSAGES: { - break; - } } return elements; diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/PicassoProvider.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/PicassoProvider.java index 57d16ddd..0d0070e1 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/PicassoProvider.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/PicassoProvider.java @@ -66,7 +66,6 @@ public Response intercept(Chain chain) throws IOException { mPicasso = new Picasso.Builder(context) .downloader(new OkHttpDownloader(client)) - .loggingEnabled(true) .build(); } From 3305dc7a9e52e9d88c7aee0bcf210c2bb08518b8 Mon Sep 17 00:00:00 2001 From: mplazaspalacio Date: Wed, 12 Jul 2017 13:55:44 +0200 Subject: [PATCH 50/69] Solved bug with the data showed in the event report tables. --- .../api/models/AttributeDimension.java | 26 ++++++++ .../dashboard/api/models/EventReport.java | 66 +++++++++++++++++++ .../dashboard/api/models/RelativePeriod.java | 2 +- .../dashboard/api/network/DhisApi.java | 3 +- .../ui/fragments/WebViewFragment.java | 43 ++++++++---- 5 files changed, 127 insertions(+), 13 deletions(-) create mode 100644 api/src/main/java/org/hisp/dhis/android/dashboard/api/models/AttributeDimension.java diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/AttributeDimension.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/AttributeDimension.java new file mode 100644 index 00000000..0a9570cc --- /dev/null +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/AttributeDimension.java @@ -0,0 +1,26 @@ +package org.hisp.dhis.android.dashboard.api.models; + +import com.raizlabs.android.dbflow.annotation.Column; +import com.raizlabs.android.dbflow.annotation.PrimaryKey; +import com.raizlabs.android.dbflow.annotation.Table; +import com.raizlabs.android.dbflow.structure.BaseModel; + +import org.hisp.dhis.android.dashboard.api.models.meta.DbDhis; + +@Table(databaseName = DbDhis.NAME) +public class AttributeDimension extends BaseModel { + + @Column(name = "id") + @PrimaryKey(autoincrement = true) + long id; + + UIDObject attribute; + + public UIDObject getAttribute() { + return attribute; + } + + public void setAttribute(UIDObject attribute) { + this.attribute = attribute; + } +} diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/EventReport.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/EventReport.java index be56fd1b..4f5f555e 100644 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/EventReport.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/EventReport.java @@ -24,6 +24,8 @@ public final class EventReport extends BaseIdentifiableObject { String aggregationType; String outputType; String dataType; + List attributeDimensions; + List filters; public UIDObject getProgram() { return program; @@ -100,6 +102,70 @@ public void setDataType(String dataType) { this.dataType = dataType; } + public List getAttributeDimensions() { + return attributeDimensions; + } + + public void setAttributeDimensions( + List attributeDimensions) { + this.attributeDimensions = attributeDimensions; + } + + public List getFilters() { + return filters; + } + + public void setFilters(List filters) { + this.filters = filters; + } + + public String getOUDimensionFilter() { + String ouDimensions = "ou:"; + boolean firstOu = true; + for (UIDObject organizationUnit : organisationUnits) { + ouDimensions += + firstOu ? organizationUnit.getuId() : ";" + organizationUnit.getuId(); + firstOu = false; + } + return ouDimensions; + } + + public String getDimensionFilter(DataElementDimension dimension) { + String dimensionUID = ""; + dimensionUID += dimension.getDataElement().getuId(); + if (dimension.getFilter() != null && !dimension.getFilter().isEmpty()) { + dimensionUID += ":" + dimension.getFilter(); + } + return dimensionUID; + } + + public boolean isOUInFilters() { + for (UIDObject filter : filters) { + if (filter.getuId().equals("ou")) { + return true; + } + } + return false; + } + + public boolean isPEInFilters() { + for (UIDObject filter : filters) { + if (filter.getuId().equals("pe")) { + return true; + } + } + return false; + } + + public boolean isDimensionInFilters(String dimension) { + for (UIDObject filter : filters) { + if (filter.getuId().equals(dimension)) { + return true; + } + } + return false; + } + public String getDataTypeString() { if (dataType.equals(AGGREGATED_VALUES_TYPE)) { return "aggregate"; diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/RelativePeriod.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/RelativePeriod.java index f8718e40..c329b883 100644 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/RelativePeriod.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/RelativePeriod.java @@ -89,7 +89,7 @@ public void setPeriodsList(boolean[] periodsList) { } public String getRelativePeriodString() { - String result = ""; + String result = "pe:"; periodsList = new boolean[]{thisYear, quartersLastYear, last52Weeks, thisWeek, lastMonth, last14Days, monthsThisYear, last2SixMonths, yesterday, thisQuarter, last12Months, last5FinancialYears, thisSixMonth, lastQuarter, thisFinancialYear, last4Weeks, diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/network/DhisApi.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/network/DhisApi.java index 27ff0b79..a5901953 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/network/DhisApi.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/network/DhisApi.java @@ -155,7 +155,8 @@ Response getEventReportTableData(@Path("program") String program, @Query("outputType") String outputType, @Query("aggregationType") String aggregationType, @Query("value") String value, - @Path("dataType") String dataType); + @Path("dataType") String dataType, + @Query("filter") List filter); @GET("/eventReports?paging=false") @Headers("Accept: application/json") diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewFragment.java index 61ff5f0f..c27b430f 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewFragment.java @@ -32,11 +32,8 @@ import static org.hisp.dhis.android.dashboard.api.models.DashboardItemContent.TYPE_REPORT_TABLE; -import static android.text.TextUtils.isEmpty; - import android.content.Context; import android.os.Bundle; -import android.support.annotation.Nullable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -47,6 +44,7 @@ import org.hisp.dhis.android.dashboard.api.controllers.DhisController; import org.hisp.dhis.android.dashboard.api.job.Job; import org.hisp.dhis.android.dashboard.api.job.JobExecutor; +import org.hisp.dhis.android.dashboard.api.models.AttributeDimension; import org.hisp.dhis.android.dashboard.api.models.DataElementDimension; import org.hisp.dhis.android.dashboard.api.models.EventReport; import org.hisp.dhis.android.dashboard.api.models.meta.ResponseHolder; @@ -218,7 +216,8 @@ public ResponseHolder inBackground() { eventReport.getDataElementValueDimension() != null ? eventReport.getDataElementValueDimension().getuId() : null, - eventReport.getDataTypeString()) + eventReport.getDataTypeString(), + getFilters(eventReport)) .getBody())); } catch (APIException exception) { responseHolder.setApiException(exception); @@ -229,17 +228,39 @@ public ResponseHolder inBackground() { private List getDimensions(EventReport eventReport) { List dimensions = new ArrayList<>(); - dimensions.add("pe:" + eventReport.getRelativePeriods().getRelativePeriodString()); - dimensions.add("ou:" + eventReport.getOrganisationUnits().get(0).getuId()); + if (!eventReport.isPEInFilters()) { + dimensions.add(eventReport.getRelativePeriods().getRelativePeriodString()); + } + if (!eventReport.isOUInFilters()) { + dimensions.add(eventReport.getOUDimensionFilter()); + } for (DataElementDimension dimension : eventReport.getDataElementDimensions()) { - String dimensionUID = ""; - dimensionUID += dimension.getDataElement().getuId(); - if (dimension.getFilter() != null && !dimension.getFilter().isEmpty()) { - dimensionUID += ":" + dimension.getFilter(); + if (!eventReport.isDimensionInFilters(dimension.getDataElement().getuId())) { + dimensions.add(eventReport.getDimensionFilter(dimension)); } - dimensions.add(dimensionUID); + } + for (AttributeDimension attributeDimension : eventReport.getAttributeDimensions()) { + dimensions.add(attributeDimension.getAttribute().getuId()); } return dimensions; } + + private List getFilters(EventReport eventReport) { + List filters = new ArrayList<>(); + if (eventReport.isOUInFilters()) { + filters.add(eventReport.getOUDimensionFilter()); + } + if (eventReport.isPEInFilters()) { + filters.add(eventReport.getRelativePeriods().getRelativePeriodString()); + } + + for (DataElementDimension dimension : eventReport.getDataElementDimensions()) { + if (eventReport.isDimensionInFilters(dimension.getDataElement().getuId())) { + filters.add(eventReport.getDimensionFilter(dimension)); + } + } + + return filters; + } } } \ No newline at end of file From 58f4ff683f817e3d76b710a48e1c67257fc6f932 Mon Sep 17 00:00:00 2001 From: ifoche Date: Wed, 12 Jul 2017 14:22:11 +0200 Subject: [PATCH 51/69] Remove EST from about us following Rodolfo & Marta instructions --- app/build.gradle | 4 ++-- app/src/main/res/raw/description | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 37ff3bd4..20adcbb1 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,8 +9,8 @@ android { applicationId "org.hisp.dhis.android.dashboard" minSdkVersion 15 targetSdkVersion 25 - versionCode 5 - versionName "0.6.5" + versionCode 6 + versionName "0.6.6" } compileOptions { diff --git a/app/src/main/res/raw/description b/app/src/main/res/raw/description index c0db1c11..bf431eee 100644 --- a/app/src/main/res/raw/description +++ b/app/src/main/res/raw/description @@ -1,4 +1,3 @@ Android application for DHIS2 which provides basic dashboard functionality.
Supports DHIS 2 version 2.25, 2.26 at the moment of release.

Developed by University of Oslo(UiO) (http://www.uio.no).

-Maintained by EyeSeeTea Ltd (http://eyeseetea.com).

From 8420792df4f0005ec92a58cb817153d61a95a0b0 Mon Sep 17 00:00:00 2001 From: mplazaspalacio Date: Wed, 12 Jul 2017 14:53:04 +0200 Subject: [PATCH 52/69] Solved bug inpatient visit overview bad server response. --- .../api/models/DataElementDimension.java | 10 ++++++++ .../dashboard/api/models/EventReport.java | 24 +++++++++++++++++++ .../ui/fragments/WebViewFragment.java | 7 ++++++ 3 files changed, 41 insertions(+) diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/DataElementDimension.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/DataElementDimension.java index 17acb1bb..14da5b36 100644 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/DataElementDimension.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/DataElementDimension.java @@ -19,6 +19,8 @@ public class DataElementDimension extends BaseModel { UIDObject dataElement; + UIDObject legendSet; + public DataElementDimension() { } @@ -41,4 +43,12 @@ public String getFilter() { public void setFilter(String filter) { this.filter = filter; } + + public UIDObject getLegendSet() { + return legendSet; + } + + public void setLegendSet(UIDObject legendSet) { + this.legendSet = legendSet; + } } diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/EventReport.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/EventReport.java index 4f5f555e..f7e78bf7 100644 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/EventReport.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/EventReport.java @@ -26,6 +26,7 @@ public final class EventReport extends BaseIdentifiableObject { String dataType; List attributeDimensions; List filters; + List columns; public UIDObject getProgram() { return program; @@ -119,6 +120,14 @@ public void setFilters(List filters) { this.filters = filters; } + public List getColumns() { + return columns; + } + + public void setColumns(List columns) { + this.columns = columns; + } + public String getOUDimensionFilter() { String ouDimensions = "ou:"; boolean firstOu = true; @@ -133,6 +142,9 @@ public String getOUDimensionFilter() { public String getDimensionFilter(DataElementDimension dimension) { String dimensionUID = ""; dimensionUID += dimension.getDataElement().getuId(); + if (dimension.getLegendSet() != null) { + dimensionUID += "-" + dimension.getLegendSet().getuId(); + } if (dimension.getFilter() != null && !dimension.getFilter().isEmpty()) { dimensionUID += ":" + dimension.getFilter(); } @@ -173,4 +185,16 @@ public String getDataTypeString() { return "query"; } } + + public boolean isValidColumn(UIDObject column) { + if (column.getuId().equals("pe") || column.getuId().equals("ou")) { + return false; + } + for (DataElementDimension dimension : dataElementDimensions) { + if (dimension.getDataElement().getuId().equals(column.getuId())) { + return false; + } + } + return true; + } } diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewFragment.java index c27b430f..6db03660 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewFragment.java @@ -47,6 +47,7 @@ import org.hisp.dhis.android.dashboard.api.models.AttributeDimension; import org.hisp.dhis.android.dashboard.api.models.DataElementDimension; import org.hisp.dhis.android.dashboard.api.models.EventReport; +import org.hisp.dhis.android.dashboard.api.models.UIDObject; import org.hisp.dhis.android.dashboard.api.models.meta.ResponseHolder; import org.hisp.dhis.android.dashboard.api.network.APIException; import org.hisp.dhis.android.dashboard.api.network.DhisApi; @@ -239,6 +240,12 @@ private List getDimensions(EventReport eventReport) { dimensions.add(eventReport.getDimensionFilter(dimension)); } } + for (UIDObject column : eventReport.getColumns()) { + if (eventReport.isValidColumn(column)) { + dimensions.add(column.getuId()); + } + } + for (AttributeDimension attributeDimension : eventReport.getAttributeDimensions()) { dimensions.add(attributeDimension.getAttribute().getuId()); } From 864b88bc517a5bd1dec03304fd8f3107b85da35a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Sa=CC=81nchez?= Date: Wed, 12 Jul 2017 15:43:35 +0200 Subject: [PATCH 53/69] Include types that provoke crash for return valid row type in InterpretationAdapter --- .../ui/adapters/InterpretationAdapter.java | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationAdapter.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationAdapter.java index 4718ac82..798ccd2e 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationAdapter.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationAdapter.java @@ -41,6 +41,7 @@ import org.hisp.dhis.android.dashboard.R; import org.hisp.dhis.android.dashboard.api.controllers.DhisController; +import org.hisp.dhis.android.dashboard.api.models.DashboardItemContent; import org.hisp.dhis.android.dashboard.api.models.Interpretation; import org.hisp.dhis.android.dashboard.api.models.InterpretationElement; import org.hisp.dhis.android.dashboard.api.utils.PicassoProvider; @@ -81,8 +82,10 @@ public int getItemViewType(int position) { switch (getItem(position).getType()) { case Interpretation.TYPE_CHART: case Interpretation.TYPE_MAP: - return ITEM_WITH_IMAGE_TYPE; + case DashboardItemContent.TYPE_EVENT_REPORT: + case DashboardItemContent.TYPE_EVENT_CHART: case Interpretation.TYPE_REPORT_TABLE: + return ITEM_WITH_IMAGE_TYPE; case Interpretation.TYPE_DATA_SET_REPORT: return ITEM_WITH_TABLE_TYPE; } @@ -220,12 +223,24 @@ private void handleItemsWithImages(ImageItemViewHolder holder, Interpretation it } else if (Interpretation.TYPE_MAP.equals(item.getType()) && item.getMap() != null) { InterpretationElement element = item.getMap(); request = DhisController.getInstance().buildImageUrl("maps", element.getUId(), getContext()); + } else if (DashboardItemContent.TYPE_REPORT_TABLE.equals(item.getType())) { + holder.imageView.setImageDrawable( + super.getContext().getResources().getDrawable(R.drawable.ic_pivot_table)); + holder.imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); + } else if (DashboardItemContent.TYPE_EVENT_REPORT.equals(item.getType())) { + + holder.imageView.setImageDrawable( + super.getContext().getResources().getDrawable(R.drawable.ic_event_report)); + holder.imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); } holder.listener.setInterpretation(item); - mImageLoader.load(request) - .placeholder(R.mipmap.ic_stub_dashboard_item) - .into(holder.imageView); + + if (request != null) { + mImageLoader.load(request) + .placeholder(R.mipmap.ic_stub_dashboard_item) + .into(holder.imageView); + } } private void handleItemsWithTables(TextItemViewHolder holder, Interpretation item) { From 16471cab2c3c3cfdacdf4d725ee1ee134396cf2a Mon Sep 17 00:00:00 2001 From: ifoche Date: Wed, 12 Jul 2017 16:14:57 +0200 Subject: [PATCH 54/69] cosmetic refactor --- .../dashboard/api/models/EventReport.java | 40 ++++++++----------- .../ui/fragments/WebViewFragment.java | 4 +- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/EventReport.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/EventReport.java index 4f5f555e..b81728f4 100644 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/EventReport.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/EventReport.java @@ -1,5 +1,7 @@ package org.hisp.dhis.android.dashboard.api.models; +import android.support.annotation.NonNull; + import com.fasterxml.jackson.annotation.JsonIgnore; import com.raizlabs.android.dbflow.annotation.Table; @@ -13,6 +15,14 @@ public final class EventReport extends BaseIdentifiableObject { static final String AGGREGATED_VALUES_TYPE = "AGGREGATED_VALUES"; @JsonIgnore static final String EVENTS_TYPE = "EVENTS"; + @JsonIgnore + static final String OU_KEY = "ou"; + @JsonIgnore + static final String PE_KEY = "pe"; + @JsonIgnore + static final String AGGREGATE_KEY = "aggregate"; + @JsonIgnore + static final String QUERY_KEY = "query"; UIDObject program; @@ -130,36 +140,24 @@ public String getOUDimensionFilter() { return ouDimensions; } - public String getDimensionFilter(DataElementDimension dimension) { + public String getDimensionFilter(@NonNull DataElementDimension dimension) { String dimensionUID = ""; dimensionUID += dimension.getDataElement().getuId(); - if (dimension.getFilter() != null && !dimension.getFilter().isEmpty()) { - dimensionUID += ":" + dimension.getFilter(); - } + dimensionUID += ":" + dimension.getFilter(); return dimensionUID; } public boolean isOUInFilters() { - for (UIDObject filter : filters) { - if (filter.getuId().equals("ou")) { - return true; - } - } - return false; + return isInFilters(OU_KEY); } public boolean isPEInFilters() { - for (UIDObject filter : filters) { - if (filter.getuId().equals("pe")) { - return true; - } - } - return false; + return isInFilters(PE_KEY); } - public boolean isDimensionInFilters(String dimension) { + public boolean isInFilters(String key){ for (UIDObject filter : filters) { - if (filter.getuId().equals(dimension)) { + if (filter.getuId().equals(key)) { return true; } } @@ -167,10 +165,6 @@ public boolean isDimensionInFilters(String dimension) { } public String getDataTypeString() { - if (dataType.equals(AGGREGATED_VALUES_TYPE)) { - return "aggregate"; - } else { - return "query"; - } + return (dataType.equals(AGGREGATED_VALUES_TYPE)) ? AGGREGATE_KEY : QUERY_KEY; } } diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewFragment.java index c27b430f..777e6d26 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/WebViewFragment.java @@ -235,7 +235,7 @@ private List getDimensions(EventReport eventReport) { dimensions.add(eventReport.getOUDimensionFilter()); } for (DataElementDimension dimension : eventReport.getDataElementDimensions()) { - if (!eventReport.isDimensionInFilters(dimension.getDataElement().getuId())) { + if (!eventReport.isInFilters(dimension.getDataElement().getuId())) { dimensions.add(eventReport.getDimensionFilter(dimension)); } } @@ -255,7 +255,7 @@ private List getFilters(EventReport eventReport) { } for (DataElementDimension dimension : eventReport.getDataElementDimensions()) { - if (eventReport.isDimensionInFilters(dimension.getDataElement().getuId())) { + if (eventReport.isInFilters(dimension.getDataElement().getuId())) { filters.add(eventReport.getDimensionFilter(dimension)); } } From ba1478cec74d7430b5cadeee0c9b816bf755b8b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Sa=CC=81nchez?= Date: Wed, 12 Jul 2017 16:39:20 +0200 Subject: [PATCH 55/69] refactor to start app sync with cache and manual sync with no_cache --- .../api/controllers/DhisController.java | 10 +++-- .../api/controllers/PullImageController.java | 42 +++++++++++++------ .../dhis/android/dashboard/DhisService.java | 8 ++-- .../dashboard/DashboardEmptyFragment.java | 9 +++- .../dashboard/DashboardViewPagerFragment.java | 9 +++- .../InterpretationEmptyFragment.java | 7 +++- .../InterpretationFragment.java | 7 +++- 7 files changed, 65 insertions(+), 27 deletions(-) diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DhisController.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DhisController.java index 87965d42..0315637c 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DhisController.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/DhisController.java @@ -52,6 +52,8 @@ public class DhisController { private DhisApi mDhisApi; private Context mContext; + public enum ImageNetworkPolicy {NO_CACHE, CACHE} + private DhisController(Context context) { mContext = context; FlowManager.init(context); @@ -164,10 +166,10 @@ public void syncInterpretations() throws APIException { (new InterpretationController(mDhisApi)).syncInterpretations(); } - public void pullDashboardImages(Context context) { - (new PullImageController(context)).pullDashboardImages(); + public void pullDashboardImages(ImageNetworkPolicy imageNetworkPolicy,Context context) { + (new PullImageController(context)).pullDashboardImages(imageNetworkPolicy); } - public void pullInterpretationImages(Context context) { - (new PullImageController(context)).pullInterpretationImages(); + public void pullInterpretationImages(ImageNetworkPolicy imageNetworkPolicy,Context context) { + (new PullImageController(context)).pullInterpretationImages(imageNetworkPolicy); } } diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/PullImageController.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/PullImageController.java index 9ba3d4d9..2b6ab6be 100644 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/PullImageController.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/PullImageController.java @@ -14,23 +14,25 @@ import java.util.ArrayList; import java.util.List; + final class PullImageController { + static Context mContext; public PullImageController(Context context) { mContext = context; } - public void pullDashboardImages() throws APIException { + public void pullDashboardImages(DhisController.ImageNetworkPolicy imageNetworkPolicy) throws APIException { List requestList = new ArrayList<>(); requestList = downloadDashboardImages(requestList); - downloadImages(requestList, mContext); + downloadImages(imageNetworkPolicy, requestList, mContext); } - public void pullInterpretationImages() throws APIException { + public void pullInterpretationImages(DhisController.ImageNetworkPolicy imageNetworkPolicy) throws APIException { List requestList = new ArrayList<>(); requestList = downloadInterpretationImages(requestList); - downloadImages(requestList, mContext); + downloadImages(imageNetworkPolicy, requestList, mContext); } public static List downloadInterpretationImages(List requestList) { @@ -40,9 +42,12 @@ public static List downloadInterpretationImages(List requestList continue; } if (Interpretation.TYPE_CHART.equals(interpretationElement.getType())) { - requestList.add(DhisController.buildImageUrl("charts", interpretationElement.getUId(), mContext)); + requestList.add( + DhisController.buildImageUrl("charts", interpretationElement.getUId(), + mContext)); } else if (Interpretation.TYPE_MAP.equals(interpretationElement.getType())) { - requestList.add(DhisController.buildImageUrl("maps", interpretationElement.getUId(), mContext)); + requestList.add(DhisController.buildImageUrl("maps", interpretationElement.getUId(), + mContext)); } } return requestList; @@ -50,21 +55,25 @@ public static List downloadInterpretationImages(List requestList public static List downloadDashboardImages(List requestList) { for (DashboardElement element : DashboardController.queryAllDashboardElement()) { - if (element.getDashboardItem() == null || element.getDashboardItem().getType() == null) { + if (element.getDashboardItem() == null + || element.getDashboardItem().getType() == null) { continue; } switch (element.getDashboardItem().getType()) { case DashboardItemContent.TYPE_CHART: { - requestList.add(DhisController.buildImageUrl("charts", element.getUId(), mContext)); + requestList.add( + DhisController.buildImageUrl("charts", element.getUId(), mContext)); break; } case DashboardItemContent.TYPE_EVENT_CHART: { - requestList.add(DhisController.buildImageUrl("eventCharts", element.getUId(), mContext)); + requestList.add(DhisController.buildImageUrl("eventCharts", element.getUId(), + mContext)); break; } case DashboardItemContent.TYPE_MAP: { - requestList.add(DhisController.buildImageUrl("maps", element.getUId(), mContext)); + requestList.add( + DhisController.buildImageUrl("maps", element.getUId(), mContext)); break; } } @@ -72,11 +81,18 @@ public static List downloadDashboardImages(List requestList) { return requestList; } - private static void downloadImages(final List requestUrlList, final Context context) { + private static void downloadImages(DhisController.ImageNetworkPolicy imageNetworkPolicy, + final List requestUrlList, final Context context) { for (int i = 0; i < requestUrlList.size(); i++) { final String request = requestUrlList.get(i); - PicassoProvider.getInstance(context) - .load(request).networkPolicy(NetworkPolicy.NO_CACHE).fetch(); + + if (imageNetworkPolicy == DhisController.ImageNetworkPolicy.NO_CACHE) { + PicassoProvider.getInstance(context) + .load(request).networkPolicy(NetworkPolicy.NO_CACHE).fetch(); + } else { + PicassoProvider.getInstance(context) + .load(request).fetch(); + } } } } diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/DhisService.java b/app/src/main/java/org/hisp/dhis/android/dashboard/DhisService.java index 6213182e..5ebf5125 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/DhisService.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/DhisService.java @@ -168,23 +168,23 @@ public Object execute() throws APIException { }); } - public void pullInterpretationImages(final Context context) { + public void pullInterpretationImages(final DhisController.ImageNetworkPolicy imageNetworkPolicy, final Context context) { JobExecutor.enqueueJob(new NetworkJob(PULL_INTERPRETATION_IMAGES, ResourceType.INTERPRETATION_IMAGES) { @Override public Object execute() throws APIException { - mDhisController.pullInterpretationImages(context); + mDhisController.pullInterpretationImages(imageNetworkPolicy,context); return new Object(); } }); } - public void pullDashboardImages(final Context context) { + public void pullDashboardImages(final DhisController.ImageNetworkPolicy imageNetworkPolicy, final Context context) { JobExecutor.enqueueJob(new NetworkJob(PULL_DASHBOARD_IMAGES, ResourceType.DASHBOARD_IMAGES) { @Override public Object execute() throws APIException { - mDhisController.pullDashboardImages(context); + mDhisController.pullDashboardImages(imageNetworkPolicy,context); return new Object(); } }); diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardEmptyFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardEmptyFragment.java index 608251dd..1960c553 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardEmptyFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardEmptyFragment.java @@ -39,6 +39,7 @@ import org.hisp.dhis.android.dashboard.DhisService; import org.hisp.dhis.android.dashboard.R; +import org.hisp.dhis.android.dashboard.api.controllers.DhisController; import org.hisp.dhis.android.dashboard.api.job.NetworkJob; import org.hisp.dhis.android.dashboard.api.network.SessionManager; import org.hisp.dhis.android.dashboard.api.persistence.preferences.ResourceType; @@ -64,6 +65,9 @@ public class DashboardEmptyFragment extends BaseFragment implements View.OnClick @Bind(R.id.progress_bar) SmoothProgressBar mProgressBar; + private DhisController.ImageNetworkPolicy mImageNetworkPolicy = + DhisController.ImageNetworkPolicy.CACHE; + @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -120,6 +124,7 @@ public void onClick(View v) { public boolean onMenuItemClicked(MenuItem item) { switch (item.getItemId()) { case R.id.refresh: { + mImageNetworkPolicy = DhisController.ImageNetworkPolicy.NO_CACHE; syncDashboards(); return true; } @@ -147,10 +152,10 @@ public void onResponseReceived(NetworkJob.NetworkJobResult result) { getDhisService().syncDashboardContents(); } if (result.getResourceType() == ResourceType.DASHBOARDS_CONTENT) { - getDhisService().pullDashboardImages(getContext()); + getDhisService().pullDashboardImages(mImageNetworkPolicy,getContext()); } if (result.getResourceType() == ResourceType.INTERPRETATIONS) { - getDhisService().pullInterpretationImages(getContext()); + getDhisService().pullInterpretationImages(mImageNetworkPolicy,getContext()); } if (result.getResourceType() == ResourceType.DASHBOARD_IMAGES) { mProgressBar.setVisibility(View.INVISIBLE); diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardViewPagerFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardViewPagerFragment.java index fa06be94..df102cf6 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardViewPagerFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardViewPagerFragment.java @@ -48,6 +48,7 @@ import org.hisp.dhis.android.dashboard.DhisService; import org.hisp.dhis.android.dashboard.R; +import org.hisp.dhis.android.dashboard.api.controllers.DhisController; import org.hisp.dhis.android.dashboard.api.job.NetworkJob; import org.hisp.dhis.android.dashboard.api.models.Access; import org.hisp.dhis.android.dashboard.api.models.Dashboard; @@ -91,6 +92,9 @@ public class DashboardViewPagerFragment extends BaseFragment DashboardAdapter mDashboardAdapter; + private DhisController.ImageNetworkPolicy mImageNetworkPolicy = + DhisController.ImageNetworkPolicy.CACHE; + @Override public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_dashboards, parent, false); @@ -235,6 +239,7 @@ public boolean onMenuItemClicked(MenuItem item) { return true; } case R.id.refresh: { + mImageNetworkPolicy = DhisController.ImageNetworkPolicy.NO_CACHE; syncDashboards(); return true; } @@ -270,10 +275,10 @@ public void onResponseReceived(NetworkJob.NetworkJobResult result) { getDhisService().syncDashboardContents(); } if (result.getResourceType() == ResourceType.DASHBOARDS_CONTENT) { - getDhisService().pullDashboardImages(getContext()); + getDhisService().pullDashboardImages(mImageNetworkPolicy,getContext()); } if (result.getResourceType() == ResourceType.INTERPRETATIONS) { - getDhisService().pullInterpretationImages(getContext()); + getDhisService().pullInterpretationImages(mImageNetworkPolicy,getContext()); } if (result.getResourceType() == ResourceType.DASHBOARD_IMAGES) { mProgressBar.setVisibility(View.INVISIBLE); diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationEmptyFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationEmptyFragment.java index 37ef2c5f..bd876d7c 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationEmptyFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationEmptyFragment.java @@ -13,6 +13,7 @@ import org.hisp.dhis.android.dashboard.DhisService; import org.hisp.dhis.android.dashboard.R; +import org.hisp.dhis.android.dashboard.api.controllers.DhisController; import org.hisp.dhis.android.dashboard.api.job.NetworkJob; import org.hisp.dhis.android.dashboard.api.network.SessionManager; import org.hisp.dhis.android.dashboard.api.persistence.preferences.ResourceType; @@ -35,6 +36,9 @@ public class InterpretationEmptyFragment extends BaseFragment implements View.On @Bind(R.id.progress_bar) SmoothProgressBar mProgressBar; + private DhisController.ImageNetworkPolicy mImageNetworkPolicy = + DhisController.ImageNetworkPolicy.CACHE; + @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -90,6 +94,7 @@ public void onSaveInstanceState(Bundle outState) { public boolean onMenuItemClicked(MenuItem item) { switch (item.getItemId()) { case R.id.refresh: { + mImageNetworkPolicy = DhisController.ImageNetworkPolicy.NO_CACHE; syncInterpretations(); return true; } @@ -109,7 +114,7 @@ private void syncInterpretations() { public void onResponseReceived(NetworkJob.NetworkJobResult result) { Log.d(TAG, "Received " + result.getResourceType()); if (result.getResourceType() == ResourceType.INTERPRETATIONS) { - getDhisService().pullInterpretationImages(getContext()); + getDhisService().pullInterpretationImages(mImageNetworkPolicy,getContext()); } else if (result.getResourceType() == ResourceType.INTERPRETATION_IMAGES) { mProgressBar.setVisibility(View.INVISIBLE); } diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationFragment.java index 1708c5a0..0b7309f5 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationFragment.java @@ -50,6 +50,7 @@ import org.hisp.dhis.android.dashboard.DhisService; import org.hisp.dhis.android.dashboard.R; +import org.hisp.dhis.android.dashboard.api.controllers.DhisController; import org.hisp.dhis.android.dashboard.api.job.NetworkJob; import org.hisp.dhis.android.dashboard.api.models.Interpretation; import org.hisp.dhis.android.dashboard.api.models.Interpretation$Table; @@ -98,6 +99,9 @@ public final class InterpretationFragment extends BaseFragment InterpretationAdapter mAdapter; + private DhisController.ImageNetworkPolicy mImageNetworkPolicy = + DhisController.ImageNetworkPolicy.CACHE; + @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -129,6 +133,7 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { @Override public boolean onMenuItemClick(MenuItem item) { if (item.getItemId() == R.id.refresh) { + mImageNetworkPolicy = DhisController.ImageNetworkPolicy.NO_CACHE; syncInterpretations(); return true; } @@ -274,7 +279,7 @@ public void onInterpretationCommentsClick(Interpretation interpretation) { public void onResponseReceived(NetworkJob.NetworkJobResult result) { Log.d(TAG, "Received " + result.getResourceType()); if (result.getResourceType() == ResourceType.INTERPRETATIONS) { - getDhisService().pullInterpretationImages(getContext()); + getDhisService().pullInterpretationImages(mImageNetworkPolicy,getContext()); } else if (result.getResourceType() == ResourceType.INTERPRETATION_IMAGES) { mProgressBar.setVisibility(View.INVISIBLE); } From b24dc2dfd728a571fc2401a34d979c06f755237b Mon Sep 17 00:00:00 2001 From: idelcano Date: Wed, 12 Jul 2017 16:56:48 +0200 Subject: [PATCH 56/69] Change IdentifiableObject date format from Datetime to String --- .../controllers/InterpretationController.java | 2 +- .../api/models/BaseIdentifiableObject.java | 17 ++-- .../dashboard/api/models/Dashboard.java | 16 +++- .../dashboard/api/models/DashboardItem.java | 4 +- .../api/models/IdentifiableObject.java | 22 +++-- .../dashboard/api/models/Interpretation.java | 8 +- .../dashboard/api/models/UserAccount.java | 12 +-- .../dashboard/api/utils/CodeGenerator.java | 95 +++++++++++++++++++ .../dashboard/api/utils/NetworkUtils.java | 1 + .../ui/adapters/DashboardItemAdapter.java | 2 +- .../ui/adapters/InterpretationAdapter.java | 2 +- .../InterpretationCommentsAdapter.java | 4 +- .../dashboard/DashboardAddFragment.java | 2 + 13 files changed, 156 insertions(+), 31 deletions(-) create mode 100644 api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/CodeGenerator.java diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/InterpretationController.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/InterpretationController.java index 787dbfd6..46a65b2b 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/InterpretationController.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/InterpretationController.java @@ -541,7 +541,7 @@ private static List createOperations(List oldModels continue; } - if (newModel.getLastUpdated().isAfter(oldModel.getLastUpdated())) { + if (DateTime.parse(newModel.getLastUpdated()).isAfter(DateTime.parse(oldModel.getLastUpdated()))) { newModel.setId(oldModel.getId()); ops.add(DbOperation.update(newModel)); } diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/BaseIdentifiableObject.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/BaseIdentifiableObject.java index f2dcd5ef..be4bd076 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/BaseIdentifiableObject.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/BaseIdentifiableObject.java @@ -36,6 +36,7 @@ import org.hisp.dhis.android.dashboard.api.utils.StringUtils; import org.joda.time.DateTime; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -45,6 +46,10 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class BaseIdentifiableObject extends BaseModel implements IdentifiableObject { + public static final String TIMESTAMP_PATTERN = "yyyy-MM-dd'T'HH:mm:ss"; + + public static final SimpleDateFormat LONG_DATE_FORMAT = new SimpleDateFormat(TIMESTAMP_PATTERN); + @JsonIgnore @Column(name = "id") @PrimaryKey(autoincrement = true) @@ -64,11 +69,11 @@ public class BaseIdentifiableObject extends BaseModel implements IdentifiableObj @JsonProperty("created") @Column(name = "created") - DateTime created; + String created; @JsonProperty("lastUpdated") @Column(name = "lastUpdated") - DateTime lastUpdated; + String lastUpdated; @JsonProperty("access") @Column(name = "access") @@ -115,22 +120,22 @@ public void setDisplayName(String displayName) { } @Override - public DateTime getCreated() { + public String getCreated() { return created; } @Override - public void setCreated(DateTime created) { + public void setCreated(String created) { this.created = created; } @Override - public DateTime getLastUpdated() { + public String getLastUpdated() { return lastUpdated; } @Override - public void setLastUpdated(DateTime lastUpdated) { + public void setLastUpdated(String lastUpdated) { this.lastUpdated = lastUpdated; } diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/Dashboard.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/Dashboard.java index 185dff99..1b16e09e 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/Dashboard.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/Dashboard.java @@ -41,6 +41,7 @@ import org.hisp.dhis.android.dashboard.api.models.meta.DbDhis; import org.hisp.dhis.android.dashboard.api.persistence.preferences.DateTimeManager; import org.hisp.dhis.android.dashboard.api.persistence.preferences.ResourceType; +import org.hisp.dhis.android.dashboard.api.utils.CodeGenerator; import org.joda.time.DateTime; import java.util.List; @@ -83,11 +84,12 @@ public static Dashboard createDashboard(String name) { .getLastUpdated(ResourceType.DASHBOARDS); Dashboard dashboard = new Dashboard(); + dashboard.setUId(CodeGenerator.generateCode()); dashboard.setState(State.TO_POST); dashboard.setName(name); dashboard.setDisplayName(name); - dashboard.setCreated(lastUpdatedDateTime); - dashboard.setLastUpdated(lastUpdatedDateTime); + dashboard.setCreated(LONG_DATE_FORMAT.format(lastUpdatedDateTime.toDate())); + dashboard.setLastUpdated(LONG_DATE_FORMAT.format(lastUpdatedDateTime.toDate())); dashboard.setAccess(Access.provideDefaultAccess()); return dashboard; @@ -275,4 +277,14 @@ public int getDashboardItemCount() { = queryRelatedDashboardItems(); return items == null ? 0 : items.size(); } + + @Override + public void setCreated(String created) { + this.created=created; + } + + @Override + public void setLastUpdated(String lastUpdated) { + this.lastUpdated = lastUpdated; + } } \ No newline at end of file diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/DashboardItem.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/DashboardItem.java index f83af67f..a1198960 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/DashboardItem.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/DashboardItem.java @@ -131,8 +131,8 @@ public static DashboardItem createDashboardItem(Dashboard dashboard, .getLastUpdated(ResourceType.DASHBOARDS); DashboardItem item = new DashboardItem(); - item.setCreated(lastUpdatedDateTime); - item.setLastUpdated(lastUpdatedDateTime); + item.setCreated(LONG_DATE_FORMAT.format(lastUpdatedDateTime.toDate())); + item.setLastUpdated(LONG_DATE_FORMAT.format(lastUpdatedDateTime.toDate())); item.setState(State.TO_POST); item.setDashboard(dashboard); item.setAccess(Access.provideDefaultAccess()); diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/IdentifiableObject.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/IdentifiableObject.java index e5a2986a..841b2690 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/IdentifiableObject.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/IdentifiableObject.java @@ -51,13 +51,13 @@ public interface IdentifiableObject { void setDisplayName(String displayName); - DateTime getCreated(); + String getCreated(); - void setCreated(DateTime created); + void setCreated(String created); - DateTime getLastUpdated(); + String getLastUpdated(); - void setLastUpdated(DateTime lastUpdated); + void setLastUpdated(String lastUpdated); Access getAccess(); @@ -80,13 +80,21 @@ class CreatedComparator implements Comparator { @Override public int compare(IdentifiableObject first, IdentifiableObject second) { + DateTime firstDate = null; + if(first!=null && first.getCreated()!=null) { + firstDate=DateTime.parse(first.getCreated()); + } + DateTime secondDate = null; + if(second!=null && second.getCreated()!=null) { + secondDate=DateTime.parse(second.getCreated()); + } if (first != null && first.getCreated() != null - && second != null && second.getCreated() != null) { - if (first.getCreated().isAfter(second.getCreated())) { + && second != null && secondDate != null) { + if (firstDate.isAfter(secondDate)) { return 1; } - if (second.getCreated().isAfter(first.getCreated())) { + if (secondDate.isAfter(firstDate)) { return -1; } } diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/Interpretation.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/Interpretation.java index 938d83b4..856d15c7 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/Interpretation.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/Interpretation.java @@ -119,8 +119,8 @@ public static InterpretationComment addComment(Interpretation interpretation, Us .getLastUpdated(ResourceType.INTERPRETATIONS); InterpretationComment comment = new InterpretationComment(); - comment.setCreated(lastUpdated); - comment.setLastUpdated(lastUpdated); + comment.setCreated(LONG_DATE_FORMAT.format(lastUpdated.toDate())); + comment.setLastUpdated(LONG_DATE_FORMAT.format(lastUpdated.toDate())); comment.setAccess(Access.provideDefaultAccess()); comment.setText(text); comment.setState(State.TO_POST); @@ -146,8 +146,8 @@ public static Interpretation createInterpretation(DashboardItem item, User user, .getLastUpdated(ResourceType.INTERPRETATIONS); Interpretation interpretation = new Interpretation(); - interpretation.setCreated(lastUpdated); - interpretation.setLastUpdated(lastUpdated); + interpretation.setCreated(LONG_DATE_FORMAT.format(lastUpdated.toDate())); + interpretation.setLastUpdated(LONG_DATE_FORMAT.format(lastUpdated.toDate())); interpretation.setAccess(Access.provideDefaultAccess()); interpretation.setText(text); interpretation.setState(State.TO_POST); diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/UserAccount.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/UserAccount.java index 6e4f4a2a..e65db0e9 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/UserAccount.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/UserAccount.java @@ -68,11 +68,11 @@ public final class UserAccount extends BaseModel implements IdentifiableObject { @JsonProperty("created") @Column(name = "created") - DateTime created; + String created; @JsonProperty("lastUpdated") @Column(name = "lastUpdated") - DateTime lastUpdated; + String lastUpdated; @JsonProperty("access") @Column(name = "access") @@ -208,25 +208,25 @@ public void setDisplayName(String displayName) { @JsonIgnore @Override - public DateTime getCreated() { + public String getCreated() { return created; } @JsonIgnore @Override - public void setCreated(DateTime created) { + public void setCreated(String created) { this.created = created; } @JsonIgnore @Override - public DateTime getLastUpdated() { + public String getLastUpdated() { return lastUpdated; } @JsonIgnore @Override - public void setLastUpdated(DateTime lastUpdated) { + public void setLastUpdated(String lastUpdated) { this.lastUpdated = lastUpdated; } diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/CodeGenerator.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/CodeGenerator.java new file mode 100644 index 00000000..fd9ea3ce --- /dev/null +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/CodeGenerator.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2016, University of Oslo + * * All rights reserved. + * * + * * Redistribution and use in source and binary forms, with or without + * * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright notice, this + * * list of conditions and the following disclaimer. + * * + * * Redistributions in binary form must reproduce the above copyright notice, + * * this list of conditions and the following disclaimer in the documentation + * * and/or other materials provided with the distribution. + * * Neither the name of the HISP project nor the names of its contributors may + * * be used to endorse or promote products derived from this software without + * * specific prior written permission. + * * + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +package org.hisp.dhis.android.dashboard.api.utils; + +import java.security.SecureRandom; +import java.util.regex.Pattern; + +/** + * @author bobj + */ +public class CodeGenerator +{ + public static final String letters = "abcdefghijklmnopqrstuvwxyz" + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + public static final String allowedChars = "0123456789" + letters; + + public static final int NUMBER_OF_CODEPOINTS = allowedChars.length(); + public static final int CODESIZE = 11; + + private static final Pattern CODE_PATTERN = Pattern.compile( "^[a-zA-Z]{1}[a-zA-Z0-9]{10}$" ); + + /** + * Generates a pseudo random string using the allowed characters. Code is + * 11 characters long. + * + * @return the code. + */ + public static String generateCode() + { + return generateCode( CODESIZE ); + } + + /** + * Generates a pseudo random string using the allowed characters. + * + * @param codeSize the number of characters in the code. + * @return the code. + */ + public static String generateCode( int codeSize ) + { + // Using the system default algorithm and seed + SecureRandom sr = new SecureRandom(); + + char[] randomChars = new char[codeSize]; + + // first char should be a letter + randomChars[0] = letters.charAt( sr.nextInt( letters.length() ) ); + + for ( int i = 1; i < codeSize; ++i ) + { + randomChars[i] = allowedChars.charAt( sr.nextInt( NUMBER_OF_CODEPOINTS ) ); + } + + return new String( randomChars ); + } + + /** + * Tests whether the given code is valid. + * + * @param code the code to validate. + * @return true if the code is valid. + */ + public static boolean isValidCode( String code ) + { + return code != null && CODE_PATTERN.matcher( code ).matches(); + } +} diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/NetworkUtils.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/NetworkUtils.java index 0316a38a..a1802d71 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/NetworkUtils.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/NetworkUtils.java @@ -67,6 +67,7 @@ public static Header findLocationHeader(List
headers) { } public static void handleApiException(APIException apiException) throws APIException { + apiException.printStackTrace(); handleApiException(apiException, null); } diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java index 8cd59b29..f5adac44 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java @@ -227,7 +227,7 @@ public void onBindViewHolder(ItemViewHolder holder, int position) { DashboardItem item = getItem(holder.getAdapterPosition()); holder.menuButtonHandler.setDashboardItem(item); - holder.lastUpdated.setText(item.getLastUpdated().toString(DATE_FORMAT)); + holder.lastUpdated.setText(item.getLastUpdated()); /* setting name extracted from content to TextView at top of item layout. */ if (DashboardItemContent.TYPE_CHART.equals(item.getType()) && item.getChart() != null) { diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationAdapter.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationAdapter.java index 798ccd2e..f94ced2d 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationAdapter.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationAdapter.java @@ -158,7 +158,7 @@ public void onBindViewHolder(InterpretationHolder holder, int position) { holder.userTextView.setText(interpretation.getUser() == null ? EMPTY_FIELD : interpretation.getUser().getDisplayName()); holder.createdTextView.setText(interpretation.getCreated() == null - ? EMPTY_FIELD : interpretation.getCreated().toString(DATE_FORMAT)); + ? EMPTY_FIELD : interpretation.getCreated()); holder.interpretationTextView.setText(interpretation.getText() == null ? EMPTY_FIELD : interpretation.getText()); diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationCommentsAdapter.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationCommentsAdapter.java index f7cb4c8c..e750b4fd 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationCommentsAdapter.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationCommentsAdapter.java @@ -43,6 +43,8 @@ import static android.text.TextUtils.isEmpty; +import java.util.Date; + /** * @author Araz Abishov . */ @@ -77,7 +79,7 @@ public CommentViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { @Override public void onBindViewHolder(CommentViewHolder holder, int position) { InterpretationComment comment = getItem(position); - DateTime lastUpdated = comment.getLastUpdated(); + DateTime lastUpdated = DateTime.parse(comment.getLastUpdated()); User user = comment.getUser(); String name = EMPTY_FIELD; diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardAddFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardAddFragment.java index a4b596b3..9a28818d 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardAddFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardAddFragment.java @@ -41,6 +41,7 @@ import org.hisp.dhis.android.dashboard.R; import org.hisp.dhis.android.dashboard.api.models.Dashboard; +import org.hisp.dhis.android.dashboard.api.models.meta.State; import org.hisp.dhis.android.dashboard.api.utils.EventBusProvider; import org.hisp.dhis.android.dashboard.ui.events.UiEvent; import org.hisp.dhis.android.dashboard.ui.fragments.BaseDialogFragment; @@ -97,6 +98,7 @@ public void onButtonClicked(View view) { if (!isEmptyName) { Dashboard newDashboard = Dashboard .createDashboard(mDashboardName.getText().toString()); + newDashboard.setState(State.TO_POST); newDashboard.save(); if (isDhisServiceBound()) { getDhisService().syncDashboards(); From deaf9fb19357175615ac1650930ab78ebcabf1bc Mon Sep 17 00:00:00 2001 From: idelcano Date: Wed, 12 Jul 2017 17:52:37 +0200 Subject: [PATCH 57/69] Remove unnecesary code --- .../dashboard/api/models/Dashboard.java | 2 - .../dashboard/api/utils/CodeGenerator.java | 95 ------------------- .../dashboard/DashboardAddFragment.java | 1 - 3 files changed, 98 deletions(-) delete mode 100644 api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/CodeGenerator.java diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/Dashboard.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/Dashboard.java index 1b16e09e..5bb92432 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/Dashboard.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/Dashboard.java @@ -41,7 +41,6 @@ import org.hisp.dhis.android.dashboard.api.models.meta.DbDhis; import org.hisp.dhis.android.dashboard.api.persistence.preferences.DateTimeManager; import org.hisp.dhis.android.dashboard.api.persistence.preferences.ResourceType; -import org.hisp.dhis.android.dashboard.api.utils.CodeGenerator; import org.joda.time.DateTime; import java.util.List; @@ -84,7 +83,6 @@ public static Dashboard createDashboard(String name) { .getLastUpdated(ResourceType.DASHBOARDS); Dashboard dashboard = new Dashboard(); - dashboard.setUId(CodeGenerator.generateCode()); dashboard.setState(State.TO_POST); dashboard.setName(name); dashboard.setDisplayName(name); diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/CodeGenerator.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/CodeGenerator.java deleted file mode 100644 index fd9ea3ce..00000000 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/CodeGenerator.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2016, University of Oslo - * * All rights reserved. - * * - * * Redistribution and use in source and binary forms, with or without - * * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright notice, this - * * list of conditions and the following disclaimer. - * * - * * Redistributions in binary form must reproduce the above copyright notice, - * * this list of conditions and the following disclaimer in the documentation - * * and/or other materials provided with the distribution. - * * Neither the name of the HISP project nor the names of its contributors may - * * be used to endorse or promote products derived from this software without - * * specific prior written permission. - * * - * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package org.hisp.dhis.android.dashboard.api.utils; - -import java.security.SecureRandom; -import java.util.regex.Pattern; - -/** - * @author bobj - */ -public class CodeGenerator -{ - public static final String letters = "abcdefghijklmnopqrstuvwxyz" - + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - - public static final String allowedChars = "0123456789" + letters; - - public static final int NUMBER_OF_CODEPOINTS = allowedChars.length(); - public static final int CODESIZE = 11; - - private static final Pattern CODE_PATTERN = Pattern.compile( "^[a-zA-Z]{1}[a-zA-Z0-9]{10}$" ); - - /** - * Generates a pseudo random string using the allowed characters. Code is - * 11 characters long. - * - * @return the code. - */ - public static String generateCode() - { - return generateCode( CODESIZE ); - } - - /** - * Generates a pseudo random string using the allowed characters. - * - * @param codeSize the number of characters in the code. - * @return the code. - */ - public static String generateCode( int codeSize ) - { - // Using the system default algorithm and seed - SecureRandom sr = new SecureRandom(); - - char[] randomChars = new char[codeSize]; - - // first char should be a letter - randomChars[0] = letters.charAt( sr.nextInt( letters.length() ) ); - - for ( int i = 1; i < codeSize; ++i ) - { - randomChars[i] = allowedChars.charAt( sr.nextInt( NUMBER_OF_CODEPOINTS ) ); - } - - return new String( randomChars ); - } - - /** - * Tests whether the given code is valid. - * - * @param code the code to validate. - * @return true if the code is valid. - */ - public static boolean isValidCode( String code ) - { - return code != null && CODE_PATTERN.matcher( code ).matches(); - } -} diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardAddFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardAddFragment.java index 9a28818d..8982995c 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardAddFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardAddFragment.java @@ -98,7 +98,6 @@ public void onButtonClicked(View view) { if (!isEmptyName) { Dashboard newDashboard = Dashboard .createDashboard(mDashboardName.getText().toString()); - newDashboard.setState(State.TO_POST); newDashboard.save(); if (isDhisServiceBound()) { getDhisService().syncDashboards(); From d9c287f7d597f98b4f4a6b82bb2610e84017c240 Mon Sep 17 00:00:00 2001 From: idelcano Date: Wed, 12 Jul 2017 17:55:11 +0200 Subject: [PATCH 58/69] Remove import --- .../dashboard/ui/fragments/dashboard/DashboardAddFragment.java | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardAddFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardAddFragment.java index 8982995c..a4b596b3 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardAddFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardAddFragment.java @@ -41,7 +41,6 @@ import org.hisp.dhis.android.dashboard.R; import org.hisp.dhis.android.dashboard.api.models.Dashboard; -import org.hisp.dhis.android.dashboard.api.models.meta.State; import org.hisp.dhis.android.dashboard.api.utils.EventBusProvider; import org.hisp.dhis.android.dashboard.ui.events.UiEvent; import org.hisp.dhis.android.dashboard.ui.fragments.BaseDialogFragment; From a939531e13539543c00d21403b025d4b0383ad97 Mon Sep 17 00:00:00 2001 From: idelcano Date: Wed, 12 Jul 2017 17:55:32 +0200 Subject: [PATCH 59/69] Fix order null check --- .../dhis/android/dashboard/api/models/IdentifiableObject.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/IdentifiableObject.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/IdentifiableObject.java index 841b2690..26e1fd2d 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/IdentifiableObject.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/models/IdentifiableObject.java @@ -88,7 +88,7 @@ public int compare(IdentifiableObject first, IdentifiableObject second) { if(second!=null && second.getCreated()!=null) { secondDate=DateTime.parse(second.getCreated()); } - if (first != null && first.getCreated() != null + if (first != null && firstDate != null && second != null && secondDate != null) { if (firstDate.isAfter(secondDate)) { return 1; From 52a5bc214875f2e0da40e5277f0be87b4eda8b9b Mon Sep 17 00:00:00 2001 From: mplazaspalacio Date: Wed, 12 Jul 2017 18:09:27 +0200 Subject: [PATCH 60/69] Solved bug adding and removing dashboards --- .../ui/fragments/dashboard/DashboardViewPagerFragment.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardViewPagerFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardViewPagerFragment.java index fa06be94..a158a704 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardViewPagerFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardViewPagerFragment.java @@ -216,6 +216,8 @@ public void onUiEventReceived(UiEvent uiEvent) { } private void setDashboards(List dashboards) { + mDashboardAdapter = new DashboardAdapter(getChildFragmentManager()); + mViewPager.setAdapter(mDashboardAdapter); mDashboardAdapter.swapData(dashboards); mTabs.removeAllTabs(); From 03d26d5930a33a29b70b91c27b2aa87ebffefb0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Sa=CC=81nchez?= Date: Wed, 12 Jul 2017 19:45:16 +0200 Subject: [PATCH 61/69] refactor to start app sync with cache and manual sync with no_cache --- .../ui/adapters/DashboardAdapter.java | 13 +++++++++-- .../dashboard/ui/fragments/BaseFragment.java | 7 ++++++ .../ui/fragments/SyncingController.java | 5 +++++ .../dashboard/DashboardEmptyFragment.java | 10 +++++++++ .../dashboard/DashboardFragment.java | 22 ++++++++++++++++++- .../dashboard/DashboardViewPagerFragment.java | 18 +++++++++++++-- .../InterpretationEmptyFragment.java | 14 ++++++++++-- .../InterpretationFragment.java | 22 +++++++++++++++++++ app/src/main/res/values/strings.xml | 1 + 9 files changed, 105 insertions(+), 7 deletions(-) create mode 100644 app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/SyncingController.java diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardAdapter.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardAdapter.java index 05a322c8..4c20f103 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardAdapter.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardAdapter.java @@ -28,11 +28,14 @@ package org.hisp.dhis.android.dashboard.ui.adapters; +import static org.hisp.dhis.android.dashboard.ui.fragments.dashboard.DashboardFragment.newInstance; + import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; import org.hisp.dhis.android.dashboard.api.models.Dashboard; +import org.hisp.dhis.android.dashboard.ui.fragments.SyncingController; import org.hisp.dhis.android.dashboard.ui.fragments.dashboard.DashboardFragment; import java.util.List; @@ -40,16 +43,22 @@ public class DashboardAdapter extends FragmentPagerAdapter { private static final String EMPTY_TITLE = ""; private List mDashboards; + private SyncingController syncingController; - public DashboardAdapter(FragmentManager fm) { + public DashboardAdapter(FragmentManager fm,SyncingController syncingController) { super(fm); + this.syncingController = syncingController; } @Override public Fragment getItem(int position) { if (mDashboards != null && mDashboards.size() > 0) { - return DashboardFragment + DashboardFragment dashboardFragment = DashboardFragment .newInstance(getDashboard(position)); + + dashboardFragment.setSyncingController(syncingController); + + return dashboardFragment; } else { return null; } diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/BaseFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/BaseFragment.java index 9f6c823a..d0ca83bf 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/BaseFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/BaseFragment.java @@ -28,10 +28,17 @@ package org.hisp.dhis.android.dashboard.ui.fragments; +import static android.R.attr.editable; + import android.app.Activity; +import android.content.Context; import android.support.v4.app.Fragment; +import android.view.View; +import android.widget.ProgressBar; +import android.widget.Toast; import org.hisp.dhis.android.dashboard.DhisService; +import org.hisp.dhis.android.dashboard.R; import org.hisp.dhis.android.dashboard.api.utils.EventBusProvider; import org.hisp.dhis.android.dashboard.ui.activities.BaseActivity; import org.hisp.dhis.android.dashboard.ui.activities.INavigationCallback; diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/SyncingController.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/SyncingController.java new file mode 100644 index 00000000..652ad183 --- /dev/null +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/SyncingController.java @@ -0,0 +1,5 @@ +package org.hisp.dhis.android.dashboard.ui.fragments; + +public interface SyncingController{ + boolean isSyncing(); +} \ No newline at end of file diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardEmptyFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardEmptyFragment.java index 1960c553..cd58687f 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardEmptyFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardEmptyFragment.java @@ -34,6 +34,7 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.Toast; import com.squareup.otto.Subscribe; @@ -122,6 +123,13 @@ public void onClick(View v) { } public boolean onMenuItemClicked(MenuItem item) { + if (mProgressBar.getVisibility() == View.VISIBLE) { + Toast.makeText(getContext(), + getString(R.string.action_not_allowed_during_sync), + Toast.LENGTH_SHORT).show(); + return false; + } + switch (item.getItemId()) { case R.id.refresh: { mImageNetworkPolicy = DhisController.ImageNetworkPolicy.NO_CACHE; @@ -162,4 +170,6 @@ public void onResponseReceived(NetworkJob.NetworkJobResult result) { getDhisService().syncInterpretations(); } } + + } diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardFragment.java index 08873663..b4251d71 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardFragment.java @@ -40,6 +40,7 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ProgressBar; import android.widget.Toast; import android.widget.ViewSwitcher; @@ -63,12 +64,17 @@ import org.hisp.dhis.android.dashboard.ui.adapters.DashboardItemAdapter; import org.hisp.dhis.android.dashboard.ui.events.UiEvent; import org.hisp.dhis.android.dashboard.ui.fragments.BaseFragment; +import org.hisp.dhis.android.dashboard.ui.fragments.SyncingController; import org.hisp.dhis.android.dashboard.ui.fragments.interpretation.InterpretationCreateFragment; import org.hisp.dhis.android.dashboard.ui.views.GridDividerDecoration; import java.util.Arrays; import java.util.List; +import butterknife.Bind; +import butterknife.ButterKnife; +import fr.castorflex.android.smoothprogressbar.SmoothProgressBar; + public class DashboardFragment extends BaseFragment implements LoaderManager.LoaderCallbacks>, DashboardItemAdapter.OnItemClickListener { private static final int LOADER_ID = 74734523; @@ -87,6 +93,8 @@ public class DashboardFragment extends BaseFragment DashboardItemAdapter mAdapter; + private SyncingController syncingController; + public static DashboardFragment newInstance(Dashboard dashboard) { DashboardFragment fragment = new DashboardFragment(); Access access = dashboard.getAccess(); @@ -104,6 +112,10 @@ public static DashboardFragment newInstance(Dashboard dashboard) { return fragment; } + public void setSyncingController(SyncingController syncingController){ + this.syncingController = syncingController; + } + private static Access getAccessFromBundle(Bundle args) { Access access = new Access(); @@ -124,6 +136,8 @@ public View onCreateView(LayoutInflater inflater, ViewGroup group, Bundle bundle @Override public void onViewCreated(View view, Bundle savedInstanceState) { + + mViewSwitcher = (ViewSwitcher) view; mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view); @@ -226,6 +240,7 @@ public void onContentClick(DashboardElement element) { @Override public void onContentDeleteClick(DashboardElement element) { + if (element != null) { element.deleteDashboardElement(); @@ -238,7 +253,12 @@ public void onContentDeleteClick(DashboardElement element) { @Override public void onItemDeleteClick(DashboardItem item) { - if (item != null) { + + if (syncingController != null && syncingController.isSyncing()){ + Toast.makeText(getContext(), + getString(R.string.action_not_allowed_during_sync), + Toast.LENGTH_SHORT).show(); + }else if (item != null) { item.deleteDashboardItem(); if (isDhisServiceBound()) { diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardViewPagerFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardViewPagerFragment.java index df102cf6..4d0bb793 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardViewPagerFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardViewPagerFragment.java @@ -41,6 +41,7 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.Toast; import com.raizlabs.android.dbflow.sql.builder.Condition; import com.raizlabs.android.dbflow.sql.language.Select; @@ -61,6 +62,7 @@ import org.hisp.dhis.android.dashboard.ui.adapters.DashboardAdapter; import org.hisp.dhis.android.dashboard.ui.events.UiEvent; import org.hisp.dhis.android.dashboard.ui.fragments.BaseFragment; +import org.hisp.dhis.android.dashboard.ui.fragments.SyncingController; import java.util.Arrays; import java.util.Collections; @@ -72,7 +74,7 @@ public class DashboardViewPagerFragment extends BaseFragment implements LoaderCallbacks>, View.OnClickListener, - ViewPager.OnPageChangeListener { + ViewPager.OnPageChangeListener, SyncingController { static final String TAG = DashboardViewPagerFragment.class.getSimpleName(); static final String IS_LOADING = "state:isLoading"; @@ -104,7 +106,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle saved public void onViewCreated(View view, Bundle savedInstanceState) { ButterKnife.bind(this, view); - mDashboardAdapter = new DashboardAdapter(getChildFragmentManager()); + mDashboardAdapter = new DashboardAdapter(getChildFragmentManager(),this); mViewPager.setAdapter(mDashboardAdapter); mViewPager.addOnPageChangeListener(this); @@ -229,6 +231,13 @@ private void setDashboards(List dashboards) { } public boolean onMenuItemClicked(MenuItem item) { + if (isSyncing()) { + Toast.makeText(getContext(), + getString(R.string.action_not_allowed_during_sync), + Toast.LENGTH_SHORT).show(); + return false; + } + switch (item.getItemId()) { case R.id.add_dashboard_item: { long dashboardId = mDashboardAdapter @@ -286,6 +295,11 @@ public void onResponseReceived(NetworkJob.NetworkJobResult result) { } } + @Override + public boolean isSyncing() { + return mProgressBar.getVisibility() == View.VISIBLE; + } + private static class DashboardQuery implements Query> { @Override diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationEmptyFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationEmptyFragment.java index bd876d7c..19e20dd4 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationEmptyFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationEmptyFragment.java @@ -8,6 +8,7 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.Toast; import com.squareup.otto.Subscribe; @@ -41,7 +42,8 @@ public class InterpretationEmptyFragment extends BaseFragment implements View.On @Nullable @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_interpretations_empty, container, false); } @@ -92,6 +94,13 @@ public void onSaveInstanceState(Bundle outState) { } public boolean onMenuItemClicked(MenuItem item) { + if (mProgressBar.getVisibility() == View.VISIBLE) { + Toast.makeText(getContext(), + getString(R.string.action_not_allowed_during_sync), + Toast.LENGTH_SHORT).show(); + return false; + } + switch (item.getItemId()) { case R.id.refresh: { mImageNetworkPolicy = DhisController.ImageNetworkPolicy.NO_CACHE; @@ -114,9 +123,10 @@ private void syncInterpretations() { public void onResponseReceived(NetworkJob.NetworkJobResult result) { Log.d(TAG, "Received " + result.getResourceType()); if (result.getResourceType() == ResourceType.INTERPRETATIONS) { - getDhisService().pullInterpretationImages(mImageNetworkPolicy,getContext()); + getDhisService().pullInterpretationImages(mImageNetworkPolicy, getContext()); } else if (result.getResourceType() == ResourceType.INTERPRETATION_IMAGES) { mProgressBar.setVisibility(View.INVISIBLE); } } + } diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationFragment.java index 0b7309f5..95c91b71 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationFragment.java @@ -132,6 +132,14 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { mToolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { + + if (mProgressBar.getVisibility() == View.VISIBLE) { + Toast.makeText(getContext(), + getString(R.string.action_not_allowed_during_sync), + Toast.LENGTH_SHORT).show(); + return false; + } + if (item.getItemId() == R.id.refresh) { mImageNetworkPolicy = DhisController.ImageNetworkPolicy.NO_CACHE; syncInterpretations(); @@ -240,6 +248,13 @@ public void onInterpretationTextClick(Interpretation interpretation) { @Override public void onInterpretationDeleteClick(Interpretation interpretation) { + if (mProgressBar.getVisibility() == View.VISIBLE) { + Toast.makeText(getContext(), + getString(R.string.action_not_allowed_during_sync), + Toast.LENGTH_SHORT).show(); + return; + } + int position = mAdapter.getData().indexOf(interpretation); if (!(position < 0)) { mAdapter.getData().remove(position); @@ -262,6 +277,13 @@ public void onInterpretationDeleteClick(Interpretation interpretation) { @Override public void onInterpretationEditClick(Interpretation interpretation) { + if (mProgressBar.getVisibility() == View.VISIBLE) { + Toast.makeText(getContext(), + getString(R.string.action_not_allowed_during_sync), + Toast.LENGTH_SHORT).show(); + return; + } + InterpretationTextEditFragment .newInstance(interpretation.getId()) .show(getChildFragmentManager()); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4f0333fb..8f2ae1f2 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -140,4 +140,5 @@ "Width:" "Height:" The value is not valid, please enter a value between %1$d and %2$d + At this time the application is synchronizing with the server and it is not possible to perform any action, please try it when finished From b9871e8f117b6a40bc60cc16dd0a6b546eb30420 Mon Sep 17 00:00:00 2001 From: ifoche Date: Wed, 12 Jul 2017 22:58:42 +0200 Subject: [PATCH 62/69] fix little merge bug and rephrase the toast message --- .../ui/fragments/dashboard/DashboardViewPagerFragment.java | 2 +- app/src/main/res/values/strings.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardViewPagerFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardViewPagerFragment.java index 2507ab13..f5df746e 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardViewPagerFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardViewPagerFragment.java @@ -222,7 +222,7 @@ public void onUiEventReceived(UiEvent uiEvent) { } private void setDashboards(List dashboards) { - mDashboardAdapter = new DashboardAdapter(getChildFragmentManager()); + mDashboardAdapter = new DashboardAdapter(getChildFragmentManager(), this); mViewPager.setAdapter(mDashboardAdapter); mDashboardAdapter.swapData(dashboards); mTabs.removeAllTabs(); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8f2ae1f2..ce4bc04a 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -140,5 +140,5 @@ "Width:" "Height:" The value is not valid, please enter a value between %1$d and %2$d - At this time the application is synchronizing with the server and it is not possible to perform any action, please try it when finished + "The App is synchronizing with the server, please try again after it's finished" From 8f258be758e2cc8ac795742a03273cb63ae63065 Mon Sep 17 00:00:00 2001 From: ifoche Date: Wed, 12 Jul 2017 23:32:38 +0200 Subject: [PATCH 63/69] fix nullPointer on fragment change and setDashboards(null) --- .../ui/fragments/dashboard/DashboardViewPagerFragment.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardViewPagerFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardViewPagerFragment.java index f5df746e..33f7f6c0 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardViewPagerFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardViewPagerFragment.java @@ -222,8 +222,10 @@ public void onUiEventReceived(UiEvent uiEvent) { } private void setDashboards(List dashboards) { - mDashboardAdapter = new DashboardAdapter(getChildFragmentManager(), this); - mViewPager.setAdapter(mDashboardAdapter); + if (dashboards != null) { + mDashboardAdapter = new DashboardAdapter(getChildFragmentManager(), this); + mViewPager.setAdapter(mDashboardAdapter); + } mDashboardAdapter.swapData(dashboards); mTabs.removeAllTabs(); From db59f25c8162f782a5aeac00e6355ef4ed92c271 Mon Sep 17 00:00:00 2001 From: ifoche Date: Wed, 12 Jul 2017 23:59:58 +0200 Subject: [PATCH 64/69] fix tests --- .../org/hisp/dhis/android/dashboard/api/user/UserTests.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/api/src/test/java/org/hisp/dhis/android/dashboard/api/user/UserTests.java b/api/src/test/java/org/hisp/dhis/android/dashboard/api/user/UserTests.java index c642a2f7..a4ff8ad8 100644 --- a/api/src/test/java/org/hisp/dhis/android/dashboard/api/user/UserTests.java +++ b/api/src/test/java/org/hisp/dhis/android/dashboard/api/user/UserTests.java @@ -7,6 +7,7 @@ import org.hisp.dhis.android.dashboard.api.commons.JsonParser; import org.hisp.dhis.android.dashboard.api.models.User; import org.hisp.dhis.android.dashboard.api.models.UserAccount; +import org.joda.time.DateTime; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -24,9 +25,9 @@ public void userAccount_conversion_to_user() throws IOException { assertTrue(user.getUId().equals("xE7jOejl9FI")); assertTrue(user.getName().equals("John Traore")); assertTrue(user.getDisplayName().equals("John Traore")); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(user.getCreated(), + assertTrue(DateTestUtils.compareParsedDateWithStringDate(DateTime.parse(user.getCreated()), "2013-04-18T17:15:08.407")); - assertTrue(DateTestUtils.compareParsedDateWithStringDate(user.getLastUpdated(), + assertTrue(DateTestUtils.compareParsedDateWithStringDate(DateTime.parse(user.getLastUpdated()), "2017-05-02T17:02:37.817")); assertTrue(user.getAccess() == null); } From 7874133e65b83eb3a6d9f595a70dc9f7eecd70cd Mon Sep 17 00:00:00 2001 From: mplazaspalacio Date: Tue, 18 Jul 2017 12:45:45 +0200 Subject: [PATCH 65/69] Remove interceptor an cache size from picasso provider and add the indicator and the log lines to enable when developing. --- .../dashboard/api/utils/PicassoProvider.java | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/PicassoProvider.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/PicassoProvider.java index 0d0070e1..b0ac8adf 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/PicassoProvider.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/PicassoProvider.java @@ -30,18 +30,13 @@ import android.content.Context; -import com.squareup.okhttp.Cache; -import com.squareup.okhttp.Interceptor; import com.squareup.okhttp.OkHttpClient; -import com.squareup.okhttp.Response; import com.squareup.picasso.OkHttpDownloader; import com.squareup.picasso.Picasso; import org.hisp.dhis.android.dashboard.api.controllers.DhisController; import org.hisp.dhis.android.dashboard.api.network.RepoManager; -import java.io.IOException; - public final class PicassoProvider { private static Picasso mPicasso; @@ -53,20 +48,11 @@ public static Picasso getInstance(Context context) { if (mPicasso == null) { OkHttpClient client = RepoManager.provideOkHttpClient( DhisController.getInstance().getUserCredentials(), context); - - client.networkInterceptors().add(new Interceptor() { - @Override - public Response intercept(Chain chain) throws IOException { - Response originalResponse = chain.proceed(chain.request()); - return originalResponse.newBuilder().header("Cache-Control", "max-age=" + (60 * 60 * 24 * 365)).build(); - } - }); - - client.setCache(new Cache(context.getCacheDir(), Integer.MAX_VALUE)); - mPicasso = new Picasso.Builder(context) .downloader(new OkHttpDownloader(client)) .build(); + mPicasso.setIndicatorsEnabled(false); + mPicasso.setLoggingEnabled(false); } return mPicasso; From bc0d159fbc5bff880cf2bf48453298fc5fd4165e Mon Sep 17 00:00:00 2001 From: mplazaspalacio Date: Tue, 18 Jul 2017 12:59:34 +0200 Subject: [PATCH 66/69] Restart picasso provider with new credentials when new login. --- .../dashboard/api/controllers/PullImageController.java | 4 ++-- .../dhis/android/dashboard/api/utils/PicassoProvider.java | 4 ++-- .../android/dashboard/ui/activities/LoginActivity.java | 6 ++++-- .../dashboard/ui/adapters/DashboardItemAdapter.java | 2 +- .../dashboard/ui/adapters/InterpretationAdapter.java | 2 +- .../android/dashboard/ui/fragments/ImageViewFragment.java | 7 +++---- 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/PullImageController.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/PullImageController.java index 2b6ab6be..4b6aafe1 100644 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/PullImageController.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/PullImageController.java @@ -87,10 +87,10 @@ private static void downloadImages(DhisController.ImageNetworkPolicy imageNetwor final String request = requestUrlList.get(i); if (imageNetworkPolicy == DhisController.ImageNetworkPolicy.NO_CACHE) { - PicassoProvider.getInstance(context) + PicassoProvider.getInstance(context, false) .load(request).networkPolicy(NetworkPolicy.NO_CACHE).fetch(); } else { - PicassoProvider.getInstance(context) + PicassoProvider.getInstance(context, false) .load(request).fetch(); } } diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/PicassoProvider.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/PicassoProvider.java index b0ac8adf..f5d868a3 100755 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/PicassoProvider.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/utils/PicassoProvider.java @@ -44,8 +44,8 @@ public final class PicassoProvider { private PicassoProvider() { } - public static Picasso getInstance(Context context) { - if (mPicasso == null) { + public static Picasso getInstance(Context context, boolean changeCredentials) { + if (mPicasso == null || changeCredentials) { OkHttpClient client = RepoManager.provideOkHttpClient( DhisController.getInstance().getUserCredentials(), context); mPicasso = new Picasso.Builder(context) diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/activities/LoginActivity.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/activities/LoginActivity.java index 69e5c6df..fa570087 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/activities/LoginActivity.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/activities/LoginActivity.java @@ -28,6 +28,8 @@ package org.hisp.dhis.android.dashboard.ui.activities; +import static org.hisp.dhis.android.dashboard.utils.TextUtils.isEmpty; + import android.content.Intent; import android.os.Bundle; import android.support.annotation.Nullable; @@ -46,6 +48,7 @@ import org.hisp.dhis.android.dashboard.api.models.meta.Credentials; import org.hisp.dhis.android.dashboard.api.models.meta.ResponseHolder; import org.hisp.dhis.android.dashboard.api.persistence.preferences.ResourceType; +import org.hisp.dhis.android.dashboard.api.utils.PicassoProvider; import butterknife.Bind; import butterknife.ButterKnife; @@ -53,8 +56,6 @@ import butterknife.OnTextChanged; import fr.castorflex.android.circularprogressbar.CircularProgressBar; -import static org.hisp.dhis.android.dashboard.utils.TextUtils.isEmpty; - public class LoginActivity extends BaseActivity { private static final String IS_LOADING = "state:isLoading"; @@ -136,6 +137,7 @@ public void onResultReceived(NetworkJob.NetworkJobResult jobResult) ResponseHolder responseHolder = jobResult.getResponseHolder(); if (responseHolder.getApiException() == null) { + PicassoProvider.getInstance(this, true); startActivity(new Intent(this, MenuActivity.class)); finish(); } else { diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java index f5adac44..45f11bc1 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java @@ -110,7 +110,7 @@ public DashboardItemAdapter(Context context, Access dashboardAccess, mResourcesName = context.getString(R.string.resources); mMessaName = context.getString(R.string.messages); - mImageLoader = PicassoProvider.getInstance(context); + mImageLoader = PicassoProvider.getInstance(context, false); } /* returns type of row depending on item content type. */ diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationAdapter.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationAdapter.java index f94ced2d..f2798ff7 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationAdapter.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationAdapter.java @@ -72,7 +72,7 @@ public InterpretationAdapter(Context context, LayoutInflater inflater, super(context, inflater); mClickListener = clickListener; - mImageLoader = PicassoProvider.getInstance(context); + mImageLoader = PicassoProvider.getInstance(context, false); } /* returns type of row depending on item content type. */ diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/ImageViewFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/ImageViewFragment.java index 70bfa160..7974e3f6 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/ImageViewFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/ImageViewFragment.java @@ -28,6 +28,8 @@ package org.hisp.dhis.android.dashboard.ui.fragments; +import static org.hisp.dhis.android.dashboard.api.utils.Preconditions.isNull; + import android.os.Bundle; import android.support.annotation.Nullable; import android.view.LayoutInflater; @@ -36,13 +38,10 @@ import android.widget.ImageView; import org.hisp.dhis.android.dashboard.R; -import org.hisp.dhis.android.dashboard.api.persistence.preferences.SettingsManager; import org.hisp.dhis.android.dashboard.api.utils.PicassoProvider; import uk.co.senab.photoview.PhotoViewAttacher; -import static org.hisp.dhis.android.dashboard.api.utils.Preconditions.isNull; - public class ImageViewFragment extends BaseFragment { private static final String IMAGE_URL = "arg:imageUrl"; @@ -77,7 +76,7 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { mAttacher = new PhotoViewAttacher(mImageView); mAttacher.update(); - PicassoProvider.getInstance(getActivity().getApplicationContext()) + PicassoProvider.getInstance(getActivity().getApplicationContext(), false) .load(getImageUrl()) .placeholder(R.mipmap.ic_stub_dashboard_item) .into(mImageView); From 55386ebd3b3073d2abdd02985bf5906f2128b06d Mon Sep 17 00:00:00 2001 From: mplazaspalacio Date: Tue, 18 Jul 2017 15:10:46 +0200 Subject: [PATCH 67/69] Solved bug not updating correctly images. --- .../dashboard/api/controllers/PullImageController.java | 5 ++++- .../dashboard/ui/adapters/DashboardItemAdapter.java | 8 ++++++++ .../dashboard/ui/adapters/InterpretationAdapter.java | 8 ++++++++ .../dashboard/ui/fragments/ImageViewFragment.java | 5 +++++ .../ui/fragments/dashboard/DashboardFragment.java | 9 ++++----- .../fragments/interpretation/InterpretationFragment.java | 1 + 6 files changed, 30 insertions(+), 6 deletions(-) diff --git a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/PullImageController.java b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/PullImageController.java index 4b6aafe1..319c2ef8 100644 --- a/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/PullImageController.java +++ b/api/src/main/java/org/hisp/dhis/android/dashboard/api/controllers/PullImageController.java @@ -2,6 +2,7 @@ import android.content.Context; +import com.squareup.picasso.MemoryPolicy; import com.squareup.picasso.NetworkPolicy; import org.hisp.dhis.android.dashboard.api.models.DashboardElement; @@ -83,12 +84,14 @@ public static List downloadDashboardImages(List requestList) { private static void downloadImages(DhisController.ImageNetworkPolicy imageNetworkPolicy, final List requestUrlList, final Context context) { + for (int i = 0; i < requestUrlList.size(); i++) { final String request = requestUrlList.get(i); if (imageNetworkPolicy == DhisController.ImageNetworkPolicy.NO_CACHE) { PicassoProvider.getInstance(context, false) - .load(request).networkPolicy(NetworkPolicy.NO_CACHE).fetch(); + .load(request).networkPolicy(NetworkPolicy.NO_CACHE) + .memoryPolicy(MemoryPolicy.NO_CACHE).fetch(); } else { PicassoProvider.getInstance(context, false) .load(request).fetch(); diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java index 45f11bc1..c7e35c77 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java @@ -39,6 +39,8 @@ import android.widget.LinearLayout; import android.widget.TextView; +import com.squareup.picasso.MemoryPolicy; +import com.squareup.picasso.NetworkPolicy; import com.squareup.picasso.Picasso; import org.hisp.dhis.android.dashboard.R; @@ -334,6 +336,8 @@ private void handleItemsWithImages(ImageItemViewHolder holder, DashboardItem ite if (request != null) { holder.imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); mImageLoader.load(request) + .networkPolicy(NetworkPolicy.NO_STORE, NetworkPolicy.OFFLINE) + .memoryPolicy(MemoryPolicy.NO_STORE, MemoryPolicy.NO_CACHE) .placeholder(R.mipmap.ic_stub_dashboard_item) .into(holder.imageView); } @@ -695,6 +699,10 @@ public View getView() { } } + public void updateImages() { + notifyDataSetChanged(); + } + private static class MenuButtonHandler implements View.OnClickListener { /* menu item ids */ static final int MENU_GROUP_ID = 9382352; diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationAdapter.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationAdapter.java index f2798ff7..101e9f6d 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationAdapter.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationAdapter.java @@ -37,6 +37,8 @@ import android.widget.ImageView; import android.widget.TextView; +import com.squareup.picasso.MemoryPolicy; +import com.squareup.picasso.NetworkPolicy; import com.squareup.picasso.Picasso; import org.hisp.dhis.android.dashboard.R; @@ -238,6 +240,8 @@ private void handleItemsWithImages(ImageItemViewHolder holder, Interpretation it if (request != null) { mImageLoader.load(request) + .networkPolicy(NetworkPolicy.NO_STORE, NetworkPolicy.OFFLINE) + .memoryPolicy(MemoryPolicy.NO_STORE, MemoryPolicy.NO_CACHE) .placeholder(R.mipmap.ic_stub_dashboard_item) .into(holder.imageView); } @@ -398,6 +402,10 @@ public interface OnItemClickListener { void onInterpretationCommentsClick(Interpretation interpretation); } + public void updateImages() { + notifyDataSetChanged(); + } + private static class MenuButtonHandler implements View.OnClickListener { /* menu item ids */ static final int MENU_GROUP_ID = 746523; diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/ImageViewFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/ImageViewFragment.java index 7974e3f6..615c796b 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/ImageViewFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/ImageViewFragment.java @@ -37,6 +37,9 @@ import android.view.ViewGroup; import android.widget.ImageView; +import com.squareup.picasso.MemoryPolicy; +import com.squareup.picasso.NetworkPolicy; + import org.hisp.dhis.android.dashboard.R; import org.hisp.dhis.android.dashboard.api.utils.PicassoProvider; @@ -78,6 +81,8 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { PicassoProvider.getInstance(getActivity().getApplicationContext(), false) .load(getImageUrl()) + .networkPolicy(NetworkPolicy.NO_STORE, NetworkPolicy.OFFLINE) + .memoryPolicy(MemoryPolicy.NO_STORE, MemoryPolicy.NO_CACHE) .placeholder(R.mipmap.ic_stub_dashboard_item) .into(mImageView); } diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardFragment.java index b4251d71..bc935552 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/dashboard/DashboardFragment.java @@ -40,7 +40,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ProgressBar; import android.widget.Toast; import android.widget.ViewSwitcher; @@ -59,6 +58,7 @@ import org.hisp.dhis.android.dashboard.api.models.meta.State; import org.hisp.dhis.android.dashboard.api.persistence.loaders.DbLoader; import org.hisp.dhis.android.dashboard.api.persistence.loaders.Query; +import org.hisp.dhis.android.dashboard.api.persistence.preferences.ResourceType; import org.hisp.dhis.android.dashboard.api.utils.EventBusProvider; import org.hisp.dhis.android.dashboard.ui.activities.DashboardElementDetailActivity; import org.hisp.dhis.android.dashboard.ui.adapters.DashboardItemAdapter; @@ -71,10 +71,6 @@ import java.util.Arrays; import java.util.List; -import butterknife.Bind; -import butterknife.ButterKnife; -import fr.castorflex.android.smoothprogressbar.SmoothProgressBar; - public class DashboardFragment extends BaseFragment implements LoaderManager.LoaderCallbacks>, DashboardItemAdapter.OnItemClickListener { private static final int LOADER_ID = 74734523; @@ -190,6 +186,9 @@ public Loader> onCreateLoader(int id, Bundle args) { @SuppressWarnings("unused") public void onResponseReceived(NetworkJob.NetworkJobResult result) { Log.d(TAG, "Received " + result.getResourceType()); + if (result.getResourceType().equals(ResourceType.DASHBOARD_IMAGES)) { + mAdapter.updateImages(); + } } @Override public void onLoadFinished(Loader> loader, diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationFragment.java index 95c91b71..1f7c2d12 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/interpretation/InterpretationFragment.java @@ -304,6 +304,7 @@ public void onResponseReceived(NetworkJob.NetworkJobResult result) { getDhisService().pullInterpretationImages(mImageNetworkPolicy,getContext()); } else if (result.getResourceType() == ResourceType.INTERPRETATION_IMAGES) { mProgressBar.setVisibility(View.INVISIBLE); + mAdapter.updateImages(); } } From 6c2f7b306a0b3489c257d9d252d8a0b8fc7937a0 Mon Sep 17 00:00:00 2001 From: mplazaspalacio Date: Tue, 18 Jul 2017 15:27:06 +0200 Subject: [PATCH 68/69] Remove no memory cache when loading images with picasso --- .../android/dashboard/ui/adapters/DashboardItemAdapter.java | 2 +- .../android/dashboard/ui/adapters/InterpretationAdapter.java | 2 +- .../dhis/android/dashboard/ui/fragments/ImageViewFragment.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java index c7e35c77..4d0b3d66 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/DashboardItemAdapter.java @@ -337,7 +337,7 @@ private void handleItemsWithImages(ImageItemViewHolder holder, DashboardItem ite holder.imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); mImageLoader.load(request) .networkPolicy(NetworkPolicy.NO_STORE, NetworkPolicy.OFFLINE) - .memoryPolicy(MemoryPolicy.NO_STORE, MemoryPolicy.NO_CACHE) + .memoryPolicy(MemoryPolicy.NO_STORE) .placeholder(R.mipmap.ic_stub_dashboard_item) .into(holder.imageView); } diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationAdapter.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationAdapter.java index 101e9f6d..b833e8f1 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationAdapter.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/adapters/InterpretationAdapter.java @@ -241,7 +241,7 @@ private void handleItemsWithImages(ImageItemViewHolder holder, Interpretation it if (request != null) { mImageLoader.load(request) .networkPolicy(NetworkPolicy.NO_STORE, NetworkPolicy.OFFLINE) - .memoryPolicy(MemoryPolicy.NO_STORE, MemoryPolicy.NO_CACHE) + .memoryPolicy(MemoryPolicy.NO_STORE) .placeholder(R.mipmap.ic_stub_dashboard_item) .into(holder.imageView); } diff --git a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/ImageViewFragment.java b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/ImageViewFragment.java index 615c796b..12888664 100755 --- a/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/ImageViewFragment.java +++ b/app/src/main/java/org/hisp/dhis/android/dashboard/ui/fragments/ImageViewFragment.java @@ -82,7 +82,7 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { PicassoProvider.getInstance(getActivity().getApplicationContext(), false) .load(getImageUrl()) .networkPolicy(NetworkPolicy.NO_STORE, NetworkPolicy.OFFLINE) - .memoryPolicy(MemoryPolicy.NO_STORE, MemoryPolicy.NO_CACHE) + .memoryPolicy(MemoryPolicy.NO_STORE) .placeholder(R.mipmap.ic_stub_dashboard_item) .into(mImageView); } From c2ca4e4d27f2193148cc2642d6aced75a7b8ae62 Mon Sep 17 00:00:00 2001 From: ifoche Date: Tue, 18 Jul 2017 22:30:42 +0200 Subject: [PATCH 69/69] version evolved --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 20adcbb1..982c2a6d 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,8 +9,8 @@ android { applicationId "org.hisp.dhis.android.dashboard" minSdkVersion 15 targetSdkVersion 25 - versionCode 6 - versionName "0.6.6" + versionCode 7 + versionName "0.6.7" } compileOptions {