From 614892c5cb213b5e901417bb77d02dcfb52f0e85 Mon Sep 17 00:00:00 2001 From: Neel Doshi Date: Thu, 26 Dec 2024 20:21:52 +0530 Subject: [PATCH 1/2] Migrated from Rxjava to Retroift, added MVVM Architecture. --- app/build.gradle | 17 +- .../free/nrw/commons/di/NetworkingModule.kt | 29 ++ .../fr/free/nrw/commons/network/APIService.kt | 23 ++ .../achievements/AchievementViewModel.kt | 41 +++ .../AchievementViewModelFactory.kt | 23 ++ .../achievements/AchievementsFragment.kt | 264 +++++------------- .../profile/model/AchievementResponse.kt | 34 +++ .../commons/profile/model/UserAchievements.kt | 15 + .../commons/repository/ProfileRepository.kt | 53 ++++ .../main/res/layout/fragment_achievements.xml | 6 +- 10 files changed, 302 insertions(+), 203 deletions(-) create mode 100644 app/src/main/java/fr/free/nrw/commons/network/APIService.kt create mode 100644 app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementViewModel.kt create mode 100644 app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementViewModelFactory.kt create mode 100644 app/src/main/java/fr/free/nrw/commons/profile/model/AchievementResponse.kt create mode 100644 app/src/main/java/fr/free/nrw/commons/profile/model/UserAchievements.kt create mode 100644 app/src/main/java/fr/free/nrw/commons/repository/ProfileRepository.kt diff --git a/app/build.gradle b/app/build.gradle index 14bf5f3b7d..4927dbc350 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -51,11 +51,26 @@ dependencies { implementation 'com.karumi:dexter:5.0.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 def composeBom = platform('androidx.compose:compose-bom:2024.11.00') implementation "androidx.activity:activity-compose:1.9.3" - implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.8.4" implementation (composeBom) implementation "androidx.compose.runtime:runtime" implementation "androidx.compose.ui:ui" diff --git a/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.kt b/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.kt index 0e9d834784..8a074f67ae 100644 --- a/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.kt +++ b/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.kt @@ -25,6 +25,7 @@ import fr.free.nrw.commons.media.PageMediaInterface import fr.free.nrw.commons.media.WikidataMediaInterface import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient 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.review.ReviewInterface import fr.free.nrw.commons.upload.UploadInterface @@ -42,6 +43,8 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor.Level +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory import timber.log.Timber import java.io.File import java.util.concurrent.TimeUnit @@ -295,6 +298,32 @@ class NetworkingModule { fun provideLanguageWikipediaSite(): WikiSite = 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 { private const val WIKIDATA_SPARQL_QUERY_URL = "https://query.wikidata.org/sparql" private const val TOOLS_FORGE_URL = diff --git a/app/src/main/java/fr/free/nrw/commons/network/APIService.kt b/app/src/main/java/fr/free/nrw/commons/network/APIService.kt new file mode 100644 index 0000000000..ba66855fd5 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/network/APIService.kt @@ -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 + + + // 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 +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementViewModel.kt b/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementViewModel.kt new file mode 100644 index 0000000000..4d26fc2c43 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementViewModel.kt @@ -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 = _achievements + + private val _loading = MutableStateFlow(true) + val loading : StateFlow = _loading + + fun getUserAchievements(username: String){ + viewModelScope.launch { + repository.getUserLevel(username = username).collect { + _loading.value = false + _achievements.value = it + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementViewModelFactory.kt b/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementViewModelFactory.kt new file mode 100644 index 0000000000..d9c936a093 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementViewModelFactory.kt @@ -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 +): ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(AchievementViewModel::class.java)) { + (@Suppress("UNCHECKED_CAST") + return viewModelProvider.get() as T) + } else { + throw IllegalArgumentException("Unknown class name") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementsFragment.kt b/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementsFragment.kt index af07423eba..f0369799af 100644 --- a/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementsFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementsFragment.kt @@ -1,15 +1,16 @@ package fr.free.nrw.commons.profile.achievements +import android.annotation.SuppressLint import android.net.Uri import android.os.Bundle -import android.util.DisplayMetrics +import android.view.ContextThemeWrapper import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.ViewTreeObserver import android.widget.Toast -import androidx.appcompat.view.ContextThemeWrapper -import androidx.constraintlayout.widget.ConstraintLayout +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat import com.google.android.material.badge.BadgeDrawable 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.mwapi.OkHttpJsonApiClient 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.DialogUtil.showAlertDialog import fr.free.nrw.commons.utils.ViewUtil.showDismissibleSnackBar import fr.free.nrw.commons.utils.ViewUtil.showLongToast -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.schedulers.Schedulers -import org.apache.commons.lang3.StringUtils +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch import timber.log.Timber -import java.util.Objects import javax.inject.Inject class AchievementsFragment : CommonsDaggerSupportFragment(){ - private lateinit var levelInfo: LevelController.LevelInfo + @Inject + lateinit var viewModelFactory: AchievementViewModelFactory + lateinit var viewModel: AchievementViewModel @Inject lateinit var sessionManager: SessionManager @@ -45,11 +45,8 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){ private var _binding: FragmentAchievementsBinding? = null 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 - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments?.let { @@ -64,6 +61,8 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){ ): View { _binding = FragmentAchievementsBinding.inflate(inflater, container, false) + viewModel = ViewModelProvider( + this@AchievementsFragment, viewModelFactory)[AchievementViewModel::class.java] binding.achievementInfo.setOnClickListener { showInfoDialog() } binding.imagesUploadInfoIcon.setOnClickListener { showUploadInfo() } binding.imagesRevertedInfoIcon.setOnClickListener { showRevertedInfo() } @@ -73,19 +72,15 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){ binding.thanksImageIcon.setOnClickListener { showThanksReceivedInfo() } binding.qualityImageIcon.setOnClickListener { showQualityImagesInfo() } - // DisplayMetrics used to fetch the size of the screen - val displayMetrics = DisplayMetrics() - requireActivity().windowManager.defaultDisplay.getMetrics(displayMetrics) - 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 + lifecycleScope.launch { + viewModel.loading.collectLatest { + if (it){ + binding.progressBar.visibility = View.VISIBLE + } else { + binding.progressBar.visibility = View.GONE + } + } + } setHasOptionsMenu(true) if (sessionManager.userName == null || sessionManager.userName == userName) { @@ -100,8 +95,6 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){ return binding.root } - - setWikidataEditCount() setAchievements() return binding.root @@ -145,72 +138,56 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){ * which then calls parseJson when results are fetched */ + @SuppressLint("SetTextI18n") private fun setAchievements() { - binding.progressBar.visibility = View.VISIBLE if (checkAccount()) { - try { - 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) - } - } + viewModel.getUserAchievements(username = userName.toString()) + + lifecycleScope.launch { + viewModel.achievements.collect{ + + binding.achievementLevel.text = getString(R.string.level,it.level.levelNumber) + val store = BasicKvStore(requireContext(), userName) + store.putString("userAchievementsLevel", it.level.levelNumber.toString()) + + binding.achievementBadgeImage.setImageDrawable( + VectorDrawableCompat.create( + resources, R.drawable.badge, + ContextThemeWrapper(activity, it.level.levelStyle).theme ) - ) - } catch (e: Exception) { - Timber.d("Exception: ${e.message}") - } - } - } + ) + binding.achievementBadgeText.text = it.level.levelNumber.toString() - /** - * To call the API to fetch the count of wiki data edits - * in the form of JavaRx Single object - */ + // TODO(use String Format) + binding.imageUploadedTVCount.text = + it.imagesUploadedCount.toString() + "/" + it.level.maxUploadCount + binding.imagesUploadedProgressbar.progress = + 100 * it.imagesUploadedCount / it.level.maxUploadCount - private fun setWikidataEditCount() { - if (StringUtils.isBlank(userName)) { - return + // 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) + } + } } - compositeDisposable.add( - okHttpJsonApiClient - .getWikidataEdits(userName) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ edits: Int -> - numberOfEdits = edits - showBadgesWithCount(view = binding.wikidataEditsIcon, count = edits) - }, { e: Throwable -> - Timber.e("Error:$e") - }) - ) } /** @@ -253,49 +230,6 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){ 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(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 @@ -306,9 +240,6 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){ setZeroAchievements() } else { 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), {} ) - + binding.layout.visibility = View.INVISIBLE // binding.imagesUploadedProgressbar.setVisibility(View.INVISIBLE); // binding.imageRevertsProgressbar.setVisibility(View.INVISIBLE); // binding.imagesUsedByWikiProgressBar.setVisibility(View.INVISIBLE); @@ -335,52 +266,6 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){ 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) * @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() { launchAlertWithHelpLink( @@ -546,9 +415,6 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){ companion object{ - private const val BADGE_IMAGE_WIDTH_RATIO = 0.4 - private const val BADGE_IMAGE_HEIGHT_RATIO = 0.3 - /** * Help link URLs */ diff --git a/app/src/main/java/fr/free/nrw/commons/profile/model/AchievementResponse.kt b/app/src/main/java/fr/free/nrw/commons/profile/model/AchievementResponse.kt new file mode 100644 index 0000000000..b44b7d16ca --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/profile/model/AchievementResponse.kt @@ -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 +) \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/profile/model/UserAchievements.kt b/app/src/main/java/fr/free/nrw/commons/profile/model/UserAchievements.kt new file mode 100644 index 0000000000..b13b4d3822 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/profile/model/UserAchievements.kt @@ -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 +) \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/repository/ProfileRepository.kt b/app/src/main/java/fr/free/nrw/commons/repository/ProfileRepository.kt new file mode 100644 index 0000000000..a441464fa5 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/repository/ProfileRepository.kt @@ -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 = 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()) + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_achievements.xml b/app/src/main/res/layout/fragment_achievements.xml index 00c18b3232..b199a23de1 100644 --- a/app/src/main/res/layout/fragment_achievements.xml +++ b/app/src/main/res/layout/fragment_achievements.xml @@ -43,7 +43,7 @@ android:id="@+id/achievement_badge_image" android:layout_width="150dp" android:layout_height="150dp" - android:layout_marginTop="16dp" + android:layout_marginTop="100dp" android:background="@drawable/badge" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" @@ -263,7 +263,7 @@ app:layout_constraintTop_toBottomOf="@+id/images_used_tv"> + /> \ No newline at end of file From ffae85b18cfa005e2b0ce2b78d46d862e50f0262 Mon Sep 17 00:00:00 2001 From: Neel Doshi Date: Thu, 26 Dec 2024 21:00:02 +0530 Subject: [PATCH 2/2] Test : Removed the test which are longer used. --- .../AchievementsFragmentUnitTests.kt | 54 +------------------ 1 file changed, 1 insertion(+), 53 deletions(-) diff --git a/app/src/test/kotlin/fr/free/nrw/commons/profile/achievements/AchievementsFragmentUnitTests.kt b/app/src/test/kotlin/fr/free/nrw/commons/profile/achievements/AchievementsFragmentUnitTests.kt index 4c2fbf52cf..789f4d8b28 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/profile/achievements/AchievementsFragmentUnitTests.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/profile/achievements/AchievementsFragmentUnitTests.kt @@ -173,33 +173,6 @@ class AchievementsFragmentUnitTests { method.invoke(fragment, "", "") } - @Test - @Throws(Exception::class) - fun testHideProgressBar() { - Shadows.shadowOf(Looper.getMainLooper()).idle() - val method: Method = - AchievementsFragment::class.java.getDeclaredMethod( - "hideProgressBar", - Achievements::class.java, - ) - method.isAccessible = true - method.invoke(fragment, achievements) - } - - @Test - @Throws(Exception::class) - fun testSetAchievementsUploadCount() { - Shadows.shadowOf(Looper.getMainLooper()).idle() - val method: Method = - AchievementsFragment::class.java.getDeclaredMethod( - "setAchievementsUploadCount", - Achievements::class.java, - Int::class.java, - ) - method.isAccessible = true - method.invoke(fragment, achievements, 0) - } - @Test @Throws(Exception::class) fun testCheckAccount() { @@ -211,20 +184,7 @@ class AchievementsFragmentUnitTests { method.isAccessible = true method.invoke(fragment) } - - @Test - @Throws(Exception::class) - fun testSetUploadCount() { - Shadows.shadowOf(Looper.getMainLooper()).idle() - val method: Method = - AchievementsFragment::class.java.getDeclaredMethod( - "setUploadCount", - Achievements::class.java, - ) - method.isAccessible = true - method.invoke(fragment, achievements) - } - + @Test @Throws(Exception::class) fun testOnError() { @@ -263,18 +223,6 @@ class AchievementsFragmentUnitTests { method.invoke(fragment, false) } - @Test - @Throws(Exception::class) - fun testSetWikidataEditCount() { - Shadows.shadowOf(Looper.getMainLooper()).idle() - val method: Method = - AchievementsFragment::class.java.getDeclaredMethod( - "setWikidataEditCount", - ) - method.isAccessible = true - method.invoke(fragment) - } - @Test @Throws(Exception::class) fun testSetAchievements() {