From 9d1653ad2d72b06a343514d2817b5531645ad73f Mon Sep 17 00:00:00 2001 From: Karthik Date: Thu, 10 Jun 2021 16:40:50 +0530 Subject: [PATCH] WIP --- app/build.gradle | 6 +- app/src/main/AndroidManifest.xml | 1 + .../testpress/network/TestpressAPIService.kt | 32 ++++++++ .../repository/DiscussionPageSource.kt | 42 ++++++++++ .../repository/DiscussionRepository.kt | 22 ++++++ .../testpress/ui/DiscussionFragment.kt | 76 +++++++++++++++++++ .../testpress/ui/DiscussionViewModel.kt | 32 ++++++++ .../testpress/testpress/ui/MainActivity.java | 37 ++++----- .../ui/adapters/DiscussionsAdapter.kt | 34 +++++++++ .../testpress/util/DiffUtilCallback.kt | 15 ++++ .../main/res/layout/course_carousel_item.xml | 4 +- app/src/main/res/layout/discussion_list.xml | 16 ++++ 12 files changed, 296 insertions(+), 21 deletions(-) create mode 100644 app/src/main/java/in/testpress/testpress/network/TestpressAPIService.kt create mode 100644 app/src/main/java/in/testpress/testpress/repository/DiscussionPageSource.kt create mode 100644 app/src/main/java/in/testpress/testpress/repository/DiscussionRepository.kt create mode 100644 app/src/main/java/in/testpress/testpress/ui/DiscussionFragment.kt create mode 100644 app/src/main/java/in/testpress/testpress/ui/DiscussionViewModel.kt create mode 100644 app/src/main/java/in/testpress/testpress/ui/adapters/DiscussionsAdapter.kt create mode 100644 app/src/main/java/in/testpress/testpress/util/DiffUtilCallback.kt create mode 100644 app/src/main/res/layout/discussion_list.xml diff --git a/app/build.gradle b/app/build.gradle index 3237ce498..93d3b2443 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -20,6 +20,9 @@ dependencies { implementation 'com.jakewharton:butterknife:6.1.0' implementation "ru.tinkoff.scrollingpagerindicator:scrollingpagerindicator:1.1.0" + + implementation 'androidx.paging:paging-runtime-ktx:3.0.0' + implementation 'com.github.kevinsawicki:wishlist:0.9@aar' // implementation project(':commonlib') // implementation project(':mobilertc') @@ -31,7 +34,7 @@ dependencies { // HTTP implementation 'com.squareup.okhttp:okhttp-urlconnection:2.0.0' - implementation 'com.squareup.okhttp:okhttp:2.3.0' + implementation 'com.squareup.okhttp:okhttp:2.3.0'- implementation 'com.squareup.retrofit:retrofit:1.9.0' // Material Dialog @@ -58,6 +61,7 @@ dependencies { // Allow methods more than 64K implementation 'androidx.multidex:multidex:2.0.1' implementation "androidx.fragment:fragment-ktx:1.2.5" + implementation 'com.squareup.retrofit2:retrofit:2.9.0' // Country code picker // https://github.com/hbb20/CountryCodePickerProject/wiki diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index aa019dc14..d32f60d5f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,6 +21,7 @@ android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" + tools:replace="android:theme" android:theme="@style/Theme.Testpress.Dark"> diff --git a/app/src/main/java/in/testpress/testpress/network/TestpressAPIService.kt b/app/src/main/java/in/testpress/testpress/network/TestpressAPIService.kt new file mode 100644 index 000000000..fb57e85ee --- /dev/null +++ b/app/src/main/java/in/testpress/testpress/network/TestpressAPIService.kt @@ -0,0 +1,32 @@ +package `in`.testpress.testpress.network + +import `in`.testpress.core.TestpressSdk +import `in`.testpress.exam.network.ExamService +import `in`.testpress.network.RetrofitCall +import `in`.testpress.network.TestpressApiClient +import `in`.testpress.testpress.core.Constants.Http.URL_FORUMS_FRAG +import `in`.testpress.testpress.models.Forum +import `in`.testpress.testpress.models.TestpressApiResponse +import `in`.testpress.v2_4.models.ApiResponse +import android.content.Context +import retrofit2.Response +import retrofit2.http.GET +import retrofit2.http.Query +import retrofit2.http.QueryMap + +@JvmSuppressWildcards +interface TestpressAPIService { + @GET(URL_FORUMS_FRAG) + fun fetchPosts( + @QueryMap options: Map + ): RetrofitCall> +} + +class APIClient(context: Context): TestpressApiClient(context, TestpressSdk.getTestpressSession(context)) { + private fun getService() = retrofit.create(TestpressAPIService::class.java) + + + fun getPosts(queryParams: Map): RetrofitCall> { + return getService().fetchPosts(queryParams) + } +} diff --git a/app/src/main/java/in/testpress/testpress/repository/DiscussionPageSource.kt b/app/src/main/java/in/testpress/testpress/repository/DiscussionPageSource.kt new file mode 100644 index 000000000..10719c81a --- /dev/null +++ b/app/src/main/java/in/testpress/testpress/repository/DiscussionPageSource.kt @@ -0,0 +1,42 @@ +package `in`.testpress.testpress.repository + +import `in`.testpress.testpress.core.TestpressService +import `in`.testpress.testpress.models.Forum +import `in`.testpress.testpress.network.APIClient +import `in`.testpress.testpress.util.SafeAsyncTask +import android.accounts.OperationCanceledException +import android.util.Log +import androidx.paging.PagingSource +import androidx.paging.PagingState +import kotlinx.coroutines.runBlocking +import java.util.* +import kotlin.jvm.Throws + +class DiscussionPageSource(private val apiClient: APIClient): PagingSource() { + override fun getRefreshKey(state: PagingState): Int? { + return state.anchorPosition + } + + override suspend fun load(params: LoadParams): LoadResult { + val queryParams: Map = LinkedHashMap() + Log.d("TAG", "load: ") + + + try { + // Start refresh at page 1 if undefined. + val nextPage: Int = params.key ?: 1 + val response = apiClient.getPosts(queryParams).execute().body() + Log.d("TAG", "load: ${response.results}") + return LoadResult.Page( + data = response.results, + prevKey = if (nextPage == 1) null else nextPage - 1, + nextKey = nextPage + 1 + ) + } catch (e: Exception) { + e.stackTrace + Log.d("TAG", "Error: ${e}") + return LoadResult.Error(e) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/in/testpress/testpress/repository/DiscussionRepository.kt b/app/src/main/java/in/testpress/testpress/repository/DiscussionRepository.kt new file mode 100644 index 000000000..9da1ef2f2 --- /dev/null +++ b/app/src/main/java/in/testpress/testpress/repository/DiscussionRepository.kt @@ -0,0 +1,22 @@ +package `in`.testpress.testpress.repository + +import `in`.testpress.testpress.core.TestpressService +import `in`.testpress.testpress.models.Forum +import `in`.testpress.testpress.network.APIClient +import android.util.Log +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import kotlinx.coroutines.flow.Flow + +class DiscussionRepository(private val testpressService: APIClient) { + + fun fetchDiscussions(): Flow> { + Log.d("TAG", "fetchDiscussions: ") + return Pager( + PagingConfig(pageSize = 40, enablePlaceholders = true) + ) { + DiscussionPageSource(testpressService) + }.flow + } +} \ No newline at end of file diff --git a/app/src/main/java/in/testpress/testpress/ui/DiscussionFragment.kt b/app/src/main/java/in/testpress/testpress/ui/DiscussionFragment.kt new file mode 100644 index 000000000..1a320f0d8 --- /dev/null +++ b/app/src/main/java/in/testpress/testpress/ui/DiscussionFragment.kt @@ -0,0 +1,76 @@ +package `in`.testpress.testpress.ui + +import `in`.testpress.testpress.Injector +import `in`.testpress.testpress.R +import `in`.testpress.testpress.TestpressServiceProvider +import `in`.testpress.testpress.core.TestpressService +import `in`.testpress.testpress.ui.adapters.DiscussionsAdapter +import android.accounts.AccountsException +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import butterknife.ButterKnife +import kotlinx.android.synthetic.main.discussion_list.* +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch +import java.io.IOException +import javax.inject.Inject + +public class DiscussionFragment: Fragment() { + @Inject + public lateinit var testpressService: TestpressService + @Inject + public lateinit var serviceProvider: TestpressServiceProvider + + private val adapter = DiscussionsAdapter() + + val viewModel: DiscussionViewModel by viewModels { + DiscussionViewModelFactory(requireActivity().application, testpressService) + } + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + Injector.inject(this) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val view: View = inflater.inflate(R.layout.discussion_list, container, false) + ButterKnife.inject(this, view) + return view + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupViews() + fetchPosts() + } + + private fun fetchPosts() { + Log.d("TAG", "fetchPosts: ") + lifecycleScope.launch { + viewModel.fetchPosts().collectLatest { pagingData -> + Log.d("TAG", "fetchPosts: ${pagingData}") + adapter.submitData(pagingData) + } + } + } + + private fun setupViews() { + rvPosts.adapter = adapter + } + + private fun getTestpressApiService(): TestpressService { + try { + testpressService = serviceProvider.getService(activity) + } catch (e: AccountsException) { + } catch (e: IOException) { + } + return testpressService + } +} \ No newline at end of file diff --git a/app/src/main/java/in/testpress/testpress/ui/DiscussionViewModel.kt b/app/src/main/java/in/testpress/testpress/ui/DiscussionViewModel.kt new file mode 100644 index 000000000..169b02f6d --- /dev/null +++ b/app/src/main/java/in/testpress/testpress/ui/DiscussionViewModel.kt @@ -0,0 +1,32 @@ +package `in`.testpress.testpress.ui + +import `in`.testpress.testpress.core.TestpressService +import `in`.testpress.testpress.models.Forum +import `in`.testpress.testpress.network.APIClient +import `in`.testpress.testpress.repository.DiscussionRepository +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.paging.PagingData +import androidx.paging.cachedIn +import kotlinx.coroutines.flow.Flow + + +class DiscussionViewModel(application: Application) : AndroidViewModel(application) { + private val service = APIClient(application) + private val repository = DiscussionRepository(service) + + fun fetchPosts(): Flow> { + return repository.fetchDiscussions().cachedIn(viewModelScope) + } +} + +class DiscussionViewModelFactory( + private val application: Application, + private val service: TestpressService +): ViewModelProvider.NewInstanceFactory() { + override fun create(modelClass: Class): T = + DiscussionViewModel(application) as T +} \ No newline at end of file diff --git a/app/src/main/java/in/testpress/testpress/ui/MainActivity.java b/app/src/main/java/in/testpress/testpress/ui/MainActivity.java index 0c2922817..6df7bfae9 100644 --- a/app/src/main/java/in/testpress/testpress/ui/MainActivity.java +++ b/app/src/main/java/in/testpress/testpress/ui/MainActivity.java @@ -289,27 +289,28 @@ private void initScreen() { CommonUtils.registerDevice(MainActivity.this, testpressService, serviceProvider); } - if (isUserAuthenticated && mInstituteSettings.getShowGameFrontend()) { - addMenuItem(R.string.dashboard, R.drawable.ic_dashboard, new DashboardFragment()); - } else { - addMenuItem(R.string.dashboard, R.drawable.profile_default, new MainMenuFragment()); - } +// if (isUserAuthenticated && mInstituteSettings.getShowGameFrontend()) { +// addMenuItem(R.string.dashboard, R.drawable.ic_dashboard, new DashboardFragment()); +// } else { +// addMenuItem(R.string.dashboard, R.drawable.profile_default, new MainMenuFragment()); +// } + addMenuItem(R.string.discussions, R.drawable.chat_icon, new DiscussionFragment()); // Show courses list if game front end is enabled, otherwise hide bottom bar if (isUserAuthenticated && mInstituteSettings.getShowGameFrontend()) { //noinspection ConstantConditions - addMenuItem(R.string.learn, R.drawable.learn, - TestpressCourse.getCoursesListFragment(this, TestpressSdk.getTestpressSession(this))); - - if (mInstituteSettings.getCoursesEnableGamification()) { - //noinspection ConstantConditions - addMenuItem(R.string.testpress_leaderboard, R.drawable.leaderboard, - TestpressCourse.getLeaderboardFragment(this, TestpressSdk.getTestpressSession(this))); - } - if (mInstituteSettings.getForumEnabled()) { - addMenuItem(R.string.discussions, R.drawable.chat_icon, new ForumListFragment()); - } - DownloadsFragment downloadsFragment = new DownloadsFragment(); - addMenuItem(R.string.downloads, R.drawable.ic_downloads, downloadsFragment); +// addMenuItem(R.string.learn, R.drawable.learn, +// TestpressCourse.getCoursesListFragment(this, TestpressSdk.getTestpressSession(this))); + +// if (mInstituteSettings.getCoursesEnableGamification()) { +// //noinspection ConstantConditions +// addMenuItem(R.string.testpress_leaderboard, R.drawable.leaderboard, +// TestpressCourse.getLeaderboardFragment(this, TestpressSdk.getTestpressSession(this))); +// } +// if (mInstituteSettings.getForumEnabled()) { +// addMenuItem(R.string.discussions, R.drawable.chat_icon, new ForumListFragment()); +// } +// DownloadsFragment downloadsFragment = new DownloadsFragment(); +// addMenuItem(R.string.downloads, R.drawable.ic_downloads, downloadsFragment); } else { grid.setVisibility(View.GONE); } diff --git a/app/src/main/java/in/testpress/testpress/ui/adapters/DiscussionsAdapter.kt b/app/src/main/java/in/testpress/testpress/ui/adapters/DiscussionsAdapter.kt new file mode 100644 index 000000000..9e118c98d --- /dev/null +++ b/app/src/main/java/in/testpress/testpress/ui/adapters/DiscussionsAdapter.kt @@ -0,0 +1,34 @@ +package `in`.testpress.testpress.ui.adapters + +import `in`.testpress.testpress.R +import `in`.testpress.testpress.models.Forum +import `in`.testpress.testpress.util.DiffUtilCallBack +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.paging.PagingDataAdapter +import androidx.recyclerview.widget.RecyclerView +import kotlinx.android.synthetic.main.forum_list_item.view.* + +class DiscussionsAdapter : + PagingDataAdapter(DiffUtilCallBack()) { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RedditViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.forum_list_item, parent, false) + return RedditViewHolder(view) + } + + override fun onBindViewHolder(holder: RedditViewHolder, position: Int) { + getItem(position)?.let { holder.bindPost(it) } + } + + class RedditViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + private val titleText: TextView = itemView.title + + fun bindPost(forumPost: Forum) { + with(forumPost) { + titleText.text = title + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/in/testpress/testpress/util/DiffUtilCallback.kt b/app/src/main/java/in/testpress/testpress/util/DiffUtilCallback.kt new file mode 100644 index 000000000..1234c8485 --- /dev/null +++ b/app/src/main/java/in/testpress/testpress/util/DiffUtilCallback.kt @@ -0,0 +1,15 @@ +package `in`.testpress.testpress.util + +import `in`.testpress.testpress.models.Forum +import androidx.recyclerview.widget.DiffUtil + +class DiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Forum, newItem: Forum): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: Forum, newItem: Forum): Boolean { + return oldItem.contentHtml == newItem.contentHtml + && oldItem.commentsCount == newItem.commentsCount + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/course_carousel_item.xml b/app/src/main/res/layout/course_carousel_item.xml index 96232e1ca..5fe4c769f 100644 --- a/app/src/main/res/layout/course_carousel_item.xml +++ b/app/src/main/res/layout/course_carousel_item.xml @@ -20,8 +20,8 @@ diff --git a/app/src/main/res/layout/discussion_list.xml b/app/src/main/res/layout/discussion_list.xml new file mode 100644 index 000000000..0604802d0 --- /dev/null +++ b/app/src/main/res/layout/discussion_list.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file