Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discussions fragment refactor #182

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
6 changes: 5 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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">
<meta-data android:name="io.sentry.auto-init" android:value="false" />
<meta-data android:name="com.facebook.sdk.ApplicationId" android:value="@string/facebook_app_id"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String, Any>
): RetrofitCall<TestpressApiResponse<Forum>>
}

class APIClient(context: Context): TestpressApiClient(context, TestpressSdk.getTestpressSession(context)) {
private fun getService() = retrofit.create(TestpressAPIService::class.java)


fun getPosts(queryParams: Map<String, Any>): RetrofitCall<TestpressApiResponse<Forum>> {
return getService().fetchPosts(queryParams)
}
}
Original file line number Diff line number Diff line change
@@ -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<Int, Forum>() {
override fun getRefreshKey(state: PagingState<Int, Forum>): Int? {
return state.anchorPosition
}

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Forum> {
val queryParams: Map<String, String> = 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)
}
}

}
Original file line number Diff line number Diff line change
@@ -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<PagingData<Forum>> {
Log.d("TAG", "fetchDiscussions: ")
return Pager(
PagingConfig(pageSize = 40, enablePlaceholders = true)
) {
DiscussionPageSource(testpressService)
}.flow
}
}
76 changes: 76 additions & 0 deletions app/src/main/java/in/testpress/testpress/ui/DiscussionFragment.kt
Original file line number Diff line number Diff line change
@@ -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
}
}
32 changes: 32 additions & 0 deletions app/src/main/java/in/testpress/testpress/ui/DiscussionViewModel.kt
Original file line number Diff line number Diff line change
@@ -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<PagingData<Forum>> {
return repository.fetchDiscussions().cachedIn(viewModelScope)
}
}

class DiscussionViewModelFactory(
private val application: Application,
private val service: TestpressService
): ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
DiscussionViewModel(application) as T
}
37 changes: 19 additions & 18 deletions app/src/main/java/in/testpress/testpress/ui/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Forum, DiscussionsAdapter.RedditViewHolder>(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
}
}
}
}
15 changes: 15 additions & 0 deletions app/src/main/java/in/testpress/testpress/util/DiffUtilCallback.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package `in`.testpress.testpress.util

import `in`.testpress.testpress.models.Forum
import androidx.recyclerview.widget.DiffUtil

class DiffUtilCallBack : DiffUtil.ItemCallback<Forum>() {
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
}
}
4 changes: 2 additions & 2 deletions app/src/main/res/layout/course_carousel_item.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@

<ImageView
android:id="@+id/image_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_width="155dp"
android:layout_height="87dp"
android:cropToPadding="true"
android:background="@drawable/border_rectangle_light"
android:scaleType="centerCrop" />
Expand Down
16 changes: 16 additions & 0 deletions app/src/main/res/layout/discussion_list.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvPosts"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/forum_list_item"
tools:itemCount="10"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>

</androidx.constraintlayout.widget.ConstraintLayout>