From 971385e1b4760fb26930dc233d66eecc1ad18eda Mon Sep 17 00:00:00 2001 From: Avinash Sharma <133222313+Avinash-Codes@users.noreply.github.com> Date: Sat, 18 Jan 2025 00:33:59 +0530 Subject: [PATCH 01/12] sync: smoother json (fixes #4981) (#4983) Co-authored-by: dogi --- app/build.gradle | 4 ++-- .../main/java/org/ole/planet/myplanet/utilities/JsonUtils.kt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 5fb1ec0fe6..c8c62719f0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "org.ole.planet.myplanet" minSdkVersion 26 targetSdkVersion 34 - versionCode 2213 - versionName "0.22.13" + versionCode 2214 + versionName "0.22.14" ndkVersion '21.3.6528147' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true diff --git a/app/src/main/java/org/ole/planet/myplanet/utilities/JsonUtils.kt b/app/src/main/java/org/ole/planet/myplanet/utilities/JsonUtils.kt index 4386a99596..689134603e 100644 --- a/app/src/main/java/org/ole/planet/myplanet/utilities/JsonUtils.kt +++ b/app/src/main/java/org/ole/planet/myplanet/utilities/JsonUtils.kt @@ -92,7 +92,7 @@ object JsonUtils { return try { if (jsonObject?.has(fieldName) == true) { val el: JsonElement = jsonObject.get(fieldName) - if (el is JsonNull) 0 else el.asInt + if (el is JsonNull || el.asString.isEmpty()) 0 else el.asInt } else { 0 } @@ -107,7 +107,7 @@ object JsonUtils { return try { if (jsonObject?.has(fieldName) == true) { val el: JsonElement = jsonObject.get(fieldName) - if (el is JsonNull) 0f else el.asFloat + if (el is JsonNull || el.asString.isEmpty()) 0f else el.asFloat } else { getInt(fieldName, jsonObject).toFloat() } From 5da6cd1dd4cfe7ed0744746d07a562abd64125da Mon Sep 17 00:00:00 2001 From: Gideon Okuro Date: Fri, 17 Jan 2025 22:06:57 +0300 Subject: [PATCH 02/12] sync: smoother compression and retries (fixes #5037) (#5038) Co-authored-by: dogi --- app/build.gradle | 4 +- .../planet/myplanet/datamanager/ApiClient.kt | 74 +++++++-- .../planet/myplanet/service/SyncManager.kt | 140 ++++++++++++++---- 3 files changed, 174 insertions(+), 44 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index c8c62719f0..1c2cfb7472 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "org.ole.planet.myplanet" minSdkVersion 26 targetSdkVersion 34 - versionCode 2214 - versionName "0.22.14" + versionCode 2215 + versionName "0.22.15" ndkVersion '21.3.6528147' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true diff --git a/app/src/main/java/org/ole/planet/myplanet/datamanager/ApiClient.kt b/app/src/main/java/org/ole/planet/myplanet/datamanager/ApiClient.kt index 4c3a5b613b..660cd4efe4 100644 --- a/app/src/main/java/org/ole/planet/myplanet/datamanager/ApiClient.kt +++ b/app/src/main/java/org/ole/planet/myplanet/datamanager/ApiClient.kt @@ -1,11 +1,15 @@ package org.ole.planet.myplanet.datamanager import com.google.gson.GsonBuilder +import okhttp3.Interceptor import okhttp3.OkHttpClient +import okhttp3.Response import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory +import java.io.IOException import java.lang.reflect.Modifier import java.util.concurrent.TimeUnit +import kotlin.math.pow object ApiClient { private const val BASE_URL = "https://vi.media.mit.edu/" @@ -13,21 +17,69 @@ object ApiClient { @JvmStatic val client: Retrofit? get() { - val client = OkHttpClient.Builder().connectTimeout(1, TimeUnit.MINUTES) - .readTimeout(30, TimeUnit.SECONDS).writeTimeout(30, TimeUnit.SECONDS).build() + val client = OkHttpClient.Builder().connectTimeout(1, TimeUnit.MINUTES).readTimeout(30, TimeUnit.SECONDS) + .writeTimeout(30, TimeUnit.SECONDS).addInterceptor { chain -> + val request = chain.request().newBuilder() + .addHeader("Accept-Encoding", "gzip").build() + chain.proceed(request) + } + .retryOnConnectionFailure(true).addInterceptor(RetryInterceptor()).build() if (retrofit == null) { retrofit = Retrofit.Builder() - .baseUrl(BASE_URL) - .client(client) - .addConverterFactory( - GsonConverterFactory.create( - GsonBuilder() - .excludeFieldsWithModifiers(Modifier.FINAL, Modifier.TRANSIENT, Modifier.STATIC) - .serializeNulls() - .create() + .baseUrl(BASE_URL).client(client) + .addConverterFactory(GsonConverterFactory.create( + GsonBuilder() + .excludeFieldsWithModifiers(Modifier.FINAL, Modifier.TRANSIENT, Modifier.STATIC) + .serializeNulls() + .create() ) ).build() } return retrofit } -} \ No newline at end of file + + class RetryInterceptor : Interceptor { + val maxRetryCount = 3 + val retryDelayMillis = 1000L + + override fun intercept(chain: Interceptor.Chain): Response { + var attempt = 0 + var response: Response? = null + val request = chain.request() + val url = request.url().toString() + + while (true) { + try { + response?.close() + response = chain.proceed(request) + + when (response.code()) { + in 200..299 -> { + return response + } + 404 -> { + return response + } + 401, 403 -> { + response.close() + throw IOException("Authentication failed: ${response.code()}") + } + else -> { + response.close() + throw IOException("Response unsuccessful: ${response.code()}") + } + } + } catch (e: IOException) { + attempt++ + + if (attempt >= maxRetryCount) { + throw IOException("Failed after $maxRetryCount attempts: $url", e) + } + + val delayMillis = retryDelayMillis * 2.0.pow((attempt - 1).toDouble()).toLong() + Thread.sleep(delayMillis) + } + } + } + } +} diff --git a/app/src/main/java/org/ole/planet/myplanet/service/SyncManager.kt b/app/src/main/java/org/ole/planet/myplanet/service/SyncManager.kt index 4766a88e3f..9814a0fc46 100644 --- a/app/src/main/java/org/ole/planet/myplanet/service/SyncManager.kt +++ b/app/src/main/java/org/ole/planet/myplanet/service/SyncManager.kt @@ -94,7 +94,7 @@ class SyncManager private constructor(private val context: Context) { settings.edit().putString("LastWifiSSID", wifiInfo.ssid).apply() } isSyncing = true - create(context, R.mipmap.ic_launcher, " Syncing data", "Please wait...") + create(context, R.mipmap.ic_launcher, "Syncing data", "Please wait...") mRealm = dbService.realmInstance TransactionSyncManager.syncDb(mRealm, "tablet_users") myLibraryTransactionSync() @@ -144,50 +144,111 @@ class SyncManager private constructor(private val context: Context) { } } - @Throws(IOException::class) private fun syncResource(dbClient: ApiInterface?) { val newIds: MutableList = ArrayList() - val allDocs = dbClient?.getJsonObject(Utilities.header, Utilities.getUrl() + "/resources/_all_docs?include_doc=false") - val all = allDocs?.execute() - val rows = getJsonArray("rows", all?.body()) - val keys: MutableList = ArrayList() - for (i in 0 until rows.size()) { - val `object` = rows[i].asJsonObject - if (!TextUtils.isEmpty(getString("id", `object`))) keys.add(getString("key", `object`)) - if (i == rows.size() - 1 || keys.size == 1000) { - val obj = JsonObject() - obj.add("keys", Gson().fromJson(Gson().toJson(keys), JsonArray::class.java)) - val response = dbClient?.findDocs(Utilities.header, "application/json", Utilities.getUrl() + "/resources/_all_docs?include_docs=true", obj)?.execute() - if (response?.body() != null) { - val ids: List = save(getJsonArray("rows", response.body()), mRealm) - newIds.addAll(ids) + try { + val allDocs = dbClient?.getJsonObject(Utilities.header, "${Utilities.getUrl()}/resources/_all_docs?include_doc=false") + val all = allDocs?.execute() + if (all?.isSuccessful != true) { + return + } + + val rows = getJsonArray("rows", all.body()) + val keys: MutableList = ArrayList() + val failedIds: MutableList = ArrayList() + + for (i in 0 until rows.size()) { + val `object` = rows[i].asJsonObject + if (!TextUtils.isEmpty(getString("id", `object`))) { + keys.add(getString("key", `object`)) + } + + if (i == rows.size() - 1 || keys.size == 1000) { + val obj = JsonObject() + obj.add("keys", Gson().fromJson(Gson().toJson(keys), JsonArray::class.java)) + val response = dbClient.findDocs(Utilities.header, "application/json", "${Utilities.getUrl()}/resources/_all_docs?include_docs=true", obj).execute() + + when { + response.isSuccessful == true -> { + response.body()?.let { body -> + val ids: List = save(getJsonArray("rows", body), mRealm) + newIds.addAll(ids) + } + } + response.code() == 404 -> { + failedIds.addAll(keys) + } + else -> { + val errorMessage = "Failed to sync resources: ${response.code()}" + handleException(errorMessage) + + when (response.code()) { + in 500..599 -> { + addToRetryQueue(keys) + } + 401, 403 -> { + handleAuthenticationError() + } + else -> { + failedIds.addAll(keys) + } + } + } + } + keys.clear() } - keys.clear() + } + } catch (e: Exception) { + e.printStackTrace() + } finally { + try { + removeDeletedResource(newIds, mRealm) + } catch (e: Exception) { + e.printStackTrace() } } - removeDeletedResource(newIds, mRealm) + } + + private fun addToRetryQueue(keys: List) { + settings.edit().apply { + val existingQueue = settings.getStringSet("retry_queue", setOf()) ?: setOf() + putStringSet("retry_queue", existingQueue + keys) + apply() + } + } + + private fun handleAuthenticationError() { + settings.edit().remove("credentials").apply() + handleException("Authentication failed.") } private fun myLibraryTransactionSync() { val apiInterface = client?.create(ApiInterface::class.java) try { - val res = apiInterface?.getDocuments(Utilities.header, Utilities.getUrl() + "/shelf/_all_docs")?.execute()?.body() - for (i in res?.rows!!.indices) { - shelfDoc = res.rows!![i] - populateShelfItems(apiInterface) + val response = apiInterface?.getDocuments(Utilities.header, "${Utilities.getUrl()}/shelf/_all_docs")?.execute() + + val res = response?.body() + res?.rows?.let { rows -> + for (i in rows.indices) { + shelfDoc = rows[i] + populateShelfItems(apiInterface) + } } } catch (e: IOException) { e.printStackTrace() } } - private fun populateShelfItems(apiInterface: ApiInterface) { + private fun populateShelfItems(apiInterface: ApiInterface?) { try { - val jsonDoc = apiInterface.getJsonObject(Utilities.header, Utilities.getUrl() + "/shelf/" + shelfDoc?.id).execute().body() - for (i in Constants.shelfDataList.indices) { - val shelfData = Constants.shelfDataList[i] - val array = getJsonArray(shelfData.key, jsonDoc) - memberShelfData(array, shelfData) + val response = apiInterface?.getJsonObject(Utilities.header, "${Utilities.getUrl()}/shelf/${shelfDoc?.id}")?.execute() + + response?.body()?.let { jsonDoc -> + for (i in Constants.shelfDataList.indices) { + val shelfData = Constants.shelfDataList[i] + val array = getJsonArray(shelfData.key, jsonDoc) + memberShelfData(array, shelfData) + } } } catch (err: Exception) { err.printStackTrace() @@ -217,10 +278,27 @@ class SyncManager private constructor(private val context: Context) { } private fun validateDocument(arrayCategoryIds: JsonArray, x: Int) { - val apiInterface = client!!.create(ApiInterface::class.java) + val apiInterface = client?.create(ApiInterface::class.java) try { - val resourceDoc = apiInterface.getJsonObject(Utilities.header, Utilities.getUrl() + "/" + stringArray[2] + "/" + arrayCategoryIds[x].asString).execute().body() - resourceDoc?.let { triggerInsert(stringArray, it) } + val response = apiInterface?.getJsonObject(Utilities.header, "${Utilities.getUrl()}/${stringArray[2]}/${arrayCategoryIds[x].asString}")?.execute() + + when { + response?.isSuccessful == true -> { + response.body()?.let { resourceDoc -> + triggerInsert(stringArray, resourceDoc) + } + } + response?.code() == 404 -> { + return + } + else -> { + val errorMessage = "Failed to validate document: ${response?.code()}" + handleException(errorMessage) + if (response?.code() in 500..599) { + throw IOException(errorMessage) + } + } + } } catch (e: IOException) { e.printStackTrace() } From a4b30281c4cd72ad2786fbeec165b57ddf0214a0 Mon Sep 17 00:00:00 2001 From: Avinash Sharma <133222313+Avinash-Codes@users.noreply.github.com> Date: Sat, 18 Jan 2025 00:41:20 +0530 Subject: [PATCH 03/12] courses: smoother filters dark mode (fixes #5042) (#5051) Co-authored-by: dogi --- app/build.gradle | 4 ++-- .../org/ole/planet/myplanet/ui/courses/CoursesFragment.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 1c2cfb7472..555e67ed5a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "org.ole.planet.myplanet" minSdkVersion 26 targetSdkVersion 34 - versionCode 2215 - versionName "0.22.15" + versionCode 2216 + versionName "0.22.16" ndkVersion '21.3.6528147' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true diff --git a/app/src/main/java/org/ole/planet/myplanet/ui/courses/CoursesFragment.kt b/app/src/main/java/org/ole/planet/myplanet/ui/courses/CoursesFragment.kt index bf35d3613c..6bf715d8e2 100644 --- a/app/src/main/java/org/ole/planet/myplanet/ui/courses/CoursesFragment.kt +++ b/app/src/main/java/org/ole/planet/myplanet/ui/courses/CoursesFragment.kt @@ -217,7 +217,7 @@ class CoursesFragment : BaseRecyclerFragment(), OnCourseItemSele spnGrade.adapter = gradeAdapter val subjectAdapter = ArrayAdapter.createFromResource(requireContext(), R.array.subject_level, R.layout.spinner_item) - subjectAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + subjectAdapter.setDropDownViewResource(R.layout.custom_simple_list_item_1) spnSubject.adapter = subjectAdapter spnGrade.onItemSelectedListener = itemSelectedListener From 2e7dc13874fa26717b4e4f338debd3b70dfa0a1b Mon Sep 17 00:00:00 2001 From: Deep Vora <70479785+deeppp15@users.noreply.github.com> Date: Fri, 17 Jan 2025 14:31:37 -0500 Subject: [PATCH 04/12] mylife: smoother mysubmissions search (fixes #5023) (#5057) Co-authored-by: dogi --- app/build.gradle | 4 ++-- .../myplanet/ui/submission/MySubmissionFragment.kt | 11 ++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 555e67ed5a..8a2904c223 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "org.ole.planet.myplanet" minSdkVersion 26 targetSdkVersion 34 - versionCode 2216 - versionName "0.22.16" + versionCode 2217 + versionName "0.22.17" ndkVersion '21.3.6528147' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true diff --git a/app/src/main/java/org/ole/planet/myplanet/ui/submission/MySubmissionFragment.kt b/app/src/main/java/org/ole/planet/myplanet/ui/submission/MySubmissionFragment.kt index 27816a1fb3..5571b233d9 100644 --- a/app/src/main/java/org/ole/planet/myplanet/ui/submission/MySubmissionFragment.kt +++ b/app/src/main/java/org/ole/planet/myplanet/ui/submission/MySubmissionFragment.kt @@ -104,11 +104,12 @@ class MySubmissionFragment : Fragment(), CompoundButton.OnCheckedChangeListener val adapter = AdapterMySubmission(requireActivity(), submissions, exams) val itemCount = adapter.itemCount - showNoData(fragmentMySubmissionBinding.tvMessage, itemCount, "submission") - - if (itemCount == 0) { - fragmentMySubmissionBinding.llSearch.visibility = View.GONE - fragmentMySubmissionBinding.title.visibility = View.GONE + if(s.isNullOrEmpty()){ + showNoData(fragmentMySubmissionBinding.tvMessage, itemCount, "submission") + if (itemCount == 0) { + fragmentMySubmissionBinding.llSearch.visibility = View.GONE + fragmentMySubmissionBinding.title.visibility = View.GONE + } } adapter.setmRealm(mRealm) adapter.setType(type) From 98643a5dda89f4a5e2f2af69789aa1827b2efc5a Mon Sep 17 00:00:00 2001 From: Deep Vora <70479785+deeppp15@users.noreply.github.com> Date: Fri, 17 Jan 2025 14:41:17 -0500 Subject: [PATCH 05/12] resources: smoother filter close (fixes #5060) (#5062) Co-authored-by: dogi --- app/build.gradle | 4 ++-- app/src/main/res/layout/fragment_library_filter.xml | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 8a2904c223..17241f8cc5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "org.ole.planet.myplanet" minSdkVersion 26 targetSdkVersion 34 - versionCode 2217 - versionName "0.22.17" + versionCode 2218 + versionName "0.22.18" ndkVersion '21.3.6528147' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true diff --git a/app/src/main/res/layout/fragment_library_filter.xml b/app/src/main/res/layout/fragment_library_filter.xml index 9984946112..aeae596201 100644 --- a/app/src/main/res/layout/fragment_library_filter.xml +++ b/app/src/main/res/layout/fragment_library_filter.xml @@ -25,6 +25,8 @@ android:layout_width="@dimen/_40dp" android:layout_height="@dimen/_40dp" android:layout_gravity="right" + android:layout_marginEnd="35dp" + app:tint="@color/daynight_textColor" android:src="@drawable/close_x" /> From cc97f014654039919ae82171c801b1c4c09a2cb1 Mon Sep 17 00:00:00 2001 From: Avinash Sharma <133222313+Avinash-Codes@users.noreply.github.com> Date: Sat, 18 Jan 2025 01:16:54 +0530 Subject: [PATCH 06/12] teams: smoother create dialog (fixes #5063) (#5064) Co-authored-by: dogi --- app/build.gradle | 4 ++-- app/src/main/res/layout/alert_create_team.xml | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 17241f8cc5..6aa9953b7c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "org.ole.planet.myplanet" minSdkVersion 26 targetSdkVersion 34 - versionCode 2218 - versionName "0.22.18" + versionCode 2219 + versionName "0.22.19" ndkVersion '21.3.6528147' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true diff --git a/app/src/main/res/layout/alert_create_team.xml b/app/src/main/res/layout/alert_create_team.xml index dad4176702..147d91b8e1 100644 --- a/app/src/main/res/layout/alert_create_team.xml +++ b/app/src/main/res/layout/alert_create_team.xml @@ -46,7 +46,8 @@ android:layout_height="match_parent" android:hint="@string/entServices" android:padding="@dimen/padding_normal" - android:textColor="@color/daynight_textColor" /> + android:textColor="@color/daynight_textColor" + android:backgroundTint="@color/hint_color" /> + android:textColor="@color/daynight_textColor" + android:backgroundTint="@color/hint_color" /> Date: Fri, 17 Jan 2025 22:55:33 +0300 Subject: [PATCH 07/12] mylife: smoother myhealth background sync (fixes #5065) (#5066) Co-authored-by: dogi --- app/build.gradle | 4 ++-- .../java/org/ole/planet/myplanet/MainApplication.kt | 2 -- .../myplanet/ui/dashboard/BaseDashboardFragment.kt | 4 ---- .../myplanet/ui/dashboard/BellDashboardFragment.kt | 11 ++--------- 4 files changed, 4 insertions(+), 17 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6aa9953b7c..03dfb69973 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "org.ole.planet.myplanet" minSdkVersion 26 targetSdkVersion 34 - versionCode 2219 - versionName "0.22.19" + versionCode 2220 + versionName "0.22.20" ndkVersion '21.3.6528147' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true diff --git a/app/src/main/java/org/ole/planet/myplanet/MainApplication.kt b/app/src/main/java/org/ole/planet/myplanet/MainApplication.kt index a2253cc769..5b26719f80 100644 --- a/app/src/main/java/org/ole/planet/myplanet/MainApplication.kt +++ b/app/src/main/java/org/ole/planet/myplanet/MainApplication.kt @@ -25,7 +25,6 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext -import org.ole.planet.myplanet.MainApplication import org.ole.planet.myplanet.base.BaseResourceFragment.Companion.backgroundDownload import org.ole.planet.myplanet.base.BaseResourceFragment.Companion.getAllLibraryList import org.ole.planet.myplanet.callback.TeamPageListener @@ -64,7 +63,6 @@ class MainApplication : Application(), Application.ActivityLifecycleCallbacks { var isCollectionSwitchOn = false var showDownload = false var isSyncRunning = false - var showHealthDialog = true var listener: TeamPageListener? = null val androidId: String get() { try { diff --git a/app/src/main/java/org/ole/planet/myplanet/ui/dashboard/BaseDashboardFragment.kt b/app/src/main/java/org/ole/planet/myplanet/ui/dashboard/BaseDashboardFragment.kt index 27898343f9..efb67ae964 100644 --- a/app/src/main/java/org/ole/planet/myplanet/ui/dashboard/BaseDashboardFragment.kt +++ b/app/src/main/java/org/ole/planet/myplanet/ui/dashboard/BaseDashboardFragment.kt @@ -385,8 +385,6 @@ open class BaseDashboardFragment : BaseDashboardFragmentPlugin(), NotificationCa } override fun syncKeyId() { - di = DialogUtils.CustomProgressDialog(requireContext()) - di?.setText(getString(R.string.syncing_health_please_wait)) if (model?.getRoleAsString()?.contains("health") == true) { settings?.let { TransactionSyncManager.syncAllHealthData(mRealm, it, this) } } else { @@ -400,12 +398,10 @@ open class BaseDashboardFragment : BaseDashboardFragmentPlugin(), NotificationCa override fun onSyncComplete() { di?.dismiss() - Utilities.toast(activity, getString(R.string.myhealth_synced_successfully)) } override fun onSyncFailed(msg: String?) { di?.dismiss() - Utilities.toast(activity, getString(R.string.myhealth_synced_failed)) } override fun showTaskListDialog() { diff --git a/app/src/main/java/org/ole/planet/myplanet/ui/dashboard/BellDashboardFragment.kt b/app/src/main/java/org/ole/planet/myplanet/ui/dashboard/BellDashboardFragment.kt index a8b0e42b54..73990b2219 100644 --- a/app/src/main/java/org/ole/planet/myplanet/ui/dashboard/BellDashboardFragment.kt +++ b/app/src/main/java/org/ole/planet/myplanet/ui/dashboard/BellDashboardFragment.kt @@ -1,6 +1,5 @@ package org.ole.planet.myplanet.ui.dashboard -import android.content.DialogInterface import android.os.Bundle import android.text.TextUtils import android.view.LayoutInflater @@ -19,7 +18,6 @@ import androidx.recyclerview.widget.RecyclerView import io.realm.Case import io.realm.Realm import kotlinx.coroutines.* -import org.ole.planet.myplanet.MainApplication import org.ole.planet.myplanet.R import org.ole.planet.myplanet.databinding.FragmentHomeBellBinding import org.ole.planet.myplanet.model.RealmCertification @@ -66,13 +64,8 @@ class BellDashboardFragment : BaseDashboardFragment() { showBadges() checkPendingSurveys() - if (model?.id?.startsWith("guest") == false && TextUtils.isEmpty(model?.key) && MainApplication.showHealthDialog) { - AlertDialog.Builder(requireActivity(), R.style.CustomAlertDialog) - .setMessage(getString(R.string.health_record_not_available_sync_health_data)) - .setPositiveButton(getString(R.string.sync)) { _: DialogInterface?, _: Int -> - syncKeyId() - MainApplication.showHealthDialog = false - }.setNegativeButton(getString(R.string.cancel), null).show() + if (model?.id?.startsWith("guest") == false && TextUtils.isEmpty(model?.key)) { + syncKeyId() } } From 54c65d0ba994f47603b1bff3ac2d42dbe8ecbfd0 Mon Sep 17 00:00:00 2001 From: Pavneet Singh Cheema <90737218+pavi38@users.noreply.github.com> Date: Mon, 20 Jan 2025 14:00:17 -0600 Subject: [PATCH 08/12] all: smoother navigation links (fixes #5046) (#5053) Co-authored-by: Gideon Okuro Co-authored-by: dogi --- app/build.gradle | 4 ++-- .../ole/planet/myplanet/ui/dashboard/DashboardActivity.kt | 4 +++- .../planet/myplanet/ui/dashboard/DashboardActivity.kt.lite | 4 +++- .../ole/planet/myplanet/ui/sync/DashboardElementActivity.kt | 5 +++-- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 03dfb69973..5ff7484566 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "org.ole.planet.myplanet" minSdkVersion 26 targetSdkVersion 34 - versionCode 2220 - versionName "0.22.20" + versionCode 2221 + versionName "0.22.21" ndkVersion '21.3.6528147' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true diff --git a/app/src/main/java/org/ole/planet/myplanet/ui/dashboard/DashboardActivity.kt b/app/src/main/java/org/ole/planet/myplanet/ui/dashboard/DashboardActivity.kt index aa18c97340..6df1c66749 100644 --- a/app/src/main/java/org/ole/planet/myplanet/ui/dashboard/DashboardActivity.kt +++ b/app/src/main/java/org/ole/planet/myplanet/ui/dashboard/DashboardActivity.kt @@ -800,7 +800,9 @@ class DashboardActivity : DashboardElementActivity(), OnHomeItemClickListener, N val b = Bundle() b.putBoolean("isMyCourseLib", true) f.arguments = b - openCallFragment(f, "shelf") + val fragmentName = f::class.java.simpleName + val tag = "My$fragmentName" + openCallFragment(f, tag) } override fun onDestroy() { diff --git a/app/src/main/java/org/ole/planet/myplanet/ui/dashboard/DashboardActivity.kt.lite b/app/src/main/java/org/ole/planet/myplanet/ui/dashboard/DashboardActivity.kt.lite index 37abe945af..8dca97ce96 100644 --- a/app/src/main/java/org/ole/planet/myplanet/ui/dashboard/DashboardActivity.kt.lite +++ b/app/src/main/java/org/ole/planet/myplanet/ui/dashboard/DashboardActivity.kt.lite @@ -800,7 +800,9 @@ class DashboardActivity : DashboardElementActivity(), OnHomeItemClickListener, N val b = Bundle() b.putBoolean("isMyCourseLib", true) f.arguments = b - openCallFragment(f, "shelf") + val fragmentName = f::class.java.simpleName + val tag = "My$fragmentName" + openCallFragment(f, tag) } override fun onDestroy() { diff --git a/app/src/main/java/org/ole/planet/myplanet/ui/sync/DashboardElementActivity.kt b/app/src/main/java/org/ole/planet/myplanet/ui/sync/DashboardElementActivity.kt index 8a09f3ba91..924e4441cc 100644 --- a/app/src/main/java/org/ole/planet/myplanet/ui/sync/DashboardElementActivity.kt +++ b/app/src/main/java/org/ole/planet/myplanet/ui/sync/DashboardElementActivity.kt @@ -54,6 +54,7 @@ abstract class DashboardElementActivity : SyncActivity(), FragmentManager.OnBack profileDbHandler = UserProfileDbHandler(this) settings = applicationContext.getSharedPreferences(PREFS_NAME, MODE_PRIVATE) prefData = SharedPrefManager(this) + supportFragmentManager.addOnBackStackChangedListener(this) } fun onClickTabItems(position: Int) { @@ -246,13 +247,13 @@ abstract class DashboardElementActivity : SyncActivity(), FragmentManager.OnBack val f = supportFragmentManager.findFragmentById(R.id.fragment_container) val fragmentTag = f?.tag if (f is CoursesFragment) { - if ("shelf" == fragmentTag) { + if ("MyCoursesFragment" == fragmentTag) { navigationView.menu.findItem(R.id.menu_mycourses).isChecked = true } else { navigationView.menu.findItem(R.id.menu_courses).isChecked = true } } else if (f is ResourcesFragment) { - if ("shelf" == fragmentTag) { + if ("MyResourcesFragment" == fragmentTag) { navigationView.menu.findItem(R.id.menu_mylibrary).isChecked = true } else { navigationView.menu.findItem(R.id.menu_library).isChecked = true From dd854ebe7b32a79154221961ac1a486af8b952a5 Mon Sep 17 00:00:00 2001 From: Pavneet Singh Cheema <90737218+pavi38@users.noreply.github.com> Date: Mon, 20 Jan 2025 14:21:20 -0600 Subject: [PATCH 09/12] resources: smoother download dialog (fixes #5050) (#5069) Co-authored-by: Gideon Okuro Co-authored-by: dogi --- app/build.gradle | 4 ++-- .../planet/myplanet/ui/resources/ResourceDetailFragment.kt | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 5ff7484566..d0c71b0c91 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "org.ole.planet.myplanet" minSdkVersion 26 targetSdkVersion 34 - versionCode 2221 - versionName "0.22.21" + versionCode 2222 + versionName "0.22.22" ndkVersion '21.3.6528147' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true diff --git a/app/src/main/java/org/ole/planet/myplanet/ui/resources/ResourceDetailFragment.kt b/app/src/main/java/org/ole/planet/myplanet/ui/resources/ResourceDetailFragment.kt index 6a6f5fc0d4..83975e0d5e 100644 --- a/app/src/main/java/org/ole/planet/myplanet/ui/resources/ResourceDetailFragment.kt +++ b/app/src/main/java/org/ole/planet/myplanet/ui/resources/ResourceDetailFragment.kt @@ -39,6 +39,8 @@ class ResourceDetailFragment : BaseContainerFragment(), OnRatingChangeListener { } override fun onDownloadComplete() { + super.onDownloadComplete() + fragmentLibraryDetailBinding.btnDownload.setImageResource(R.drawable.ic_play) if (!library.userId?.contains(profileDbHandler.userModel?.id)!!) { if (!lRealm.isInTransaction) lRealm.beginTransaction() From 1c77c4e0c4ee9e68301ca39d849d505fa270369f Mon Sep 17 00:00:00 2001 From: Pavneet Singh Cheema <90737218+pavi38@users.noreply.github.com> Date: Mon, 20 Jan 2025 14:29:30 -0600 Subject: [PATCH 10/12] resources: smoother tag text (fixes #5055) (#5072) Co-authored-by: Gideon Okuro Co-authored-by: dogi --- app/build.gradle | 4 ++-- .../ui/resources/ResourcesFragment.kt | 8 +++++++- .../main/res/layout/fragment_my_library.xml | 19 ++++++++++--------- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d0c71b0c91..58be8be9f0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "org.ole.planet.myplanet" minSdkVersion 26 targetSdkVersion 34 - versionCode 2222 - versionName "0.22.22" + versionCode 2223 + versionName "0.22.23" ndkVersion '21.3.6528147' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true diff --git a/app/src/main/java/org/ole/planet/myplanet/ui/resources/ResourcesFragment.kt b/app/src/main/java/org/ole/planet/myplanet/ui/resources/ResourcesFragment.kt index bf9727a5e9..19ce419431 100644 --- a/app/src/main/java/org/ole/planet/myplanet/ui/resources/ResourcesFragment.kt +++ b/app/src/main/java/org/ole/planet/myplanet/ui/resources/ResourcesFragment.kt @@ -88,7 +88,11 @@ class ResourcesFragment : BaseRecyclerFragment(), OnLibraryItem selectAll = view.findViewById(R.id.selectAll) filter = view.findViewById(R.id.filter) addResourceButton = view.findViewById(R.id.addResource) - + if (tvSelected.text.isNullOrEmpty()) { + tvSelected.visibility = View.GONE + } else { + tvSelected.visibility = View.VISIBLE + } initArrays() updateTvDelete() @@ -241,6 +245,7 @@ class ResourcesFragment : BaseRecyclerFragment(), OnLibraryItem } override fun onTagClicked(realmTag: RealmTag) { + tvSelected.visibility = View.VISIBLE flexBoxTags.removeAllViews() val chipCloud = ChipCloud(activity, flexBoxTags, config) chipCloud.setDeleteListener(this) @@ -252,6 +257,7 @@ class ResourcesFragment : BaseRecyclerFragment(), OnLibraryItem } override fun onTagSelected(tag: RealmTag) { + tvSelected.visibility = View.VISIBLE val li: MutableList = ArrayList() li.add(tag) searchTags = li diff --git a/app/src/main/res/layout/fragment_my_library.xml b/app/src/main/res/layout/fragment_my_library.xml index a72c82e279..df9c332547 100644 --- a/app/src/main/res/layout/fragment_my_library.xml +++ b/app/src/main/res/layout/fragment_my_library.xml @@ -17,10 +17,18 @@ android:layout_height="wrap_content" android:layout_gravity="end" android:background="@color/colorPrimary" - android:gravity="end" + android:gravity="center" android:orientation="vertical" - android:padding="@dimen/padding_large"> + android:padding="@dimen/padding_small"> + - Date: Tue, 21 Jan 2025 17:41:30 +0300 Subject: [PATCH 11/12] sync: less compression is more (fixes #5075) (#5080) Co-authored-by: dogi --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 58be8be9f0..7a08ec9463 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "org.ole.planet.myplanet" minSdkVersion 26 targetSdkVersion 34 - versionCode 2223 - versionName "0.22.23" + versionCode 2224 + versionName "0.22.24" ndkVersion '21.3.6528147' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true From fd4376ef8ab396e66fd1fe669db0621d9fea3278 Mon Sep 17 00:00:00 2001 From: Gideon Okuro Date: Tue, 21 Jan 2025 18:53:53 +0300 Subject: [PATCH 12/12] sync: less compression is more (fixes #5082) (#5083) Co-authored-by: dogi --- app/build.gradle | 4 +- .../planet/myplanet/datamanager/ApiClient.kt | 69 +-------- .../planet/myplanet/service/SyncManager.kt | 138 ++++-------------- 3 files changed, 39 insertions(+), 172 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 7a08ec9463..dcfec893a0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "org.ole.planet.myplanet" minSdkVersion 26 targetSdkVersion 34 - versionCode 2224 - versionName "0.22.24" + versionCode 2225 + versionName "0.22.25" ndkVersion '21.3.6528147' testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true diff --git a/app/src/main/java/org/ole/planet/myplanet/datamanager/ApiClient.kt b/app/src/main/java/org/ole/planet/myplanet/datamanager/ApiClient.kt index 660cd4efe4..dd02f556de 100644 --- a/app/src/main/java/org/ole/planet/myplanet/datamanager/ApiClient.kt +++ b/app/src/main/java/org/ole/planet/myplanet/datamanager/ApiClient.kt @@ -1,15 +1,11 @@ package org.ole.planet.myplanet.datamanager import com.google.gson.GsonBuilder -import okhttp3.Interceptor import okhttp3.OkHttpClient -import okhttp3.Response import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory -import java.io.IOException import java.lang.reflect.Modifier import java.util.concurrent.TimeUnit -import kotlin.math.pow object ApiClient { private const val BASE_URL = "https://vi.media.mit.edu/" @@ -17,69 +13,18 @@ object ApiClient { @JvmStatic val client: Retrofit? get() { - val client = OkHttpClient.Builder().connectTimeout(1, TimeUnit.MINUTES).readTimeout(30, TimeUnit.SECONDS) - .writeTimeout(30, TimeUnit.SECONDS).addInterceptor { chain -> - val request = chain.request().newBuilder() - .addHeader("Accept-Encoding", "gzip").build() - chain.proceed(request) - } - .retryOnConnectionFailure(true).addInterceptor(RetryInterceptor()).build() + val client = OkHttpClient.Builder().connectTimeout(1, TimeUnit.MINUTES) + .readTimeout(30, TimeUnit.SECONDS).writeTimeout(30, TimeUnit.SECONDS).build() if (retrofit == null) { retrofit = Retrofit.Builder() - .baseUrl(BASE_URL).client(client) - .addConverterFactory(GsonConverterFactory.create( - GsonBuilder() - .excludeFieldsWithModifiers(Modifier.FINAL, Modifier.TRANSIENT, Modifier.STATIC) - .serializeNulls() - .create() + .baseUrl(BASE_URL).client(client).addConverterFactory( + GsonConverterFactory.create( + GsonBuilder() + .excludeFieldsWithModifiers(Modifier.FINAL, Modifier.TRANSIENT, Modifier.STATIC) + .serializeNulls().create() ) ).build() } return retrofit } - - class RetryInterceptor : Interceptor { - val maxRetryCount = 3 - val retryDelayMillis = 1000L - - override fun intercept(chain: Interceptor.Chain): Response { - var attempt = 0 - var response: Response? = null - val request = chain.request() - val url = request.url().toString() - - while (true) { - try { - response?.close() - response = chain.proceed(request) - - when (response.code()) { - in 200..299 -> { - return response - } - 404 -> { - return response - } - 401, 403 -> { - response.close() - throw IOException("Authentication failed: ${response.code()}") - } - else -> { - response.close() - throw IOException("Response unsuccessful: ${response.code()}") - } - } - } catch (e: IOException) { - attempt++ - - if (attempt >= maxRetryCount) { - throw IOException("Failed after $maxRetryCount attempts: $url", e) - } - - val delayMillis = retryDelayMillis * 2.0.pow((attempt - 1).toDouble()).toLong() - Thread.sleep(delayMillis) - } - } - } - } } diff --git a/app/src/main/java/org/ole/planet/myplanet/service/SyncManager.kt b/app/src/main/java/org/ole/planet/myplanet/service/SyncManager.kt index 9814a0fc46..7a59695980 100644 --- a/app/src/main/java/org/ole/planet/myplanet/service/SyncManager.kt +++ b/app/src/main/java/org/ole/planet/myplanet/service/SyncManager.kt @@ -144,111 +144,50 @@ class SyncManager private constructor(private val context: Context) { } } + @Throws(IOException::class) private fun syncResource(dbClient: ApiInterface?) { val newIds: MutableList = ArrayList() - try { - val allDocs = dbClient?.getJsonObject(Utilities.header, "${Utilities.getUrl()}/resources/_all_docs?include_doc=false") - val all = allDocs?.execute() - if (all?.isSuccessful != true) { - return - } - - val rows = getJsonArray("rows", all.body()) - val keys: MutableList = ArrayList() - val failedIds: MutableList = ArrayList() - - for (i in 0 until rows.size()) { - val `object` = rows[i].asJsonObject - if (!TextUtils.isEmpty(getString("id", `object`))) { - keys.add(getString("key", `object`)) - } - - if (i == rows.size() - 1 || keys.size == 1000) { - val obj = JsonObject() - obj.add("keys", Gson().fromJson(Gson().toJson(keys), JsonArray::class.java)) - val response = dbClient.findDocs(Utilities.header, "application/json", "${Utilities.getUrl()}/resources/_all_docs?include_docs=true", obj).execute() - - when { - response.isSuccessful == true -> { - response.body()?.let { body -> - val ids: List = save(getJsonArray("rows", body), mRealm) - newIds.addAll(ids) - } - } - response.code() == 404 -> { - failedIds.addAll(keys) - } - else -> { - val errorMessage = "Failed to sync resources: ${response.code()}" - handleException(errorMessage) - - when (response.code()) { - in 500..599 -> { - addToRetryQueue(keys) - } - 401, 403 -> { - handleAuthenticationError() - } - else -> { - failedIds.addAll(keys) - } - } - } - } - keys.clear() + val allDocs = dbClient?.getJsonObject(Utilities.header, Utilities.getUrl() + "/resources/_all_docs?include_doc=false") + val all = allDocs?.execute() + val rows = getJsonArray("rows", all?.body()) + val keys: MutableList = ArrayList() + for (i in 0 until rows.size()) { + val `object` = rows[i].asJsonObject + if (!TextUtils.isEmpty(getString("id", `object`))) keys.add(getString("key", `object`)) + if (i == rows.size() - 1 || keys.size == 1000) { + val obj = JsonObject() + obj.add("keys", Gson().fromJson(Gson().toJson(keys), JsonArray::class.java)) + val response = dbClient?.findDocs(Utilities.header, "application/json", Utilities.getUrl() + "/resources/_all_docs?include_docs=true", obj)?.execute() + if (response?.body() != null) { + val ids: List = save(getJsonArray("rows", response.body()), mRealm) + newIds.addAll(ids) } - } - } catch (e: Exception) { - e.printStackTrace() - } finally { - try { - removeDeletedResource(newIds, mRealm) - } catch (e: Exception) { - e.printStackTrace() + keys.clear() } } - } - - private fun addToRetryQueue(keys: List) { - settings.edit().apply { - val existingQueue = settings.getStringSet("retry_queue", setOf()) ?: setOf() - putStringSet("retry_queue", existingQueue + keys) - apply() - } - } - - private fun handleAuthenticationError() { - settings.edit().remove("credentials").apply() - handleException("Authentication failed.") + removeDeletedResource(newIds, mRealm) } private fun myLibraryTransactionSync() { val apiInterface = client?.create(ApiInterface::class.java) try { - val response = apiInterface?.getDocuments(Utilities.header, "${Utilities.getUrl()}/shelf/_all_docs")?.execute() - - val res = response?.body() - res?.rows?.let { rows -> - for (i in rows.indices) { - shelfDoc = rows[i] - populateShelfItems(apiInterface) - } + val res = apiInterface?.getDocuments(Utilities.header, "${Utilities.getUrl()}/shelf/_all_docs")?.execute()?.body() + for (i in res?.rows!!.indices) { + shelfDoc = res.rows!![i] + populateShelfItems(apiInterface) } } catch (e: IOException) { e.printStackTrace() } } - private fun populateShelfItems(apiInterface: ApiInterface?) { + private fun populateShelfItems(apiInterface: ApiInterface) { try { - val response = apiInterface?.getJsonObject(Utilities.header, "${Utilities.getUrl()}/shelf/${shelfDoc?.id}")?.execute() - - response?.body()?.let { jsonDoc -> - for (i in Constants.shelfDataList.indices) { - val shelfData = Constants.shelfDataList[i] - val array = getJsonArray(shelfData.key, jsonDoc) - memberShelfData(array, shelfData) - } + val jsonDoc = apiInterface.getJsonObject(Utilities.header, "${Utilities.getUrl()}/shelf/${shelfDoc?.id}").execute().body() + for (i in Constants.shelfDataList.indices) { + val shelfData = Constants.shelfDataList[i] + val array = getJsonArray(shelfData.key, jsonDoc) + memberShelfData(array, shelfData) } } catch (err: Exception) { err.printStackTrace() @@ -278,27 +217,10 @@ class SyncManager private constructor(private val context: Context) { } private fun validateDocument(arrayCategoryIds: JsonArray, x: Int) { - val apiInterface = client?.create(ApiInterface::class.java) + val apiInterface = client!!.create(ApiInterface::class.java) try { - val response = apiInterface?.getJsonObject(Utilities.header, "${Utilities.getUrl()}/${stringArray[2]}/${arrayCategoryIds[x].asString}")?.execute() - - when { - response?.isSuccessful == true -> { - response.body()?.let { resourceDoc -> - triggerInsert(stringArray, resourceDoc) - } - } - response?.code() == 404 -> { - return - } - else -> { - val errorMessage = "Failed to validate document: ${response?.code()}" - handleException(errorMessage) - if (response?.code() in 500..599) { - throw IOException(errorMessage) - } - } - } + val resourceDoc = apiInterface.getJsonObject(Utilities.header, "${Utilities.getUrl()}/${stringArray[2]}/${arrayCategoryIds[x].asString}").execute().body() + resourceDoc?.let { triggerInsert(stringArray, it) } } catch (e: IOException) { e.printStackTrace() }