Skip to content

Commit

Permalink
Merge branch 'master' into 5042-subject-level-list-in-filter-option-o…
Browse files Browse the repository at this point in the history
…f-mycourse-has-white-background-in-dark
  • Loading branch information
dogi authored Jan 17, 2025
2 parents 4304b75 + 5da6cd1 commit 3b937bb
Show file tree
Hide file tree
Showing 40 changed files with 577 additions and 250 deletions.
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ dependencies {
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'androidx.recyclerview:recyclerview:1.4.0'
implementation 'androidx.annotation:annotation:1.9.1'
implementation 'androidx.exifinterface:exifinterface:1.3.7'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ 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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import androidx.fragment.app.DialogFragment
abstract class BaseDialogFragment : DialogFragment() {
@JvmField
var id: String? = null
var teamId: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, R.style.AppTheme_Dialog_NoActionBar_MinWidth)
if (arguments != null) {
id = requireArguments().getString(key)
teamId = requireArguments().getString("teamId")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ abstract class BaseResourceFragment : Fragment() {
}
AlertDialog.Builder(requireActivity()).setTitle("Pending Surveys")
.setAdapter(arrayAdapter) { _: DialogInterface?, i: Int ->
AdapterMySubmission.openSurvey(homeItemClickListener, list[i].id, true)
AdapterMySubmission.openSurvey(homeItemClickListener, list[i].id, true, false, "")
}.setPositiveButton(R.string.dismiss, null).show()
}

Expand Down
74 changes: 63 additions & 11 deletions app/src/main/java/org/ole/planet/myplanet/datamanager/ApiClient.kt
Original file line number Diff line number Diff line change
@@ -1,33 +1,85 @@
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/"
private var retrofit: Retrofit? = null
@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
}
}

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)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ open class RealmStepExam : RealmObject() {
var passingPercentage: String? = null
var noOfQuestions = 0
var isFromNation = false
var teamId: String? = null

companion object {
val examDataList: MutableList<Array<String>> = mutableListOf()
Expand Down Expand Up @@ -66,6 +67,7 @@ open class RealmStepExam : RealmObject() {
myExam?.totalMarks = JsonUtils.getInt("totalMarks", exam)
myExam?.noOfQuestions = JsonUtils.getJsonArray("questions", exam).size()
myExam?.isFromNation = !TextUtils.isEmpty(parentId)
myExam.teamId = JsonUtils.getString("teamId", exam)
val oldQuestions: RealmResults<*>? = mRealm.where(RealmExamQuestion::class.java).equalTo("examId", JsonUtils.getString("_id", exam)).findAll()
if (oldQuestions == null || oldQuestions.isEmpty()) {
RealmExamQuestion.insertExamQuestions(JsonUtils.getJsonArray("questions", exam), JsonUtils.getString("_id", exam), mRealm)
Expand All @@ -84,8 +86,10 @@ open class RealmStepExam : RealmObject() {
JsonUtils.getString("updatedDate", exam),
JsonUtils.getString("totalMarks", exam),
JsonUtils.getString("noOfQuestions", exam),
JsonUtils.getString("isFromNation", exam)
JsonUtils.getString("isFromNation", exam),
JsonUtils.getString("teamId", exam)
)

examDataList.add(csvRow)
}

Expand All @@ -94,7 +98,7 @@ open class RealmStepExam : RealmObject() {
val file = File(filePath)
file.parentFile?.mkdirs()
val writer = CSVWriter(FileWriter(file))
writer.writeNext(arrayOf("_id", "_rev", "name", "passingPercentage", "type", "createdBy", "sourcePlanet", "createdDate", "updatedDate", "totalMarks", "noOfQuestions", "isFromNation"))
writer.writeNext(arrayOf("_id", "_rev", "name", "passingPercentage", "type", "createdBy", "sourcePlanet", "createdDate", "updatedDate", "totalMarks", "noOfQuestions", "isFromNation", "teamId"))
for (row in data) {
writer.writeNext(row)
}
Expand Down
140 changes: 109 additions & 31 deletions app/src/main/java/org/ole/planet/myplanet/service/SyncManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -144,50 +144,111 @@ class SyncManager private constructor(private val context: Context) {
}
}

@Throws(IOException::class)
private fun syncResource(dbClient: ApiInterface?) {
val newIds: MutableList<String?> = 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<String> = 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<String?> = 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<String> = ArrayList()
val failedIds: MutableList<String> = 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<String?> = 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<String>) {
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()
Expand Down Expand Up @@ -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()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,12 @@ object TransactionSyncManager {
val apiInterface = client?.create(ApiInterface::class.java)
val response: Response<DocumentResponse>?
try {
response = apiInterface?.getDocuments(header, Utilities.getUrl() + "/" + table + "/_all_docs")?.execute()
response = apiInterface?.getDocuments(header, "${Utilities.getUrl()}/$table/_all_docs")?.execute()
val ob = response?.body()
if (ob != null && ob.rows?.isNotEmpty() == true) {
val r = ob.rows!![0]
val jsonDoc = apiInterface?.getJsonObject(header, Utilities.getUrl() + "/" + table + "/" + r.id)?.execute()?.body()
val jsonDoc = apiInterface.getJsonObject(header, "${Utilities.getUrl()}/$table/${r.id}")
.execute().body()
userModel?.key = getString("key", jsonDoc)
userModel?.iv = getString("iv", jsonDoc)
}
Expand Down
Loading

0 comments on commit 3b937bb

Please sign in to comment.