mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-28 21:33:53 +01:00
Migrated from Rxjava to Retroift, added MVVM Architecture.
This commit is contained in:
parent
f1e8e48769
commit
614892c5cb
10 changed files with 303 additions and 204 deletions
|
|
@ -51,11 +51,26 @@ dependencies {
|
||||||
implementation 'com.karumi:dexter:5.0.0'
|
implementation 'com.karumi:dexter:5.0.0'
|
||||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
||||||
|
|
||||||
|
def lifecycle_version = "2.8.7"
|
||||||
|
// ViewModel
|
||||||
|
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
|
||||||
|
// ViewModel utilities for Compose
|
||||||
|
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version"
|
||||||
|
|
||||||
|
// Lifecycles only (without ViewModel or LiveData)
|
||||||
|
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
|
||||||
|
// Lifecycle utilities for Compose
|
||||||
|
implementation "androidx.lifecycle:lifecycle-runtime-compose:$lifecycle_version"
|
||||||
|
|
||||||
|
// Saved state module for ViewModel
|
||||||
|
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
|
||||||
|
|
||||||
|
// Annotation processor
|
||||||
|
kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
|
||||||
// Jetpack Compose
|
// Jetpack Compose
|
||||||
def composeBom = platform('androidx.compose:compose-bom:2024.11.00')
|
def composeBom = platform('androidx.compose:compose-bom:2024.11.00')
|
||||||
|
|
||||||
implementation "androidx.activity:activity-compose:1.9.3"
|
implementation "androidx.activity:activity-compose:1.9.3"
|
||||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.8.4"
|
|
||||||
implementation (composeBom)
|
implementation (composeBom)
|
||||||
implementation "androidx.compose.runtime:runtime"
|
implementation "androidx.compose.runtime:runtime"
|
||||||
implementation "androidx.compose.ui:ui"
|
implementation "androidx.compose.ui:ui"
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import fr.free.nrw.commons.media.PageMediaInterface
|
||||||
import fr.free.nrw.commons.media.WikidataMediaInterface
|
import fr.free.nrw.commons.media.WikidataMediaInterface
|
||||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
|
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
|
||||||
import fr.free.nrw.commons.mwapi.UserInterface
|
import fr.free.nrw.commons.mwapi.UserInterface
|
||||||
|
import fr.free.nrw.commons.network.APIService
|
||||||
import fr.free.nrw.commons.notification.NotificationInterface
|
import fr.free.nrw.commons.notification.NotificationInterface
|
||||||
import fr.free.nrw.commons.review.ReviewInterface
|
import fr.free.nrw.commons.review.ReviewInterface
|
||||||
import fr.free.nrw.commons.upload.UploadInterface
|
import fr.free.nrw.commons.upload.UploadInterface
|
||||||
|
|
@ -42,6 +43,8 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
import okhttp3.logging.HttpLoggingInterceptor.Level
|
import okhttp3.logging.HttpLoggingInterceptor.Level
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import retrofit2.converter.gson.GsonConverterFactory
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
@ -295,6 +298,32 @@ class NetworkingModule {
|
||||||
fun provideLanguageWikipediaSite(): WikiSite =
|
fun provideLanguageWikipediaSite(): WikiSite =
|
||||||
WikiSite.forDefaultLocaleLanguageCode()
|
WikiSite.forDefaultLocaleLanguageCode()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Named("tool_wmflabs_base_url")
|
||||||
|
fun provideToolWmflabsBaseUrl() : HttpUrl =
|
||||||
|
"https://tools.wmflabs.org/commons-android-app/tool-commons-android-app/".toHttpUrlOrNull()!!
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
@Named("tool_wmflabs_retrofit")
|
||||||
|
fun provideToolWmflabsBaseUrlRetrofit(
|
||||||
|
@Named("tool_wmflabs_base_url") baseUrl: HttpUrl,
|
||||||
|
okHttpClient: OkHttpClient
|
||||||
|
) : Retrofit {
|
||||||
|
return Retrofit.Builder()
|
||||||
|
.baseUrl(baseUrl)
|
||||||
|
.client(okHttpClient)
|
||||||
|
.addConverterFactory(GsonConverterFactory.create())
|
||||||
|
// .addCallAdapterFactory(CoroutineCallAdapterFactory())
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideAPIService(
|
||||||
|
@Named("tool_wmflabs_retrofit") retrofit: Retrofit
|
||||||
|
): APIService = retrofit.create(APIService::class.java)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val WIKIDATA_SPARQL_QUERY_URL = "https://query.wikidata.org/sparql"
|
private const val WIKIDATA_SPARQL_QUERY_URL = "https://query.wikidata.org/sparql"
|
||||||
private const val TOOLS_FORGE_URL =
|
private const val TOOLS_FORGE_URL =
|
||||||
|
|
|
||||||
23
app/src/main/java/fr/free/nrw/commons/network/APIService.kt
Normal file
23
app/src/main/java/fr/free/nrw/commons/network/APIService.kt
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
package fr.free.nrw.commons.network
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.profile.model.AchievementResponse
|
||||||
|
import retrofit2.Response
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Query
|
||||||
|
|
||||||
|
|
||||||
|
interface APIService {
|
||||||
|
|
||||||
|
// https://tools.wmflabs.org/commons-android-app/tool-commons-android-app/uploadsbyuser.py?user=Devanonymous
|
||||||
|
@GET("uploadsbyuser.py")
|
||||||
|
suspend fun getImageUploadCount(
|
||||||
|
@Query("user") username : String
|
||||||
|
) : Response<Int>
|
||||||
|
|
||||||
|
|
||||||
|
// https://tools.wmflabs.org/commons-android-app/tool-commons-android-app//feedback.py?user=Devanonymous
|
||||||
|
@GET("feedback.py")
|
||||||
|
suspend fun getUserAchievements(
|
||||||
|
@Query("user") username: String
|
||||||
|
) : Response<AchievementResponse>
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package fr.free.nrw.commons.profile.achievements
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import fr.free.nrw.commons.profile.model.UserAchievements
|
||||||
|
import fr.free.nrw.commons.repository.ProfileRepository
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class AchievementViewModel @Inject constructor(
|
||||||
|
private val repository: ProfileRepository
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val _achievements = MutableStateFlow(UserAchievements(
|
||||||
|
LevelController.LevelInfo.LEVEL_1,
|
||||||
|
articlesUsingImagesCount = 0,
|
||||||
|
thanksReceivedCount = 0,
|
||||||
|
featuredImagesCount = 0,
|
||||||
|
qualityImagesCount = 0,
|
||||||
|
imagesUploadedCount = 0,
|
||||||
|
revertedCount = 0,
|
||||||
|
uniqueImagesCount = 0,
|
||||||
|
imagesEditedBySomeoneElseCount = 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val achievements : StateFlow<UserAchievements> = _achievements
|
||||||
|
|
||||||
|
private val _loading = MutableStateFlow(true)
|
||||||
|
val loading : StateFlow<Boolean> = _loading
|
||||||
|
|
||||||
|
fun getUserAchievements(username: String){
|
||||||
|
viewModelScope.launch {
|
||||||
|
repository.getUserLevel(username = username).collect {
|
||||||
|
_loading.value = false
|
||||||
|
_achievements.value = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package fr.free.nrw.commons.profile.achievements
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Provider
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class extends the ViewModelProvider.Factory and creates a ViewModelFactory class
|
||||||
|
* for AchievementViewModel
|
||||||
|
*/
|
||||||
|
class AchievementViewModelFactory @Inject constructor(
|
||||||
|
private val viewModelProvider: Provider<AchievementViewModel>
|
||||||
|
): ViewModelProvider.Factory {
|
||||||
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
|
if (modelClass.isAssignableFrom(AchievementViewModel::class.java)) {
|
||||||
|
(@Suppress("UNCHECKED_CAST")
|
||||||
|
return viewModelProvider.get() as T)
|
||||||
|
} else {
|
||||||
|
throw IllegalArgumentException("Unknown class name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
package fr.free.nrw.commons.profile.achievements
|
package fr.free.nrw.commons.profile.achievements
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.DisplayMetrics
|
import android.view.ContextThemeWrapper
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.ViewTreeObserver
|
import android.view.ViewTreeObserver
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.view.ContextThemeWrapper
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
|
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
|
||||||
import com.google.android.material.badge.BadgeDrawable
|
import com.google.android.material.badge.BadgeDrawable
|
||||||
import com.google.android.material.badge.BadgeUtils
|
import com.google.android.material.badge.BadgeUtils
|
||||||
|
|
@ -22,21 +23,20 @@ import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
|
||||||
import fr.free.nrw.commons.kvstore.BasicKvStore
|
import fr.free.nrw.commons.kvstore.BasicKvStore
|
||||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
|
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
|
||||||
import fr.free.nrw.commons.profile.ProfileActivity
|
import fr.free.nrw.commons.profile.ProfileActivity
|
||||||
import fr.free.nrw.commons.profile.achievements.LevelController.LevelInfo.Companion.from
|
|
||||||
import fr.free.nrw.commons.utils.ConfigUtils.isBetaFlavour
|
import fr.free.nrw.commons.utils.ConfigUtils.isBetaFlavour
|
||||||
import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog
|
import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog
|
||||||
import fr.free.nrw.commons.utils.ViewUtil.showDismissibleSnackBar
|
import fr.free.nrw.commons.utils.ViewUtil.showDismissibleSnackBar
|
||||||
import fr.free.nrw.commons.utils.ViewUtil.showLongToast
|
import fr.free.nrw.commons.utils.ViewUtil.showLongToast
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import io.reactivex.schedulers.Schedulers
|
import kotlinx.coroutines.launch
|
||||||
import org.apache.commons.lang3.StringUtils
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.Objects
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class AchievementsFragment : CommonsDaggerSupportFragment(){
|
class AchievementsFragment : CommonsDaggerSupportFragment(){
|
||||||
private lateinit var levelInfo: LevelController.LevelInfo
|
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var viewModelFactory: AchievementViewModelFactory
|
||||||
|
lateinit var viewModel: AchievementViewModel
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var sessionManager: SessionManager
|
lateinit var sessionManager: SessionManager
|
||||||
|
|
||||||
|
|
@ -45,11 +45,8 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){
|
||||||
|
|
||||||
private var _binding: FragmentAchievementsBinding? = null
|
private var _binding: FragmentAchievementsBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
// To keep track of the number of wiki edits made by a user
|
|
||||||
private var numberOfEdits: Int = 0
|
|
||||||
|
|
||||||
private var userName: String? = null
|
private var userName: String? = null
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
arguments?.let {
|
arguments?.let {
|
||||||
|
|
@ -64,6 +61,8 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){
|
||||||
): View {
|
): View {
|
||||||
_binding = FragmentAchievementsBinding.inflate(inflater, container, false)
|
_binding = FragmentAchievementsBinding.inflate(inflater, container, false)
|
||||||
|
|
||||||
|
viewModel = ViewModelProvider(
|
||||||
|
this@AchievementsFragment, viewModelFactory)[AchievementViewModel::class.java]
|
||||||
binding.achievementInfo.setOnClickListener { showInfoDialog() }
|
binding.achievementInfo.setOnClickListener { showInfoDialog() }
|
||||||
binding.imagesUploadInfoIcon.setOnClickListener { showUploadInfo() }
|
binding.imagesUploadInfoIcon.setOnClickListener { showUploadInfo() }
|
||||||
binding.imagesRevertedInfoIcon.setOnClickListener { showRevertedInfo() }
|
binding.imagesRevertedInfoIcon.setOnClickListener { showRevertedInfo() }
|
||||||
|
|
@ -73,19 +72,15 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){
|
||||||
binding.thanksImageIcon.setOnClickListener { showThanksReceivedInfo() }
|
binding.thanksImageIcon.setOnClickListener { showThanksReceivedInfo() }
|
||||||
binding.qualityImageIcon.setOnClickListener { showQualityImagesInfo() }
|
binding.qualityImageIcon.setOnClickListener { showQualityImagesInfo() }
|
||||||
|
|
||||||
// DisplayMetrics used to fetch the size of the screen
|
lifecycleScope.launch {
|
||||||
val displayMetrics = DisplayMetrics()
|
viewModel.loading.collectLatest {
|
||||||
requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics)
|
if (it){
|
||||||
val height = displayMetrics.heightPixels
|
|
||||||
val width = displayMetrics.widthPixels
|
|
||||||
|
|
||||||
// Used for the setting the size of imageView at runtime
|
|
||||||
// TODO REMOVE
|
|
||||||
val params = binding.achievementBadgeImage.layoutParams as ConstraintLayout.LayoutParams
|
|
||||||
params.height = (height * BADGE_IMAGE_HEIGHT_RATIO).toInt()
|
|
||||||
params.width = (width * BADGE_IMAGE_WIDTH_RATIO).toInt()
|
|
||||||
binding.achievementBadgeImage.requestLayout()
|
|
||||||
binding.progressBar.visibility = View.VISIBLE
|
binding.progressBar.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
binding.progressBar.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
if (sessionManager.userName == null || sessionManager.userName == userName) {
|
if (sessionManager.userName == null || sessionManager.userName == userName) {
|
||||||
|
|
@ -100,8 +95,6 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
setWikidataEditCount()
|
|
||||||
setAchievements()
|
setAchievements()
|
||||||
return binding.root
|
return binding.root
|
||||||
|
|
||||||
|
|
@ -145,72 +138,56 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){
|
||||||
* which then calls parseJson when results are fetched
|
* which then calls parseJson when results are fetched
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
private fun setAchievements() {
|
private fun setAchievements() {
|
||||||
binding.progressBar.visibility = View.VISIBLE
|
|
||||||
if (checkAccount()) {
|
if (checkAccount()) {
|
||||||
try {
|
viewModel.getUserAchievements(username = userName.toString())
|
||||||
compositeDisposable.add(
|
|
||||||
okHttpJsonApiClient
|
|
||||||
.getAchievements(userName ?: return)
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(
|
|
||||||
{ response ->
|
|
||||||
if (response != null) {
|
|
||||||
setUploadCount(Achievements.from(response))
|
|
||||||
} else {
|
|
||||||
Timber.d("Success")
|
|
||||||
// TODO Create a Method to Hide all the Statistics
|
|
||||||
// binding.layoutImageReverts.visibility = View.INVISIBLE
|
|
||||||
// binding.achievementBadgeImage.visibility = View.INVISIBLE
|
|
||||||
// If the number of edits made by the user are more than 150,000
|
|
||||||
// in some cases such high number of wiki edit counts cause the
|
|
||||||
// achievements calculator to fail in some cases, for more details
|
|
||||||
// refer Issue: #3295
|
|
||||||
if (numberOfEdits <= 150_000) {
|
|
||||||
showSnackBarWithRetry(false)
|
|
||||||
} else {
|
|
||||||
showSnackBarWithRetry(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ throwable ->
|
|
||||||
Timber.e(throwable, "Fetching achievements statistics failed")
|
|
||||||
if (numberOfEdits <= 150_000) {
|
|
||||||
showSnackBarWithRetry(false)
|
|
||||||
} else {
|
|
||||||
showSnackBarWithRetry(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Timber.d("Exception: ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
lifecycleScope.launch {
|
||||||
* To call the API to fetch the count of wiki data edits
|
viewModel.achievements.collect{
|
||||||
* in the form of JavaRx Single object<JSONobject>
|
|
||||||
</JSONobject> */
|
|
||||||
|
|
||||||
private fun setWikidataEditCount() {
|
binding.achievementLevel.text = getString(R.string.level,it.level.levelNumber)
|
||||||
if (StringUtils.isBlank(userName)) {
|
val store = BasicKvStore(requireContext(), userName)
|
||||||
return
|
store.putString("userAchievementsLevel", it.level.levelNumber.toString())
|
||||||
}
|
|
||||||
compositeDisposable.add(
|
binding.achievementBadgeImage.setImageDrawable(
|
||||||
okHttpJsonApiClient
|
VectorDrawableCompat.create(
|
||||||
.getWikidataEdits(userName)
|
resources, R.drawable.badge,
|
||||||
.subscribeOn(Schedulers.io())
|
ContextThemeWrapper(activity, it.level.levelStyle).theme
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe({ edits: Int ->
|
|
||||||
numberOfEdits = edits
|
|
||||||
showBadgesWithCount(view = binding.wikidataEditsIcon, count = edits)
|
|
||||||
}, { e: Throwable ->
|
|
||||||
Timber.e("Error:$e")
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
binding.achievementBadgeText.text = it.level.levelNumber.toString()
|
||||||
|
|
||||||
|
// TODO(use String Format)
|
||||||
|
binding.imageUploadedTVCount.text =
|
||||||
|
it.imagesUploadedCount.toString() + "/" + it.level.maxUploadCount
|
||||||
|
binding.imagesUploadedProgressbar.progress =
|
||||||
|
100 * it.imagesUploadedCount / it.level.maxUploadCount
|
||||||
|
|
||||||
|
// Revert
|
||||||
|
binding.imageRevertTVCount.text = it.revertedCount.toString() + "%"
|
||||||
|
binding.imageRevertsProgressbar.progress = it.revertedCount
|
||||||
|
binding.imagesRevertLimitText.text =
|
||||||
|
resources.getString(R.string.achievements_revert_limit_message) + it.level.minNonRevertPercentage + "%"
|
||||||
|
|
||||||
|
// Images Used
|
||||||
|
binding.imagesUsedProgressbar.progress = (100 * it.uniqueImagesCount) / it.level.maxUniqueImages
|
||||||
|
binding.imagesUsedCount.text = (it.uniqueImagesCount.toString() + "/"
|
||||||
|
+ it.level.maxUniqueImages)
|
||||||
|
|
||||||
|
// Thanks Received Badge
|
||||||
|
showBadgesWithCount(view = binding.thanksImageIcon, count = it.thanksReceivedCount)
|
||||||
|
|
||||||
|
// Featured Images Badge
|
||||||
|
showBadgesWithCount(view = binding.featuredImageIcon, count = it.featuredImagesCount)
|
||||||
|
|
||||||
|
// Quality Images Badge
|
||||||
|
showBadgesWithCount(view = binding.qualityImageIcon, count = it.qualityImagesCount)
|
||||||
|
|
||||||
|
showBadgesWithCount(view = binding.wikidataEditsIcon, count = it.imagesEditedBySomeoneElseCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -253,49 +230,6 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){
|
||||||
binding.progressBar.visibility = View.GONE
|
binding.progressBar.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* used to the count of images uploaded by user
|
|
||||||
*/
|
|
||||||
|
|
||||||
private fun setUploadCount(achievements: Achievements) {
|
|
||||||
if (checkAccount()) {
|
|
||||||
compositeDisposable.add(okHttpJsonApiClient
|
|
||||||
.getUploadCount(Objects.requireNonNull<String>(userName))
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(
|
|
||||||
{ uploadCount: Int? ->
|
|
||||||
setAchievementsUploadCount(
|
|
||||||
achievements,
|
|
||||||
uploadCount ?:0
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{ t: Throwable? ->
|
|
||||||
Timber.e(t, "Fetching upload count failed")
|
|
||||||
onError()
|
|
||||||
}
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* used to set achievements upload count and call hideProgressbar
|
|
||||||
* @param uploadCount
|
|
||||||
*/
|
|
||||||
private fun setAchievementsUploadCount(achievements: Achievements, uploadCount: Int) {
|
|
||||||
// Create a new instance of Achievements with updated imagesUploaded
|
|
||||||
val updatedAchievements = Achievements(
|
|
||||||
achievements.uniqueUsedImages,
|
|
||||||
achievements.articlesUsingImages,
|
|
||||||
achievements.thanksReceived,
|
|
||||||
achievements.featuredImages,
|
|
||||||
achievements.qualityImages,
|
|
||||||
uploadCount, // Update imagesUploaded with new value
|
|
||||||
achievements.revertCount
|
|
||||||
)
|
|
||||||
|
|
||||||
hideProgressBar(updatedAchievements)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* used to the uploaded images progressbar
|
* used to the uploaded images progressbar
|
||||||
|
|
@ -306,9 +240,6 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){
|
||||||
setZeroAchievements()
|
setZeroAchievements()
|
||||||
} else {
|
} else {
|
||||||
binding.imagesUploadedProgressbar.visibility = View.VISIBLE
|
binding.imagesUploadedProgressbar.visibility = View.VISIBLE
|
||||||
binding.imagesUploadedProgressbar.progress =
|
|
||||||
100 * uploadCount / levelInfo.maxUploadCount
|
|
||||||
binding.imageUploadedTVCount.text = uploadCount.toString() + "/" + levelInfo.maxUploadCount
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -325,7 +256,7 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){
|
||||||
getString(R.string.ok),
|
getString(R.string.ok),
|
||||||
{}
|
{}
|
||||||
)
|
)
|
||||||
|
binding.layout.visibility = View.INVISIBLE
|
||||||
// binding.imagesUploadedProgressbar.setVisibility(View.INVISIBLE);
|
// binding.imagesUploadedProgressbar.setVisibility(View.INVISIBLE);
|
||||||
// binding.imageRevertsProgressbar.setVisibility(View.INVISIBLE);
|
// binding.imageRevertsProgressbar.setVisibility(View.INVISIBLE);
|
||||||
// binding.imagesUsedByWikiProgressBar.setVisibility(View.INVISIBLE);
|
// binding.imagesUsedByWikiProgressBar.setVisibility(View.INVISIBLE);
|
||||||
|
|
@ -335,52 +266,6 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){
|
||||||
binding.imagesUploadTextParam.setText(R.string.no_image_uploaded)
|
binding.imagesUploadTextParam.setText(R.string.no_image_uploaded)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* used to set the non revert image percentage
|
|
||||||
* @param notRevertPercentage
|
|
||||||
*/
|
|
||||||
private fun setImageRevertPercentage(notRevertPercentage: Int) {
|
|
||||||
binding.imageRevertsProgressbar.visibility = View.VISIBLE
|
|
||||||
binding.imageRevertsProgressbar.progress = notRevertPercentage
|
|
||||||
val revertPercentage = notRevertPercentage.toString()
|
|
||||||
binding.imageRevertTVCount.text = "$revertPercentage%"
|
|
||||||
binding.imagesRevertLimitText.text =
|
|
||||||
resources.getString(R.string.achievements_revert_limit_message) + levelInfo.minNonRevertPercentage + "%"
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used the inflate the fetched statistics of the images uploaded by user
|
|
||||||
* and assign badge and level. Also stores the achievements level of the user in BasicKvStore to display in menu
|
|
||||||
* @param achievements
|
|
||||||
*/
|
|
||||||
private fun inflateAchievements(achievements: Achievements) {
|
|
||||||
|
|
||||||
// Thanks Received Badge
|
|
||||||
showBadgesWithCount(view = binding.thanksImageIcon, count = achievements.thanksReceived)
|
|
||||||
|
|
||||||
// Featured Images Badge
|
|
||||||
showBadgesWithCount(view = binding.featuredImageIcon, count = achievements.featuredImages)
|
|
||||||
|
|
||||||
// Quality Images Badge
|
|
||||||
showBadgesWithCount(view = binding.qualityImageIcon, count = achievements.qualityImages)
|
|
||||||
|
|
||||||
binding.imagesUsedByWikiProgressBar.progress =
|
|
||||||
100 * achievements.uniqueUsedImages / levelInfo.maxUniqueImages
|
|
||||||
binding.imagesUsedCount.text = (achievements.uniqueUsedImages.toString() + "/"
|
|
||||||
+ levelInfo.maxUniqueImages)
|
|
||||||
|
|
||||||
binding.achievementLevel.text = getString(R.string.level,levelInfo.levelNumber)
|
|
||||||
binding.achievementBadgeImage.setImageDrawable(
|
|
||||||
VectorDrawableCompat.create(
|
|
||||||
resources, R.drawable.badge,
|
|
||||||
ContextThemeWrapper(activity, levelInfo.levelStyle).theme
|
|
||||||
)
|
|
||||||
)
|
|
||||||
binding.achievementBadgeText.text = levelInfo.levelNumber.toString()
|
|
||||||
val store = BasicKvStore(requireContext(), userName)
|
|
||||||
store.putString("userAchievementsLevel", levelInfo.levelNumber.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function is used to show badge on any view (button, imageView, etc)
|
* This function is used to show badge on any view (button, imageView, etc)
|
||||||
* @param view The View on which the badge will be displayed eg (button, imageView, etc)
|
* @param view The View on which the badge will be displayed eg (button, imageView, etc)
|
||||||
|
|
@ -425,22 +310,6 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* to hide progressbar
|
|
||||||
*/
|
|
||||||
private fun hideProgressBar(achievements: Achievements) {
|
|
||||||
if (binding.progressBar != null) {
|
|
||||||
levelInfo = from(
|
|
||||||
achievements.imagesUploaded,
|
|
||||||
achievements.uniqueUsedImages,
|
|
||||||
achievements.notRevertPercentage
|
|
||||||
)
|
|
||||||
inflateAchievements(achievements)
|
|
||||||
setUploadProgress(achievements.imagesUploaded)
|
|
||||||
setImageRevertPercentage(achievements.notRevertPercentage)
|
|
||||||
binding.progressBar.visibility = View.GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun showUploadInfo() {
|
fun showUploadInfo() {
|
||||||
launchAlertWithHelpLink(
|
launchAlertWithHelpLink(
|
||||||
|
|
@ -546,9 +415,6 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){
|
||||||
|
|
||||||
|
|
||||||
companion object{
|
companion object{
|
||||||
private const val BADGE_IMAGE_WIDTH_RATIO = 0.4
|
|
||||||
private const val BADGE_IMAGE_HEIGHT_RATIO = 0.3
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Help link URLs
|
* Help link URLs
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package fr.free.nrw.commons.profile.model
|
||||||
|
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class AchievementResponse(
|
||||||
|
@SerializedName("articlesUsingImages")
|
||||||
|
val articlesUsingImages: Int,
|
||||||
|
@SerializedName("database")
|
||||||
|
val database: String,
|
||||||
|
@SerializedName("deletedUploads")
|
||||||
|
val deletedUploads: Int,
|
||||||
|
@SerializedName("featuredImages")
|
||||||
|
val featuredImages: FeaturedImages,
|
||||||
|
@SerializedName("imagesEditedBySomeoneElse")
|
||||||
|
val imagesEditedBySomeoneElse: Int,
|
||||||
|
@SerializedName("labs")
|
||||||
|
val labs: Boolean,
|
||||||
|
@SerializedName("status")
|
||||||
|
val status: String,
|
||||||
|
@SerializedName("thanksReceived")
|
||||||
|
val thanksReceived: Int,
|
||||||
|
@SerializedName("uniqueUsedImages")
|
||||||
|
val uniqueUsedImages: Int,
|
||||||
|
@SerializedName("user")
|
||||||
|
val user: String
|
||||||
|
)
|
||||||
|
|
||||||
|
data class FeaturedImages(
|
||||||
|
@SerializedName("Featured_pictures_on_Wikimedia_Commons")
|
||||||
|
val featuredPicturesOnWikimediaCommons: Int,
|
||||||
|
@SerializedName("Quality_images")
|
||||||
|
val qualityImages: Int
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package fr.free.nrw.commons.profile.model
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.profile.achievements.LevelController
|
||||||
|
|
||||||
|
data class UserAchievements(
|
||||||
|
val level: LevelController.LevelInfo,
|
||||||
|
val articlesUsingImagesCount: Int = 0,
|
||||||
|
val thanksReceivedCount: Int = 0,
|
||||||
|
val featuredImagesCount: Int = 0,
|
||||||
|
val qualityImagesCount: Int = 0,
|
||||||
|
val imagesUploadedCount: Int = 0,
|
||||||
|
val revertedCount: Int = 0,
|
||||||
|
val uniqueImagesCount: Int = 0,
|
||||||
|
val imagesEditedBySomeoneElseCount: Int = 0
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
package fr.free.nrw.commons.repository
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.network.APIService
|
||||||
|
import fr.free.nrw.commons.profile.achievements.LevelController
|
||||||
|
import fr.free.nrw.commons.profile.model.UserAchievements
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class ProfileRepository @Inject constructor(private val apiService: APIService) {
|
||||||
|
|
||||||
|
fun getUserLevel(username: String) : Flow<UserAchievements> = flow {
|
||||||
|
try {
|
||||||
|
val uploadCountResponse = apiService.getImageUploadCount(username)
|
||||||
|
val imagesUploaded = uploadCountResponse.body() ?:0
|
||||||
|
|
||||||
|
val achievementResponse = apiService.getUserAchievements(username)
|
||||||
|
|
||||||
|
val uniqueImages = achievementResponse.body()?.uniqueUsedImages ?: 0
|
||||||
|
val articlesUsingImages = achievementResponse.body()?.articlesUsingImages ?: 0
|
||||||
|
val thanksReceived = achievementResponse.body()?.thanksReceived ?: 0
|
||||||
|
val featuredImages = achievementResponse.body()?.featuredImages?.featuredPicturesOnWikimediaCommons ?:0
|
||||||
|
val qualityImages = achievementResponse.body()?.featuredImages?.qualityImages ?: 0
|
||||||
|
val deletedUploads = achievementResponse.body()?.deletedUploads ?:0
|
||||||
|
val revertCount = (imagesUploaded - deletedUploads) * 100 / imagesUploaded
|
||||||
|
val imagesEditedBySomeoneElse = achievementResponse.body()?.imagesEditedBySomeoneElse ?:0
|
||||||
|
|
||||||
|
val level = LevelController.LevelInfo.from(
|
||||||
|
imagesUploaded = imagesUploaded,
|
||||||
|
uniqueImagesUsed = uniqueImages,
|
||||||
|
nonRevertRate = revertCount)
|
||||||
|
|
||||||
|
emit(
|
||||||
|
UserAchievements(
|
||||||
|
level = level,
|
||||||
|
articlesUsingImagesCount = articlesUsingImages,
|
||||||
|
featuredImagesCount = featuredImages,
|
||||||
|
imagesUploadedCount = imagesUploaded,
|
||||||
|
qualityImagesCount = qualityImages,
|
||||||
|
revertedCount = revertCount,
|
||||||
|
thanksReceivedCount = thanksReceived,
|
||||||
|
uniqueImagesCount = uniqueImages,
|
||||||
|
imagesEditedBySomeoneElseCount = imagesEditedBySomeoneElse
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
catch(e : Exception) {
|
||||||
|
Timber.e(e.printStackTrace().toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -43,7 +43,7 @@
|
||||||
android:id="@+id/achievement_badge_image"
|
android:id="@+id/achievement_badge_image"
|
||||||
android:layout_width="150dp"
|
android:layout_width="150dp"
|
||||||
android:layout_height="150dp"
|
android:layout_height="150dp"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="100dp"
|
||||||
android:background="@drawable/badge"
|
android:background="@drawable/badge"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
|
@ -263,7 +263,7 @@
|
||||||
app:layout_constraintTop_toBottomOf="@+id/images_used_tv">
|
app:layout_constraintTop_toBottomOf="@+id/images_used_tv">
|
||||||
|
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
android:id="@+id/images_used_by_wiki_progress_bar"
|
android:id="@+id/images_used_progressbar"
|
||||||
style="?android:attr/progressBarStyleHorizontal"
|
style="?android:attr/progressBarStyleHorizontal"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|
@ -362,7 +362,7 @@
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintVertical_bias="0.5"
|
app:layout_constraintVertical_bias="0.5"
|
||||||
app:layout_constraintHorizontal_bias="0.5"
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
android:visibility="gone"/>
|
/>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue