From 6ecd2dccecb64c4f22bbd5ba8e64d7035ca67ed6 Mon Sep 17 00:00:00 2001 From: Nya Elimu Date: Wed, 10 Jun 2020 16:59:45 +0800 Subject: [PATCH 1/6] #214 Download Applications from REST API - Fixed NullPointerException --- .../java/ai/elimu/appstore/provider/ApplicationProvider.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/ai/elimu/appstore/provider/ApplicationProvider.java b/app/src/main/java/ai/elimu/appstore/provider/ApplicationProvider.java index f1b1eac0..4c82aa9a 100644 --- a/app/src/main/java/ai/elimu/appstore/provider/ApplicationProvider.java +++ b/app/src/main/java/ai/elimu/appstore/provider/ApplicationProvider.java @@ -50,7 +50,7 @@ public Cursor query(Uri uri, String[] projection, String selection, String[] sel BaseApplication baseApplication = (BaseApplication) context; Cursor cursor = null; // TODO - cursor.setNotificationUri(context.getContentResolver(), uri); +// cursor.setNotificationUri(context.getContentResolver(), uri); return cursor; } else { throw new IllegalArgumentException("Unknown URI: " + uri); From 509a51e64add386d9a9b8d7a88158973df3a99fe Mon Sep 17 00:00:00 2001 From: Nya Elimu Date: Wed, 10 Jun 2020 20:32:07 +0800 Subject: [PATCH 2/6] #214 Download Applications from REST API - Added ApplicationsService --- app/build.gradle | 16 ++++++++-- .../ai/elimu/appstore/BaseApplication.java | 29 +++++++++++++++++++ .../appstore/rest/ApplicationsService.java | 13 +++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/ai/elimu/appstore/rest/ApplicationsService.java diff --git a/app/build.gradle b/app/build.gradle index 83c2bf20..2b78a764 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,19 +23,31 @@ android { minifyEnabled false } } + + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'ai.elimu:model:2.0.14' + // https://github.com/elimu-ai/model + implementation 'ai.elimu:model:2.0.15' implementation 'com.jakewharton.timber:timber:4.5.1' + implementation 'com.google.android.material:material:1.1.0' + + // AndroidX implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.1.0' - implementation 'com.google.android.material:material:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + // Retrofit + implementation 'com.squareup.retrofit2:retrofit:2.7.2' + implementation 'com.squareup.retrofit2:converter-gson:2.7.2' + testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.1' diff --git a/app/src/main/java/ai/elimu/appstore/BaseApplication.java b/app/src/main/java/ai/elimu/appstore/BaseApplication.java index dd7f4f3d..35582dc4 100644 --- a/app/src/main/java/ai/elimu/appstore/BaseApplication.java +++ b/app/src/main/java/ai/elimu/appstore/BaseApplication.java @@ -3,7 +3,11 @@ import android.app.Application; import android.util.Log; +import ai.elimu.appstore.util.SharedPreferencesHelper; import ai.elimu.appstore.util.VersionHelper; +import ai.elimu.model.enums.Language; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; import timber.log.Timber; public class BaseApplication extends Application { @@ -19,4 +23,29 @@ public void onCreate() { VersionHelper.updateAppVersion(getApplicationContext()); } + + public Retrofit getRetrofit() { + Retrofit retrofit = new Retrofit.Builder() + .baseUrl(getRestUrl() + "/") + .addConverterFactory(GsonConverterFactory.create()) + .build(); + return retrofit; + } + + /** + * E.g. "http://hin.test.elimu.ai" or "http://hin.elimu.ai" + */ + public String getBaseUrl() { + Language language = SharedPreferencesHelper.getLanguage(getApplicationContext()); + String url = "http://" + language.getIsoCode(); + if (BuildConfig.DEBUG) { + url += ".test"; + } + url += ".elimu.ai"; + return url; + } + + public String getRestUrl() { + return getBaseUrl() + "/rest/v2"; + } } diff --git a/app/src/main/java/ai/elimu/appstore/rest/ApplicationsService.java b/app/src/main/java/ai/elimu/appstore/rest/ApplicationsService.java new file mode 100644 index 00000000..d970f7b8 --- /dev/null +++ b/app/src/main/java/ai/elimu/appstore/rest/ApplicationsService.java @@ -0,0 +1,13 @@ +package ai.elimu.appstore.rest; + +import java.util.List; + +import ai.elimu.model.v2.gson.application.ApplicationGson; +import retrofit2.Call; +import retrofit2.http.GET; + +public interface ApplicationsService { + + @GET("applications") + Call> listApplications(); +} From 992a19d52a64f6f4f4bc23261c13f5da758b7d06 Mon Sep 17 00:00:00 2001 From: Nya Elimu Date: Wed, 10 Jun 2020 21:04:17 +0800 Subject: [PATCH 3/6] #214 Download Applications from REST API - Display loading indicator during initial synchronization with the REST API --- app/src/main/AndroidManifest.xml | 6 +- .../java/ai/elimu/appstore/MainActivity.java | 7 +- .../ui/applications/InitialSyncActivity.java | 56 ++++++++ app/src/main/res/drawable/ic_elimu.xml | 125 ++++++++++++++++++ .../main/res/layout/activity_initial_sync.xml | 27 ++++ .../main/res/xml/network_security_config.xml | 8 ++ 6 files changed, 226 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/ai/elimu/appstore/ui/applications/InitialSyncActivity.java create mode 100644 app/src/main/res/drawable/ic_elimu.xml create mode 100644 app/src/main/res/layout/activity_initial_sync.xml create mode 100644 app/src/main/res/xml/network_security_config.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 24df386d..8ec04f05 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,7 @@ + @@ -12,7 +13,8 @@ android:roundIcon="@mipmap/ic_launcher_round" android:label="@string/app_name" android:supportsRtl="true" - android:theme="@style/AppTheme"> + android:theme="@style/AppTheme" + android:networkSecurityConfig="@xml/network_security_config"> @@ -23,6 +25,8 @@ + + diff --git a/app/src/main/java/ai/elimu/appstore/MainActivity.java b/app/src/main/java/ai/elimu/appstore/MainActivity.java index 0b34f85f..fa8fa4c6 100644 --- a/app/src/main/java/ai/elimu/appstore/MainActivity.java +++ b/app/src/main/java/ai/elimu/appstore/MainActivity.java @@ -6,6 +6,7 @@ import androidx.appcompat.app.AppCompatActivity; import ai.elimu.appstore.ui.SelectLanguageActivity; +import ai.elimu.appstore.ui.applications.InitialSyncActivity; import ai.elimu.appstore.util.SharedPreferencesHelper; import ai.elimu.model.enums.Language; import timber.log.Timber; @@ -33,8 +34,10 @@ protected void onStart() { startActivity(intent); finish(); } else { - // Download list of Applications from REST API - // TODO + // Redirect to Activity for downloading list of Applications from REST API + Intent intent = new Intent(getApplicationContext(), InitialSyncActivity.class); + startActivity(intent); + finish(); } } } diff --git a/app/src/main/java/ai/elimu/appstore/ui/applications/InitialSyncActivity.java b/app/src/main/java/ai/elimu/appstore/ui/applications/InitialSyncActivity.java new file mode 100644 index 00000000..377cd688 --- /dev/null +++ b/app/src/main/java/ai/elimu/appstore/ui/applications/InitialSyncActivity.java @@ -0,0 +1,56 @@ +package ai.elimu.appstore.ui.applications; + +import androidx.appcompat.app.AppCompatActivity; + +import android.os.Bundle; + +import java.util.List; + +import ai.elimu.appstore.BaseApplication; +import ai.elimu.appstore.R; +import ai.elimu.appstore.rest.ApplicationsService; +import ai.elimu.model.v2.gson.application.ApplicationGson; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import retrofit2.Retrofit; +import timber.log.Timber; + +public class InitialSyncActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + Timber.i("onCreate"); + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_initial_sync); + } + + @Override + protected void onStart() { + Timber.i("onStart"); + super.onStart(); + + // Download list of Applications from REST API + BaseApplication baseApplication = (BaseApplication) getApplication(); + Retrofit retrofit = baseApplication.getRetrofit(); + ApplicationsService applicationsService = retrofit.create(ApplicationsService.class); + Call> call = applicationsService.listApplications(); + Timber.i("call.request(): " + call.request()); + call.enqueue(new Callback>() { + @Override + public void onResponse(Call> call, Response> response) { + Timber.i("onResponse"); + + // TODO + } + + @Override + public void onFailure(Call> call, Throwable t) { + Timber.i("onFailure"); + + // TODO + } + }); + } +} diff --git a/app/src/main/res/drawable/ic_elimu.xml b/app/src/main/res/drawable/ic_elimu.xml new file mode 100644 index 00000000..0b5c095d --- /dev/null +++ b/app/src/main/res/drawable/ic_elimu.xml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_initial_sync.xml b/app/src/main/res/layout/activity_initial_sync.xml new file mode 100644 index 00000000..51c3c3b0 --- /dev/null +++ b/app/src/main/res/layout/activity_initial_sync.xml @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 00000000..d7b4192e --- /dev/null +++ b/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,8 @@ + + + + + + + + From c1d5aab0c57236d440878869a3511fc722a3c877 Mon Sep 17 00:00:00 2001 From: Nya Elimu Date: Thu, 11 Jun 2020 08:23:48 +0800 Subject: [PATCH 4/6] #214 Download Applications from REST API - Display REST API URL during synchronization --- .../ui/applications/InitialSyncActivity.java | 10 ++++-- .../main/res/layout/activity_initial_sync.xml | 36 ++++++++++++------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/ai/elimu/appstore/ui/applications/InitialSyncActivity.java b/app/src/main/java/ai/elimu/appstore/ui/applications/InitialSyncActivity.java index 377cd688..85602ae9 100644 --- a/app/src/main/java/ai/elimu/appstore/ui/applications/InitialSyncActivity.java +++ b/app/src/main/java/ai/elimu/appstore/ui/applications/InitialSyncActivity.java @@ -1,8 +1,9 @@ package ai.elimu.appstore.ui.applications; -import androidx.appcompat.app.AppCompatActivity; - import android.os.Bundle; +import android.widget.TextView; + +import androidx.appcompat.app.AppCompatActivity; import java.util.List; @@ -18,12 +19,16 @@ public class InitialSyncActivity extends AppCompatActivity { + private TextView textView; + @Override protected void onCreate(Bundle savedInstanceState) { Timber.i("onCreate"); super.onCreate(savedInstanceState); setContentView(R.layout.activity_initial_sync); + + textView = findViewById(R.id.initial_sync_textview); } @Override @@ -37,6 +42,7 @@ protected void onStart() { ApplicationsService applicationsService = retrofit.create(ApplicationsService.class); Call> call = applicationsService.listApplications(); Timber.i("call.request(): " + call.request()); + textView.setText("Connecting to " + call.request().url()); call.enqueue(new Callback>() { @Override public void onResponse(Call> call, Response> response) { diff --git a/app/src/main/res/layout/activity_initial_sync.xml b/app/src/main/res/layout/activity_initial_sync.xml index 51c3c3b0..8455ec08 100644 --- a/app/src/main/res/layout/activity_initial_sync.xml +++ b/app/src/main/res/layout/activity_initial_sync.xml @@ -6,22 +6,32 @@ android:layout_height="match_parent" tools:context="ai.elimu.appstore.ui.applications.InitialSyncActivity"> - + app:layout_constraintBottom_toBottomOf="parent" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="center_horizontal"> - + + + + + From 9d9ade0e90d8ed4542c5e0afe1a50acddf5953f1 Mon Sep 17 00:00:00 2001 From: Nya Elimu Date: Thu, 11 Jun 2020 11:00:44 +0800 Subject: [PATCH 5/6] #214 Download Applications from REST API - Parse JSON response - Store Applications in Room database --- app/build.gradle | 11 ++++ .../ai.elimu.appstore.room.RoomDb/1.json | 52 ++++++++++++++++ .../ai/elimu/appstore/room/EnumConverter.java | 31 ++++++++++ .../appstore/room/GsonToRoomConverter.java | 25 ++++++++ .../java/ai/elimu/appstore/room/RoomDb.java | 55 +++++++++++++++++ .../appstore/room/dao/ApplicationDao.java | 26 ++++++++ .../appstore/room/entity/Application.java | 45 ++++++++++++++ .../appstore/room/entity/BaseEntity.java | 24 ++++++++ .../ui/applications/InitialSyncActivity.java | 60 ++++++++++++++++++- 9 files changed, 327 insertions(+), 2 deletions(-) create mode 100644 app/schemas/ai.elimu.appstore.room.RoomDb/1.json create mode 100644 app/src/main/java/ai/elimu/appstore/room/EnumConverter.java create mode 100644 app/src/main/java/ai/elimu/appstore/room/GsonToRoomConverter.java create mode 100644 app/src/main/java/ai/elimu/appstore/room/RoomDb.java create mode 100644 app/src/main/java/ai/elimu/appstore/room/dao/ApplicationDao.java create mode 100644 app/src/main/java/ai/elimu/appstore/room/entity/Application.java create mode 100644 app/src/main/java/ai/elimu/appstore/room/entity/BaseEntity.java diff --git a/app/build.gradle b/app/build.gradle index 2b78a764..9c9e7858 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,6 +12,12 @@ android { versionName "2.3.0-SNAPSHOT" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + + javaCompileOptions { + annotationProcessorOptions { + arguments = ["room.schemaLocation": "${projectDir}/schemas".toString()] + } + } } buildTypes { @@ -48,6 +54,11 @@ dependencies { implementation 'com.squareup.retrofit2:retrofit:2.7.2' implementation 'com.squareup.retrofit2:converter-gson:2.7.2' + // Room components + implementation "androidx.room:room-runtime:2.2.5" + annotationProcessor "androidx.room:room-compiler:2.2.5" + androidTestImplementation "androidx.room:room-testing:2.2.5" + testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.1' diff --git a/app/schemas/ai.elimu.appstore.room.RoomDb/1.json b/app/schemas/ai.elimu.appstore.room.RoomDb/1.json new file mode 100644 index 00000000..fc561295 --- /dev/null +++ b/app/schemas/ai.elimu.appstore.room.RoomDb/1.json @@ -0,0 +1,52 @@ +{ + "formatVersion": 1, + "database": { + "version": 1, + "identityHash": "4f7ae834a52f85a4fcbcfaa3d7d0653f", + "entities": [ + { + "tableName": "Application", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`packageName` TEXT NOT NULL, `infrastructural` INTEGER, `applicationStatus` TEXT NOT NULL, `id` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "packageName", + "columnName": "packageName", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "infrastructural", + "columnName": "infrastructural", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "applicationStatus", + "columnName": "applicationStatus", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4f7ae834a52f85a4fcbcfaa3d7d0653f')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/ai/elimu/appstore/room/EnumConverter.java b/app/src/main/java/ai/elimu/appstore/room/EnumConverter.java new file mode 100644 index 00000000..a25cf395 --- /dev/null +++ b/app/src/main/java/ai/elimu/appstore/room/EnumConverter.java @@ -0,0 +1,31 @@ +package ai.elimu.appstore.room; + +import android.text.TextUtils; + +import androidx.room.TypeConverter; + +import ai.elimu.model.enums.admin.ApplicationStatus; + +/** + * See https://developer.android.com/training/data-storage/room/referencing-data + */ +public class EnumConverter { + + @TypeConverter + public static ApplicationStatus fromApplicationStatus(String value) { + ApplicationStatus applicationStatus = null; + if (!TextUtils.isEmpty(value)) { + applicationStatus = ApplicationStatus.valueOf(value); + } + return applicationStatus; + } + + @TypeConverter + public static String toApplicationStatus(ApplicationStatus applicationStatus) { + String value = null; + if (applicationStatus != null) { + value = applicationStatus.toString(); + } + return value; + } +} diff --git a/app/src/main/java/ai/elimu/appstore/room/GsonToRoomConverter.java b/app/src/main/java/ai/elimu/appstore/room/GsonToRoomConverter.java new file mode 100644 index 00000000..bd80b997 --- /dev/null +++ b/app/src/main/java/ai/elimu/appstore/room/GsonToRoomConverter.java @@ -0,0 +1,25 @@ +package ai.elimu.appstore.room; + +import ai.elimu.appstore.room.entity.Application; +import ai.elimu.model.v2.gson.application.ApplicationGson; + +public class GsonToRoomConverter { + + public static Application getApplication(ApplicationGson applicationGson) { + if (applicationGson == null) { + return null; + } else { + Application application = new Application(); + + // BaseEntity + application.setId(applicationGson.getId()); + + // Application + application.setPackageName(applicationGson.getPackageName()); + application.setInfrastructural(applicationGson.isInfrastructural()); + application.setApplicationStatus(applicationGson.getApplicationStatus()); + + return application; + } + } +} diff --git a/app/src/main/java/ai/elimu/appstore/room/RoomDb.java b/app/src/main/java/ai/elimu/appstore/room/RoomDb.java new file mode 100644 index 00000000..a44be679 --- /dev/null +++ b/app/src/main/java/ai/elimu/appstore/room/RoomDb.java @@ -0,0 +1,55 @@ +package ai.elimu.appstore.room; + +import android.content.Context; + +import androidx.room.Database; +import androidx.room.Room; +import androidx.room.RoomDatabase; +import androidx.room.TypeConverters; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import ai.elimu.appstore.room.dao.ApplicationDao; +import ai.elimu.appstore.room.entity.Application; + +@Database(version = 1, entities = {Application.class}) +@TypeConverters({EnumConverter.class}) +public abstract class RoomDb extends RoomDatabase { + + public abstract ApplicationDao applicationDao(); + + private static volatile RoomDb INSTANCE; + + public static final ExecutorService databaseWriteExecutor = Executors.newFixedThreadPool(4); + + public static RoomDb getDatabase(final Context context) { + if (INSTANCE == null) { + synchronized (RoomDb.class) { + if (INSTANCE == null) { + INSTANCE = Room + .databaseBuilder( + context.getApplicationContext(), + RoomDb.class, + "appstore_db" + ) +// .addMigrations( +// MIGRATION_1_2 +// ) + .build(); + } + } + } + return INSTANCE; + } + +// private static final Migration MIGRATION_1_2 = new Migration(1, 2) { +// @Override +// public void migrate(SupportSQLiteDatabase database) { +// Log.i(getClass().getName(), "migrate (1 --> 2)"); +// String sql = "..."; +// Log.i(getClass().getName(), "sql: " + sql); +// database.execSQL(sql); +// } +// }; +} diff --git a/app/src/main/java/ai/elimu/appstore/room/dao/ApplicationDao.java b/app/src/main/java/ai/elimu/appstore/room/dao/ApplicationDao.java new file mode 100644 index 00000000..7ab897ba --- /dev/null +++ b/app/src/main/java/ai/elimu/appstore/room/dao/ApplicationDao.java @@ -0,0 +1,26 @@ +package ai.elimu.appstore.room.dao; + +import androidx.room.Dao; +import androidx.room.Insert; +import androidx.room.Query; +import androidx.room.Update; + +import java.util.List; + +import ai.elimu.appstore.room.entity.Application; + +@Dao +public interface ApplicationDao { + + @Insert + void insert(Application application); + + @Query("SELECT * FROM Application a WHERE a.id = :id") + Application load(Long id); + + @Query("SELECT * FROM Application a") + List loadAll(); + + @Update + void update(Application application); +} diff --git a/app/src/main/java/ai/elimu/appstore/room/entity/Application.java b/app/src/main/java/ai/elimu/appstore/room/entity/Application.java new file mode 100644 index 00000000..db43fa38 --- /dev/null +++ b/app/src/main/java/ai/elimu/appstore/room/entity/Application.java @@ -0,0 +1,45 @@ +package ai.elimu.appstore.room.entity; + +import androidx.annotation.NonNull; +import androidx.room.Entity; + +import ai.elimu.model.enums.admin.ApplicationStatus; + +/** + * For documentation, see https://github.com/elimu-ai/webapp/tree/master/src/main/java/ai/elimu/model + */ +@Entity +public class Application extends BaseEntity { + + @NonNull + private String packageName; + + private Boolean infrastructural; + + @NonNull + private ApplicationStatus applicationStatus; + + public String getPackageName() { + return packageName; + } + + public void setPackageName(String packageName) { + this.packageName = packageName; + } + + public Boolean getInfrastructural() { + return infrastructural; + } + + public void setInfrastructural(Boolean infrastructural) { + this.infrastructural = infrastructural; + } + + public ApplicationStatus getApplicationStatus() { + return applicationStatus; + } + + public void setApplicationStatus(ApplicationStatus applicationStatus) { + this.applicationStatus = applicationStatus; + } +} diff --git a/app/src/main/java/ai/elimu/appstore/room/entity/BaseEntity.java b/app/src/main/java/ai/elimu/appstore/room/entity/BaseEntity.java new file mode 100644 index 00000000..9686b84e --- /dev/null +++ b/app/src/main/java/ai/elimu/appstore/room/entity/BaseEntity.java @@ -0,0 +1,24 @@ +package ai.elimu.appstore.room.entity; + +import androidx.room.PrimaryKey; + +/** + * For documentation, see https://github.com/elimu-ai/webapp/tree/master/src/main/java/ai/elimu/model + */ +public class BaseEntity { + + /** + * Reflects the ID stored in the backend webapp's database. Therefore, {@code @PrimaryKey(autoGenerate = true)} is + * not used. + */ + @PrimaryKey + private Long id; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } +} diff --git a/app/src/main/java/ai/elimu/appstore/ui/applications/InitialSyncActivity.java b/app/src/main/java/ai/elimu/appstore/ui/applications/InitialSyncActivity.java index 85602ae9..6fbe41ec 100644 --- a/app/src/main/java/ai/elimu/appstore/ui/applications/InitialSyncActivity.java +++ b/app/src/main/java/ai/elimu/appstore/ui/applications/InitialSyncActivity.java @@ -5,11 +5,19 @@ import androidx.appcompat.app.AppCompatActivity; +import com.google.android.material.snackbar.Snackbar; + import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import ai.elimu.appstore.BaseApplication; import ai.elimu.appstore.R; import ai.elimu.appstore.rest.ApplicationsService; +import ai.elimu.appstore.room.GsonToRoomConverter; +import ai.elimu.appstore.room.RoomDb; +import ai.elimu.appstore.room.dao.ApplicationDao; +import ai.elimu.appstore.room.entity.Application; import ai.elimu.model.v2.gson.application.ApplicationGson; import retrofit2.Call; import retrofit2.Callback; @@ -48,13 +56,61 @@ protected void onStart() { public void onResponse(Call> call, Response> response) { Timber.i("onResponse"); - // TODO + Timber.i("response: " + response); + + // Parse the JSON response + Snackbar.make(textView, "Synchronizing database...", Snackbar.LENGTH_LONG).show(); + List applicationGsons = response.body(); + Timber.i("applicationGsons.size(): " + applicationGsons.size()); + if (applicationGsons.size() > 0) { + processResponseBody(applicationGsons); + } } @Override public void onFailure(Call> call, Throwable t) { - Timber.i("onFailure"); + Timber.e(t, "onFailure"); + + Timber.e(t, "t.getCause(): " + t.getCause()); + + // Handle error + Snackbar.make(textView, t.getCause().toString(), Snackbar.LENGTH_LONG).show(); + } + }); + } + + private void processResponseBody(List applicationGsons) { + Timber.i("processResponseBody"); + + ExecutorService executorService = Executors.newSingleThreadExecutor(); + executorService.execute(new Runnable() { + @Override + public void run() { + Timber.i("run"); + + RoomDb roomDb = RoomDb.getDatabase(getApplicationContext()); + ApplicationDao applicationDao = roomDb.applicationDao(); + + for (ApplicationGson applicationGson : applicationGsons) { + Timber.i("applicationGson.getId(): " + applicationGson.getId()); + + // Check if the Application has already been stored in the database + Application application = applicationDao.load(applicationGson.getId()); + Timber.i("application: " + application); + if (application == null) { + // Store the new Application in the database + application = GsonToRoomConverter.getApplication(applicationGson); + applicationDao.insert(application); + Timber.i("Stored Application in database with ID " + application.getId()); + } else { + // Update the existing Application in the database + application = GsonToRoomConverter.getApplication(applicationGson); + applicationDao.update(application); + Timber.i("Updated Application in database with ID " + application.getId()); + } + } + // Redirect to the list of Applications // TODO } }); From f346412ace3ae31cedb82bcb406383b9a1245119 Mon Sep 17 00:00:00 2001 From: Nya Elimu Date: Thu, 11 Jun 2020 12:52:23 +0800 Subject: [PATCH 6/6] #214 Download Applications from REST API - Display list of Applications --- app/src/main/AndroidManifest.xml | 3 + .../applications/ApplicationListActivity.java | 68 +++++++++++++++++++ .../applications/ApplicationListAdapter.java | 68 +++++++++++++++++++ .../ui/applications/InitialSyncActivity.java | 7 +- .../res/layout/activity_application_list.xml | 54 +++++++++++++++ .../layout/activity_application_list_item.xml | 23 +++++++ app/src/main/res/layout/content_scrolling.xml | 19 ++++++ app/src/main/res/values-w820dp/dimens.xml | 5 +- app/src/main/res/values/dimens.xml | 4 +- app/src/main/res/values/styles.xml | 7 ++ 10 files changed, 252 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/ai/elimu/appstore/ui/applications/ApplicationListActivity.java create mode 100644 app/src/main/java/ai/elimu/appstore/ui/applications/ApplicationListAdapter.java create mode 100644 app/src/main/res/layout/activity_application_list.xml create mode 100644 app/src/main/res/layout/activity_application_list_item.xml create mode 100644 app/src/main/res/layout/content_scrolling.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8ec04f05..7e7ec3eb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -27,6 +27,9 @@ + + diff --git a/app/src/main/java/ai/elimu/appstore/ui/applications/ApplicationListActivity.java b/app/src/main/java/ai/elimu/appstore/ui/applications/ApplicationListActivity.java new file mode 100644 index 00000000..77d8208f --- /dev/null +++ b/app/src/main/java/ai/elimu/appstore/ui/applications/ApplicationListActivity.java @@ -0,0 +1,68 @@ +package ai.elimu.appstore.ui.applications; + +import android.os.Bundle; +import android.util.Log; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.appbar.CollapsingToolbarLayout; + +import java.util.List; + +import ai.elimu.appstore.R; +import ai.elimu.appstore.room.RoomDb; +import ai.elimu.appstore.room.dao.ApplicationDao; +import ai.elimu.appstore.room.entity.Application; +import timber.log.Timber; + +public class ApplicationListActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + Timber.i("onCreate"); + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_application_list); + + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + + CollapsingToolbarLayout toolBarLayout = findViewById(R.id.toolbar_layout); + toolBarLayout.setTitle(getTitle()); + +// FloatingActionButton fab = findViewById(R.id.fab); +// fab.setOnClickListener(view -> { +// Timber.i("onClick"); +// Snackbar.make(view, "Synchronizing...", Snackbar.LENGTH_LONG).show(); +// // TODO: Download list of Applications from REST API +// }); + } + + @Override + protected void onStart() { + Timber.i("onStart"); + super.onStart(); + + // Configure list adapter + RecyclerView recyclerView = findViewById(R.id.recycler_view); + final ApplicationListAdapter applicationListAdapter = new ApplicationListAdapter(this); + recyclerView.setAdapter(applicationListAdapter); + LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this); + recyclerView.setLayoutManager(linearLayoutManager); + DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(), linearLayoutManager.getOrientation()); + recyclerView.addItemDecoration(dividerItemDecoration); + + // Fetch all Applications from database, and update the list adapter + RoomDb roomDb = RoomDb.getDatabase(getApplicationContext()); + ApplicationDao applicationDao = roomDb.applicationDao(); + RoomDb.databaseWriteExecutor.execute(() -> { + List applications = applicationDao.loadAll(); + Log.d(getClass().getName(), "applications.size(): " + applications.size()); + applicationListAdapter.setApplications(applications); + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/ai/elimu/appstore/ui/applications/ApplicationListAdapter.java b/app/src/main/java/ai/elimu/appstore/ui/applications/ApplicationListAdapter.java new file mode 100644 index 00000000..c9fa9aa8 --- /dev/null +++ b/app/src/main/java/ai/elimu/appstore/ui/applications/ApplicationListAdapter.java @@ -0,0 +1,68 @@ +package ai.elimu.appstore.ui.applications; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.List; + +import ai.elimu.appstore.R; +import ai.elimu.appstore.room.entity.Application; + +public class ApplicationListAdapter extends RecyclerView.Adapter { + + class ApplicationViewHolder extends RecyclerView.ViewHolder { + + private final TextView textViewFirstLine; + private final TextView textViewSecondLine; + + private ApplicationViewHolder(View itemView) { + super(itemView); + textViewFirstLine = itemView.findViewById(R.id.textViewFirstLine); + textViewSecondLine = itemView.findViewById(R.id.textViewSecondLine); + } + } + + private final LayoutInflater layoutInflater; + + private List applications; + + ApplicationListAdapter(Context context) { + layoutInflater = LayoutInflater.from(context); + } + + @NonNull + @Override + public ApplicationViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View itemView = layoutInflater.inflate(R.layout.activity_application_list_item, parent, false); + return new ApplicationViewHolder(itemView); + } + + @Override + public void onBindViewHolder(@NonNull ApplicationViewHolder viewHolder, int position) { + if (applications != null) { + Application application = applications.get(position); + viewHolder.textViewFirstLine.setText(application.getPackageName()); + viewHolder.textViewSecondLine.setText(application.getApplicationStatus().toString()); + } + } + + @Override + public int getItemCount() { + if (applications == null) { + return 0; + } else { + return applications.size(); + } + } + + public void setApplications(List applications) { + this.applications = applications; + notifyDataSetChanged(); + } +} diff --git a/app/src/main/java/ai/elimu/appstore/ui/applications/InitialSyncActivity.java b/app/src/main/java/ai/elimu/appstore/ui/applications/InitialSyncActivity.java index 6fbe41ec..b97cd74b 100644 --- a/app/src/main/java/ai/elimu/appstore/ui/applications/InitialSyncActivity.java +++ b/app/src/main/java/ai/elimu/appstore/ui/applications/InitialSyncActivity.java @@ -1,5 +1,6 @@ package ai.elimu.appstore.ui.applications; +import android.content.Intent; import android.os.Bundle; import android.widget.TextView; @@ -59,7 +60,7 @@ public void onResponse(Call> call, Response applicationGsons = response.body(); Timber.i("applicationGsons.size(): " + applicationGsons.size()); if (applicationGsons.size() > 0) { @@ -111,7 +112,9 @@ public void run() { } // Redirect to the list of Applications - // TODO + Intent intent = new Intent(getApplicationContext(), ApplicationListActivity.class); + startActivity(intent); + finish(); } }); } diff --git a/app/src/main/res/layout/activity_application_list.xml b/app/src/main/res/layout/activity_application_list.xml new file mode 100644 index 00000000..16731117 --- /dev/null +++ b/app/src/main/res/layout/activity_application_list.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_application_list_item.xml b/app/src/main/res/layout/activity_application_list_item.xml new file mode 100644 index 00000000..8caedbe0 --- /dev/null +++ b/app/src/main/res/layout/activity_application_list_item.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/app/src/main/res/layout/content_scrolling.xml b/app/src/main/res/layout/content_scrolling.xml new file mode 100644 index 00000000..386808ec --- /dev/null +++ b/app/src/main/res/layout/content_scrolling.xml @@ -0,0 +1,19 @@ + + + + + diff --git a/app/src/main/res/values-w820dp/dimens.xml b/app/src/main/res/values-w820dp/dimens.xml index 63fc8164..8d8ab77e 100644 --- a/app/src/main/res/values-w820dp/dimens.xml +++ b/app/src/main/res/values-w820dp/dimens.xml @@ -1,6 +1,5 @@ - 64dp + 32dp + 32dp diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index fb045653..0fa5c068 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -1,8 +1,10 @@ - 16dp 16dp 16dp 16dp 8dp + 180dp + 16dp + 16dp diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 53229292..ac4e4946 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -9,6 +9,13 @@ @font/poppins + +