mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
Convert profile package to kotlin (#5979)
* Convert ViewModelFactory to kotlin
* Convert UpdateAvatarResponse and related test to Kotlin
* Convert LeaderboardResponse and related test to kotlin
* Convert LeaderboardListAdapter to kotlin
* Convert UserDetailAdapter to kotlin
* Convert LeaderboardListViewModel to kotlin
* Convert DataSourceClass to kotlin
* Convert the LeaderboardFragment to kotlin
* Converted AchievementsFragment to kotlin
* Revert "Converted AchievementsFragment to kotlin"
This reverts commit 4fcbb81e5d.
---------
Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
This commit is contained in:
parent
8265cc6306
commit
33548fa57d
26 changed files with 1042 additions and 1694 deletions
|
|
@ -1,125 +0,0 @@
|
|||
package fr.free.nrw.commons.profile.leaderboard;
|
||||
|
||||
import static fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.LOADED;
|
||||
import static fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.LOADING;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.paging.PageKeyedDataSource;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import java.util.Objects;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* This class will call the leaderboard API to get new list when the pagination is performed
|
||||
*/
|
||||
public class DataSourceClass extends PageKeyedDataSource<Integer, LeaderboardList> {
|
||||
|
||||
private OkHttpJsonApiClient okHttpJsonApiClient;
|
||||
private SessionManager sessionManager;
|
||||
private MutableLiveData<String> progressLiveStatus;
|
||||
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||
private String duration;
|
||||
private String category;
|
||||
private int limit;
|
||||
private int offset;
|
||||
|
||||
/**
|
||||
* Initialise the Data Source Class with API params
|
||||
* @param okHttpJsonApiClient
|
||||
* @param sessionManager
|
||||
* @param duration
|
||||
* @param category
|
||||
* @param limit
|
||||
* @param offset
|
||||
*/
|
||||
public DataSourceClass(OkHttpJsonApiClient okHttpJsonApiClient,SessionManager sessionManager,
|
||||
String duration, String category, int limit, int offset) {
|
||||
this.okHttpJsonApiClient = okHttpJsonApiClient;
|
||||
this.sessionManager = sessionManager;
|
||||
this.duration = duration;
|
||||
this.category = category;
|
||||
this.limit = limit;
|
||||
this.offset = offset;
|
||||
progressLiveStatus = new MutableLiveData<>();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the status of the list
|
||||
*/
|
||||
public MutableLiveData<String> getProgressLiveStatus() {
|
||||
return progressLiveStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the initial set of data from API
|
||||
* @param params
|
||||
* @param callback
|
||||
*/
|
||||
@Override
|
||||
public void loadInitial(@NonNull LoadInitialParams<Integer> params,
|
||||
@NonNull LoadInitialCallback<Integer, LeaderboardList> callback) {
|
||||
|
||||
compositeDisposable.add(okHttpJsonApiClient
|
||||
.getLeaderboard(Objects.requireNonNull(sessionManager.getCurrentAccount()).name,
|
||||
duration, category, String.valueOf(limit), String.valueOf(offset))
|
||||
.doOnSubscribe(disposable -> {
|
||||
compositeDisposable.add(disposable);
|
||||
progressLiveStatus.postValue(LOADING);
|
||||
}).subscribe(
|
||||
response -> {
|
||||
if (response != null && response.getStatus() == 200) {
|
||||
progressLiveStatus.postValue(LOADED);
|
||||
callback.onResult(response.getLeaderboardList(), null, response.getLimit());
|
||||
}
|
||||
},
|
||||
t -> {
|
||||
Timber.e(t, "Fetching leaderboard statistics failed");
|
||||
progressLiveStatus.postValue(LOADING);
|
||||
}
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads any data before the inital page is loaded
|
||||
* @param params
|
||||
* @param callback
|
||||
*/
|
||||
@Override
|
||||
public void loadBefore(@NonNull LoadParams<Integer> params,
|
||||
@NonNull LoadCallback<Integer, LeaderboardList> callback) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the next set of data on scrolling with offset as the limit of the last set of data
|
||||
* @param params
|
||||
* @param callback
|
||||
*/
|
||||
@Override
|
||||
public void loadAfter(@NonNull LoadParams<Integer> params,
|
||||
@NonNull LoadCallback<Integer, LeaderboardList> callback) {
|
||||
compositeDisposable.add(okHttpJsonApiClient
|
||||
.getLeaderboard(Objects.requireNonNull(sessionManager.getCurrentAccount()).name,
|
||||
duration, category, String.valueOf(limit), String.valueOf(params.key))
|
||||
.doOnSubscribe(disposable -> {
|
||||
compositeDisposable.add(disposable);
|
||||
progressLiveStatus.postValue(LOADING);
|
||||
}).subscribe(
|
||||
response -> {
|
||||
if (response != null && response.getStatus() == 200) {
|
||||
progressLiveStatus.postValue(LOADED);
|
||||
callback.onResult(response.getLeaderboardList(), params.key + limit);
|
||||
}
|
||||
},
|
||||
t -> {
|
||||
Timber.e(t, "Fetching leaderboard statistics failed");
|
||||
progressLiveStatus.postValue(LOADING);
|
||||
}
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
package fr.free.nrw.commons.profile.leaderboard
|
||||
|
||||
import android.accounts.Account
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.paging.PageKeyedDataSource
|
||||
import fr.free.nrw.commons.auth.SessionManager
|
||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
|
||||
import fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.LoadingStatus.LOADING
|
||||
import fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.LoadingStatus.LOADED
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.disposables.Disposable
|
||||
import timber.log.Timber
|
||||
import java.util.Objects
|
||||
|
||||
/**
|
||||
* This class will call the leaderboard API to get new list when the pagination is performed
|
||||
*/
|
||||
class DataSourceClass(
|
||||
private val okHttpJsonApiClient: OkHttpJsonApiClient,
|
||||
private val sessionManager: SessionManager,
|
||||
private val duration: String?,
|
||||
private val category: String?,
|
||||
private val limit: Int,
|
||||
private val offset: Int
|
||||
) : PageKeyedDataSource<Int, LeaderboardList>() {
|
||||
val progressLiveStatus: MutableLiveData<LeaderboardConstants.LoadingStatus> = MutableLiveData()
|
||||
private val compositeDisposable = CompositeDisposable()
|
||||
|
||||
|
||||
override fun loadInitial(
|
||||
params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, LeaderboardList?>
|
||||
) {
|
||||
compositeDisposable.add(okHttpJsonApiClient.getLeaderboard(
|
||||
sessionManager.currentAccount?.name,
|
||||
duration,
|
||||
category,
|
||||
limit.toString(),
|
||||
offset.toString()
|
||||
).doOnSubscribe { disposable: Disposable? ->
|
||||
compositeDisposable.add(disposable!!)
|
||||
progressLiveStatus.postValue(LOADING)
|
||||
}.subscribe({ response: LeaderboardResponse? ->
|
||||
if (response != null && response.status == 200) {
|
||||
progressLiveStatus.postValue(LOADED)
|
||||
callback.onResult(response.leaderboardList!!, null, response.limit)
|
||||
}
|
||||
}, { t: Throwable? ->
|
||||
Timber.e(t, "Fetching leaderboard statistics failed")
|
||||
progressLiveStatus.postValue(LOADING)
|
||||
}))
|
||||
}
|
||||
|
||||
override fun loadBefore(
|
||||
params: LoadParams<Int>, callback: LoadCallback<Int, LeaderboardList?>
|
||||
) = Unit
|
||||
|
||||
override fun loadAfter(
|
||||
params: LoadParams<Int>, callback: LoadCallback<Int, LeaderboardList?>
|
||||
) {
|
||||
compositeDisposable.add(okHttpJsonApiClient.getLeaderboard(
|
||||
Objects.requireNonNull<Account?>(sessionManager.currentAccount).name,
|
||||
duration,
|
||||
category,
|
||||
limit.toString(),
|
||||
params.key.toString()
|
||||
).doOnSubscribe { disposable: Disposable? ->
|
||||
compositeDisposable.add(disposable!!)
|
||||
progressLiveStatus.postValue(LOADING)
|
||||
}.subscribe({ response: LeaderboardResponse? ->
|
||||
if (response != null && response.status == 200) {
|
||||
progressLiveStatus.postValue(LOADED)
|
||||
callback.onResult(response.leaderboardList!!, params.key + limit)
|
||||
}
|
||||
}, { t: Throwable? ->
|
||||
Timber.e(t, "Fetching leaderboard statistics failed")
|
||||
progressLiveStatus.postValue(LOADING)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
package fr.free.nrw.commons.profile.leaderboard;
|
||||
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.paging.DataSource;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
|
||||
/**
|
||||
* This class will create a new instance of the data source class on pagination
|
||||
*/
|
||||
public class DataSourceFactory extends DataSource.Factory<Integer, LeaderboardList> {
|
||||
|
||||
private MutableLiveData<DataSourceClass> liveData;
|
||||
private OkHttpJsonApiClient okHttpJsonApiClient;
|
||||
private CompositeDisposable compositeDisposable;
|
||||
private SessionManager sessionManager;
|
||||
private String duration;
|
||||
private String category;
|
||||
private int limit;
|
||||
private int offset;
|
||||
|
||||
/**
|
||||
* Gets the current set leaderboard list duration
|
||||
*/
|
||||
public String getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current set leaderboard duration with the new duration
|
||||
*/
|
||||
public void setDuration(final String duration) {
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current set leaderboard list category
|
||||
*/
|
||||
public String getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current set leaderboard category with the new category
|
||||
*/
|
||||
public void setCategory(final String category) {
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current set leaderboard list limit
|
||||
*/
|
||||
public int getLimit() {
|
||||
return limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current set leaderboard limit with the new limit
|
||||
*/
|
||||
public void setLimit(final int limit) {
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current set leaderboard list offset
|
||||
*/
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current set leaderboard offset with the new offset
|
||||
*/
|
||||
public void setOffset(final int offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for DataSourceFactory class
|
||||
* @param okHttpJsonApiClient client for OKhttp
|
||||
* @param compositeDisposable composite disposable
|
||||
* @param sessionManager sessionManager
|
||||
*/
|
||||
public DataSourceFactory(OkHttpJsonApiClient okHttpJsonApiClient, CompositeDisposable compositeDisposable,
|
||||
SessionManager sessionManager) {
|
||||
this.okHttpJsonApiClient = okHttpJsonApiClient;
|
||||
this.compositeDisposable = compositeDisposable;
|
||||
this.sessionManager = sessionManager;
|
||||
liveData = new MutableLiveData<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the live data
|
||||
*/
|
||||
public MutableLiveData<DataSourceClass> getMutableLiveData() {
|
||||
return liveData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the new instance of data source class
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public DataSource<Integer, LeaderboardList> create() {
|
||||
DataSourceClass dataSourceClass = new DataSourceClass(okHttpJsonApiClient, sessionManager, duration, category, limit, offset);
|
||||
liveData.postValue(dataSourceClass);
|
||||
return dataSourceClass;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package fr.free.nrw.commons.profile.leaderboard
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.paging.DataSource
|
||||
import fr.free.nrw.commons.auth.SessionManager
|
||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
|
||||
|
||||
/**
|
||||
* This class will create a new instance of the data source class on pagination
|
||||
*/
|
||||
class DataSourceFactory(
|
||||
private val okHttpJsonApiClient: OkHttpJsonApiClient,
|
||||
private val sessionManager: SessionManager
|
||||
) : DataSource.Factory<Int, LeaderboardList>() {
|
||||
val mutableLiveData: MutableLiveData<DataSourceClass> = MutableLiveData()
|
||||
var duration: String? = null
|
||||
var category: String? = null
|
||||
var limit: Int = 0
|
||||
var offset: Int = 0
|
||||
|
||||
/**
|
||||
* Creates the new instance of data source class
|
||||
*/
|
||||
override fun create(): DataSource<Int, LeaderboardList> = DataSourceClass(
|
||||
okHttpJsonApiClient, sessionManager, duration, category, limit, offset
|
||||
).also { mutableLiveData.postValue(it) }
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
package fr.free.nrw.commons.profile.leaderboard;
|
||||
|
||||
/**
|
||||
* This class contains the constant variables for leaderboard
|
||||
*/
|
||||
public class LeaderboardConstants {
|
||||
|
||||
/**
|
||||
* This is the size of the page i.e. number items to load in a batch when pagination is performed
|
||||
*/
|
||||
public static final int PAGE_SIZE = 100;
|
||||
|
||||
/**
|
||||
* This is the starting offset, we set it to 0 to start loading from rank 1
|
||||
*/
|
||||
public static final int START_OFFSET = 0;
|
||||
|
||||
/**
|
||||
* This is the prefix of the user's homepage url, appending the username will give us complete url
|
||||
*/
|
||||
public static final String USER_LINK_PREFIX = "https://commons.wikimedia.org/wiki/User:";
|
||||
|
||||
/**
|
||||
* This is the a constant string for the state loading, when the pages are getting loaded we can
|
||||
* use this constant to identify if we need to show the progress bar or not
|
||||
*/
|
||||
public final static String LOADING = "Loading";
|
||||
|
||||
/**
|
||||
* This is the a constant string for the state loaded, when the pages are loaded we can
|
||||
* use this constant to identify if we need to show the progress bar or not
|
||||
*/
|
||||
public final static String LOADED = "Loaded";
|
||||
|
||||
/**
|
||||
* This API endpoint is to update the leaderboard avatar
|
||||
*/
|
||||
public final static String UPDATE_AVATAR_END_POINT = "/update_avatar.py";
|
||||
|
||||
/**
|
||||
* This API endpoint is to get leaderboard data
|
||||
*/
|
||||
public final static String LEADERBOARD_END_POINT = "/leaderboard.py";
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package fr.free.nrw.commons.profile.leaderboard
|
||||
|
||||
/**
|
||||
* This class contains the constant variables for leaderboard
|
||||
*/
|
||||
object LeaderboardConstants {
|
||||
/**
|
||||
* This is the size of the page i.e. number items to load in a batch when pagination is performed
|
||||
*/
|
||||
const val PAGE_SIZE: Int = 100
|
||||
|
||||
/**
|
||||
* This is the starting offset, we set it to 0 to start loading from rank 1
|
||||
*/
|
||||
const val START_OFFSET: Int = 0
|
||||
|
||||
/**
|
||||
* This is the prefix of the user's homepage url, appending the username will give us complete url
|
||||
*/
|
||||
const val USER_LINK_PREFIX: String = "https://commons.wikimedia.org/wiki/User:"
|
||||
|
||||
sealed class LoadingStatus {
|
||||
/**
|
||||
* This is the state loading, when the pages are getting loaded we can
|
||||
* use this constant to identify if we need to show the progress bar or not
|
||||
*/
|
||||
data object LOADING: LoadingStatus()
|
||||
/**
|
||||
* This is the state loaded, when the pages are loaded we can
|
||||
* use this constant to identify if we need to show the progress bar or not
|
||||
*/
|
||||
data object LOADED: LoadingStatus()
|
||||
}
|
||||
|
||||
/**
|
||||
* This API endpoint is to update the leaderboard avatar
|
||||
*/
|
||||
const val UPDATE_AVATAR_END_POINT: String = "/update_avatar.py"
|
||||
|
||||
/**
|
||||
* This API endpoint is to get leaderboard data
|
||||
*/
|
||||
const val LEADERBOARD_END_POINT: String = "/leaderboard.py"
|
||||
}
|
||||
|
|
@ -1,363 +0,0 @@
|
|||
package fr.free.nrw.commons.profile.leaderboard;
|
||||
|
||||
import static fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.LOADED;
|
||||
import static fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.LOADING;
|
||||
import static fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.PAGE_SIZE;
|
||||
import static fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.START_OFFSET;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemSelectedListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.MergeAdapter;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.databinding.FragmentLeaderboardBinding;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
|
||||
import fr.free.nrw.commons.profile.ProfileActivity;
|
||||
import fr.free.nrw.commons.utils.ConfigUtils;
|
||||
import fr.free.nrw.commons.utils.ViewUtil;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import java.util.Objects;
|
||||
import javax.inject.Inject;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* This class extends the CommonsDaggerSupportFragment and creates leaderboard fragment
|
||||
*/
|
||||
public class LeaderboardFragment extends CommonsDaggerSupportFragment {
|
||||
|
||||
|
||||
@Inject
|
||||
SessionManager sessionManager;
|
||||
|
||||
@Inject
|
||||
OkHttpJsonApiClient okHttpJsonApiClient;
|
||||
|
||||
@Inject
|
||||
ViewModelFactory viewModelFactory;
|
||||
|
||||
/**
|
||||
* View model for the paged leaderboard list
|
||||
*/
|
||||
private LeaderboardListViewModel viewModel;
|
||||
|
||||
/**
|
||||
* Composite disposable for API call
|
||||
*/
|
||||
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||
|
||||
/**
|
||||
* Duration of the leaderboard API
|
||||
*/
|
||||
private String duration;
|
||||
|
||||
/**
|
||||
* Category of the Leaderboard API
|
||||
*/
|
||||
private String category;
|
||||
|
||||
/**
|
||||
* Page size of the leaderboard API
|
||||
*/
|
||||
private int limit = PAGE_SIZE;
|
||||
|
||||
/**
|
||||
* offset for the leaderboard API
|
||||
*/
|
||||
private int offset = START_OFFSET;
|
||||
|
||||
/**
|
||||
* Set initial User Rank to 0
|
||||
*/
|
||||
private int userRank;
|
||||
|
||||
/**
|
||||
* This variable represents if user wants to scroll to his rank or not
|
||||
*/
|
||||
private boolean scrollToRank;
|
||||
|
||||
private String userName;
|
||||
|
||||
private FragmentLeaderboardBinding binding;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (getArguments() != null) {
|
||||
userName = getArguments().getString(ProfileActivity.KEY_USERNAME);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
binding = FragmentLeaderboardBinding.inflate(inflater, container, false);
|
||||
|
||||
hideLayouts();
|
||||
|
||||
// Leaderboard currently unimplemented in Beta flavor. Skip all API calls and disable menu
|
||||
if(ConfigUtils.isBetaFlavour()) {
|
||||
binding.progressBar.setVisibility(View.GONE);
|
||||
binding.scroll.setVisibility(View.GONE);
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
binding.progressBar.setVisibility(View.VISIBLE);
|
||||
setSpinners();
|
||||
|
||||
/**
|
||||
* This array is for the duration filter, we have three filters weekly, yearly and all-time
|
||||
* each filter have a key and value pair, the value represents the param of the API
|
||||
*/
|
||||
String[] durationValues = getContext().getResources().getStringArray(R.array.leaderboard_duration_values);
|
||||
|
||||
/**
|
||||
* This array is for the category filter, we have three filters upload, used and nearby
|
||||
* each filter have a key and value pair, the value represents the param of the API
|
||||
*/
|
||||
String[] categoryValues = getContext().getResources().getStringArray(R.array.leaderboard_category_values);
|
||||
|
||||
duration = durationValues[0];
|
||||
category = categoryValues[0];
|
||||
|
||||
setLeaderboard(duration, category, limit, offset);
|
||||
|
||||
binding.durationSpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
|
||||
|
||||
duration = durationValues[binding.durationSpinner.getSelectedItemPosition()];
|
||||
refreshLeaderboard();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> adapterView) {
|
||||
}
|
||||
});
|
||||
|
||||
binding.categorySpinner.setOnItemSelectedListener(new OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
|
||||
category = categoryValues[binding.categorySpinner.getSelectedItemPosition()];
|
||||
refreshLeaderboard();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> adapterView) {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
binding.scroll.setOnClickListener(view -> scrollToUserRank());
|
||||
|
||||
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMenuVisibility(boolean visible) {
|
||||
super.setMenuVisibility(visible);
|
||||
|
||||
// Whenever this fragment is revealed in a menu,
|
||||
// notify Beta users the page data is unavailable
|
||||
if(ConfigUtils.isBetaFlavour() && visible) {
|
||||
Context ctx = null;
|
||||
if(getContext() != null) {
|
||||
ctx = getContext();
|
||||
} else if(getView() != null && getView().getContext() != null) {
|
||||
ctx = getView().getContext();
|
||||
}
|
||||
if(ctx != null) {
|
||||
Toast.makeText(ctx,
|
||||
R.string.leaderboard_unavailable_beta,
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the leaderboard list
|
||||
*/
|
||||
private void refreshLeaderboard() {
|
||||
scrollToRank = false;
|
||||
if (viewModel != null) {
|
||||
viewModel.refresh(duration, category, limit, offset);
|
||||
setLeaderboard(duration, category, limit, offset);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs Auto Scroll to the User's Rank
|
||||
* We use userRank+1 to load one extra user and prevent overlapping of my rank button
|
||||
* If you are viewing the leaderboard below userRank, it scrolls to the user rank at the top
|
||||
*/
|
||||
private void scrollToUserRank() {
|
||||
|
||||
if(userRank==0){
|
||||
Toast.makeText(getContext(),R.string.no_achievements_yet,Toast.LENGTH_SHORT).show();
|
||||
}else {
|
||||
if (binding == null) {
|
||||
return;
|
||||
}
|
||||
if (Objects.requireNonNull(binding.leaderboardList.getAdapter()).getItemCount()
|
||||
> userRank + 1) {
|
||||
binding.leaderboardList.smoothScrollToPosition(userRank + 1);
|
||||
} else {
|
||||
if (viewModel != null) {
|
||||
viewModel.refresh(duration, category, userRank + 1, 0);
|
||||
setLeaderboard(duration, category, userRank + 1, 0);
|
||||
scrollToRank = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the spinners for the leaderboard filters
|
||||
*/
|
||||
private void setSpinners() {
|
||||
ArrayAdapter<CharSequence> categoryAdapter = ArrayAdapter.createFromResource(getContext(),
|
||||
R.array.leaderboard_categories, android.R.layout.simple_spinner_item);
|
||||
categoryAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
binding.categorySpinner.setAdapter(categoryAdapter);
|
||||
|
||||
ArrayAdapter<CharSequence> durationAdapter = ArrayAdapter.createFromResource(getContext(),
|
||||
R.array.leaderboard_durations, android.R.layout.simple_spinner_item);
|
||||
durationAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
binding.durationSpinner.setAdapter(durationAdapter);
|
||||
}
|
||||
|
||||
/**
|
||||
* To call the API to get results
|
||||
* which then sets the views using setLeaderboardUser method
|
||||
*/
|
||||
private void setLeaderboard(String duration, String category, int limit, int offset) {
|
||||
if (checkAccount()) {
|
||||
try {
|
||||
compositeDisposable.add(okHttpJsonApiClient
|
||||
.getLeaderboard(Objects.requireNonNull(userName),
|
||||
duration, category, null, null)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
response -> {
|
||||
if (response != null && response.getStatus() == 200) {
|
||||
userRank = response.getRank();
|
||||
setViews(response, duration, category, limit, offset);
|
||||
}
|
||||
},
|
||||
t -> {
|
||||
Timber.e(t, "Fetching leaderboard statistics failed");
|
||||
onError();
|
||||
}
|
||||
));
|
||||
}
|
||||
catch (Exception e){
|
||||
Timber.d(e+"success");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the views
|
||||
* @param response Leaderboard Response Object
|
||||
*/
|
||||
private void setViews(LeaderboardResponse response, String duration, String category, int limit, int offset) {
|
||||
viewModel = new ViewModelProvider(this, viewModelFactory).get(LeaderboardListViewModel.class);
|
||||
viewModel.setParams(duration, category, limit, offset);
|
||||
LeaderboardListAdapter leaderboardListAdapter = new LeaderboardListAdapter();
|
||||
UserDetailAdapter userDetailAdapter= new UserDetailAdapter(response);
|
||||
MergeAdapter mergeAdapter = new MergeAdapter(userDetailAdapter, leaderboardListAdapter);
|
||||
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext());
|
||||
binding.leaderboardList.setLayoutManager(linearLayoutManager);
|
||||
binding.leaderboardList.setAdapter(mergeAdapter);
|
||||
viewModel.getListLiveData().observe(getViewLifecycleOwner(), leaderboardListAdapter::submitList);
|
||||
viewModel.getProgressLoadStatus().observe(getViewLifecycleOwner(), status -> {
|
||||
if (Objects.requireNonNull(status).equalsIgnoreCase(LOADING)) {
|
||||
showProgressBar();
|
||||
} else if (status.equalsIgnoreCase(LOADED)) {
|
||||
hideProgressBar();
|
||||
if (scrollToRank) {
|
||||
binding.leaderboardList.smoothScrollToPosition(userRank + 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* to hide progressbar
|
||||
*/
|
||||
private void hideProgressBar() {
|
||||
if (binding != null) {
|
||||
binding.progressBar.setVisibility(View.GONE);
|
||||
binding.categorySpinner.setVisibility(View.VISIBLE);
|
||||
binding.durationSpinner.setVisibility(View.VISIBLE);
|
||||
binding.scroll.setVisibility(View.VISIBLE);
|
||||
binding.leaderboardList.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* to show progressbar
|
||||
*/
|
||||
private void showProgressBar() {
|
||||
if (binding != null) {
|
||||
binding.progressBar.setVisibility(View.VISIBLE);
|
||||
binding.scroll.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* used to hide the layouts while fetching results from api
|
||||
*/
|
||||
private void hideLayouts(){
|
||||
binding.categorySpinner.setVisibility(View.INVISIBLE);
|
||||
binding.durationSpinner.setVisibility(View.INVISIBLE);
|
||||
binding.leaderboardList.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* check to ensure that user is logged in
|
||||
* @return
|
||||
*/
|
||||
private boolean checkAccount(){
|
||||
Account currentAccount = sessionManager.getCurrentAccount();
|
||||
if (currentAccount == null) {
|
||||
Timber.d("Current account is null");
|
||||
ViewUtil.showLongToast(getActivity(), getResources().getString(R.string.user_not_logged_in));
|
||||
sessionManager.forceLogin(getActivity());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a generic error toast when error occurs while loading leaderboard
|
||||
*/
|
||||
private void onError() {
|
||||
ViewUtil.showLongToast(getActivity(), getResources().getString(R.string.error_occurred));
|
||||
if (binding!=null) {
|
||||
binding.progressBar.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
compositeDisposable.clear();
|
||||
binding = null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,319 @@
|
|||
package fr.free.nrw.commons.profile.leaderboard
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.MergeAdapter
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.auth.SessionManager
|
||||
import fr.free.nrw.commons.databinding.FragmentLeaderboardBinding
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
|
||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
|
||||
import fr.free.nrw.commons.profile.ProfileActivity
|
||||
import fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.LoadingStatus.LOADED
|
||||
import fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.LoadingStatus.LOADING
|
||||
import fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.PAGE_SIZE
|
||||
import fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.START_OFFSET
|
||||
import fr.free.nrw.commons.utils.ConfigUtils.isBetaFlavour
|
||||
import fr.free.nrw.commons.utils.ViewUtil.showLongToast
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import timber.log.Timber
|
||||
import java.util.Objects
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* This class extends the CommonsDaggerSupportFragment and creates leaderboard fragment
|
||||
*/
|
||||
class LeaderboardFragment : CommonsDaggerSupportFragment() {
|
||||
@Inject
|
||||
lateinit var sessionManager: SessionManager
|
||||
|
||||
@Inject
|
||||
lateinit var okHttpJsonApiClient: OkHttpJsonApiClient
|
||||
|
||||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelFactory
|
||||
|
||||
private var viewModel: LeaderboardListViewModel? = null
|
||||
private var duration: String? = null
|
||||
private var category: String? = null
|
||||
private val limit: Int = PAGE_SIZE
|
||||
private val offset: Int = START_OFFSET
|
||||
private var userRank = 0
|
||||
private var scrollToRank = false
|
||||
private var userName: String? = null
|
||||
private var binding: FragmentLeaderboardBinding? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
arguments?.let { userName = it.getString(ProfileActivity.KEY_USERNAME) }
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = FragmentLeaderboardBinding.inflate(inflater, container, false)
|
||||
|
||||
hideLayouts()
|
||||
|
||||
// Leaderboard currently unimplemented in Beta flavor. Skip all API calls and disable menu
|
||||
if (isBetaFlavour) {
|
||||
binding!!.progressBar.visibility = View.GONE
|
||||
binding!!.scroll.visibility = View.GONE
|
||||
return binding!!.root
|
||||
}
|
||||
|
||||
binding!!.progressBar.visibility = View.VISIBLE
|
||||
setSpinners()
|
||||
|
||||
/*
|
||||
* This array is for the duration filter, we have three filters weekly, yearly and all-time
|
||||
* each filter have a key and value pair, the value represents the param of the API
|
||||
*/
|
||||
val durationValues = requireContext().resources
|
||||
.getStringArray(R.array.leaderboard_duration_values)
|
||||
duration = durationValues[0]
|
||||
|
||||
/*
|
||||
* This array is for the category filter, we have three filters upload, used and nearby
|
||||
* each filter have a key and value pair, the value represents the param of the API
|
||||
*/
|
||||
val categoryValues = requireContext().resources
|
||||
.getStringArray(R.array.leaderboard_category_values)
|
||||
category = categoryValues[0]
|
||||
|
||||
setLeaderboard(duration, category, limit, offset)
|
||||
|
||||
with(binding!!) {
|
||||
durationSpinner.onItemSelectedListener = SelectionListener {
|
||||
duration = durationValues[durationSpinner.selectedItemPosition]
|
||||
refreshLeaderboard()
|
||||
}
|
||||
|
||||
categorySpinner.onItemSelectedListener = SelectionListener {
|
||||
category = categoryValues[categorySpinner.selectedItemPosition]
|
||||
refreshLeaderboard()
|
||||
}
|
||||
|
||||
scroll.setOnClickListener { scrollToUserRank() }
|
||||
|
||||
return root
|
||||
}
|
||||
}
|
||||
|
||||
override fun setMenuVisibility(visible: Boolean) {
|
||||
super.setMenuVisibility(visible)
|
||||
|
||||
// Whenever this fragment is revealed in a menu,
|
||||
// notify Beta users the page data is unavailable
|
||||
if (isBetaFlavour && visible) {
|
||||
val ctx: Context? = if (context != null) {
|
||||
context
|
||||
} else if (view != null && requireView().context != null) {
|
||||
requireView().context
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
ctx?.let {
|
||||
Toast.makeText(it, R.string.leaderboard_unavailable_beta, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the leaderboard list
|
||||
*/
|
||||
private fun refreshLeaderboard() {
|
||||
scrollToRank = false
|
||||
viewModel?.let {
|
||||
it.refresh(duration, category, limit, offset)
|
||||
setLeaderboard(duration, category, limit, offset)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs Auto Scroll to the User's Rank
|
||||
* We use userRank+1 to load one extra user and prevent overlapping of my rank button
|
||||
* If you are viewing the leaderboard below userRank, it scrolls to the user rank at the top
|
||||
*/
|
||||
private fun scrollToUserRank() {
|
||||
if (userRank == 0) {
|
||||
Toast.makeText(context, R.string.no_achievements_yet, Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
if (binding == null) {
|
||||
return
|
||||
}
|
||||
val itemCount = binding?.leaderboardList?.adapter?.itemCount ?: 0
|
||||
if (itemCount > userRank + 1) {
|
||||
binding!!.leaderboardList.smoothScrollToPosition(userRank + 1)
|
||||
} else {
|
||||
viewModel?.let {
|
||||
it.refresh(duration, category, userRank + 1, 0)
|
||||
setLeaderboard(duration, category, userRank + 1, 0)
|
||||
scrollToRank = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the spinners for the leaderboard filters
|
||||
*/
|
||||
private fun setSpinners() {
|
||||
val categoryAdapter = ArrayAdapter.createFromResource(
|
||||
requireContext(),
|
||||
R.array.leaderboard_categories, android.R.layout.simple_spinner_item
|
||||
)
|
||||
categoryAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
binding!!.categorySpinner.adapter = categoryAdapter
|
||||
|
||||
val durationAdapter = ArrayAdapter.createFromResource(
|
||||
requireContext(),
|
||||
R.array.leaderboard_durations, android.R.layout.simple_spinner_item
|
||||
)
|
||||
durationAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
binding!!.durationSpinner.adapter = durationAdapter
|
||||
}
|
||||
|
||||
/**
|
||||
* To call the API to get results
|
||||
* which then sets the views using setLeaderboardUser method
|
||||
*/
|
||||
private fun setLeaderboard(duration: String?, category: String?, limit: Int, offset: Int) {
|
||||
if (checkAccount()) {
|
||||
try {
|
||||
compositeDisposable.add(
|
||||
okHttpJsonApiClient.getLeaderboard(
|
||||
Objects.requireNonNull(userName),
|
||||
duration, category, null, null
|
||||
)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(
|
||||
{ response: LeaderboardResponse? ->
|
||||
if (response != null && response.status == 200) {
|
||||
userRank = response.rank!!
|
||||
setViews(response, duration, category, limit, offset)
|
||||
}
|
||||
},
|
||||
{ t: Throwable? ->
|
||||
Timber.e(t, "Fetching leaderboard statistics failed")
|
||||
onError()
|
||||
}
|
||||
))
|
||||
} catch (e: Exception) {
|
||||
Timber.d(e, "success")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the views
|
||||
* @param response Leaderboard Response Object
|
||||
*/
|
||||
private fun setViews(
|
||||
response: LeaderboardResponse,
|
||||
duration: String?,
|
||||
category: String?,
|
||||
limit: Int,
|
||||
offset: Int
|
||||
) {
|
||||
viewModel = ViewModelProvider(this, viewModelFactory).get(
|
||||
LeaderboardListViewModel::class.java
|
||||
)
|
||||
viewModel!!.setParams(duration, category, limit, offset)
|
||||
val leaderboardListAdapter = LeaderboardListAdapter()
|
||||
val userDetailAdapter = UserDetailAdapter(response)
|
||||
val mergeAdapter = MergeAdapter(userDetailAdapter, leaderboardListAdapter)
|
||||
val linearLayoutManager = LinearLayoutManager(context)
|
||||
binding!!.leaderboardList.layoutManager = linearLayoutManager
|
||||
binding!!.leaderboardList.adapter = mergeAdapter
|
||||
viewModel!!.listLiveData.observe(viewLifecycleOwner, leaderboardListAdapter::submitList)
|
||||
|
||||
viewModel!!.progressLoadStatus.observe(viewLifecycleOwner) { status ->
|
||||
when (status) {
|
||||
LOADING -> {
|
||||
showProgressBar()
|
||||
}
|
||||
LOADED -> {
|
||||
hideProgressBar()
|
||||
if (scrollToRank) {
|
||||
binding!!.leaderboardList.smoothScrollToPosition(userRank + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* to hide progressbar
|
||||
*/
|
||||
private fun hideProgressBar() = binding?.let {
|
||||
it.progressBar.visibility = View.GONE
|
||||
it.categorySpinner.visibility = View.VISIBLE
|
||||
it.durationSpinner.visibility = View.VISIBLE
|
||||
it.scroll.visibility = View.VISIBLE
|
||||
it.leaderboardList.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
/**
|
||||
* to show progressbar
|
||||
*/
|
||||
private fun showProgressBar() = binding?.let {
|
||||
it.progressBar.visibility = View.VISIBLE
|
||||
it.scroll.visibility = View.INVISIBLE
|
||||
}
|
||||
|
||||
/**
|
||||
* used to hide the layouts while fetching results from api
|
||||
*/
|
||||
private fun hideLayouts() = binding?.let {
|
||||
it.categorySpinner.visibility = View.INVISIBLE
|
||||
it.durationSpinner.visibility = View.INVISIBLE
|
||||
it.leaderboardList.visibility = View.INVISIBLE
|
||||
}
|
||||
|
||||
/**
|
||||
* check to ensure that user is logged in
|
||||
*/
|
||||
private fun checkAccount() = if (sessionManager.currentAccount == null) {
|
||||
Timber.d("Current account is null")
|
||||
showLongToast(requireActivity(), resources.getString(R.string.user_not_logged_in))
|
||||
sessionManager.forceLogin(requireActivity())
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a generic error toast when error occurs while loading leaderboard
|
||||
*/
|
||||
private fun onError() {
|
||||
showLongToast(requireActivity(), resources.getString(R.string.error_occurred))
|
||||
binding?.let { it.progressBar.visibility = View.GONE }
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
compositeDisposable.clear()
|
||||
binding = null
|
||||
}
|
||||
|
||||
private class SelectionListener(private val handler: () -> Unit): AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(adapterView: AdapterView<*>?, view: View, i: Int, l: Long) =
|
||||
handler()
|
||||
|
||||
override fun onNothingSelected(p0: AdapterView<*>?) = Unit
|
||||
}
|
||||
}
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
package fr.free.nrw.commons.profile.leaderboard;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.DiffUtil.ItemCallback;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* This class represents the leaderboard API response sub part of i.e. leaderboard list
|
||||
* The leaderboard list will contain the ranking of the users from 1 to n,
|
||||
* avatars, username and count in the selected category.
|
||||
*/
|
||||
public class LeaderboardList {
|
||||
|
||||
/**
|
||||
* Username of the user
|
||||
* Example value - Syced
|
||||
*/
|
||||
@SerializedName("username")
|
||||
@Expose
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* Count in the category
|
||||
* Example value - 10
|
||||
*/
|
||||
@SerializedName("category_count")
|
||||
@Expose
|
||||
private Integer categoryCount;
|
||||
|
||||
/**
|
||||
* URL of the avatar of user
|
||||
* Example value = https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/Gnome-stock_person.svg/200px-Gnome-stock_person.svg.png
|
||||
*/
|
||||
@SerializedName("avatar")
|
||||
@Expose
|
||||
private String avatar;
|
||||
|
||||
/**
|
||||
* Rank of the user
|
||||
* Example value - 1
|
||||
*/
|
||||
@SerializedName("rank")
|
||||
@Expose
|
||||
private Integer rank;
|
||||
|
||||
/**
|
||||
* @return the username of the user in the leaderboard list
|
||||
*/
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the username of the user in the leaderboard list
|
||||
*/
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the category count of the user in the leaderboard list
|
||||
*/
|
||||
public Integer getCategoryCount() {
|
||||
return categoryCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the category count of the user in the leaderboard list
|
||||
*/
|
||||
public void setCategoryCount(Integer categoryCount) {
|
||||
this.categoryCount = categoryCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the avatar of the user in the leaderboard list
|
||||
*/
|
||||
public String getAvatar() {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the avatar of the user in the leaderboard list
|
||||
*/
|
||||
public void setAvatar(String avatar) {
|
||||
this.avatar = avatar;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the rank of the user in the leaderboard list
|
||||
*/
|
||||
public Integer getRank() {
|
||||
return rank;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the rank of the user in the leaderboard list
|
||||
*/
|
||||
public void setRank(Integer rank) {
|
||||
this.rank = rank;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method checks for the diff in the callbacks for paged lists
|
||||
*/
|
||||
public static DiffUtil.ItemCallback<LeaderboardList> DIFF_CALLBACK =
|
||||
new ItemCallback<LeaderboardList>() {
|
||||
@Override
|
||||
public boolean areItemsTheSame(@NonNull LeaderboardList oldItem,
|
||||
@NonNull LeaderboardList newItem) {
|
||||
return newItem == oldItem;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(@NonNull LeaderboardList oldItem,
|
||||
@NonNull LeaderboardList newItem) {
|
||||
return newItem.getRank().equals(oldItem.getRank());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if two objects are equal, false otherwise
|
||||
* @param obj
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
|
||||
LeaderboardList leaderboardList = (LeaderboardList) obj;
|
||||
return leaderboardList.getRank().equals(this.getRank());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package fr.free.nrw.commons.profile.leaderboard
|
||||
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
/**
|
||||
* This class represents the leaderboard API response sub part of i.e. leaderboard list
|
||||
* The leaderboard list will contain the ranking of the users from 1 to n,
|
||||
* avatars, username and count in the selected category.
|
||||
*/
|
||||
data class LeaderboardList (
|
||||
@SerializedName("username")
|
||||
var username: String? = null,
|
||||
@SerializedName("category_count")
|
||||
var categoryCount: Int? = null,
|
||||
@SerializedName("avatar")
|
||||
var avatar: String? = null,
|
||||
@SerializedName("rank")
|
||||
var rank: Int? = null
|
||||
) {
|
||||
|
||||
/**
|
||||
* Returns true if two objects are equal, false otherwise
|
||||
* @param other
|
||||
* @return
|
||||
*/
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other === this) {
|
||||
return true
|
||||
}
|
||||
|
||||
val leaderboardList = other as LeaderboardList
|
||||
return leaderboardList.rank == rank
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = username?.hashCode() ?: 0
|
||||
result = 31 * result + (categoryCount ?: 0)
|
||||
result = 31 * result + (avatar?.hashCode() ?: 0)
|
||||
result = 31 * result + (rank ?: 0)
|
||||
return result
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* This method checks for the diff in the callbacks for paged lists
|
||||
*/
|
||||
var DIFF_CALLBACK: DiffUtil.ItemCallback<LeaderboardList> =
|
||||
object : DiffUtil.ItemCallback<LeaderboardList>() {
|
||||
override fun areItemsTheSame(
|
||||
oldItem: LeaderboardList,
|
||||
newItem: LeaderboardList
|
||||
): Boolean = newItem === oldItem
|
||||
|
||||
override fun areContentsTheSame(
|
||||
oldItem: LeaderboardList,
|
||||
newItem: LeaderboardList
|
||||
): Boolean = newItem.rank == oldItem.rank
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
package fr.free.nrw.commons.profile.leaderboard;
|
||||
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.paging.PagedListAdapter;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.facebook.drawee.view.SimpleDraweeView;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.profile.ProfileActivity;
|
||||
|
||||
/**
|
||||
* This class extends RecyclerView.Adapter and creates the List section of the leaderboard
|
||||
*/
|
||||
public class LeaderboardListAdapter extends PagedListAdapter<LeaderboardList, LeaderboardListAdapter.ListViewHolder> {
|
||||
|
||||
public LeaderboardListAdapter() {
|
||||
super(LeaderboardList.DIFF_CALLBACK);
|
||||
}
|
||||
|
||||
public class ListViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView rank;
|
||||
SimpleDraweeView avatar;
|
||||
TextView username;
|
||||
TextView count;
|
||||
|
||||
public ListViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
this.rank = itemView.findViewById(R.id.user_rank);
|
||||
this.avatar = itemView.findViewById(R.id.user_avatar);
|
||||
this.username = itemView.findViewById(R.id.user_name);
|
||||
this.count = itemView.findViewById(R.id.user_count);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will return the Context
|
||||
* @return Context
|
||||
*/
|
||||
public Context getContext() {
|
||||
return itemView.getContext();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the onCreateViewHolder and inflates the recyclerview list item layout
|
||||
* @param parent
|
||||
* @param viewType
|
||||
* @return
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public LeaderboardListAdapter.ListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.leaderboard_list_element, parent, false);
|
||||
|
||||
return new ListViewHolder(view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the onBindViewHolder Set the view at the specific position with the specific value
|
||||
* @param holder
|
||||
* @param position
|
||||
*/
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull LeaderboardListAdapter.ListViewHolder holder, int position) {
|
||||
TextView rank = holder.rank;
|
||||
SimpleDraweeView avatar = holder.avatar;
|
||||
TextView username = holder.username;
|
||||
TextView count = holder.count;
|
||||
|
||||
rank.setText(getItem(position).getRank().toString());
|
||||
|
||||
avatar.setImageURI(Uri.parse(getItem(position).getAvatar()));
|
||||
username.setText(getItem(position).getUsername());
|
||||
count.setText(getItem(position).getCategoryCount().toString());
|
||||
|
||||
/*
|
||||
Now that we have our in app profile-section, lets take the user there
|
||||
*/
|
||||
holder.itemView.setOnClickListener(view -> {
|
||||
if (view.getContext() instanceof ProfileActivity) {
|
||||
((Activity) (view.getContext())).finish();
|
||||
}
|
||||
ProfileActivity.startYourself(view.getContext(), getItem(position).getUsername(), true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package fr.free.nrw.commons.profile.leaderboard
|
||||
|
||||
import android.app.Activity
|
||||
import android.net.Uri
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.paging.PagedListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.facebook.drawee.view.SimpleDraweeView
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.profile.ProfileActivity
|
||||
import fr.free.nrw.commons.profile.leaderboard.LeaderboardList.Companion.DIFF_CALLBACK
|
||||
import fr.free.nrw.commons.profile.leaderboard.LeaderboardListAdapter.ListViewHolder
|
||||
|
||||
|
||||
/**
|
||||
* This class extends RecyclerView.Adapter and creates the List section of the leaderboard
|
||||
*/
|
||||
class LeaderboardListAdapter : PagedListAdapter<LeaderboardList, ListViewHolder>(DIFF_CALLBACK) {
|
||||
inner class ListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
var rank: TextView? = itemView.findViewById(R.id.user_rank)
|
||||
var avatar: SimpleDraweeView? = itemView.findViewById(R.id.user_avatar)
|
||||
var username: TextView? = itemView.findViewById(R.id.user_name)
|
||||
var count: TextView? = itemView.findViewById(R.id.user_count)
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the onCreateViewHolder and inflates the recyclerview list item layout
|
||||
* @param parent
|
||||
* @param viewType
|
||||
* @return
|
||||
*/
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder =
|
||||
ListViewHolder(
|
||||
LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.leaderboard_list_element, parent, false)
|
||||
)
|
||||
|
||||
/**
|
||||
* Overrides the onBindViewHolder Set the view at the specific position with the specific value
|
||||
* @param holder
|
||||
* @param position
|
||||
*/
|
||||
override fun onBindViewHolder(holder: ListViewHolder, position: Int) = with (holder) {
|
||||
val item = getItem(position)!!
|
||||
|
||||
rank?.text = item.rank.toString()
|
||||
avatar?.setImageURI(Uri.parse(item.avatar))
|
||||
username?.text = item.username
|
||||
count?.text = item.categoryCount.toString()
|
||||
|
||||
/*
|
||||
Now that we have our in app profile-section, lets take the user there
|
||||
*/
|
||||
itemView.setOnClickListener { view: View ->
|
||||
if (view.context is ProfileActivity) {
|
||||
((view.context) as Activity).finish()
|
||||
}
|
||||
ProfileActivity.startYourself(view.context, item.username, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,107 +0,0 @@
|
|||
package fr.free.nrw.commons.profile.leaderboard;
|
||||
|
||||
import static fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.PAGE_SIZE;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.Transformations;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.paging.LivePagedListBuilder;
|
||||
import androidx.paging.PagedList;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
|
||||
/**
|
||||
* Extends the ViewModel class and creates the LeaderboardList View Model
|
||||
*/
|
||||
public class LeaderboardListViewModel extends ViewModel {
|
||||
|
||||
private DataSourceFactory dataSourceFactory;
|
||||
private LiveData<PagedList<LeaderboardList>> listLiveData;
|
||||
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||
private LiveData<String> progressLoadStatus = new MutableLiveData<>();
|
||||
|
||||
/**
|
||||
* Constructor for a new LeaderboardListViewModel
|
||||
* @param okHttpJsonApiClient
|
||||
* @param sessionManager
|
||||
*/
|
||||
public LeaderboardListViewModel(OkHttpJsonApiClient okHttpJsonApiClient, SessionManager
|
||||
sessionManager) {
|
||||
|
||||
dataSourceFactory = new DataSourceFactory(okHttpJsonApiClient,
|
||||
compositeDisposable, sessionManager);
|
||||
initializePaging();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Initialises the paging
|
||||
*/
|
||||
private void initializePaging() {
|
||||
|
||||
PagedList.Config pagedListConfig =
|
||||
new PagedList.Config.Builder()
|
||||
.setEnablePlaceholders(false)
|
||||
.setInitialLoadSizeHint(PAGE_SIZE)
|
||||
.setPageSize(PAGE_SIZE).build();
|
||||
|
||||
listLiveData = new LivePagedListBuilder<>(dataSourceFactory, pagedListConfig)
|
||||
.build();
|
||||
|
||||
progressLoadStatus = Transformations
|
||||
.switchMap(dataSourceFactory.getMutableLiveData(), DataSourceClass::getProgressLiveStatus);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the paged list with the new params and starts the loading of new data
|
||||
* @param duration
|
||||
* @param category
|
||||
* @param limit
|
||||
* @param offset
|
||||
*/
|
||||
public void refresh(String duration, String category, int limit, int offset) {
|
||||
dataSourceFactory.setDuration(duration);
|
||||
dataSourceFactory.setCategory(category);
|
||||
dataSourceFactory.setLimit(limit);
|
||||
dataSourceFactory.setOffset(offset);
|
||||
dataSourceFactory.getMutableLiveData().getValue().invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the new params for the paged list API calls
|
||||
* @param duration
|
||||
* @param category
|
||||
* @param limit
|
||||
* @param offset
|
||||
*/
|
||||
public void setParams(String duration, String category, int limit, int offset) {
|
||||
dataSourceFactory.setDuration(duration);
|
||||
dataSourceFactory.setCategory(category);
|
||||
dataSourceFactory.setLimit(limit);
|
||||
dataSourceFactory.setOffset(offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the loading status of paged list
|
||||
*/
|
||||
public LiveData<String> getProgressLoadStatus() {
|
||||
return progressLoadStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the paged list with live data
|
||||
*/
|
||||
public LiveData<PagedList<LeaderboardList>> getListLiveData() {
|
||||
return listLiveData;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCleared() {
|
||||
super.onCleared();
|
||||
compositeDisposable.clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package fr.free.nrw.commons.profile.leaderboard
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.map
|
||||
import androidx.lifecycle.switchMap
|
||||
import androidx.paging.LivePagedListBuilder
|
||||
import androidx.paging.PagedList
|
||||
import fr.free.nrw.commons.auth.SessionManager
|
||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
|
||||
import fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.LoadingStatus
|
||||
import fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.PAGE_SIZE
|
||||
|
||||
/**
|
||||
* Extends the ViewModel class and creates the LeaderboardList View Model
|
||||
*/
|
||||
class LeaderboardListViewModel(
|
||||
okHttpJsonApiClient: OkHttpJsonApiClient,
|
||||
sessionManager: SessionManager
|
||||
) : ViewModel() {
|
||||
private val dataSourceFactory = DataSourceFactory(okHttpJsonApiClient, sessionManager)
|
||||
|
||||
val listLiveData: LiveData<PagedList<LeaderboardList>> = LivePagedListBuilder(
|
||||
dataSourceFactory,
|
||||
PagedList.Config.Builder()
|
||||
.setEnablePlaceholders(false)
|
||||
.setInitialLoadSizeHint(PAGE_SIZE)
|
||||
.setPageSize(PAGE_SIZE).build()
|
||||
).build()
|
||||
|
||||
val progressLoadStatus: LiveData<LoadingStatus> =
|
||||
dataSourceFactory.mutableLiveData.switchMap { it.progressLiveStatus }
|
||||
|
||||
/**
|
||||
* Refreshes the paged list with the new params and starts the loading of new data
|
||||
*/
|
||||
fun refresh(duration: String?, category: String?, limit: Int, offset: Int) {
|
||||
dataSourceFactory.duration = duration
|
||||
dataSourceFactory.category = category
|
||||
dataSourceFactory.limit = limit
|
||||
dataSourceFactory.offset = offset
|
||||
dataSourceFactory.mutableLiveData.value!!.invalidate()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the new params for the paged list API calls
|
||||
*/
|
||||
fun setParams(duration: String?, category: String?, limit: Int, offset: Int) {
|
||||
dataSourceFactory.duration = duration
|
||||
dataSourceFactory.category = category
|
||||
dataSourceFactory.limit = limit
|
||||
dataSourceFactory.offset = offset
|
||||
}
|
||||
}
|
||||
|
|
@ -1,237 +0,0 @@
|
|||
package fr.free.nrw.commons.profile.leaderboard;
|
||||
|
||||
import java.util.List;
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* GSON Response Class for Leaderboard API response
|
||||
*/
|
||||
public class LeaderboardResponse {
|
||||
|
||||
/**
|
||||
* Status Code returned from the API
|
||||
* Example value - 200
|
||||
*/
|
||||
@SerializedName("status")
|
||||
@Expose
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* Username returned from the API
|
||||
* Example value - Syced
|
||||
*/
|
||||
@SerializedName("username")
|
||||
@Expose
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* Category count returned from the API
|
||||
* Example value - 10
|
||||
*/
|
||||
@SerializedName("category_count")
|
||||
@Expose
|
||||
private Integer categoryCount;
|
||||
|
||||
/**
|
||||
* Limit returned from the API
|
||||
* Example value - 10
|
||||
*/
|
||||
@SerializedName("limit")
|
||||
@Expose
|
||||
private int limit;
|
||||
|
||||
/**
|
||||
* Avatar returned from the API
|
||||
* Example value - https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/Gnome-stock_person.svg/200px-Gnome-stock_person.svg.png
|
||||
*/
|
||||
@SerializedName("avatar")
|
||||
@Expose
|
||||
private String avatar;
|
||||
|
||||
/**
|
||||
* Offset returned from the API
|
||||
* Example value - 0
|
||||
*/
|
||||
@SerializedName("offset")
|
||||
@Expose
|
||||
private int offset;
|
||||
|
||||
/**
|
||||
* Duration returned from the API
|
||||
* Example value - yearly
|
||||
*/
|
||||
@SerializedName("duration")
|
||||
@Expose
|
||||
private String duration;
|
||||
|
||||
/**
|
||||
* Leaderboard list returned from the API
|
||||
* Example value - [{
|
||||
* "username": "Fæ",
|
||||
* "category_count": 107147,
|
||||
* "avatar": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/Gnome-stock_person.svg/200px-Gnome-stock_person.svg.png",
|
||||
* "rank": 1
|
||||
* }]
|
||||
*/
|
||||
@SerializedName("leaderboard_list")
|
||||
@Expose
|
||||
private List<LeaderboardList> leaderboardList = null;
|
||||
|
||||
/**
|
||||
* Category returned from the API
|
||||
* Example value - upload
|
||||
*/
|
||||
@SerializedName("category")
|
||||
@Expose
|
||||
private String category;
|
||||
|
||||
/**
|
||||
* Rank returned from the API
|
||||
* Example value - 1
|
||||
*/
|
||||
@SerializedName("rank")
|
||||
@Expose
|
||||
private Integer rank;
|
||||
|
||||
/**
|
||||
* @return the status code
|
||||
*/
|
||||
public Integer getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the status code
|
||||
*/
|
||||
public void setStatus(Integer status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the username
|
||||
*/
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the username
|
||||
*/
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the category count
|
||||
*/
|
||||
public Integer getCategoryCount() {
|
||||
return categoryCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the category count
|
||||
*/
|
||||
public void setCategoryCount(Integer categoryCount) {
|
||||
this.categoryCount = categoryCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the limit
|
||||
*/
|
||||
public int getLimit() {
|
||||
return limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the limit
|
||||
*/
|
||||
public void setLimit(int limit) {
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the avatar
|
||||
*/
|
||||
public String getAvatar() {
|
||||
return avatar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the avatar
|
||||
*/
|
||||
public void setAvatar(String avatar) {
|
||||
this.avatar = avatar;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the offset
|
||||
*/
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the offset
|
||||
*/
|
||||
public void setOffset(int offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the duration
|
||||
*/
|
||||
public String getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the duration
|
||||
*/
|
||||
public void setDuration(String duration) {
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the leaderboard list
|
||||
*/
|
||||
public List<LeaderboardList> getLeaderboardList() {
|
||||
return leaderboardList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the leaderboard list
|
||||
*/
|
||||
public void setLeaderboardList(List<LeaderboardList> leaderboardList) {
|
||||
this.leaderboardList = leaderboardList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the category
|
||||
*/
|
||||
public String getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the category
|
||||
*/
|
||||
public void setCategory(String category) {
|
||||
this.category = category;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the rank
|
||||
*/
|
||||
public Integer getRank() {
|
||||
return rank;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the rank
|
||||
*/
|
||||
public void setRank(Integer rank) {
|
||||
this.rank = rank;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package fr.free.nrw.commons.profile.leaderboard
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
/**
|
||||
* GSON Response Class for Leaderboard API response
|
||||
*/
|
||||
data class LeaderboardResponse(
|
||||
@SerializedName("status") var status: Int? = null,
|
||||
@SerializedName("username") var username: String? = null,
|
||||
@SerializedName("category_count") var categoryCount: Int? = null,
|
||||
@SerializedName("limit") var limit: Int = 0,
|
||||
@SerializedName("avatar") var avatar: String? = null,
|
||||
@SerializedName("offset") var offset: Int = 0,
|
||||
@SerializedName("duration") var duration: String? = null,
|
||||
@SerializedName("leaderboard_list") var leaderboardList: List<LeaderboardList>? = null,
|
||||
@SerializedName("category") var category: String? = null,
|
||||
@SerializedName("rank") var rank: Int? = null
|
||||
)
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
package fr.free.nrw.commons.profile.leaderboard;
|
||||
|
||||
import com.google.gson.annotations.Expose;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* GSON Response Class for Update Avatar API response
|
||||
*/
|
||||
public class UpdateAvatarResponse {
|
||||
|
||||
/**
|
||||
* Status Code returned from the API
|
||||
* Example value - 200
|
||||
*/
|
||||
@SerializedName("status")
|
||||
@Expose
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* Message returned from the API
|
||||
* Example value - Avatar Updated
|
||||
*/
|
||||
@SerializedName("message")
|
||||
@Expose
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* Username returned from the API
|
||||
* Example value - Syced
|
||||
*/
|
||||
@SerializedName("user")
|
||||
@Expose
|
||||
private String user;
|
||||
|
||||
/**
|
||||
* @return the status code
|
||||
*/
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the status code
|
||||
*/
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the message
|
||||
*/
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the message
|
||||
*/
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the username
|
||||
*/
|
||||
public String getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the username
|
||||
*/
|
||||
public void setUser(String user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package fr.free.nrw.commons.profile.leaderboard
|
||||
|
||||
/**
|
||||
* GSON Response Class for Update Avatar API response
|
||||
*/
|
||||
data class UpdateAvatarResponse(
|
||||
var status: String? = null,
|
||||
var message: String? = null,
|
||||
var user: String? = null
|
||||
)
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
package fr.free.nrw.commons.profile.leaderboard;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.facebook.drawee.view.SimpleDraweeView;
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import fr.free.nrw.commons.R;
|
||||
|
||||
|
||||
/**
|
||||
* This class extends RecyclerView.Adapter and creates the UserDetail section of the leaderboard
|
||||
*/
|
||||
public class UserDetailAdapter extends RecyclerView.Adapter<UserDetailAdapter.DataViewHolder> {
|
||||
|
||||
private LeaderboardResponse leaderboardResponse;
|
||||
|
||||
/**
|
||||
* Stores the username of currently logged in user.
|
||||
*/
|
||||
private String currentlyLoggedInUserName = null;
|
||||
|
||||
public UserDetailAdapter(LeaderboardResponse leaderboardResponse) {
|
||||
this.leaderboardResponse = leaderboardResponse;
|
||||
}
|
||||
|
||||
public class DataViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private TextView rank;
|
||||
private SimpleDraweeView avatar;
|
||||
private TextView username;
|
||||
private TextView count;
|
||||
|
||||
public DataViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
this.rank = itemView.findViewById(R.id.rank);
|
||||
this.avatar = itemView.findViewById(R.id.avatar);
|
||||
this.username = itemView.findViewById(R.id.username);
|
||||
this.count = itemView.findViewById(R.id.count);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will return the Context
|
||||
* @return Context
|
||||
*/
|
||||
public Context getContext() {
|
||||
return itemView.getContext();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the onCreateViewHolder and sets the view with leaderboard user element layout
|
||||
* @param parent
|
||||
* @param viewType
|
||||
* @return
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public UserDetailAdapter.DataViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
|
||||
int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.leaderboard_user_element, parent, false);
|
||||
return new DataViewHolder(view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the onBindViewHolder Set the view at the specific position with the specific value
|
||||
* @param holder
|
||||
* @param position
|
||||
*/
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull UserDetailAdapter.DataViewHolder holder, int position) {
|
||||
TextView rank = holder.rank;
|
||||
SimpleDraweeView avatar = holder.avatar;
|
||||
TextView username = holder.username;
|
||||
TextView count = holder.count;
|
||||
|
||||
rank.setText(String.format("%s %d",
|
||||
holder.getContext().getResources().getString(R.string.rank_prefix),
|
||||
leaderboardResponse.getRank()));
|
||||
|
||||
avatar.setImageURI(
|
||||
Uri.parse(leaderboardResponse.getAvatar()));
|
||||
username.setText(leaderboardResponse.getUsername());
|
||||
count.setText(String.format("%s %d",
|
||||
holder.getContext().getResources().getString(R.string.count_prefix),
|
||||
leaderboardResponse.getCategoryCount()));
|
||||
|
||||
// When user tap on avatar shows the toast on how to change avatar
|
||||
// fixing: https://github.com/commons-app/apps-android-commons/issues/47747
|
||||
if (currentlyLoggedInUserName == null) {
|
||||
// If the current login username has not been fetched yet, then fetch it.
|
||||
final AccountManager accountManager = AccountManager.get(username.getContext());
|
||||
final Account[] allAccounts = accountManager.getAccountsByType(
|
||||
BuildConfig.ACCOUNT_TYPE);
|
||||
if (allAccounts.length != 0) {
|
||||
currentlyLoggedInUserName = allAccounts[0].name;
|
||||
}
|
||||
}
|
||||
if (currentlyLoggedInUserName != null && currentlyLoggedInUserName.equals(
|
||||
leaderboardResponse.getUsername())) {
|
||||
|
||||
avatar.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Toast.makeText(v.getContext(),
|
||||
R.string.set_up_avatar_toast_string,
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
package fr.free.nrw.commons.profile.leaderboard
|
||||
|
||||
import android.accounts.AccountManager
|
||||
import android.net.Uri
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.facebook.drawee.view.SimpleDraweeView
|
||||
import fr.free.nrw.commons.BuildConfig
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.profile.leaderboard.UserDetailAdapter.DataViewHolder
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* This class extends RecyclerView.Adapter and creates the UserDetail section of the leaderboard
|
||||
*/
|
||||
class UserDetailAdapter(private val leaderboardResponse: LeaderboardResponse) :
|
||||
RecyclerView.Adapter<DataViewHolder>() {
|
||||
/**
|
||||
* Stores the username of currently logged in user.
|
||||
*/
|
||||
private var currentlyLoggedInUserName: String? = null
|
||||
|
||||
class DataViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val rank: TextView = itemView.findViewById(R.id.rank)
|
||||
val avatar: SimpleDraweeView = itemView.findViewById(R.id.avatar)
|
||||
val username: TextView = itemView.findViewById(R.id.username)
|
||||
val count: TextView = itemView.findViewById(R.id.count)
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the onCreateViewHolder and sets the view with leaderboard user element layout
|
||||
* @param parent
|
||||
* @param viewType
|
||||
* @return
|
||||
*/
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
): DataViewHolder = DataViewHolder(
|
||||
LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.leaderboard_user_element, parent, false)
|
||||
)
|
||||
|
||||
/**
|
||||
* Overrides the onBindViewHolder Set the view at the specific position with the specific value
|
||||
* @param holder
|
||||
* @param position
|
||||
*/
|
||||
override fun onBindViewHolder(holder: DataViewHolder, position: Int) = with(holder) {
|
||||
val resources = itemView.context.resources
|
||||
|
||||
avatar.setImageURI(Uri.parse(leaderboardResponse.avatar))
|
||||
username.text = leaderboardResponse.username
|
||||
rank.text = String.format(
|
||||
Locale.getDefault(),
|
||||
"%s %d",
|
||||
resources.getString(R.string.rank_prefix),
|
||||
leaderboardResponse.rank
|
||||
)
|
||||
count.text = String.format(
|
||||
Locale.getDefault(),
|
||||
"%s %d",
|
||||
resources.getString(R.string.count_prefix),
|
||||
leaderboardResponse.categoryCount
|
||||
)
|
||||
|
||||
// When user tap on avatar shows the toast on how to change avatar
|
||||
// fixing: https://github.com/commons-app/apps-android-commons/issues/47747
|
||||
if (currentlyLoggedInUserName == null) {
|
||||
// If the current login username has not been fetched yet, then fetch it.
|
||||
val accountManager = AccountManager.get(itemView.context)
|
||||
val allAccounts = accountManager.getAccountsByType(BuildConfig.ACCOUNT_TYPE)
|
||||
if (allAccounts.isNotEmpty()) {
|
||||
currentlyLoggedInUserName = allAccounts[0].name
|
||||
}
|
||||
}
|
||||
if (currentlyLoggedInUserName != null && currentlyLoggedInUserName == leaderboardResponse.username) {
|
||||
avatar.setOnClickListener { v: View ->
|
||||
Toast.makeText(
|
||||
v.context, R.string.set_up_avatar_toast_string, Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = 1
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
package fr.free.nrw.commons.profile.leaderboard;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* This class extends the ViewModelProvider.Factory and creates a ViewModelFactory class
|
||||
* for leaderboardListViewModel
|
||||
*/
|
||||
public class ViewModelFactory implements ViewModelProvider.Factory {
|
||||
|
||||
private OkHttpJsonApiClient okHttpJsonApiClient;
|
||||
private SessionManager sessionManager;
|
||||
|
||||
|
||||
@Inject
|
||||
public ViewModelFactory(OkHttpJsonApiClient okHttpJsonApiClient, SessionManager sessionManager) {
|
||||
this.okHttpJsonApiClient = okHttpJsonApiClient;
|
||||
this.sessionManager = sessionManager;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creats a new LeaderboardListViewModel
|
||||
* @param modelClass
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
|
||||
if (modelClass.isAssignableFrom(LeaderboardListViewModel.class)) {
|
||||
return (T) new LeaderboardListViewModel(okHttpJsonApiClient, sessionManager);
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown class name");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package fr.free.nrw.commons.profile.leaderboard
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import fr.free.nrw.commons.auth.SessionManager
|
||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
/**
|
||||
* This class extends the ViewModelProvider.Factory and creates a ViewModelFactory class
|
||||
* for leaderboardListViewModel
|
||||
*/
|
||||
class ViewModelFactory @Inject constructor(
|
||||
private val okHttpJsonApiClient: OkHttpJsonApiClient,
|
||||
private val sessionManager: SessionManager
|
||||
) : ViewModelProvider.Factory {
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T =
|
||||
if (modelClass.isAssignableFrom(LeaderboardListViewModel::class.java)) {
|
||||
LeaderboardListViewModel(okHttpJsonApiClient, sessionManager) as T
|
||||
} else {
|
||||
throw IllegalArgumentException("Unknown class name")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,116 +0,0 @@
|
|||
package fr.free.nrw.commons.leaderboard;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import fr.free.nrw.commons.profile.leaderboard.LeaderboardResponse;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Request.Builder;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* This class tests the Leaderboard API calls
|
||||
*/
|
||||
public class LeaderboardApiTest {
|
||||
|
||||
MockWebServer server;
|
||||
private static final String TEST_USERNAME = "user";
|
||||
private static final String TEST_AVATAR = "avatar";
|
||||
private static final int TEST_USER_RANK = 1;
|
||||
private static final int TEST_USER_COUNT = 0;
|
||||
|
||||
private static final String FILE_NAME = "leaderboard_sample_response.json";
|
||||
private static final String ENDPOINT = "/leaderboard.py";
|
||||
|
||||
/**
|
||||
* This method initialises a Mock Server
|
||||
*/
|
||||
@Before
|
||||
public void initTest() {
|
||||
server = new MockWebServer();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will setup a Mock Server and load Test JSON Response File
|
||||
* @throws Exception
|
||||
*/
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
||||
String testResponseBody = convertStreamToString(getClass().getClassLoader().getResourceAsStream(FILE_NAME));
|
||||
|
||||
server.enqueue(new MockResponse().setBody(testResponseBody));
|
||||
server.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method converts a Input Stream to String
|
||||
* @param is takes Input Stream of JSON File as Parameter
|
||||
* @return a String with JSON data
|
||||
* @throws Exception
|
||||
*/
|
||||
private static String convertStreamToString(InputStream is) throws Exception {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line).append("\n");
|
||||
}
|
||||
reader.close();
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will call the Mock Server and Test it with sample values.
|
||||
* It will test the Leaderboard API call functionality and check if the object is
|
||||
* being created with the correct values
|
||||
* @throws IOException
|
||||
*/
|
||||
@Test
|
||||
public void apiTest() throws IOException {
|
||||
HttpUrl httpUrl = server.url(ENDPOINT);
|
||||
LeaderboardResponse response = sendRequest(new OkHttpClient(), httpUrl);
|
||||
|
||||
Assert.assertEquals(TEST_AVATAR, response.getAvatar());
|
||||
Assert.assertEquals(TEST_USERNAME, response.getUsername());
|
||||
Assert.assertEquals(Integer.valueOf(TEST_USER_RANK), response.getRank());
|
||||
Assert.assertEquals(Integer.valueOf(TEST_USER_COUNT), response.getCategoryCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will call the Mock API and returns the Leaderboard Response Object
|
||||
* @param okHttpClient
|
||||
* @param httpUrl
|
||||
* @return Leaderboard Response Object
|
||||
* @throws IOException
|
||||
*/
|
||||
private LeaderboardResponse sendRequest(OkHttpClient okHttpClient, HttpUrl httpUrl)
|
||||
throws IOException {
|
||||
Request request = new Builder().url(httpUrl).build();
|
||||
Response response = okHttpClient.newCall(request).execute();
|
||||
if (response.isSuccessful()) {
|
||||
Gson gson = new Gson();
|
||||
return gson.fromJson(response.body().string(), LeaderboardResponse.class);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method shuts down the Mock Server
|
||||
* @throws IOException
|
||||
*/
|
||||
@After
|
||||
public void shutdown() throws IOException {
|
||||
server.shutdown();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
package fr.free.nrw.commons.leaderboard
|
||||
|
||||
import com.google.gson.Gson
|
||||
import fr.free.nrw.commons.profile.leaderboard.LeaderboardResponse
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import okhttp3.mockwebserver.MockWebServer
|
||||
import org.junit.After
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.io.BufferedReader
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.InputStreamReader
|
||||
|
||||
/**
|
||||
* This class tests the Leaderboard API calls
|
||||
*/
|
||||
class LeaderboardApiTest {
|
||||
lateinit var server: MockWebServer
|
||||
|
||||
/**
|
||||
* This method initialises a Mock Server
|
||||
*/
|
||||
@Before
|
||||
fun initTest() {
|
||||
server = MockWebServer()
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will setup a Mock Server and load Test JSON Response File
|
||||
* @throws Exception
|
||||
*/
|
||||
@Before
|
||||
@Throws(Exception::class)
|
||||
fun setUp() {
|
||||
val testResponseBody = convertStreamToString(
|
||||
javaClass.classLoader!!.getResourceAsStream(FILE_NAME)
|
||||
)
|
||||
|
||||
server.enqueue(MockResponse().setBody(testResponseBody))
|
||||
server.start()
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will call the Mock Server and Test it with sample values.
|
||||
* It will test the Leaderboard API call functionality and check if the object is
|
||||
* being created with the correct values
|
||||
* @throws IOException
|
||||
*/
|
||||
@Test
|
||||
@Throws(IOException::class)
|
||||
fun apiTest() {
|
||||
val httpUrl = server.url(ENDPOINT)
|
||||
val response = sendRequest(OkHttpClient(), httpUrl)
|
||||
|
||||
Assert.assertEquals(TEST_AVATAR, response!!.avatar)
|
||||
Assert.assertEquals(TEST_USERNAME, response.username)
|
||||
Assert.assertEquals(TEST_USER_RANK, response.rank)
|
||||
Assert.assertEquals(TEST_USER_COUNT, response.categoryCount)
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will call the Mock API and returns the Leaderboard Response Object
|
||||
* @param okHttpClient
|
||||
* @param httpUrl
|
||||
* @return Leaderboard Response Object
|
||||
* @throws IOException
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
private fun sendRequest(okHttpClient: OkHttpClient, httpUrl: HttpUrl): LeaderboardResponse? {
|
||||
val request: Request = Request.Builder().url(httpUrl).build()
|
||||
val response = okHttpClient.newCall(request).execute()
|
||||
if (response.isSuccessful) {
|
||||
val gson = Gson()
|
||||
return gson.fromJson(response.body!!.string(), LeaderboardResponse::class.java)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* This method shuts down the Mock Server
|
||||
* @throws IOException
|
||||
*/
|
||||
@After
|
||||
@Throws(IOException::class)
|
||||
fun shutdown() {
|
||||
server.shutdown()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TEST_USERNAME = "user"
|
||||
private const val TEST_AVATAR = "avatar"
|
||||
private const val TEST_USER_RANK = 1
|
||||
private const val TEST_USER_COUNT = 0
|
||||
|
||||
private const val FILE_NAME = "leaderboard_sample_response.json"
|
||||
private const val ENDPOINT = "/leaderboard.py"
|
||||
|
||||
/**
|
||||
* This method converts a Input Stream to String
|
||||
* @param is takes Input Stream of JSON File as Parameter
|
||||
* @return a String with JSON data
|
||||
* @throws Exception
|
||||
*/
|
||||
@Throws(Exception::class)
|
||||
private fun convertStreamToString(`is`: InputStream): String {
|
||||
val reader = BufferedReader(InputStreamReader(`is`))
|
||||
val sb = StringBuilder()
|
||||
var line: String?
|
||||
while ((reader.readLine().also { line = it }) != null) {
|
||||
sb.append(line).append("\n")
|
||||
}
|
||||
reader.close()
|
||||
return sb.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
package fr.free.nrw.commons.leaderboard;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import fr.free.nrw.commons.profile.leaderboard.UpdateAvatarResponse;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Request.Builder;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class UpdateAvatarApiTest {
|
||||
|
||||
private static final String TEST_USERNAME = "user";
|
||||
private static final String TEST_STATUS = "200";
|
||||
private static final String TEST_MESSAGE = "Avatar Updated";
|
||||
private static final String FILE_NAME = "update_leaderboard_avatar_sample_response.json";
|
||||
private static final String ENDPOINT = "/update_avatar.py";
|
||||
MockWebServer server;
|
||||
|
||||
/**
|
||||
* This method converts a Input Stream to String
|
||||
*
|
||||
* @param is takes Input Stream of JSON File as Parameter
|
||||
* @return a String with JSON data
|
||||
* @throws Exception
|
||||
*/
|
||||
private static String convertStreamToString(final InputStream is) throws Exception {
|
||||
final BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
sb.append(line).append("\n");
|
||||
}
|
||||
reader.close();
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method initialises a Mock Server
|
||||
*/
|
||||
@Before
|
||||
public void initTest() {
|
||||
server = new MockWebServer();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will setup a Mock Server and load Test JSON Response File
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
||||
final String testResponseBody = convertStreamToString(
|
||||
getClass().getClassLoader().getResourceAsStream(FILE_NAME));
|
||||
|
||||
server.enqueue(new MockResponse().setBody(testResponseBody));
|
||||
server.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will call the Mock Server and Test it with sample values. It will test the Update
|
||||
* Avatar API call functionality and check if the object is being created with the correct
|
||||
* values
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@Test
|
||||
public void apiTest() throws IOException {
|
||||
final HttpUrl httpUrl = server.url(ENDPOINT);
|
||||
final UpdateAvatarResponse response = sendRequest(new OkHttpClient(), httpUrl);
|
||||
|
||||
Assert.assertEquals(TEST_USERNAME, response.getUser());
|
||||
Assert.assertEquals(TEST_STATUS, response.getStatus());
|
||||
Assert.assertEquals(TEST_MESSAGE, response.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will call the Mock API and returns the Update Avatar Response Object
|
||||
*
|
||||
* @param okHttpClient
|
||||
* @param httpUrl
|
||||
* @return Update Avatar Response Object
|
||||
* @throws IOException
|
||||
*/
|
||||
private UpdateAvatarResponse sendRequest(final OkHttpClient okHttpClient, final HttpUrl httpUrl)
|
||||
throws IOException {
|
||||
final Request request = new Builder().url(httpUrl).build();
|
||||
final Response response = okHttpClient.newCall(request).execute();
|
||||
if (response.isSuccessful()) {
|
||||
final Gson gson = new Gson();
|
||||
return gson.fromJson(response.body().string(), UpdateAvatarResponse.class);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method shuts down the Mock Server
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@After
|
||||
public void shutdown() throws IOException {
|
||||
server.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
package fr.free.nrw.commons.leaderboard
|
||||
|
||||
import com.google.gson.Gson
|
||||
import fr.free.nrw.commons.profile.leaderboard.UpdateAvatarResponse
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import okhttp3.mockwebserver.MockWebServer
|
||||
import org.junit.After
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.io.BufferedReader
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.InputStreamReader
|
||||
|
||||
class UpdateAvatarApiTest {
|
||||
lateinit var server: MockWebServer
|
||||
|
||||
/**
|
||||
* This method initialises a Mock Server
|
||||
*/
|
||||
@Before
|
||||
fun initTest() {
|
||||
server = MockWebServer()
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will setup a Mock Server and load Test JSON Response File
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
@Before
|
||||
@Throws(Exception::class)
|
||||
fun setUp() {
|
||||
val testResponseBody = convertStreamToString(
|
||||
javaClass.classLoader!!.getResourceAsStream(FILE_NAME)
|
||||
)
|
||||
|
||||
server.enqueue(MockResponse().setBody(testResponseBody))
|
||||
server.start()
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will call the Mock Server and Test it with sample values. It will test the Update
|
||||
* Avatar API call functionality and check if the object is being created with the correct
|
||||
* values
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@Test
|
||||
@Throws(IOException::class)
|
||||
fun apiTest() {
|
||||
val httpUrl = server.url(ENDPOINT)
|
||||
val response = sendRequest(OkHttpClient(), httpUrl)
|
||||
Assert.assertNotNull(response)
|
||||
|
||||
with(response!!) {
|
||||
Assert.assertEquals(TEST_USERNAME, user)
|
||||
Assert.assertEquals(TEST_STATUS, status)
|
||||
Assert.assertEquals(TEST_MESSAGE, message)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will call the Mock API and returns the Update Avatar Response Object
|
||||
*
|
||||
* @param okHttpClient
|
||||
* @param httpUrl
|
||||
* @return Update Avatar Response Object
|
||||
* @throws IOException
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
private fun sendRequest(okHttpClient: OkHttpClient, httpUrl: HttpUrl): UpdateAvatarResponse? {
|
||||
val request: Request = Request.Builder().url(httpUrl).build()
|
||||
val response = okHttpClient.newCall(request).execute()
|
||||
if (response.isSuccessful) {
|
||||
val gson = Gson()
|
||||
return gson.fromJson(
|
||||
response.body!!.string(),
|
||||
UpdateAvatarResponse::class.java
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* This method shuts down the Mock Server
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@After
|
||||
@Throws(IOException::class)
|
||||
fun shutdown() {
|
||||
server.shutdown()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TEST_USERNAME = "user"
|
||||
private const val TEST_STATUS = "200"
|
||||
private const val TEST_MESSAGE = "Avatar Updated"
|
||||
private const val FILE_NAME = "update_leaderboard_avatar_sample_response.json"
|
||||
private const val ENDPOINT = "/update_avatar.py"
|
||||
|
||||
/**
|
||||
* This method converts a Input Stream to String
|
||||
*
|
||||
* @param is takes Input Stream of JSON File as Parameter
|
||||
* @return a String with JSON data
|
||||
* @throws Exception
|
||||
*/
|
||||
@Throws(Exception::class)
|
||||
private fun convertStreamToString(`is`: InputStream): String {
|
||||
val reader = BufferedReader(InputStreamReader(`is`))
|
||||
val sb = StringBuilder()
|
||||
var line: String?
|
||||
while ((reader.readLine().also { line = it }) != null) {
|
||||
sb.append(line).append("\n")
|
||||
}
|
||||
reader.close()
|
||||
return sb.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue