Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions commonlib/commonlib.aar

This file was deleted.

2 changes: 2 additions & 0 deletions core/src/main/java/in/testpress/database/Room.kt
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ abstract class TestpressDatabase : RoomDatabase() {
abstract fun productCategoryDao(): ProductCategoryDao
abstract fun contentLiteDao(): ContentLiteDao
abstract fun contentLiteRemoteKeyDao():ContentLiteRemoteKeyDao
abstract fun offlineExamDao():OfflineExamDao
abstract fun languageDao(): LanguageDao

companion object {
private lateinit var INSTANCE: TestpressDatabase
Expand Down
12 changes: 12 additions & 0 deletions core/src/main/java/in/testpress/database/dao/LanguageDao.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package `in`.testpress.database.dao

import `in`.testpress.database.BaseDao
import `in`.testpress.database.entities.CategoryEntity
import `in`.testpress.database.entities.Language
import `in`.testpress.database.entities.OfflineExam
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Query

@Dao
interface LanguageDao: BaseDao<Language>
23 changes: 23 additions & 0 deletions core/src/main/java/in/testpress/database/dao/OfflineExamDao.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package `in`.testpress.database.dao

import `in`.testpress.database.BaseDao
import `in`.testpress.database.entities.CategoryEntity
import `in`.testpress.database.entities.OfflineExam
import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Query

@Dao
interface OfflineExamDao: BaseDao<OfflineExam> {

@Query("SELECT * FROM OfflineExam ")
fun getAll(): LiveData<List<OfflineExam>>

@Query("SELECT * FROM OfflineExam WHERE id = :examId")
fun get(examId: Long): OfflineExam?

@Query("DELETE FROM OfflineExam WHERE id = :examId")
fun deleteById(examId: Long)

}
5 changes: 5 additions & 0 deletions course/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@
android:configChanges="orientation|keyboardHidden|screenSize"
android:theme="@style/TestpressTheme" />

<activity
android:name=".ui.OfflineExamListActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:theme="@style/TestpressTheme" />

<service
android:name=".services.VideoDownloadService"
android:exported="false">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ import `in`.testpress.exam.domain.toGreenDaoModels
import `in`.testpress.enums.Status
import `in`.testpress.network.Resource
import `in`.testpress.course.repository.ExamContentRepository
import `in`.testpress.course.repository.OfflineExamRepository
import `in`.testpress.course.ui.ContentActivity
import `in`.testpress.course.ui.QuizActivity
import `in`.testpress.course.ui.WebViewWithSSO
import `in`.testpress.course.viewmodels.ExamContentViewModel
import `in`.testpress.course.viewmodels.OfflineExamViewModel
import `in`.testpress.exam.TestpressExam
import `in`.testpress.exam.api.TestpressExamApiClient
import `in`.testpress.exam.util.MultiLanguagesUtil
Expand All @@ -28,6 +30,8 @@ import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.ProgressBar
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
Expand All @@ -36,7 +40,10 @@ import androidx.lifecycle.ViewModelProvider

open class BaseExamWidgetFragment : Fragment() {
lateinit var startButton: Button
private lateinit var downloadExamButton: Button
private lateinit var loadingProgressBar: ProgressBar
protected lateinit var viewModel: ExamContentViewModel
protected lateinit var offlineExamViewModel: OfflineExamViewModel
protected lateinit var content: DomainContent
protected var contentId: Long = -1
lateinit var contentAttempts: ArrayList<DomainContentAttempt>
Expand All @@ -51,6 +58,13 @@ open class BaseExamWidgetFragment : Fragment() {
) as T
}
}).get(ExamContentViewModel::class.java)
offlineExamViewModel = ViewModelProvider(this, object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return OfflineExamViewModel(
OfflineExamRepository(context!!)
) as T
}
}).get(OfflineExamViewModel::class.java)
}

override fun onAttach(context: Context) {
Expand All @@ -65,19 +79,55 @@ open class BaseExamWidgetFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
startButton = view.findViewById(R.id.start_exam)
downloadExamButton = view.findViewById(R.id.download_exam)
loadingProgressBar = view.findViewById(R.id.loading_progress_bar)
contentId = requireArguments().getLong(ContentActivity.CONTENT_ID)

viewModel.getContent(contentId).observe(viewLifecycleOwner, Observer {
when (it?.status) {
Status.SUCCESS -> {
content = it.data!!
loadAttemptsAndUpdateStartButton()

}
else -> {}
}
})
}

private fun checkExamIsDownloaded() {
offlineExamViewModel.checkIfExamIsDownloaded(content.exam?.id!!)
offlineExamViewModel.isExamDownloaded.observe(viewLifecycleOwner, Observer { isDownloaded ->
if (isDownloaded) {
downloadExamButton.visibility = View.VISIBLE
downloadExamButton.text = "Start Exam in Offline"
} else {
downloadExamButton.visibility = View.VISIBLE
}
})
}

private fun observeViewModelLoading() {
offlineExamViewModel.isLoading.observe(requireActivity(), Observer { isLoading ->
loadingProgressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
downloadExamButton.visibility = if (isLoading) View.GONE else View.VISIBLE
})
}

private fun observeDownloadComplete() {
offlineExamViewModel.downloadComplete.observe(requireActivity(), Observer { result ->
val message = when (result.status) {
Status.SUCCESS -> {
downloadExamButton.text = "Start Exam in Offline"
"Download successful"
}
Status.ERROR -> "Download failed: ${result.exception?.message}"
Status.LOADING -> return@Observer
}
Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
})
}

fun loadAttemptsAndUpdateStartButton() {
val observer = Observer<Resource<List<DomainLanguage>>> { resource ->
examRefreshListener.showOrHideRefresh(false)
Expand Down Expand Up @@ -129,6 +179,19 @@ open class BaseExamWidgetFragment : Fragment() {

updateStartButtonTextAndVisibility(exam, pausedAttempt)
updateStartButtonListener(exam, pausedAttempt)


checkExamIsDownloaded()
observeViewModelLoading()
observeDownloadComplete()
downloadExamButton.setOnClickListener {
if (downloadExamButton.text == "Start Exam in Offline"){
Toast.makeText(requireContext(),"Exam already downloaded", Toast.LENGTH_SHORT).show()
} else {
offlineExamViewModel.downloadExam(contentId, content.exam?.id!!, content.exam?.slug!!)
}
}

}

private fun updateStartButtonTextAndVisibility(exam: DomainExamContent, pausedAttempt: DomainContentAttempt?) {
Expand Down
33 changes: 33 additions & 0 deletions course/src/main/java/in/testpress/course/network/CourseService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import `in`.testpress.course.api.TestpressCourseApiClient
import `in`.testpress.course.api.TestpressCourseApiClient.*
import `in`.testpress.database.entities.ContentEntityLite
import `in`.testpress.database.entities.ProductCategoryEntity
import `in`.testpress.exam.api.TestpressExamApiClient
import `in`.testpress.exam.network.NetworkAttempt
import `in`.testpress.exam.network.NetworkLanguage
import `in`.testpress.models.TestpressApiResponse
import `in`.testpress.models.greendao.Course
import `in`.testpress.network.RetrofitCall
Expand Down Expand Up @@ -73,6 +75,22 @@ interface CourseService {
@Path(value = "course_id", encoded = true) courseId: Long,
@QueryMap queryParams: HashMap<String, Any>
): ApiResponse<List<ContentEntityLite>>

@GET("api/v2.4/exams/{exam_id}/questions/")
fun getQuestions(
@Path(value = "exam_id", encoded = true) examId: Long,
@QueryMap queryParams: HashMap<String, Any>
): RetrofitCall<ApiResponse<NetworkOfflineQuestionResponse>>

@GET("${TestpressExamApiClient.EXAMS_LIST_v2_3_PATH}{exam_slug}${TestpressExamApiClient.LANGUAGES_PATH}")
fun getLanguages(
@Path(value = "exam_slug", encoded = true) examSlug: String?
): RetrofitCall<TestpressApiResponse<NetworkLanguage>>

@GET("/api/v2.4/contents/{content_id}/")
fun getNetworkContentWithId(
@Path(value = "content_id", encoded = true) contentId: Long
): RetrofitCall<NetworkContent>
}


Expand Down Expand Up @@ -130,4 +148,19 @@ class CourseNetwork(context: Context) : TestpressApiClient(context, TestpressSdk
suspend fun getUpcomingContents(courseId: Long, arguments: HashMap<String, Any>): ApiResponse<List<ContentEntityLite>> {
return getCourseService().getUpcomingContents(courseId, arguments)
}

fun getNetworkContentWithId(contentId: Long): RetrofitCall<NetworkContent> {
return getCourseService().getNetworkContentWithId(contentId)
}

fun getQuestions(
examId: Long,
queryParams: HashMap<String, Any>
): RetrofitCall<ApiResponse<NetworkOfflineQuestionResponse>> {
return getCourseService().getQuestions(examId, queryParams)
}

fun getLanguages(slug: String): RetrofitCall<TestpressApiResponse<NetworkLanguage>> {
return getCourseService().getLanguages(slug)
}
}
47 changes: 47 additions & 0 deletions course/src/main/java/in/testpress/course/network/NetworkContent.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package `in`.testpress.course.network

import `in`.testpress.database.ContentEntity
import `in`.testpress.database.entities.OfflineExam
import `in`.testpress.exam.network.NetworkExamContent
import `in`.testpress.models.greendao.Content
import android.util.Log

data class NetworkContent(
val id: Long,
Expand Down Expand Up @@ -141,3 +143,48 @@ fun NetworkContent.asGreenDaoModel(): Content {
this.examId
)
}

fun NetworkContent.asOfflineExam(): OfflineExam {
return OfflineExam(
this.exam?.id,
this.exam?.totalMarks,
this.exam?.url,
this.exam?.attemptsCount,
this.exam?.pausedAttemptsCount,
this.exam?.title,
this.exam?.description,
this.exam?.startDate,
this.exam?.endDate,
this.exam?.duration,
this.exam?.numberOfQuestions,
this.exam?.negativeMarks,
this.exam?.markPerQuestion,
this.exam?.templateType,
this.exam?.allowRetake,
this.exam?.allowPdf,
this.exam?.showAnswers,
this.exam?.maxRetakes,
this.exam?.attemptsUrl,
this.exam?.deviceAccessControl,
this.exam?.commentsCount,
this.exam?.slug,
selectedLanguage = null,
this.exam?.variableMarkPerQuestion,
this.exam?.passPercentage,
this.exam?.enableRanks,
this.exam?.showScore,
this.exam?.showPercentile,
categories = null,
isDetailsFetched = null,
this.exam?.isGrowthHackEnabled,
this.exam?.shareTextForSolutionUnlock,
this.exam?.showAnalytics,
this.exam?.instructions,
this.exam?.hasAudioQuestions,
this.exam?.rankPublishingDate,
this.exam?.enableQuizMode,
this.exam?.disableAttemptResume,
this.exam?.allowPreemptiveSectionEnding,
examDataModifiedOn = null
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package `in`.testpress.course.network

import `in`.testpress.database.entities.*

class NetworkOfflineQuestionResponse(
val directions: List<Direction>,
val subjects: List<Subject>,
val sections: List<Section>,
val examQuestions: List<ExamQuestion>,
val questions: List<Question>
)
Loading