converted/Migrated

This commit is contained in:
Sujal-Gupta-SG 2025-02-03 16:46:28 +05:30
parent 2d1834aa38
commit adf15f2fbd
17 changed files with 1098 additions and 1130 deletions

View file

@ -1,110 +1,96 @@
package fr.free.nrw.commons.contributions; package fr.free.nrw.commons.contributions
import android.net.Uri; import android.net.Uri
import android.text.TextUtils; import android.text.TextUtils
import android.view.View; import android.view.View
import android.webkit.URLUtil; import android.webkit.URLUtil
import android.widget.ImageButton; import androidx.appcompat.app.AlertDialog
import android.widget.ProgressBar; import androidx.recyclerview.widget.RecyclerView
import android.widget.RelativeLayout; import com.facebook.imagepipeline.request.ImageRequest
import android.widget.TextView; import com.facebook.imagepipeline.request.ImageRequestBuilder
import androidx.annotation.Nullable; import fr.free.nrw.commons.R
import androidx.appcompat.app.AlertDialog; import fr.free.nrw.commons.databinding.LayoutContributionBinding
import androidx.appcompat.app.AlertDialog.Builder; import fr.free.nrw.commons.media.MediaClient
import androidx.recyclerview.widget.RecyclerView; import io.reactivex.android.schedulers.AndroidSchedulers
import com.facebook.drawee.view.SimpleDraweeView; import io.reactivex.disposables.CompositeDisposable
import com.facebook.imagepipeline.request.ImageRequest; import io.reactivex.schedulers.Schedulers
import com.facebook.imagepipeline.request.ImageRequestBuilder; import java.io.File
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.ContributionsListAdapter.Callback;
import fr.free.nrw.commons.databinding.LayoutContributionBinding;
import fr.free.nrw.commons.media.MediaClient;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import java.io.File;
public class ContributionViewHolder extends RecyclerView.ViewHolder { class ContributionViewHolder internal constructor(
private val parent: View, private val callback: ContributionsListAdapter.Callback,
private val mediaClient: MediaClient
) : RecyclerView.ViewHolder(parent) {
var binding: LayoutContributionBinding = LayoutContributionBinding.bind(parent)
private final Callback callback; private var position = 0
private var contribution: Contribution? = null
private val compositeDisposable = CompositeDisposable()
private var isWikipediaButtonDisplayed = false
private val pausingPopUp: AlertDialog
var imageRequest: ImageRequest? = null
private set
LayoutContributionBinding binding; init {
binding.contributionImage.setOnClickListener { v: View? -> imageClicked() }
private int position; binding.wikipediaButton.setOnClickListener { v: View? -> wikipediaButtonClicked() }
private Contribution contribution;
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
private final MediaClient mediaClient;
private boolean isWikipediaButtonDisplayed;
private AlertDialog pausingPopUp;
private View parent;
private ImageRequest imageRequest;
ContributionViewHolder(final View parent, final Callback callback,
final MediaClient mediaClient) {
super(parent);
this.parent = parent;
this.mediaClient = mediaClient;
this.callback = callback;
binding = LayoutContributionBinding.bind(parent);
binding.contributionImage.setOnClickListener(v -> imageClicked());
binding.wikipediaButton.setOnClickListener(v -> wikipediaButtonClicked());
/* Set a dialog indicating that the upload is being paused. This is needed because pausing /* Set a dialog indicating that the upload is being paused. This is needed because pausing
an upload might take a dozen seconds. */ an upload might take a dozen seconds. */
AlertDialog.Builder builder = new Builder(parent.getContext()); val builder = AlertDialog.Builder(
builder.setCancelable(false); parent.context
builder.setView(R.layout.progress_dialog); )
pausingPopUp = builder.create(); builder.setCancelable(false)
builder.setView(R.layout.progress_dialog)
pausingPopUp = builder.create()
} }
public void init(final int position, final Contribution contribution) { fun init(position: Int, contribution: Contribution?) {
//handling crashes when the contribution is null. //handling crashes when the contribution is null.
if (null == contribution) { if (null == contribution) {
return; return
} }
this.contribution = contribution; this.contribution = contribution
this.position = position; this.position = position
binding.contributionTitle.setText(contribution.getMedia().getMostRelevantCaption()); binding.contributionTitle.text = contribution.media.mostRelevantCaption
binding.authorView.setText(contribution.getMedia().getAuthor()); binding.authorView.text = contribution.media.author
//Removes flicker of loading image. //Removes flicker of loading image.
binding.contributionImage.getHierarchy().setFadeDuration(0); binding.contributionImage.hierarchy.fadeDuration = 0
binding.contributionImage.getHierarchy().setPlaceholderImage(R.drawable.image_placeholder); binding.contributionImage.hierarchy.setPlaceholderImage(R.drawable.image_placeholder)
binding.contributionImage.getHierarchy().setFailureImage(R.drawable.image_placeholder); binding.contributionImage.hierarchy.setFailureImage(R.drawable.image_placeholder)
final String imageSource = chooseImageSource(contribution.getMedia().getThumbUrl(), val imageSource = chooseImageSource(
contribution.getLocalUri()); contribution.media.thumbUrl,
contribution.localUri
)
if (!TextUtils.isEmpty(imageSource)) { if (!TextUtils.isEmpty(imageSource)) {
if (URLUtil.isHttpsUrl(imageSource)) { if (URLUtil.isHttpsUrl(imageSource)) {
imageRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(imageSource)) imageRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(imageSource))
.setProgressiveRenderingEnabled(true) .setProgressiveRenderingEnabled(true)
.build(); .build()
} else if (URLUtil.isFileUrl(imageSource)) { } else if (URLUtil.isFileUrl(imageSource)) {
imageRequest = ImageRequest.fromUri(Uri.parse(imageSource)); imageRequest = ImageRequest.fromUri(Uri.parse(imageSource))
} else if (imageSource != null) { } else if (imageSource != null) {
final File file = new File(imageSource); val file = File(imageSource)
imageRequest = ImageRequest.fromFile(file); imageRequest = ImageRequest.fromFile(file)
} }
if (imageRequest != null) { if (imageRequest != null) {
binding.contributionImage.setImageRequest(imageRequest); binding.contributionImage.setImageRequest(imageRequest)
} }
} }
binding.contributionSequenceNumber.setText(String.valueOf(position + 1)); binding.contributionSequenceNumber.text = (position + 1).toString()
binding.contributionSequenceNumber.setVisibility(View.VISIBLE); binding.contributionSequenceNumber.visibility = View.VISIBLE
binding.wikipediaButton.setVisibility(View.GONE); binding.wikipediaButton.visibility = View.GONE
binding.contributionState.setVisibility(View.GONE); binding.contributionState.visibility = View.GONE
binding.contributionProgress.setVisibility(View.GONE); binding.contributionProgress.visibility = View.GONE
binding.imageOptions.setVisibility(View.GONE); binding.imageOptions.visibility = View.GONE
binding.contributionState.setText(""); binding.contributionState.text = ""
checkIfMediaExistsOnWikipediaPage(contribution); checkIfMediaExistsOnWikipediaPage(contribution)
} }
/** /**
@ -113,18 +99,20 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
* *
* @param contribution * @param contribution
*/ */
private void checkIfMediaExistsOnWikipediaPage(final Contribution contribution) { private fun checkIfMediaExistsOnWikipediaPage(contribution: Contribution) {
if (contribution.getWikidataPlace() == null if (contribution.wikidataPlace == null
|| contribution.getWikidataPlace().getWikipediaArticle() == null) { || contribution.wikidataPlace!!.wikipediaArticle == null
return; ) {
return
} }
final String wikipediaArticle = contribution.getWikidataPlace().getWikipediaPageTitle(); val wikipediaArticle = contribution.wikidataPlace!!.getWikipediaPageTitle()
compositeDisposable.add(mediaClient.doesPageContainMedia(wikipediaArticle) compositeDisposable.add(
.subscribeOn(Schedulers.io()) mediaClient.doesPageContainMedia(wikipediaArticle)
.observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io())
.subscribe(mediaExists -> { .observeOn(AndroidSchedulers.mainThread())
displayWikipediaButton(mediaExists); .subscribe { mediaExists: Boolean ->
})); displayWikipediaButton(mediaExists)
})
} }
/** /**
@ -134,11 +122,11 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
* *
* @param mediaExists * @param mediaExists
*/ */
private void displayWikipediaButton(Boolean mediaExists) { private fun displayWikipediaButton(mediaExists: Boolean) {
if (!mediaExists) { if (!mediaExists) {
binding.wikipediaButton.setVisibility(View.VISIBLE); binding.wikipediaButton.visibility = View.VISIBLE
isWikipediaButtonDisplayed = true; isWikipediaButtonDisplayed = true
binding.imageOptions.setVisibility(View.VISIBLE); binding.imageOptions.visibility = View.VISIBLE
} }
} }
@ -150,22 +138,15 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
* @param localUri * @param localUri
* @return * @return
*/ */
@Nullable private fun chooseImageSource(thumbUrl: String?, localUri: Uri?): String? {
private String chooseImageSource(final String thumbUrl, final Uri localUri) { return if (!TextUtils.isEmpty(thumbUrl)) thumbUrl else localUri?.toString()
return !TextUtils.isEmpty(thumbUrl) ? thumbUrl :
localUri != null ? localUri.toString() :
null;
} }
public void imageClicked() { fun imageClicked() {
callback.openMediaDetail(position, isWikipediaButtonDisplayed); callback.openMediaDetail(position, isWikipediaButtonDisplayed)
} }
public void wikipediaButtonClicked() { fun wikipediaButtonClicked() {
callback.addImageToWikipedia(contribution); callback.addImageToWikipedia(contribution)
}
public ImageRequest getImageRequest() {
return imageRequest;
} }
} }

View file

@ -500,7 +500,7 @@ class ContributionsFragment
private fun setUploadCount() { private fun setUploadCount() {
okHttpJsonApiClient okHttpJsonApiClient
?.getUploadCount((activity as MainActivity).sessionManager.currentAccount!!.name) ?.getUploadCount((activity as MainActivity).sessionManager?.currentAccount!!.name)
?.subscribeOn(Schedulers.io()) ?.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())?.let { ?.observeOn(AndroidSchedulers.mainThread())?.let {
compositeDisposable.add( compositeDisposable.add(
@ -838,7 +838,7 @@ class ContributionsFragment
mediaDetailPagerFragment!!.showImage(position, isWikipediaButtonDisplayed) mediaDetailPagerFragment!!.showImage(position, isWikipediaButtonDisplayed)
} }
override fun getMediaAtPosition(i: Int): Media { override fun getMediaAtPosition(i: Int): Media? {
return contributionsListFragment!!.getMediaAtPosition(i) return contributionsListFragment!!.getMediaAtPosition(i)
} }

View file

@ -1,273 +1,286 @@
package fr.free.nrw.commons.contributions; package fr.free.nrw.commons.contributions
import static android.view.View.GONE; import android.Manifest.permission
import static android.view.View.VISIBLE; import android.annotation.SuppressLint
import static fr.free.nrw.commons.di.NetworkingModule.NAMED_LANGUAGE_WIKI_PEDIA_WIKI_SITE; import android.content.Context
import android.content.Intent
import android.Manifest.permission; import android.content.res.Configuration
import android.content.Context; import android.net.Uri
import android.content.Intent; import android.os.Bundle
import android.content.res.Configuration; import android.os.Parcelable
import android.net.Uri; import android.view.LayoutInflater
import android.os.Bundle; import android.view.MotionEvent
import android.os.Parcelable; import android.view.View
import android.view.LayoutInflater; import android.view.ViewGroup
import android.view.MotionEvent; import android.view.animation.Animation
import android.view.View; import android.view.animation.AnimationUtils
import android.view.ViewGroup; import android.widget.LinearLayout
import android.view.animation.Animation; import androidx.activity.result.ActivityResult
import android.view.animation.AnimationUtils; import androidx.activity.result.ActivityResultLauncher
import android.widget.LinearLayout; import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions
import androidx.activity.result.ActivityResultCallback; import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.VisibleForTesting
import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions; import androidx.paging.PagedList
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult; import androidx.recyclerview.widget.GridLayoutManager
import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView
import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver
import androidx.annotation.VisibleForTesting; import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener
import androidx.fragment.app.FragmentManager; import androidx.recyclerview.widget.SimpleItemAnimator
import androidx.recyclerview.widget.GridLayoutManager; import fr.free.nrw.commons.Media
import androidx.recyclerview.widget.RecyclerView; import fr.free.nrw.commons.R
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver; import fr.free.nrw.commons.Utils
import androidx.recyclerview.widget.RecyclerView.ItemAnimator; import fr.free.nrw.commons.auth.SessionManager
import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener; import fr.free.nrw.commons.contributions.WikipediaInstructionsDialogFragment.Companion.newInstance
import androidx.recyclerview.widget.SimpleItemAnimator; import fr.free.nrw.commons.databinding.FragmentContributionsListBinding
import fr.free.nrw.commons.Media; import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
import fr.free.nrw.commons.R; import fr.free.nrw.commons.di.NetworkingModule
import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.filepicker.FilePicker
import fr.free.nrw.commons.auth.SessionManager; import fr.free.nrw.commons.media.MediaClient
import fr.free.nrw.commons.contributions.ContributionsListAdapter.Callback; import fr.free.nrw.commons.profile.ProfileActivity
import fr.free.nrw.commons.databinding.FragmentContributionsListBinding; import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment; import fr.free.nrw.commons.utils.SystemThemeUtils
import fr.free.nrw.commons.media.MediaClient; import fr.free.nrw.commons.utils.ViewUtil.showShortToast
import fr.free.nrw.commons.profile.ProfileActivity; import fr.free.nrw.commons.wikidata.model.WikiSite
import fr.free.nrw.commons.utils.DialogUtil; import org.apache.commons.lang3.StringUtils
import fr.free.nrw.commons.utils.SystemThemeUtils; import javax.inject.Inject
import fr.free.nrw.commons.utils.ViewUtil; import javax.inject.Named
import java.util.Map;
import java.util.Objects;
import javax.inject.Inject;
import javax.inject.Named;
import org.apache.commons.lang3.StringUtils;
import fr.free.nrw.commons.wikidata.model.WikiSite;
/** /**
* Created by root on 01.06.2018. * Created by root on 01.06.2018.
*/ */
class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsListContract.View,
ContributionsListAdapter.Callback, WikipediaInstructionsDialogFragment.Callback {
@JvmField
@Inject
var systemThemeUtils: SystemThemeUtils? = null
public class ContributionsListFragment extends CommonsDaggerSupportFragment implements @JvmField
ContributionsListContract.View, Callback, @Inject
WikipediaInstructionsDialogFragment.Callback { var controller: ContributionController? = null
private static final String RV_STATE = "rv_scroll_state"; @JvmField
@Inject
var mediaClient: MediaClient? = null
@JvmField
@Named(NetworkingModule.NAMED_LANGUAGE_WIKI_PEDIA_WIKI_SITE)
@Inject @Inject
SystemThemeUtils systemThemeUtils; var languageWikipediaSite: WikiSite? = null
@Inject
ContributionController controller;
@Inject
MediaClient mediaClient;
@Named(NAMED_LANGUAGE_WIKI_PEDIA_WIKI_SITE)
@Inject
WikiSite languageWikipediaSite;
@Inject
ContributionsListPresenter contributionsListPresenter;
@Inject
SessionManager sessionManager;
private FragmentContributionsListBinding binding; @JvmField
private Animation fab_close; @Inject
private Animation fab_open; var contributionsListPresenter: ContributionsListPresenter? = null
private Animation rotate_forward;
private Animation rotate_backward; @JvmField
private boolean isFabOpen; @Inject
@VisibleForTesting var sessionManager: SessionManager? = null
protected RecyclerView rvContributionsList;
private var binding: FragmentContributionsListBinding? = null
private var fab_close: Animation? = null
private var fab_open: Animation? = null
private var rotate_forward: Animation? = null
private var rotate_backward: Animation? = null
private var isFabOpen = false
private lateinit var inAppCameraLocationPermissionLauncher: ActivityResultLauncher<Array<String>>
@VisibleForTesting @VisibleForTesting
protected ContributionsListAdapter adapter; var rvContributionsList: RecyclerView? = null
@Nullable
@VisibleForTesting @VisibleForTesting
protected Callback callback; var adapter: ContributionsListAdapter? = null
private final int SPAN_COUNT_LANDSCAPE = 3; @VisibleForTesting
private final int SPAN_COUNT_PORTRAIT = 1; var callback: Callback? = null
private int contributionsSize; private val SPAN_COUNT_LANDSCAPE = 3
private String userName; private val SPAN_COUNT_PORTRAIT = 1
private final ActivityResultLauncher<Intent> galleryPickLauncherForResult = private var contributionsSize = 0
registerForActivityResult(new StartActivityForResult(), private var userName: String? = null
result -> {
controller.handleActivityResultWithCallback(requireActivity(), callbacks -> {
controller.onPictureReturnedFromGallery(result, requireActivity(), callbacks);
});
});
private final ActivityResultLauncher<Intent> customSelectorLauncherForResult = private val galleryPickLauncherForResult = registerForActivityResult<Intent, ActivityResult>(
registerForActivityResult(new StartActivityForResult(), StartActivityForResult()
result -> { ) { result: ActivityResult? ->
controller.handleActivityResultWithCallback(requireActivity(), callbacks -> { controller!!.handleActivityResultWithCallback(requireActivity()
controller.onPictureReturnedFromCustomSelector(result, requireActivity(), callbacks); ) { callbacks: FilePicker.Callbacks? ->
}); controller!!.onPictureReturnedFromGallery(
}); result!!, requireActivity(), callbacks!!
)
}
}
private final ActivityResultLauncher<Intent> cameraPickLauncherForResult = private val customSelectorLauncherForResult = registerForActivityResult<Intent, ActivityResult>(
registerForActivityResult(new StartActivityForResult(), StartActivityForResult()
result -> { ) { result: ActivityResult? ->
controller.handleActivityResultWithCallback(requireActivity(), callbacks -> { controller!!.handleActivityResultWithCallback(requireActivity()
controller.onPictureReturnedFromCamera(result, requireActivity(), callbacks); ) { callbacks: FilePicker.Callbacks? ->
}); controller!!.onPictureReturnedFromCustomSelector(
}); result!!, requireActivity(), callbacks!!
)
}
}
private ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher = registerForActivityResult( private val cameraPickLauncherForResult = registerForActivityResult<Intent, ActivityResult>(
new RequestMultiplePermissions(), StartActivityForResult()
new ActivityResultCallback<Map<String, Boolean>>() { ) { result: ActivityResult? ->
@Override controller!!.handleActivityResultWithCallback(requireActivity()
public void onActivityResult(Map<String, Boolean> result) { ) { callbacks: FilePicker.Callbacks? ->
boolean areAllGranted = true; controller!!.onPictureReturnedFromCamera(
for (final boolean b : result.values()) { result!!, requireActivity(), callbacks!!
areAllGranted = areAllGranted && b; )
} }
}
if (areAllGranted) { @SuppressLint("NewApi")
controller.locationPermissionCallback.onLocationPermissionGranted(); override fun onCreate(
} else { savedInstanceState: Bundle?
if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) { ) {
controller.handleShowRationaleFlowCameraLocation(getActivity(), super.onCreate(savedInstanceState)
inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult);
} else {
controller.locationPermissionCallback.onLocationPermissionDenied(
getActivity().getString(
R.string.in_app_camera_location_permission_denied));
}
}
}
});
@Override
public void onCreate(
@Nullable @org.jetbrains.annotations.Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Now that we are allowing this fragment to be started for //Now that we are allowing this fragment to be started for
// any userName- we expect it to be passed as an argument // any userName- we expect it to be passed as an argument
if (getArguments() != null) { if (arguments != null) {
userName = getArguments().getString(ProfileActivity.KEY_USERNAME); userName = requireArguments().getString(ProfileActivity.KEY_USERNAME)
} }
if (StringUtils.isEmpty(userName)) { if (StringUtils.isEmpty(userName)) {
userName = sessionManager.getUserName(); userName = sessionManager!!.userName
}
inAppCameraLocationPermissionLauncher =
registerForActivityResult(RequestMultiplePermissions()) { result ->
val areAllGranted = result.values.all { it }
if (areAllGranted) {
controller?.locationPermissionCallback?.onLocationPermissionGranted()
} else {
activity?.let { currentActivity ->
if (currentActivity.shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) {
controller?.handleShowRationaleFlowCameraLocation(
currentActivity,
inAppCameraLocationPermissionLauncher, // Pass launcher
cameraPickLauncherForResult
)
} else {
controller?.locationPermissionCallback?.onLocationPermissionDenied(
currentActivity.getString(R.string.in_app_camera_location_permission_denied)
)
}
}
}
} }
} }
@Override override fun onCreateView(
public View onCreateView( inflater: LayoutInflater, container: ViewGroup?,
final LayoutInflater inflater, @Nullable final ViewGroup container, savedInstanceState: Bundle?
@Nullable final Bundle savedInstanceState) { ): View? {
binding = FragmentContributionsListBinding.inflate( binding = FragmentContributionsListBinding.inflate(
inflater, container, false inflater, container, false
); )
rvContributionsList = binding.contributionsList; rvContributionsList = binding!!.contributionsList
contributionsListPresenter.onAttachView(this); contributionsListPresenter!!.onAttachView(this)
binding.fabCustomGallery.setOnClickListener(v -> launchCustomSelector()); binding!!.fabCustomGallery.setOnClickListener { v: View? -> launchCustomSelector() }
binding.fabCustomGallery.setOnLongClickListener(view -> { binding!!.fabCustomGallery.setOnLongClickListener { view: View? ->
ViewUtil.showShortToast(getContext(), R.string.custom_selector_title); showShortToast(context, fr.free.nrw.commons.R.string.custom_selector_title)
return true; true
});
if (Objects.equals(sessionManager.getUserName(), userName)) {
binding.tvContributionsOfUser.setVisibility(GONE);
binding.fabLayout.setVisibility(VISIBLE);
} else {
binding.tvContributionsOfUser.setVisibility(VISIBLE);
binding.tvContributionsOfUser.setText(
getString(R.string.contributions_of_user, userName));
binding.fabLayout.setVisibility(GONE);
} }
initAdapter(); if (sessionManager!!.userName == userName) {
binding!!.tvContributionsOfUser.visibility = View.GONE
binding!!.fabLayout.visibility = View.VISIBLE
} else {
binding!!.tvContributionsOfUser.visibility = View.VISIBLE
binding!!.tvContributionsOfUser.text =
getString(fr.free.nrw.commons.R.string.contributions_of_user, userName)
binding!!.fabLayout.visibility = View.GONE
}
initAdapter()
// pull down to refresh only enabled for self user. // pull down to refresh only enabled for self user.
if(Objects.equals(sessionManager.getUserName(), userName)){ if (sessionManager!!.userName == userName) {
binding.swipeRefreshLayout.setOnRefreshListener(() -> { binding!!.swipeRefreshLayout.setOnRefreshListener {
contributionsListPresenter.refreshList(binding.swipeRefreshLayout); contributionsListPresenter!!.refreshList(
}); binding!!.swipeRefreshLayout
)
}
} else { } else {
binding.swipeRefreshLayout.setEnabled(false); binding!!.swipeRefreshLayout.isEnabled = false
} }
return binding.getRoot(); return binding!!.root
} }
@Override override fun onDestroyView() {
public void onDestroyView() { binding = null
binding = null; super.onDestroyView()
super.onDestroyView();
} }
@Override override fun onAttach(context: Context) {
public void onAttach(Context context) { super.onAttach(context)
super.onAttach(context); if (parentFragment != null && parentFragment is ContributionsFragment) {
if (getParentFragment() != null && getParentFragment() instanceof ContributionsFragment) { callback = (parentFragment as ContributionsFragment)
callback = ((ContributionsFragment) getParentFragment());
} }
} }
@Override override fun onDetach() {
public void onDetach() { super.onDetach()
super.onDetach(); callback = null //To avoid possible memory leak
callback = null;//To avoid possible memory leak
} }
private void initAdapter() { private fun initAdapter() {
adapter = new ContributionsListAdapter(this, mediaClient); adapter = ContributionsListAdapter(this, mediaClient!!)
} }
@Override override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
public void onViewCreated(final View view, @Nullable final Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState)
super.onViewCreated(view, savedInstanceState); initRecyclerView()
initRecyclerView(); initializeAnimations()
initializeAnimations(); setListeners()
setListeners();
} }
private void initRecyclerView() { private fun initRecyclerView() {
final GridLayoutManager layoutManager = new GridLayoutManager(getContext(), val layoutManager = GridLayoutManager(
getSpanCount(getResources().getConfiguration().orientation)); context,
rvContributionsList.setLayoutManager(layoutManager); getSpanCount(resources.configuration.orientation)
)
rvContributionsList!!.layoutManager = layoutManager
//Setting flicker animation of recycler view to false. //Setting flicker animation of recycler view to false.
final ItemAnimator animator = rvContributionsList.getItemAnimator(); val animator = rvContributionsList!!.itemAnimator
if (animator instanceof SimpleItemAnimator) { if (animator is SimpleItemAnimator) {
((SimpleItemAnimator) animator).setSupportsChangeAnimations(false); animator.supportsChangeAnimations = false
} }
contributionsListPresenter.setup(userName, contributionsListPresenter!!.setup(
Objects.equals(sessionManager.getUserName(), userName)); userName,
contributionsListPresenter.contributionList.observe(getViewLifecycleOwner(), list -> { sessionManager!!.userName == userName
contributionsSize = list.size(); )
adapter.submitList(list); contributionsListPresenter!!.contributionList?.observe(
if (callback != null) { viewLifecycleOwner
callback.notifyDataSetChanged(); ) { list: PagedList<Contribution>? ->
if (list != null) {
contributionsSize = list.size
} }
}); adapter!!.submitList(list)
rvContributionsList.setAdapter(adapter); if (callback != null) {
adapter.registerAdapterDataObserver(new AdapterDataObserver() { callback!!.notifyDataSetChanged()
@Override }
public void onItemRangeInserted(int positionStart, int itemCount) { }
super.onItemRangeInserted(positionStart, itemCount); rvContributionsList!!.adapter = adapter
contributionsSize = adapter.getItemCount(); adapter!!.registerAdapterDataObserver(object : AdapterDataObserver() {
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
super.onItemRangeInserted(positionStart, itemCount)
contributionsSize = adapter!!.itemCount
if (callback != null) { if (callback != null) {
callback.notifyDataSetChanged(); callback!!.notifyDataSetChanged()
} }
if (itemCount > 0 && positionStart == 0) { if (itemCount > 0 && positionStart == 0) {
if (adapter.getContributionForPosition(positionStart) != null) { if (adapter!!.getContributionForPosition(positionStart) != null) {
rvContributionsList rvContributionsList!!
.scrollToPosition(0);//Newly upload items are always added to the top .scrollToPosition(0) //Newly upload items are always added to the top
} }
} }
} }
@ -276,146 +289,148 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
* Called whenever items in the list have changed * Called whenever items in the list have changed
* Calls viewPagerNotifyDataSetChanged() that will notify the viewpager * Calls viewPagerNotifyDataSetChanged() that will notify the viewpager
*/ */
@Override override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
public void onItemRangeChanged(final int positionStart, final int itemCount) { super.onItemRangeChanged(positionStart, itemCount)
super.onItemRangeChanged(positionStart, itemCount);
if (callback != null) { if (callback != null) {
callback.viewPagerNotifyDataSetChanged(); callback!!.viewPagerNotifyDataSetChanged()
} }
} }
}); })
//Fab close on touch outside (Scrolling or taping on item triggers this action). //Fab close on touch outside (Scrolling or taping on item triggers this action).
rvContributionsList.addOnItemTouchListener(new OnItemTouchListener() { rvContributionsList!!.addOnItemTouchListener(object : OnItemTouchListener {
/** /**
* Silently observe and/or take over touch events sent to the RecyclerView before * Silently observe and/or take over touch events sent to the RecyclerView before
* they are handled by either the RecyclerView itself or its child views. * they are handled by either the RecyclerView itself or its child views.
*/ */
@Override override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
public boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) { if (e.action == MotionEvent.ACTION_DOWN) {
if (e.getAction() == MotionEvent.ACTION_DOWN) {
if (isFabOpen) { if (isFabOpen) {
animateFAB(isFabOpen); animateFAB(isFabOpen)
} }
} }
return false; return false
} }
/** /**
* Process a touch event as part of a gesture that was claimed by returning true * Process a touch event as part of a gesture that was claimed by returning true
* from a previous call to {@link #onInterceptTouchEvent}. * from a previous call to [.onInterceptTouchEvent].
* *
* @param rv * @param rv
* @param e MotionEvent describing the touch event. All coordinates are in the * @param e MotionEvent describing the touch event. All coordinates are in the
* RecyclerView's coordinate system. * RecyclerView's coordinate system.
*/ */
@Override override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {
public void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e) {
//required abstract method DO NOT DELETE //required abstract method DO NOT DELETE
} }
/** /**
* Called when a child of RecyclerView does not want RecyclerView and its ancestors * Called when a child of RecyclerView does not want RecyclerView and its ancestors
* to intercept touch events with {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}. * to intercept touch events with [ViewGroup.onInterceptTouchEvent].
* *
* @param disallowIntercept True if the child does not want the parent to intercept * @param disallowIntercept True if the child does not want the parent to intercept
* touch events. * touch events.
*/ */
@Override override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
//required abstract method DO NOT DELETE //required abstract method DO NOT DELETE
} }
})
});
} }
private int getSpanCount(final int orientation) { private fun getSpanCount(orientation: Int): Int {
return orientation == Configuration.ORIENTATION_LANDSCAPE ? return if (orientation == Configuration.ORIENTATION_LANDSCAPE) SPAN_COUNT_LANDSCAPE else SPAN_COUNT_PORTRAIT
SPAN_COUNT_LANDSCAPE : SPAN_COUNT_PORTRAIT;
} }
@Override override fun onConfigurationChanged(newConfig: Configuration) {
public void onConfigurationChanged(final Configuration newConfig) { super.onConfigurationChanged(newConfig)
super.onConfigurationChanged(newConfig);
// check orientation // check orientation
binding.fabLayout.setOrientation( binding!!.fabLayout.orientation =
newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE ? if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) LinearLayout.HORIZONTAL else LinearLayout.VERTICAL
LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);
rvContributionsList rvContributionsList
.setLayoutManager( ?.setLayoutManager(
new GridLayoutManager(getContext(), getSpanCount(newConfig.orientation))); GridLayoutManager(context, getSpanCount(newConfig.orientation))
)
} }
private void initializeAnimations() { private fun initializeAnimations() {
fab_open = AnimationUtils.loadAnimation(getActivity(), R.anim.fab_open); fab_open = AnimationUtils.loadAnimation(activity, fr.free.nrw.commons.R.anim.fab_open)
fab_close = AnimationUtils.loadAnimation(getActivity(), R.anim.fab_close); fab_close = AnimationUtils.loadAnimation(activity, fr.free.nrw.commons.R.anim.fab_close)
rotate_forward = AnimationUtils.loadAnimation(getActivity(), R.anim.rotate_forward); rotate_forward = AnimationUtils.loadAnimation(activity, fr.free.nrw.commons.R.anim.rotate_forward)
rotate_backward = AnimationUtils.loadAnimation(getActivity(), R.anim.rotate_backward); rotate_backward = AnimationUtils.loadAnimation(activity, fr.free.nrw.commons.R.anim.rotate_backward)
} }
private void setListeners() { private fun setListeners() {
binding.fabPlus.setOnClickListener(view -> animateFAB(isFabOpen)); binding!!.fabPlus.setOnClickListener { view: View? -> animateFAB(isFabOpen) }
binding.fabCamera.setOnClickListener(view -> { binding!!.fabCamera.setOnClickListener { view: View? ->
controller.initiateCameraPick(getActivity(), inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult); controller!!.initiateCameraPick(
animateFAB(isFabOpen); requireActivity(),
}); inAppCameraLocationPermissionLauncher,
binding.fabCamera.setOnLongClickListener(view -> { cameraPickLauncherForResult
ViewUtil.showShortToast(getContext(), R.string.add_contribution_from_camera); )
return true; animateFAB(isFabOpen)
}); }
binding.fabGallery.setOnClickListener(view -> { binding!!.fabCamera.setOnLongClickListener { view: View? ->
controller.initiateGalleryPick(getActivity(), galleryPickLauncherForResult, true); showShortToast(
animateFAB(isFabOpen); context,
}); fr.free.nrw.commons.R.string.add_contribution_from_camera
binding.fabGallery.setOnLongClickListener(view -> { )
ViewUtil.showShortToast(getContext(), R.string.menu_from_gallery); true
return true; }
}); binding!!.fabGallery.setOnClickListener { view: View? ->
controller!!.initiateGalleryPick(requireActivity(), galleryPickLauncherForResult, true)
animateFAB(isFabOpen)
}
binding!!.fabGallery.setOnLongClickListener { view: View? ->
showShortToast(context, fr.free.nrw.commons.R.string.menu_from_gallery)
true
}
} }
/** /**
* Launch Custom Selector. * Launch Custom Selector.
*/ */
protected void launchCustomSelector() { protected fun launchCustomSelector() {
controller.initiateCustomGalleryPickWithPermission(getActivity(), customSelectorLauncherForResult); controller!!.initiateCustomGalleryPickWithPermission(
animateFAB(isFabOpen); requireActivity(),
customSelectorLauncherForResult
)
animateFAB(isFabOpen)
} }
public void scrollToTop() { fun scrollToTop() {
rvContributionsList.smoothScrollToPosition(0); rvContributionsList!!.smoothScrollToPosition(0)
} }
private void animateFAB(final boolean isFabOpen) { private fun animateFAB(isFabOpen: Boolean) {
this.isFabOpen = !isFabOpen; this.isFabOpen = !isFabOpen
if (binding.fabPlus.isShown()) { if (binding!!.fabPlus.isShown) {
if (isFabOpen) { if (isFabOpen) {
binding.fabPlus.startAnimation(rotate_backward); binding!!.fabPlus.startAnimation(rotate_backward)
binding.fabCamera.startAnimation(fab_close); binding!!.fabCamera.startAnimation(fab_close)
binding.fabGallery.startAnimation(fab_close); binding!!.fabGallery.startAnimation(fab_close)
binding.fabCustomGallery.startAnimation(fab_close); binding!!.fabCustomGallery.startAnimation(fab_close)
binding.fabCamera.hide(); binding!!.fabCamera.hide()
binding.fabGallery.hide(); binding!!.fabGallery.hide()
binding.fabCustomGallery.hide(); binding!!.fabCustomGallery.hide()
} else { } else {
binding.fabPlus.startAnimation(rotate_forward); binding!!.fabPlus.startAnimation(rotate_forward)
binding.fabCamera.startAnimation(fab_open); binding!!.fabCamera.startAnimation(fab_open)
binding.fabGallery.startAnimation(fab_open); binding!!.fabGallery.startAnimation(fab_open)
binding.fabCustomGallery.startAnimation(fab_open); binding!!.fabCustomGallery.startAnimation(fab_open)
binding.fabCamera.show(); binding!!.fabCamera.show()
binding.fabGallery.show(); binding!!.fabGallery.show()
binding.fabCustomGallery.show(); binding!!.fabCustomGallery.show()
} }
this.isFabOpen = !isFabOpen; this.isFabOpen = !isFabOpen
} }
} }
/** /**
* Shows welcome message if user has no contributions yet i.e. new user. * Shows welcome message if user has no contributions yet i.e. new user.
*/ */
@Override override fun showWelcomeTip(shouldShow: Boolean) {
public void showWelcomeTip(final boolean shouldShow) { binding!!.noContributionsYet.visibility =
binding.noContributionsYet.setVisibility(shouldShow ? VISIBLE : GONE); if (shouldShow) View.VISIBLE else View.GONE
} }
/** /**
@ -423,37 +438,34 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
* *
* @param shouldShow True when contributions list should be hidden. * @param shouldShow True when contributions list should be hidden.
*/ */
@Override override fun showProgress(shouldShow: Boolean) {
public void showProgress(final boolean shouldShow) { binding!!.loadingContributionsProgressBar.visibility =
binding.loadingContributionsProgressBar.setVisibility(shouldShow ? VISIBLE : GONE); if (shouldShow) View.VISIBLE else View.GONE
} }
@Override override fun showNoContributionsUI(shouldShow: Boolean) {
public void showNoContributionsUI(final boolean shouldShow) { binding!!.noContributionsYet.visibility =
binding.noContributionsYet.setVisibility(shouldShow ? VISIBLE : GONE); if (shouldShow) View.VISIBLE else View.GONE
} }
@Override override fun onSaveInstanceState(outState: Bundle) {
public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState)
super.onSaveInstanceState(outState); val layoutManager = rvContributionsList
final GridLayoutManager layoutManager = (GridLayoutManager) rvContributionsList ?.getLayoutManager() as GridLayoutManager?
.getLayoutManager(); outState.putParcelable(RV_STATE, layoutManager!!.onSaveInstanceState())
outState.putParcelable(RV_STATE, layoutManager.onSaveInstanceState());
} }
@Override override fun onViewStateRestored(savedInstanceState: Bundle?) {
public void onViewStateRestored(@Nullable Bundle savedInstanceState) { super.onViewStateRestored(savedInstanceState)
super.onViewStateRestored(savedInstanceState);
if (null != savedInstanceState) { if (null != savedInstanceState) {
final Parcelable savedRecyclerLayoutState = savedInstanceState.getParcelable(RV_STATE); val savedRecyclerLayoutState = savedInstanceState.getParcelable<Parcelable>(RV_STATE)
rvContributionsList.getLayoutManager().onRestoreInstanceState(savedRecyclerLayoutState); rvContributionsList!!.layoutManager!!.onRestoreInstanceState(savedRecyclerLayoutState)
} }
} }
@Override override fun openMediaDetail(position: Int, isWikipediaButtonDisplayed: Boolean) {
public void openMediaDetail(final int position, boolean isWikipediaButtonDisplayed) { if (null != callback) { //Just being safe, ideally they won't be called when detached
if (null != callback) {//Just being safe, ideally they won't be called when detached callback!!.showDetail(position, isWikipediaButtonDisplayed)
callback.showDetail(position, isWikipediaButtonDisplayed);
} }
} }
@ -462,16 +474,16 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
* *
* @param contribution * @param contribution
*/ */
@Override override fun addImageToWikipedia(contribution: Contribution?) {
public void addImageToWikipedia(Contribution contribution) { showAlertDialog(
DialogUtil.showAlertDialog(getActivity(), requireActivity(),
getString(R.string.add_picture_to_wikipedia_article_title), getString(fr.free.nrw.commons.R.string.add_picture_to_wikipedia_article_title),
getString(R.string.add_picture_to_wikipedia_article_desc), getString(fr.free.nrw.commons.R.string.add_picture_to_wikipedia_article_desc),
() -> { {
showAddImageToWikipediaInstructions(contribution); if (contribution != null) {
}, () -> { showAddImageToWikipediaInstructions(contribution)
// do nothing }
}); }, {})
} }
/** /**
@ -479,56 +491,61 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
* *
* @param contribution * @param contribution
*/ */
private void showAddImageToWikipediaInstructions(Contribution contribution) { private fun showAddImageToWikipediaInstructions(contribution: Contribution) {
FragmentManager fragmentManager = getFragmentManager(); val fragmentManager = fragmentManager
WikipediaInstructionsDialogFragment fragment = WikipediaInstructionsDialogFragment val fragment = newInstance(contribution)
.newInstance(contribution); fragment.callback =
fragment.setCallback(this::onConfirmClicked); WikipediaInstructionsDialogFragment.Callback { contribution: Contribution?, copyWikicode: Boolean ->
fragment.show(fragmentManager, "WikimediaFragment"); this.onConfirmClicked(
contribution,
copyWikicode
)
}
fragment.show(fragmentManager!!, "WikimediaFragment")
} }
public Media getMediaAtPosition(final int i) { fun getMediaAtPosition(i: Int): Media? {
if (adapter.getContributionForPosition(i) != null) { if (adapter!!.getContributionForPosition(i) != null) {
return adapter.getContributionForPosition(i).getMedia(); return adapter!!.getContributionForPosition(i)!!.media
} }
return null; return null
} }
public int getTotalMediaCount() { val totalMediaCount: Int
return contributionsSize; get() = contributionsSize
}
/** /**
* Open the editor for the language Wikipedia * Open the editor for the language Wikipedia
* *
* @param contribution * @param contribution
*/ */
@Override override fun onConfirmClicked(contribution: Contribution?, copyWikicode: Boolean) {
public void onConfirmClicked(@Nullable Contribution contribution, boolean copyWikicode) {
if (copyWikicode) { if (copyWikicode) {
String wikicode = contribution.getMedia().getWikiCode(); val wikicode = contribution!!.media.wikiCode
Utils.copy("wikicode", wikicode, getContext()); Utils.copy("wikicode", wikicode, context)
} }
final String url = val url =
languageWikipediaSite.mobileUrl() + "/wiki/" + contribution.getWikidataPlace() languageWikipediaSite!!.mobileUrl() + "/wiki/" + (contribution!!.wikidataPlace
.getWikipediaPageTitle(); ?.getWikipediaPageTitle())
Utils.handleWebUrl(getContext(), Uri.parse(url)); Utils.handleWebUrl(context, Uri.parse(url))
} }
public Integer getContributionStateAt(int position) { fun getContributionStateAt(position: Int): Int {
return adapter.getContributionForPosition(position).getState(); return adapter!!.getContributionForPosition(position)!!.state
} }
public interface Callback { interface Callback {
fun notifyDataSetChanged()
void notifyDataSetChanged(); fun showDetail(position: Int, isWikipediaButtonDisplayed: Boolean)
void showDetail(int position, boolean isWikipediaButtonDisplayed);
// Notify the viewpager that number of items have changed. // Notify the viewpager that number of items have changed.
void viewPagerNotifyDataSetChanged(); fun viewPagerNotifyDataSetChanged()
}
companion object {
private const val RV_STATE = "rv_scroll_state"
} }
} }

View file

@ -1,52 +1,30 @@
package fr.free.nrw.commons.contributions; package fr.free.nrw.commons.contributions
import static fr.free.nrw.commons.di.CommonsApplicationModule.IO_THREAD; import androidx.lifecycle.LiveData
import androidx.paging.DataSource
import androidx.annotation.NonNull; import androidx.paging.LivePagedListBuilder
import androidx.lifecycle.LiveData; import androidx.paging.PagedList
import androidx.paging.DataSource; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import androidx.paging.DataSource.Factory; import fr.free.nrw.commons.di.CommonsApplicationModule
import androidx.paging.LivePagedListBuilder; import io.reactivex.Scheduler
import androidx.paging.PagedList; import io.reactivex.disposables.CompositeDisposable
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import javax.inject.Inject
import fr.free.nrw.commons.contributions.ContributionsListContract.UserActionListener; import javax.inject.Named
import io.reactivex.Scheduler;
import io.reactivex.disposables.CompositeDisposable;
import java.util.Collections;
import javax.inject.Inject;
import javax.inject.Named;
import kotlin.Unit;
import kotlin.jvm.functions.Function0;
/** /**
* The presenter class for Contributions * The presenter class for Contributions
*/ */
public class ContributionsListPresenter implements UserActionListener { class ContributionsListPresenter @Inject internal constructor(
private val contributionBoundaryCallback: ContributionBoundaryCallback,
private val contributionsRemoteDataSource: ContributionsRemoteDataSource,
private val repository: ContributionsRepository,
@param:Named(CommonsApplicationModule.IO_THREAD) private val ioThreadScheduler: Scheduler
) : ContributionsListContract.UserActionListener {
private val compositeDisposable = CompositeDisposable()
private final ContributionBoundaryCallback contributionBoundaryCallback; var contributionList: LiveData<PagedList<Contribution>>? = null
private final ContributionsRepository repository;
private final Scheduler ioThreadScheduler;
private final CompositeDisposable compositeDisposable; override fun onAttachView(view: ContributionsListContract.View) {
private final ContributionsRemoteDataSource contributionsRemoteDataSource;
LiveData<PagedList<Contribution>> contributionList;
@Inject
ContributionsListPresenter(
final ContributionBoundaryCallback contributionBoundaryCallback,
final ContributionsRemoteDataSource contributionsRemoteDataSource,
final ContributionsRepository repository,
@Named(IO_THREAD) final Scheduler ioThreadScheduler) {
this.contributionBoundaryCallback = contributionBoundaryCallback;
this.repository = repository;
this.ioThreadScheduler = ioThreadScheduler;
this.contributionsRemoteDataSource = contributionsRemoteDataSource;
compositeDisposable = new CompositeDisposable();
}
@Override
public void onAttachView(final ContributionsListContract.View view) {
} }
/** /**
@ -54,46 +32,46 @@ public class ContributionsListPresenter implements UserActionListener {
* the live data object. This method can be tweaked to update the lazy loading behavior of the * the live data object. This method can be tweaked to update the lazy loading behavior of the
* contributions list * contributions list
*/ */
void setup(String userName, boolean isSelf) { fun setup(userName: String?, isSelf: Boolean) {
final PagedList.Config pagedListConfig = val pagedListConfig =
(new PagedList.Config.Builder()) (PagedList.Config.Builder())
.setPrefetchDistance(50) .setPrefetchDistance(50)
.setPageSize(10).build(); .setPageSize(10).build()
Factory<Integer, Contribution> factory; val factory: DataSource.Factory<Int, Contribution>
boolean shouldSetBoundaryCallback; val shouldSetBoundaryCallback: Boolean
if (!isSelf) { if (!isSelf) {
//We don't want to persist contributions for other user's, therefore //We don't want to persist contributions for other user's, therefore
// creating a new DataSource for them // creating a new DataSource for them
contributionsRemoteDataSource.setUserName(userName); contributionsRemoteDataSource.userName = userName
factory = new Factory<Integer, Contribution>() { factory = object : DataSource.Factory<Int, Contribution>() {
@NonNull override fun create(): DataSource<Int, Contribution> {
@Override return contributionsRemoteDataSource
public DataSource<Integer, Contribution> create() {
return contributionsRemoteDataSource;
} }
}; }
shouldSetBoundaryCallback = false; shouldSetBoundaryCallback = false
} else { } else {
contributionBoundaryCallback.setUserName(userName); contributionBoundaryCallback.userName = userName
shouldSetBoundaryCallback = true; shouldSetBoundaryCallback = true
factory = repository.fetchContributionsWithStates( factory = repository.fetchContributionsWithStates(
Collections.singletonList(Contribution.STATE_COMPLETED)); listOf(Contribution.STATE_COMPLETED)
)
} }
LivePagedListBuilder livePagedListBuilder = new LivePagedListBuilder(factory, val livePagedListBuilder: LivePagedListBuilder<Int, Contribution> = LivePagedListBuilder(
pagedListConfig); factory,
pagedListConfig
)
if (shouldSetBoundaryCallback) { if (shouldSetBoundaryCallback) {
livePagedListBuilder.setBoundaryCallback(contributionBoundaryCallback); livePagedListBuilder.setBoundaryCallback(contributionBoundaryCallback)
} }
contributionList = livePagedListBuilder.build(); contributionList = livePagedListBuilder.build()
} }
@Override override fun onDetachView() {
public void onDetachView() { compositeDisposable.clear()
compositeDisposable.clear(); contributionsRemoteDataSource.dispose()
contributionsRemoteDataSource.dispose(); contributionBoundaryCallback.dispose()
contributionBoundaryCallback.dispose();
} }
/** /**
@ -102,11 +80,12 @@ public class ContributionsListPresenter implements UserActionListener {
* @param swipeRefreshLayout used to stop refresh animation when * @param swipeRefreshLayout used to stop refresh animation when
* refresh finishes. * refresh finishes.
*/ */
@Override override fun refreshList(swipeRefreshLayout: SwipeRefreshLayout?) {
public void refreshList(final SwipeRefreshLayout swipeRefreshLayout) { contributionBoundaryCallback.refreshList {
contributionBoundaryCallback.refreshList(() -> { if (swipeRefreshLayout != null) {
swipeRefreshLayout.setRefreshing(false); swipeRefreshLayout.isRefreshing = false
return Unit.INSTANCE; }
}); Unit
}
} }
} }

View file

@ -1,42 +1,31 @@
package fr.free.nrw.commons.contributions; package fr.free.nrw.commons.contributions
import androidx.paging.DataSource.Factory; import androidx.paging.DataSource
import fr.free.nrw.commons.kvstore.JsonKvStore; import fr.free.nrw.commons.kvstore.JsonKvStore
import io.reactivex.Completable; import io.reactivex.Completable
import io.reactivex.Single; import io.reactivex.Single
import java.util.ArrayList; import javax.inject.Inject
import java.util.List; import javax.inject.Named
import javax.inject.Inject;
import javax.inject.Named;
/** /**
* The LocalDataSource class for Contributions * The LocalDataSource class for Contributions
*/ */
class ContributionsLocalDataSource { class ContributionsLocalDataSource @Inject constructor(
@param:Named("default_preferences") private val defaultKVStore: JsonKvStore,
private final ContributionDao contributionDao; private val contributionDao: ContributionDao
private final JsonKvStore defaultKVStore; ) {
/**
@Inject * Fetch default number of contributions to be show, based on user preferences
public ContributionsLocalDataSource( */
@Named("default_preferences") final JsonKvStore defaultKVStore, fun getString(key: String): String? {
final ContributionDao contributionDao) { return defaultKVStore.getString(key)
this.defaultKVStore = defaultKVStore;
this.contributionDao = contributionDao;
} }
/** /**
* Fetch default number of contributions to be show, based on user preferences * Fetch default number of contributions to be show, based on user preferences
*/ */
public String getString(final String key) { fun getLong(key: String): Long {
return defaultKVStore.getString(key); return defaultKVStore.getLong(key)
}
/**
* Fetch default number of contributions to be show, based on user preferences
*/
public long getLong(final String key) {
return defaultKVStore.getLong(key);
} }
/** /**
@ -45,13 +34,14 @@ class ContributionsLocalDataSource {
* @param uri * @param uri
* @return * @return
*/ */
public Contribution getContributionWithFileName(final String uri) { fun getContributionWithFileName(uri: String): Contribution? {
final List<Contribution> contributionWithUri = contributionDao.getContributionWithTitle( val contributionWithUri = contributionDao.getContributionWithTitle(
uri); uri
)
if (!contributionWithUri.isEmpty()) { if (!contributionWithUri.isEmpty()) {
return contributionWithUri.get(0); return contributionWithUri[0]
} }
return null; return null
} }
/** /**
@ -60,8 +50,8 @@ class ContributionsLocalDataSource {
* @param contribution * @param contribution
* @return * @return
*/ */
public Completable deleteContribution(final Contribution contribution) { fun deleteContribution(contribution: Contribution): Completable {
return contributionDao.delete(contribution); return contributionDao.delete(contribution)
} }
/** /**
@ -70,13 +60,12 @@ class ContributionsLocalDataSource {
* @param states The states of the contributions to delete. * @param states The states of the contributions to delete.
* @return A Completable indicating the result of the operation. * @return A Completable indicating the result of the operation.
*/ */
public Completable deleteContributionsWithStates(List<Integer> states) { fun deleteContributionsWithStates(states: List<Int>): Completable {
return contributionDao.deleteContributionsWithStates(states); return contributionDao.deleteContributionsWithStates(states)
} }
public Factory<Integer, Contribution> getContributions() { val contributions: DataSource.Factory<Int, Contribution>
return contributionDao.fetchContributions(); get() = contributionDao.fetchContributions()
}
/** /**
* Fetches contributions with specific states. * Fetches contributions with specific states.
@ -84,8 +73,8 @@ class ContributionsLocalDataSource {
* @param states The states of the contributions to fetch. * @param states The states of the contributions to fetch.
* @return A DataSource factory for paginated contributions with the specified states. * @return A DataSource factory for paginated contributions with the specified states.
*/ */
public Factory<Integer, Contribution> getContributionsWithStates(List<Integer> states) { fun getContributionsWithStates(states: List<Int>): DataSource.Factory<Int, Contribution> {
return contributionDao.getContributions(states); return contributionDao.getContributions(states)
} }
/** /**
@ -95,37 +84,39 @@ class ContributionsLocalDataSource {
* @return A DataSource factory for paginated contributions with the specified states sorted by * @return A DataSource factory for paginated contributions with the specified states sorted by
* date upload started. * date upload started.
*/ */
public Factory<Integer, Contribution> getContributionsWithStatesSortedByDateUploadStarted( fun getContributionsWithStatesSortedByDateUploadStarted(
List<Integer> states) { states: List<Int>
return contributionDao.getContributionsSortedByDateUploadStarted(states); ): DataSource.Factory<Int, Contribution> {
return contributionDao.getContributionsSortedByDateUploadStarted(states)
} }
public Single<List<Long>> saveContributions(final List<Contribution> contributions) { fun saveContributions(contributions: List<Contribution>): Single<List<Long>> {
final List<Contribution> contributionList = new ArrayList<>(); val contributionList: MutableList<Contribution> = ArrayList()
for (final Contribution contribution : contributions) { for (contribution in contributions) {
final Contribution oldContribution = contributionDao.getContribution( val oldContribution = contributionDao.getContribution(
contribution.getPageId()); contribution.pageId
)
if (oldContribution != null) { if (oldContribution != null) {
contribution.setWikidataPlace(oldContribution.getWikidataPlace()); contribution.wikidataPlace = oldContribution.wikidataPlace
} }
contributionList.add(contribution); contributionList.add(contribution)
} }
return contributionDao.save(contributionList); return contributionDao.save(contributionList)
} }
public Completable saveContributions(Contribution contribution) { fun saveContributions(contribution: Contribution): Completable {
return contributionDao.save(contribution); return contributionDao.save(contribution)
} }
public void set(final String key, final long value) { fun set(key: String, value: Long) {
defaultKVStore.putLong(key, value); defaultKVStore.putLong(key, value)
} }
public Completable updateContribution(final Contribution contribution) { fun updateContribution(contribution: Contribution): Completable {
return contributionDao.update(contribution); return contributionDao.update(contribution)
} }
public Completable updateContributionsWithStates(List<Integer> states, int newState) { fun updateContributionsWithStates(states: List<Int>, newState: Int): Completable {
return contributionDao.updateContributionsWithStates(states, newState); return contributionDao.updateContributionsWithStates(states, newState)
} }
} }

View file

@ -1,26 +1,16 @@
package fr.free.nrw.commons.contributions; package fr.free.nrw.commons.contributions
import javax.inject.Named; import dagger.Binds
import dagger.Binds; import dagger.Module
import dagger.Module;
import dagger.Provides;
import fr.free.nrw.commons.kvstore.JsonKvStore;
/** /**
* The Dagger Module for contributions-related presenters and other dependencies * The Dagger Module for contributions-related presenters and other dependencies
*/ */
@Module @Module
public abstract class ContributionsModule { abstract class ContributionsModule {
@Binds @Binds
public abstract ContributionsContract.UserActionListener bindsContributionsPresenter( abstract fun bindsContributionsPresenter(
ContributionsPresenter presenter presenter: ContributionsPresenter?
); ): ContributionsContract.UserActionListener?
}
@Provides
static JsonKvStore providesApplicationKvStore(
@Named("default_preferences") JsonKvStore kvStore
) {
return kvStore;
}
}

View file

@ -1,58 +1,45 @@
package fr.free.nrw.commons.contributions; package fr.free.nrw.commons.contributions
import static fr.free.nrw.commons.di.CommonsApplicationModule.IO_THREAD; import fr.free.nrw.commons.utils.ImageUtils
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK; import androidx.work.ExistingWorkPolicy
import fr.free.nrw.commons.MediaDataExtractor
import androidx.work.ExistingWorkPolicy; import fr.free.nrw.commons.di.CommonsApplicationModule
import fr.free.nrw.commons.MediaDataExtractor; import fr.free.nrw.commons.repository.UploadRepository
import fr.free.nrw.commons.contributions.ContributionsContract.UserActionListener; import fr.free.nrw.commons.upload.worker.WorkRequestHelper.Companion.makeOneTimeWorkRequest
import fr.free.nrw.commons.di.CommonsApplicationModule; import io.reactivex.Scheduler
import fr.free.nrw.commons.repository.UploadRepository; import io.reactivex.disposables.CompositeDisposable
import fr.free.nrw.commons.upload.worker.WorkRequestHelper; import timber.log.Timber
import io.reactivex.Scheduler; import javax.inject.Inject
import io.reactivex.disposables.CompositeDisposable; import javax.inject.Named
import javax.inject.Inject;
import javax.inject.Named;
import timber.log.Timber;
/** /**
* The presenter class for Contributions * The presenter class for Contributions
*/ */
public class ContributionsPresenter implements UserActionListener { class ContributionsPresenter @Inject internal constructor(
private val contributionsRepository: ContributionsRepository,
private final ContributionsRepository contributionsRepository; private val uploadRepository: UploadRepository,
private final UploadRepository uploadRepository; @param:Named(CommonsApplicationModule.IO_THREAD) private val ioThreadScheduler: Scheduler
private final Scheduler ioThreadScheduler; ) : ContributionsContract.UserActionListener {
private CompositeDisposable compositeDisposable; private var compositeDisposable: CompositeDisposable? = null
private ContributionsContract.View view; private var view: ContributionsContract.View? = null
@JvmField
@Inject @Inject
MediaDataExtractor mediaDataExtractor; var mediaDataExtractor: MediaDataExtractor? = null
@Inject override fun onAttachView(view: ContributionsContract.View) {
ContributionsPresenter(ContributionsRepository repository, this.view = view
UploadRepository uploadRepository, compositeDisposable = CompositeDisposable()
@Named(IO_THREAD) Scheduler ioThreadScheduler) {
this.contributionsRepository = repository;
this.uploadRepository = uploadRepository;
this.ioThreadScheduler = ioThreadScheduler;
} }
@Override override fun onDetachView() {
public void onAttachView(ContributionsContract.View view) { this.view = null
this.view = view; compositeDisposable!!.clear()
compositeDisposable = new CompositeDisposable();
} }
@Override override fun getContributionsWithTitle(title: String): Contribution {
public void onDetachView() { return contributionsRepository.getContributionWithFileName(title)
this.view = null; ?: throw IllegalArgumentException("Contribution not found for title: $title")
compositeDisposable.clear();
}
@Override
public Contribution getContributionsWithTitle(String title) {
return contributionsRepository.getContributionWithFileName(title);
} }
/** /**
@ -60,22 +47,25 @@ public class ContributionsPresenter implements UserActionListener {
* *
* @param contribution The contribution to check and potentially restart. * @param contribution The contribution to check and potentially restart.
*/ */
public void checkDuplicateImageAndRestartContribution(Contribution contribution) { fun checkDuplicateImageAndRestartContribution(contribution: Contribution) {
compositeDisposable.add(uploadRepository compositeDisposable!!.add(
.checkDuplicateImage(contribution.getLocalUriPath().getPath()) uploadRepository
.subscribeOn(ioThreadScheduler) .checkDuplicateImage(contribution.localUriPath!!.path)
.subscribe(imageCheckResult -> { .subscribeOn(ioThreadScheduler)
if (imageCheckResult == IMAGE_OK) { .subscribe { imageCheckResult: Int ->
contribution.setState(Contribution.STATE_QUEUED); if (imageCheckResult == ImageUtils.IMAGE_OK) {
saveContribution(contribution); contribution.state = Contribution.STATE_QUEUED
} else { saveContribution(contribution)
Timber.e("Contribution already exists"); } else {
compositeDisposable.add(contributionsRepository Timber.e("Contribution already exists")
.deleteContributionFromDB(contribution) compositeDisposable!!.add(
.subscribeOn(ioThreadScheduler) contributionsRepository
.subscribe()); .deleteContributionFromDB(contribution)
} .subscribeOn(ioThreadScheduler)
})); .subscribe()
)
}
})
} }
/** /**
@ -84,11 +74,14 @@ public class ContributionsPresenter implements UserActionListener {
* *
* @param contribution * @param contribution
*/ */
public void saveContribution(Contribution contribution) { fun saveContribution(contribution: Contribution) {
compositeDisposable.add(contributionsRepository compositeDisposable!!.add(contributionsRepository
.save(contribution) .save(contribution)
.subscribeOn(ioThreadScheduler) .subscribeOn(ioThreadScheduler)
.subscribe(() -> WorkRequestHelper.Companion.makeOneTimeWorkRequest( .subscribe {
view.getContext().getApplicationContext(), ExistingWorkPolicy.KEEP))); makeOneTimeWorkRequest(
view!!.getContext().applicationContext, ExistingWorkPolicy.KEEP
)
})
} }
} }

View file

@ -0,0 +1,28 @@
package fr.free.nrw.commons.contributions
import dagger.Module
import dagger.Provides
import fr.free.nrw.commons.kvstore.JsonKvStore
import fr.free.nrw.commons.wikidata.model.WikiSite
import javax.inject.Named
/**
* The Dagger Module for contributions-related providers
*/
@Module
class ContributionsProvidesModule {
@Provides
fun providesApplicationKvStore(
@Named("default_preferences") kvStore: JsonKvStore
): JsonKvStore {
return kvStore
}
@Provides
fun providesLanguageWikipediaSite(
@Named("language-wikipedia-wikisite") languageWikipediaSite: WikiSite
): WikiSite {
return languageWikipediaSite
}
}

View file

@ -1,30 +1,19 @@
package fr.free.nrw.commons.contributions; package fr.free.nrw.commons.contributions
import androidx.paging.DataSource.Factory; import androidx.paging.DataSource
import io.reactivex.Completable; import io.reactivex.Completable
import java.util.List; import io.reactivex.Single
import javax.inject.Inject
import javax.inject.Inject;
import io.reactivex.Single;
/** /**
* The repository class for contributions * The repository class for contributions
*/ */
public class ContributionsRepository { class ContributionsRepository @Inject constructor(private val localDataSource: ContributionsLocalDataSource) {
private ContributionsLocalDataSource localDataSource;
@Inject
public ContributionsRepository(ContributionsLocalDataSource localDataSource) {
this.localDataSource = localDataSource;
}
/** /**
* Fetch default number of contributions to be show, based on user preferences * Fetch default number of contributions to be show, based on user preferences
*/ */
public String getString(String key) { fun getString(key: String): String? {
return localDataSource.getString(key); return localDataSource.getString(key)
} }
/** /**
@ -33,8 +22,8 @@ public class ContributionsRepository {
* @param contribution * @param contribution
* @return * @return
*/ */
public Completable deleteContributionFromDB(Contribution contribution) { fun deleteContributionFromDB(contribution: Contribution): Completable {
return localDataSource.deleteContribution(contribution); return localDataSource.deleteContribution(contribution)
} }
/** /**
@ -43,8 +32,8 @@ public class ContributionsRepository {
* @param states The states of the contributions to delete. * @param states The states of the contributions to delete.
* @return A Completable indicating the result of the operation. * @return A Completable indicating the result of the operation.
*/ */
public Completable deleteContributionsFromDBWithStates(List<Integer> states) { fun deleteContributionsFromDBWithStates(states: List<Int>): Completable {
return localDataSource.deleteContributionsWithStates(states); return localDataSource.deleteContributionsWithStates(states)
} }
/** /**
@ -53,12 +42,12 @@ public class ContributionsRepository {
* @param fileName * @param fileName
* @return * @return
*/ */
public Contribution getContributionWithFileName(String fileName) { fun getContributionWithFileName(fileName: String): Contribution? {
return localDataSource.getContributionWithFileName(fileName); return localDataSource.getContributionWithFileName(fileName)
} }
public Factory<Integer, Contribution> fetchContributions() { fun fetchContributions(): DataSource.Factory<Int, Contribution> {
return localDataSource.getContributions(); return localDataSource.contributions
} }
/** /**
@ -67,8 +56,8 @@ public class ContributionsRepository {
* @param states The states of the contributions to fetch. * @param states The states of the contributions to fetch.
* @return A DataSource factory for paginated contributions with the specified states. * @return A DataSource factory for paginated contributions with the specified states.
*/ */
public Factory<Integer, Contribution> fetchContributionsWithStates(List<Integer> states) { fun fetchContributionsWithStates(states: List<Int>): DataSource.Factory<Int, Contribution> {
return localDataSource.getContributionsWithStates(states); return localDataSource.getContributionsWithStates(states)
} }
/** /**
@ -78,25 +67,26 @@ public class ContributionsRepository {
* @return A DataSource factory for paginated contributions with the specified states sorted by * @return A DataSource factory for paginated contributions with the specified states sorted by
* date upload started. * date upload started.
*/ */
public Factory<Integer, Contribution> fetchContributionsWithStatesSortedByDateUploadStarted( fun fetchContributionsWithStatesSortedByDateUploadStarted(
List<Integer> states) { states: List<Int>
return localDataSource.getContributionsWithStatesSortedByDateUploadStarted(states); ): DataSource.Factory<Int, Contribution> {
return localDataSource.getContributionsWithStatesSortedByDateUploadStarted(states)
} }
public Single<List<Long>> save(List<Contribution> contributions) { fun save(contributions: List<Contribution>): Single<List<Long>> {
return localDataSource.saveContributions(contributions); return localDataSource.saveContributions(contributions)
} }
public Completable save(Contribution contributions) { fun save(contributions: Contribution): Completable {
return localDataSource.saveContributions(contributions); return localDataSource.saveContributions(contributions)
} }
public void set(String key, long value) { operator fun set(key: String, value: Long) {
localDataSource.set(key, value); localDataSource.set(key, value)
} }
public Completable updateContribution(Contribution contribution) { fun updateContribution(contribution: Contribution): Completable {
return localDataSource.updateContribution(contribution); return localDataSource.updateContribution(contribution)
} }
/** /**
@ -106,7 +96,7 @@ public class ContributionsRepository {
* @param newState The new state to set. * @param newState The new state to set.
* @return A Completable indicating the result of the operation. * @return A Completable indicating the result of the operation.
*/ */
public Completable updateContributionsWithStates(List<Integer> states, int newState) { fun updateContributionsWithStates(states: List<Int>, newState: Int): Completable {
return localDataSource.updateContributionsWithStates(states, newState); return localDataSource.updateContributionsWithStates(states, newState)
} }
} }

View file

@ -1,159 +1,156 @@
package fr.free.nrw.commons.contributions; package fr.free.nrw.commons.contributions
import android.annotation.SuppressLint; import android.annotation.SuppressLint
import android.app.Activity; import android.content.Context
import android.content.Context; import android.content.Intent
import android.content.Intent; import android.os.Bundle
import android.content.SharedPreferences; import android.view.Menu
import android.os.Bundle; import android.view.MenuItem
import android.view.Menu; import android.view.View
import android.view.MenuItem; import androidx.fragment.app.Fragment
import android.view.View; import androidx.fragment.app.FragmentManager
import androidx.annotation.NonNull; import androidx.work.ExistingWorkPolicy
import androidx.annotation.Nullable; import com.google.android.material.bottomnavigation.BottomNavigationView
import androidx.fragment.app.Fragment; import fr.free.nrw.commons.R
import androidx.fragment.app.FragmentManager; import fr.free.nrw.commons.WelcomeActivity
import androidx.work.ExistingWorkPolicy; import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.databinding.MainBinding; import fr.free.nrw.commons.bookmarks.BookmarkFragment
import fr.free.nrw.commons.R; import fr.free.nrw.commons.contributions.ContributionsFragment.Companion.newInstance
import fr.free.nrw.commons.WelcomeActivity; import fr.free.nrw.commons.databinding.MainBinding
import fr.free.nrw.commons.auth.SessionManager; import fr.free.nrw.commons.explore.ExploreFragment
import fr.free.nrw.commons.bookmarks.BookmarkFragment; import fr.free.nrw.commons.kvstore.JsonKvStore
import fr.free.nrw.commons.explore.ExploreFragment; import fr.free.nrw.commons.location.LocationServiceManager
import fr.free.nrw.commons.kvstore.JsonKvStore; import fr.free.nrw.commons.media.MediaDetailPagerFragment
import fr.free.nrw.commons.location.LocationServiceManager; import fr.free.nrw.commons.navtab.MoreBottomSheetFragment
import fr.free.nrw.commons.media.MediaDetailPagerFragment; import fr.free.nrw.commons.navtab.MoreBottomSheetLoggedOutFragment
import fr.free.nrw.commons.navtab.MoreBottomSheetFragment; import fr.free.nrw.commons.navtab.NavTab
import fr.free.nrw.commons.navtab.MoreBottomSheetLoggedOutFragment; import fr.free.nrw.commons.navtab.NavTabLayout
import fr.free.nrw.commons.navtab.NavTab; import fr.free.nrw.commons.navtab.NavTabLoggedOut
import fr.free.nrw.commons.navtab.NavTabLayout; import fr.free.nrw.commons.nearby.Place
import fr.free.nrw.commons.navtab.NavTabLoggedOut; import fr.free.nrw.commons.nearby.fragments.NearbyParentFragment
import fr.free.nrw.commons.nearby.Place; import fr.free.nrw.commons.notification.NotificationActivity.Companion.startYourself
import fr.free.nrw.commons.nearby.fragments.NearbyParentFragment; import fr.free.nrw.commons.notification.NotificationController
import fr.free.nrw.commons.nearby.fragments.NearbyParentFragment.NearbyParentFragmentInstanceReadyCallback; import fr.free.nrw.commons.quiz.QuizChecker
import fr.free.nrw.commons.notification.NotificationActivity; import fr.free.nrw.commons.settings.SettingsFragment
import fr.free.nrw.commons.notification.NotificationController; import fr.free.nrw.commons.theme.BaseActivity
import fr.free.nrw.commons.quiz.QuizChecker; import fr.free.nrw.commons.upload.UploadProgressActivity
import fr.free.nrw.commons.settings.SettingsFragment; import fr.free.nrw.commons.upload.worker.WorkRequestHelper.Companion.makeOneTimeWorkRequest
import fr.free.nrw.commons.theme.BaseActivity; import fr.free.nrw.commons.utils.ViewUtilWrapper
import fr.free.nrw.commons.upload.UploadProgressActivity; import io.reactivex.Completable
import fr.free.nrw.commons.upload.worker.WorkRequestHelper; import io.reactivex.functions.Consumer
import fr.free.nrw.commons.utils.PermissionUtils; import io.reactivex.schedulers.Schedulers
import fr.free.nrw.commons.utils.ViewUtilWrapper; import timber.log.Timber
import io.reactivex.Completable; import java.util.Calendar
import io.reactivex.schedulers.Schedulers; import javax.inject.Inject
import java.util.Calendar; import javax.inject.Named
import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import timber.log.Timber;
public class MainActivity extends BaseActivity
implements FragmentManager.OnBackStackChangedListener {
class MainActivity : BaseActivity(), FragmentManager.OnBackStackChangedListener {
@JvmField
@Inject @Inject
SessionManager sessionManager; var sessionManager: SessionManager? = null
@Inject
ContributionController controller;
@Inject
ContributionDao contributionDao;
private ContributionsFragment contributionsFragment; @JvmField
private NearbyParentFragment nearbyParentFragment; @Inject
private ExploreFragment exploreFragment; var controller: ContributionController? = null
private BookmarkFragment bookmarkFragment;
public ActiveFragment activeFragment;
private MediaDetailPagerFragment mediaDetailPagerFragment;
private NavTabLayout.OnNavigationItemSelectedListener navListener;
@JvmField
@Inject @Inject
public LocationServiceManager locationManager; var contributionDao: ContributionDao? = null
private var contributionsFragment: ContributionsFragment? = null
private var nearbyParentFragment: NearbyParentFragment? = null
private var exploreFragment: ExploreFragment? = null
private var bookmarkFragment: BookmarkFragment? = null
@JvmField
var activeFragment: ActiveFragment? = null
private val mediaDetailPagerFragment: MediaDetailPagerFragment? = null
var navListener: BottomNavigationView.OnNavigationItemSelectedListener? = null
private set
@JvmField
@Inject @Inject
NotificationController notificationController; var locationManager: LocationServiceManager? = null
@JvmField
@Inject @Inject
QuizChecker quizChecker; var notificationController: NotificationController? = null
@JvmField
@Inject
var quizChecker: QuizChecker? = null
@JvmField
@Inject @Inject
@Named("default_preferences") @Named("default_preferences")
public var applicationKvStore: JsonKvStore? = null
JsonKvStore applicationKvStore;
@JvmField
@Inject @Inject
ViewUtilWrapper viewUtilWrapper; var viewUtilWrapper: ViewUtilWrapper? = null
public Menu menu; var menu: Menu? = null
public MainBinding binding; @JvmField
var binding: MainBinding? = null
NavTabLayout tabLayout; var tabLayout: NavTabLayout? = null
/** override fun onSupportNavigateUp(): Boolean {
* Consumers should be simply using this method to use this activity.
*
* @param context A Context of the application package implementing this class.
*/
public static void startYourself(Context context) {
Intent intent = new Intent(context, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP);
context.startActivity(intent);
}
@Override
public boolean onSupportNavigateUp() {
if (activeFragment == ActiveFragment.CONTRIBUTIONS) { if (activeFragment == ActiveFragment.CONTRIBUTIONS) {
if (!contributionsFragment.backButtonClicked()) { if (!contributionsFragment!!.backButtonClicked()) {
return false; return false
} }
} else { } else {
onBackPressed(); onBackPressed()
showTabs(); showTabs()
} }
return true; return true
} }
@Override public override fun onCreate(savedInstanceState: Bundle?) {
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState)
super.onCreate(savedInstanceState); binding = MainBinding.inflate(layoutInflater)
binding = MainBinding.inflate(getLayoutInflater()); setContentView(binding!!.root)
setContentView(binding.getRoot()); setSupportActionBar(binding!!.toolbarBinding.toolbar)
setSupportActionBar(binding.toolbarBinding.toolbar); tabLayout = binding!!.fragmentMainNavTabLayout
tabLayout = binding.fragmentMainNavTabLayout; loadLocale()
loadLocale();
binding.toolbarBinding.toolbar.setNavigationOnClickListener(view -> { binding!!.toolbarBinding.toolbar.setNavigationOnClickListener { view: View? ->
onSupportNavigateUp(); onSupportNavigateUp()
}); }
/* /*
"first_edit_depict" is a key for getting information about opening the depiction editor "first_edit_depict" is a key for getting information about opening the depiction editor
screen for the first time after opening the app. screen for the first time after opening the app.
Getting true by the key means the depiction editor screen is opened for the first time Getting true by the key means the depiction editor screen is opened for the first time
after opening the app. after opening the app.
Getting false by the key means the depiction editor screen is not opened for the first time Getting false by the key means the depiction editor screen is not opened for the first time
after opening the app. after opening the app.
*/ */
applicationKvStore.putBoolean("first_edit_depict", true); applicationKvStore!!.putBoolean("first_edit_depict", true)
if (applicationKvStore.getBoolean("login_skipped") == true) { if (applicationKvStore!!.getBoolean("login_skipped") == true) {
setTitle(getString(R.string.navigation_item_explore)); title = getString(R.string.navigation_item_explore)
setUpLoggedOutPager(); setUpLoggedOutPager()
} else { } else {
if (applicationKvStore.getBoolean("firstrun", true)) { if (applicationKvStore!!.getBoolean("firstrun", true)) {
applicationKvStore.putBoolean("hasAlreadyLaunchedBigMultiupload", false); applicationKvStore!!.putBoolean("hasAlreadyLaunchedBigMultiupload", false)
applicationKvStore.putBoolean("hasAlreadyLaunchedCategoriesDialog", false); applicationKvStore!!.putBoolean("hasAlreadyLaunchedCategoriesDialog", false)
} }
if (savedInstanceState == null) { if (savedInstanceState == null) {
//starting a fresh fragment. //starting a fresh fragment.
// Open Last opened screen if it is Contributions or Nearby, otherwise Contributions // Open Last opened screen if it is Contributions or Nearby, otherwise Contributions
if (applicationKvStore.getBoolean("last_opened_nearby")) { if (applicationKvStore!!.getBoolean("last_opened_nearby")) {
setTitle(getString(R.string.nearby_fragment)); title = getString(R.string.nearby_fragment)
showNearby(); showNearby()
loadFragment(NearbyParentFragment.newInstance(), false); loadFragment(NearbyParentFragment.newInstance(), false)
} else { } else {
setTitle(getString(R.string.contributions_fragment)); title = getString(R.string.contributions_fragment)
loadFragment(ContributionsFragment.newInstance(), false); loadFragment(newInstance(), false)
} }
} }
setUpPager(); setUpPager()
/** /**
* Ask the user for media location access just after login * Ask the user for media location access just after login
* so that location in the EXIF metadata of the images shared by the user * so that location in the EXIF metadata of the images shared by the user
@ -169,99 +166,107 @@ public class MainActivity extends BaseActivity
// R.string.add_location_manually, // R.string.add_location_manually,
// permission.ACCESS_MEDIA_LOCATION); // permission.ACCESS_MEDIA_LOCATION);
// } // }
checkAndResumeStuckUploads(); checkAndResumeStuckUploads()
} }
} }
public void setSelectedItemId(int id) { fun setSelectedItemId(id: Int) {
binding.fragmentMainNavTabLayout.setSelectedItemId(id); binding!!.fragmentMainNavTabLayout.selectedItemId = id
} }
private void setUpPager() { private fun setUpPager() {
binding.fragmentMainNavTabLayout.setOnNavigationItemSelectedListener( binding!!.fragmentMainNavTabLayout.setOnNavigationItemSelectedListener(
navListener = (item) -> { BottomNavigationView.OnNavigationItemSelectedListener { item: MenuItem ->
if (!item.getTitle().equals(getString(R.string.more))) { if (item.title != getString(R.string.more)) {
// do not change title for more fragment // do not change title for more fragment
setTitle(item.getTitle()); title = item.title
} }
// set last_opened_nearby true if item is nearby screen else set false // set last_opened_nearby true if item is nearby screen else set false
applicationKvStore.putBoolean("last_opened_nearby", applicationKvStore!!.putBoolean(
item.getTitle().equals(getString(R.string.nearby_fragment))); "last_opened_nearby",
final Fragment fragment = NavTab.of(item.getOrder()).newInstance(); item.title == getString(R.string.nearby_fragment)
return loadFragment(fragment, true); )
}); val fragment = NavTab.of(item.order).newInstance()
loadFragment(fragment, true)
}.also { navListener = it })
} }
private void setUpLoggedOutPager() { private fun setUpLoggedOutPager() {
loadFragment(ExploreFragment.newInstance(), false); loadFragment(ExploreFragment.newInstance(), false)
binding.fragmentMainNavTabLayout.setOnNavigationItemSelectedListener(item -> { binding!!.fragmentMainNavTabLayout.setOnNavigationItemSelectedListener { item: MenuItem ->
if (!item.getTitle().equals(getString(R.string.more))) { if (item.title != getString(R.string.more)) {
// do not change title for more fragment // do not change title for more fragment
setTitle(item.getTitle()); title = item.title
} }
Fragment fragment = NavTabLoggedOut.of(item.getOrder()).newInstance(); val fragment =
return loadFragment(fragment, true); NavTabLoggedOut.of(item.order).newInstance()
}); loadFragment(fragment, true)
}
} }
private boolean loadFragment(Fragment fragment, boolean showBottom) { private fun loadFragment(fragment: Fragment?, showBottom: Boolean): Boolean {
//showBottom so that we do not show the bottom tray again when constructing //showBottom so that we do not show the bottom tray again when constructing
//from the saved instance state. //from the saved instance state.
if (fragment instanceof ContributionsFragment) { if (fragment is ContributionsFragment) {
if (activeFragment == ActiveFragment.CONTRIBUTIONS) { if (activeFragment == ActiveFragment.CONTRIBUTIONS) {
// scroll to top if already on the Contributions tab // scroll to top if already on the Contributions tab
contributionsFragment.scrollToTop(); contributionsFragment!!.scrollToTop()
return true; return true
} }
contributionsFragment = (ContributionsFragment) fragment; contributionsFragment = fragment
activeFragment = ActiveFragment.CONTRIBUTIONS; activeFragment = ActiveFragment.CONTRIBUTIONS
} else if (fragment instanceof NearbyParentFragment) { } else if (fragment is NearbyParentFragment) {
if (activeFragment == ActiveFragment.NEARBY) { // Do nothing if same tab if (activeFragment == ActiveFragment.NEARBY) { // Do nothing if same tab
return true; return true
} }
nearbyParentFragment = (NearbyParentFragment) fragment; nearbyParentFragment = fragment
activeFragment = ActiveFragment.NEARBY; activeFragment = ActiveFragment.NEARBY
} else if (fragment instanceof ExploreFragment) { } else if (fragment is ExploreFragment) {
if (activeFragment == ActiveFragment.EXPLORE) { // Do nothing if same tab if (activeFragment == ActiveFragment.EXPLORE) { // Do nothing if same tab
return true; return true
} }
exploreFragment = (ExploreFragment) fragment; exploreFragment = fragment
activeFragment = ActiveFragment.EXPLORE; activeFragment = ActiveFragment.EXPLORE
} else if (fragment instanceof BookmarkFragment) { } else if (fragment is BookmarkFragment) {
if (activeFragment == ActiveFragment.BOOKMARK) { // Do nothing if same tab if (activeFragment == ActiveFragment.BOOKMARK) { // Do nothing if same tab
return true; return true
} }
bookmarkFragment = (BookmarkFragment) fragment; bookmarkFragment = fragment
activeFragment = ActiveFragment.BOOKMARK; activeFragment = ActiveFragment.BOOKMARK
} else if (fragment == null && showBottom) { } else if (fragment == null && showBottom) {
if (applicationKvStore.getBoolean("login_skipped") if (applicationKvStore!!.getBoolean("login_skipped")
== true) { // If logged out, more sheet is different == true
MoreBottomSheetLoggedOutFragment bottomSheet = new MoreBottomSheetLoggedOutFragment(); ) { // If logged out, more sheet is different
bottomSheet.show(getSupportFragmentManager(), val bottomSheet = MoreBottomSheetLoggedOutFragment()
"MoreBottomSheetLoggedOut"); bottomSheet.show(
supportFragmentManager,
"MoreBottomSheetLoggedOut"
)
} else { } else {
MoreBottomSheetFragment bottomSheet = new MoreBottomSheetFragment(); val bottomSheet = MoreBottomSheetFragment()
bottomSheet.show(getSupportFragmentManager(), bottomSheet.show(
"MoreBottomSheet"); supportFragmentManager,
"MoreBottomSheet"
)
} }
} }
if (fragment != null) { if (fragment != null) {
getSupportFragmentManager() supportFragmentManager
.beginTransaction() .beginTransaction()
.replace(R.id.fragmentContainer, fragment) .replace(R.id.fragmentContainer, fragment)
.commit(); .commit()
return true; return true
} }
return false; return false
} }
public void hideTabs() { fun hideTabs() {
binding.fragmentMainNavTabLayout.setVisibility(View.GONE); binding!!.fragmentMainNavTabLayout.visibility = View.GONE
} }
public void showTabs() { fun showTabs() {
binding.fragmentMainNavTabLayout.setVisibility(View.VISIBLE); binding!!.fragmentMainNavTabLayout.visibility = View.VISIBLE
} }
/** /**
@ -270,120 +275,121 @@ public class MainActivity extends BaseActivity
* *
* @param uploadCount * @param uploadCount
*/ */
public void setNumOfUploads(int uploadCount) { fun setNumOfUploads(uploadCount: Int) {
if (activeFragment == ActiveFragment.CONTRIBUTIONS) { if (activeFragment == ActiveFragment.CONTRIBUTIONS) {
setTitle(getResources().getString(R.string.contributions_fragment) + " " + ( title =
!(uploadCount == 0) ? resources.getString(R.string.contributions_fragment) + " " + (if (uploadCount != 0)
getResources() resources
.getQuantityString(R.plurals.contributions_subtitle, .getQuantityString(
uploadCount, uploadCount) R.plurals.contributions_subtitle,
: getString(R.string.contributions_subtitle_zero))); uploadCount, uploadCount
)
else
getString(R.string.contributions_subtitle_zero))
} }
} }
/** /**
* Resume the uploads that got stuck because of the app being killed or the device being * Resume the uploads that got stuck because of the app being killed or the device being
* rebooted. * rebooted.
* <p> *
*
* When the app is terminated or the device is restarted, contributions remain in the * When the app is terminated or the device is restarted, contributions remain in the
* 'STATE_IN_PROGRESS' state. This status persists and doesn't change during these events. So, * 'STATE_IN_PROGRESS' state. This status persists and doesn't change during these events. So,
* retrieving contributions labeled as 'STATE_IN_PROGRESS' from the database will provide the * retrieving contributions labeled as 'STATE_IN_PROGRESS' from the database will provide the
* list of uploads that appear as stuck on opening the app again * list of uploads that appear as stuck on opening the app again
*/ */
@SuppressLint("CheckResult") @SuppressLint("CheckResult")
private void checkAndResumeStuckUploads() { private fun checkAndResumeStuckUploads() {
List<Contribution> stuckUploads = contributionDao.getContribution( val stuckUploads = contributionDao!!.getContribution(
Collections.singletonList(Contribution.STATE_IN_PROGRESS)) listOf(Contribution.STATE_IN_PROGRESS)
)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.blockingGet(); .blockingGet()
Timber.d("Resuming " + stuckUploads.size() + " uploads..."); Timber.d("Resuming " + stuckUploads.size + " uploads...")
if (!stuckUploads.isEmpty()) { if (!stuckUploads.isEmpty()) {
for (Contribution contribution : stuckUploads) { for (contribution in stuckUploads) {
contribution.setState(Contribution.STATE_QUEUED); contribution.state = Contribution.STATE_QUEUED
contribution.setDateUploadStarted(Calendar.getInstance().getTime()); contribution.dateUploadStarted = Calendar.getInstance().time
Completable.fromAction(() -> contributionDao.saveSynchronous(contribution)) Completable.fromAction { contributionDao!!.saveSynchronous(contribution) }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.subscribe(); .subscribe()
} }
WorkRequestHelper.Companion.makeOneTimeWorkRequest( makeOneTimeWorkRequest(
this, ExistingWorkPolicy.APPEND_OR_REPLACE); this, ExistingWorkPolicy.APPEND_OR_REPLACE
)
} }
} }
@Override override fun onPostCreate(savedInstanceState: Bundle?) {
protected void onPostCreate(@Nullable Bundle savedInstanceState) { super.onPostCreate(savedInstanceState)
super.onPostCreate(savedInstanceState);
//quizChecker.initQuizCheck(this); //quizChecker.initQuizCheck(this);
} }
@Override override fun onSaveInstanceState(outState: Bundle) {
protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState)
super.onSaveInstanceState(outState); outState.putInt("viewPagerCurrentItem", binding!!.pager.currentItem)
outState.putInt("viewPagerCurrentItem", binding.pager.getCurrentItem()); outState.putString("activeFragment", activeFragment!!.name)
outState.putString("activeFragment", activeFragment.name());
} }
@Override override fun onRestoreInstanceState(savedInstanceState: Bundle) {
protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState)
super.onRestoreInstanceState(savedInstanceState); val activeFragmentName = savedInstanceState.getString("activeFragment")
String activeFragmentName = savedInstanceState.getString("activeFragment");
if (activeFragmentName != null) { if (activeFragmentName != null) {
restoreActiveFragment(activeFragmentName); restoreActiveFragment(activeFragmentName)
} }
} }
private void restoreActiveFragment(@NonNull String fragmentName) { private fun restoreActiveFragment(fragmentName: String) {
if (fragmentName.equals(ActiveFragment.CONTRIBUTIONS.name())) { if (fragmentName == ActiveFragment.CONTRIBUTIONS.name) {
setTitle(getString(R.string.contributions_fragment)); title = getString(R.string.contributions_fragment)
loadFragment(ContributionsFragment.newInstance(), false); loadFragment(newInstance(), false)
} else if (fragmentName.equals(ActiveFragment.NEARBY.name())) { } else if (fragmentName == ActiveFragment.NEARBY.name) {
setTitle(getString(R.string.nearby_fragment)); title = getString(R.string.nearby_fragment)
loadFragment(NearbyParentFragment.newInstance(), false); loadFragment(NearbyParentFragment.newInstance(), false)
} else if (fragmentName.equals(ActiveFragment.EXPLORE.name())) { } else if (fragmentName == ActiveFragment.EXPLORE.name) {
setTitle(getString(R.string.navigation_item_explore)); title = getString(R.string.navigation_item_explore)
loadFragment(ExploreFragment.newInstance(), false); loadFragment(ExploreFragment.newInstance(), false)
} else if (fragmentName.equals(ActiveFragment.BOOKMARK.name())) { } else if (fragmentName == ActiveFragment.BOOKMARK.name) {
setTitle(getString(R.string.bookmarks)); title = getString(R.string.bookmarks)
loadFragment(BookmarkFragment.newInstance(), false); loadFragment(BookmarkFragment.newInstance(), false)
} }
} }
@Override override fun onBackPressed() {
public void onBackPressed() {
if (contributionsFragment != null && activeFragment == ActiveFragment.CONTRIBUTIONS) { if (contributionsFragment != null && activeFragment == ActiveFragment.CONTRIBUTIONS) {
// Means that contribution fragment is visible // Means that contribution fragment is visible
if (!contributionsFragment.backButtonClicked()) {//If this one does not wan't to handle if (!contributionsFragment!!.backButtonClicked()) { //If this one does not wan't to handle
// the back press, let the activity do so // the back press, let the activity do so
super.onBackPressed(); super.onBackPressed()
} }
} else if (nearbyParentFragment != null && activeFragment == ActiveFragment.NEARBY) { } else if (nearbyParentFragment != null && activeFragment == ActiveFragment.NEARBY) {
// Means that nearby fragment is visible // Means that nearby fragment is visible
/* If function nearbyParentFragment.backButtonClick() returns false, it means that the bottomsheet is /* If function nearbyParentFragment.backButtonClick() returns false, it means that the bottomsheet is
not expanded. So if the back button is pressed, then go back to the Contributions tab */ not expanded. So if the back button is pressed, then go back to the Contributions tab */
if (!nearbyParentFragment.backButtonClicked()) { if (!nearbyParentFragment!!.backButtonClicked()) {
getSupportFragmentManager().beginTransaction().remove(nearbyParentFragment) supportFragmentManager.beginTransaction().remove(nearbyParentFragment!!)
.commit(); .commit()
setSelectedItemId(NavTab.CONTRIBUTIONS.code()); setSelectedItemId(NavTab.CONTRIBUTIONS.code())
} }
} else if (exploreFragment != null && activeFragment == ActiveFragment.EXPLORE) { } else if (exploreFragment != null && activeFragment == ActiveFragment.EXPLORE) {
// Means that explore fragment is visible // Means that explore fragment is visible
if (!exploreFragment.onBackPressed()) { if (!exploreFragment!!.onBackPressed()) {
if (applicationKvStore.getBoolean("login_skipped")) { if (applicationKvStore!!.getBoolean("login_skipped")) {
super.onBackPressed(); super.onBackPressed()
} else { } else {
setSelectedItemId(NavTab.CONTRIBUTIONS.code()); setSelectedItemId(NavTab.CONTRIBUTIONS.code())
} }
} }
} else if (bookmarkFragment != null && activeFragment == ActiveFragment.BOOKMARK) { } else if (bookmarkFragment != null && activeFragment == ActiveFragment.BOOKMARK) {
// Means that bookmark fragment is visible // Means that bookmark fragment is visible
bookmarkFragment.onBackPressed(); bookmarkFragment!!.onBackPressed()
} else { } else {
super.onBackPressed(); super.onBackPressed()
} }
} }
@Override override fun onBackStackChanged() {
public void onBackStackChanged() {
//initBackButton(); //initBackButton();
} }
@ -391,77 +397,76 @@ public class MainActivity extends BaseActivity
* Retry all failed uploads as soon as the user returns to the app * Retry all failed uploads as soon as the user returns to the app
*/ */
@SuppressLint("CheckResult") @SuppressLint("CheckResult")
private void retryAllFailedUploads() { private fun retryAllFailedUploads() {
contributionDao. contributionDao
getContribution(Collections.singletonList(Contribution.STATE_FAILED)) ?.getContribution(listOf(Contribution.STATE_FAILED))
.subscribeOn(Schedulers.io()) ?.subscribeOn(Schedulers.io())
.subscribe(failedUploads -> { ?.subscribe { failedUploads ->
for (Contribution contribution : failedUploads) { failedUploads.forEach { contribution ->
contributionsFragment.retryUpload(contribution); contributionsFragment?.retryUpload(contribution)
} }
}); }
} }
/** /**
* Handles item selection in the options menu. This method is called when a user interacts with * Handles item selection in the options menu. This method is called when a user interacts with
* the options menu in the Top Bar. * the options menu in the Top Bar.
*/ */
@Override override fun onOptionsItemSelected(item: MenuItem): Boolean {
public boolean onOptionsItemSelected(MenuItem item) { when (item.itemId) {
switch (item.getItemId()) { R.id.upload_tab -> {
case R.id.upload_tab: startActivity(Intent(this, UploadProgressActivity::class.java))
startActivity(new Intent(this, UploadProgressActivity.class)); return true
return true; }
case R.id.notifications:
R.id.notifications -> {
// Starts notification activity on click to notification icon // Starts notification activity on click to notification icon
NotificationActivity.Companion.startYourself(this, "unread"); startYourself(this, "unread")
return true; return true
default: }
return super.onOptionsItemSelected(item);
else -> return super.onOptionsItemSelected(item)
} }
} }
public void centerMapToPlace(Place place) { fun centerMapToPlace(place: Place?) {
setSelectedItemId(NavTab.NEARBY.code()); setSelectedItemId(NavTab.NEARBY.code())
nearbyParentFragment.setNearbyParentFragmentInstanceReadyCallback( nearbyParentFragment!!.setNearbyParentFragmentInstanceReadyCallback {
new NearbyParentFragmentInstanceReadyCallback() { nearbyParentFragment!!.centerMapToPlace(
@Override place
public void onReady() { )
nearbyParentFragment.centerMapToPlace(place); }
}
});
} }
@Override override fun onResume() {
protected void onResume() { super.onResume()
super.onResume();
if ((applicationKvStore.getBoolean("firstrun", true)) && if ((applicationKvStore!!.getBoolean("firstrun", true)) &&
(!applicationKvStore.getBoolean("login_skipped"))) { (!applicationKvStore!!.getBoolean("login_skipped"))
defaultKvStore.putBoolean("inAppCameraFirstRun", true); ) {
WelcomeActivity.startYourself(this); defaultKvStore.putBoolean("inAppCameraFirstRun", true)
WelcomeActivity.startYourself(this)
} }
retryAllFailedUploads(); retryAllFailedUploads()
} }
@Override override fun onDestroy() {
protected void onDestroy() { quizChecker!!.cleanup()
quizChecker.cleanup(); locationManager!!.unregisterLocationManager()
locationManager.unregisterLocationManager();
// Remove ourself from hashmap to prevent memory leaks // Remove ourself from hashmap to prevent memory leaks
locationManager = null; locationManager = null
super.onDestroy(); super.onDestroy()
} }
/** /**
* Public method to show nearby from the reference of this. * Public method to show nearby from the reference of this.
*/ */
public void showNearby() { fun showNearby() {
binding.fragmentMainNavTabLayout.setSelectedItemId(NavTab.NEARBY.code()); binding!!.fragmentMainNavTabLayout.selectedItemId = NavTab.NEARBY.code()
} }
public enum ActiveFragment { enum class ActiveFragment {
CONTRIBUTIONS, CONTRIBUTIONS,
NEARBY, NEARBY,
EXPLORE, EXPLORE,
@ -472,15 +477,26 @@ public class MainActivity extends BaseActivity
/** /**
* Load default language in onCreate from SharedPreferences * Load default language in onCreate from SharedPreferences
*/ */
private void loadLocale() { private fun loadLocale() {
final SharedPreferences preferences = getSharedPreferences("Settings", val preferences = getSharedPreferences(
Activity.MODE_PRIVATE); "Settings",
final String language = preferences.getString("language", ""); MODE_PRIVATE
final SettingsFragment settingsFragment = new SettingsFragment(); )
settingsFragment.setLocale(this, language); val language = preferences.getString("language", "")!!
val settingsFragment = SettingsFragment()
settingsFragment.setLocale(this, language)
} }
public NavTabLayout.OnNavigationItemSelectedListener getNavListener() { companion object {
return navListener; /**
* Consumers should be simply using this method to use this activity.
*
* @param context A Context of the application package implementing this class.
*/
fun startYourself(context: Context) {
val intent = Intent(context, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT or Intent.FLAG_ACTIVITY_SINGLE_TOP)
context.startActivity(intent)
}
} }
} }

View file

@ -1,126 +1,113 @@
package fr.free.nrw.commons.contributions; package fr.free.nrw.commons.contributions
import android.app.NotificationChannel; import android.app.NotificationChannel
import android.app.NotificationManager; import android.app.NotificationManager
import android.app.WallpaperManager; import android.app.WallpaperManager
import android.content.Context; import android.content.Context
import android.graphics.Bitmap; import android.graphics.Bitmap
import android.net.Uri; import android.net.Uri
import android.os.Build; import android.os.Build
import androidx.annotation.NonNull; import androidx.core.app.NotificationCompat
import androidx.annotation.Nullable; import androidx.work.Worker
import androidx.core.app.NotificationCompat; import androidx.work.WorkerParameters
import androidx.work.Worker; import com.facebook.common.executors.CallerThreadExecutor
import androidx.work.WorkerParameters; import com.facebook.common.references.CloseableReference
import com.facebook.common.executors.CallerThreadExecutor; import com.facebook.datasource.DataSource
import com.facebook.common.references.CloseableReference; import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.datasource.DataSource; import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber
import com.facebook.drawee.backends.pipeline.Fresco; import com.facebook.imagepipeline.image.CloseableImage
import com.facebook.imagepipeline.core.ImagePipeline; import com.facebook.imagepipeline.request.ImageRequestBuilder
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber; import fr.free.nrw.commons.R
import com.facebook.imagepipeline.image.CloseableImage; import timber.log.Timber
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import fr.free.nrw.commons.R;
import timber.log.Timber;
public class SetWallpaperWorker extends Worker { class SetWallpaperWorker(context: Context, params: WorkerParameters) :
Worker(context, params) {
override fun doWork(): Result {
val context = applicationContext
createNotificationChannel(context)
showProgressNotification(context)
private static final String NOTIFICATION_CHANNEL_ID = "set_wallpaper_channel"; val imageUrl = inputData.getString("imageUrl") ?: return Result.failure()
private static final int NOTIFICATION_ID = 1;
public SetWallpaperWorker(@NonNull Context context, @NonNull WorkerParameters params) { val imageRequest = ImageRequestBuilder
super(context, params);
}
@NonNull
@Override
public Result doWork() {
Context context = getApplicationContext();
createNotificationChannel(context);
showProgressNotification(context);
String imageUrl = getInputData().getString("imageUrl");
if (imageUrl == null) {
return Result.failure();
}
ImageRequest imageRequest = ImageRequestBuilder
.newBuilderWithSource(Uri.parse(imageUrl)) .newBuilderWithSource(Uri.parse(imageUrl))
.build(); .build()
ImagePipeline imagePipeline = Fresco.getImagePipeline(); val imagePipeline = Fresco.getImagePipeline()
final DataSource<CloseableReference<CloseableImage>> val dataSource = imagePipeline.fetchDecodedImage(imageRequest, context)
dataSource = imagePipeline.fetchDecodedImage(imageRequest, context);
dataSource.subscribe(new BaseBitmapDataSubscriber() { dataSource.subscribe(object : BaseBitmapDataSubscriber() {
@Override public override fun onNewResultImpl(bitmap: Bitmap?) {
public void onNewResultImpl(@Nullable Bitmap bitmap) { if (dataSource.isFinished && bitmap != null) {
if (dataSource.isFinished() && bitmap != null) { Timber.d("Bitmap loaded from url %s", imageUrl.toString())
Timber.d("Bitmap loaded from url %s", imageUrl.toString()); setWallpaper(context, Bitmap.createBitmap(bitmap))
setWallpaper(context, Bitmap.createBitmap(bitmap)); dataSource.close()
dataSource.close();
} }
} }
@Override override fun onFailureImpl(dataSource: DataSource<CloseableReference<CloseableImage>>?) {
public void onFailureImpl(DataSource dataSource) { Timber.d("Error getting bitmap from image url %s", imageUrl.toString())
Timber.d("Error getting bitmap from image url %s", imageUrl.toString()); showNotification(context, "Setting Wallpaper Failed", "Failed to download image.")
showNotification(context, "Setting Wallpaper Failed", "Failed to download image."); dataSource?.close()
if (dataSource != null) {
dataSource.close();
}
} }
}, CallerThreadExecutor.getInstance()); }, CallerThreadExecutor.getInstance())
return Result.success(); return Result.success()
} }
private void setWallpaper(Context context, Bitmap bitmap) { private fun setWallpaper(context: Context, bitmap: Bitmap) {
WallpaperManager wallpaperManager = WallpaperManager.getInstance(context); val wallpaperManager = WallpaperManager.getInstance(context)
try { try {
wallpaperManager.setBitmap(bitmap); wallpaperManager.setBitmap(bitmap)
showNotification(context, "Wallpaper Set", "Wallpaper has been updated successfully."); showNotification(context, "Wallpaper Set", "Wallpaper has been updated successfully.")
} catch (e: Exception) {
} catch (Exception e) { Timber.e(e, "Error setting wallpaper")
Timber.e(e, "Error setting wallpaper"); showNotification(context, "Setting Wallpaper Failed", " " + e.localizedMessage)
showNotification(context, "Setting Wallpaper Failed", " "+e.getLocalizedMessage());
} }
} }
private void showProgressNotification(Context context) { private fun showProgressNotification(context: Context) {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); val notificationManager =
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID) context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.commons_logo) .setSmallIcon(R.drawable.commons_logo)
.setContentTitle("Setting Wallpaper") .setContentTitle("Setting Wallpaper")
.setContentText("Please wait...") .setContentText("Please wait...")
.setPriority(NotificationCompat.PRIORITY_HIGH) .setPriority(NotificationCompat.PRIORITY_HIGH)
.setOngoing(true) .setOngoing(true)
.setProgress(0, 0, true); .setProgress(0, 0, true)
notificationManager.notify(NOTIFICATION_ID, builder.build()); notificationManager.notify(NOTIFICATION_ID, builder.build())
} }
private void showNotification(Context context, String title, String content) { private fun showNotification(context: Context, title: String, content: String) {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); val notificationManager =
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID) context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.commons_logo) .setSmallIcon(R.drawable.commons_logo)
.setContentTitle(title) .setContentTitle(title)
.setContentText(content) .setContentText(content)
.setPriority(NotificationCompat.PRIORITY_HIGH) .setPriority(NotificationCompat.PRIORITY_HIGH)
.setOngoing(false); .setOngoing(false)
notificationManager.notify(NOTIFICATION_ID, builder.build()); notificationManager.notify(NOTIFICATION_ID, builder.build())
} }
private void createNotificationChannel(Context context) { private fun createNotificationChannel(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = "Wallpaper Setting"; val name: CharSequence = "Wallpaper Setting"
String description = "Notifications for wallpaper setting progress"; val description = "Notifications for wallpaper setting progress"
int importance = NotificationManager.IMPORTANCE_HIGH; val importance = NotificationManager.IMPORTANCE_HIGH
NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, name, importance); val channel = NotificationChannel(NOTIFICATION_CHANNEL_ID, name, importance)
channel.setDescription(description); channel.description = description
NotificationManager notificationManager = context.getSystemService(NotificationManager.class); val notificationManager = context.getSystemService(
notificationManager.createNotificationChannel(channel); NotificationManager::class.java
)
notificationManager.createNotificationChannel(channel)
} }
} }
companion object {
private const val NOTIFICATION_CHANNEL_ID = "set_wallpaper_channel"
private const val NOTIFICATION_ID = 1
}
} }

View file

@ -1,31 +1,22 @@
package fr.free.nrw.commons.contributions; package fr.free.nrw.commons.contributions
import android.content.Context; import android.content.Context
import android.util.AttributeSet; import android.util.AttributeSet
import android.view.MotionEvent; import android.view.MotionEvent
import androidx.viewpager.widget.ViewPager
import androidx.annotation.NonNull; class UnswipableViewPager : ViewPager {
import androidx.annotation.Nullable; constructor(context: Context) : super(context)
import androidx.viewpager.widget.ViewPager;
public class UnswipableViewPager extends ViewPager{ constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
public UnswipableViewPager(@NonNull Context context) {
super(context);
}
public UnswipableViewPager(@NonNull Context context, @Nullable AttributeSet attrs) { override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
super(context, attrs);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
// Unswipable // Unswipable
return false; return false
} }
@Override override fun onTouchEvent(event: MotionEvent): Boolean {
public boolean onTouchEvent(MotionEvent event) {
// Unswipable // Unswipable
return false; return false
} }
} }

View file

@ -43,7 +43,7 @@ class WikipediaInstructionsDialogFragment : DialogFragment() {
/** /**
* Callback for handling confirm button clicked * Callback for handling confirm button clicked
*/ */
interface Callback { fun interface Callback {
fun onConfirmClicked( fun onConfirmClicked(
contribution: Contribution?, contribution: Contribution?,
copyWikicode: Boolean, copyWikicode: Boolean,

View file

@ -9,6 +9,7 @@ import fr.free.nrw.commons.CommonsApplication
import fr.free.nrw.commons.activity.SingleWebViewActivity import fr.free.nrw.commons.activity.SingleWebViewActivity
import fr.free.nrw.commons.auth.LoginActivity import fr.free.nrw.commons.auth.LoginActivity
import fr.free.nrw.commons.contributions.ContributionsModule import fr.free.nrw.commons.contributions.ContributionsModule
import fr.free.nrw.commons.contributions.ContributionsProvidesModule
import fr.free.nrw.commons.explore.SearchModule import fr.free.nrw.commons.explore.SearchModule
import fr.free.nrw.commons.explore.categories.CategoriesModule import fr.free.nrw.commons.explore.categories.CategoriesModule
import fr.free.nrw.commons.explore.depictions.DepictionModule import fr.free.nrw.commons.explore.depictions.DepictionModule
@ -40,6 +41,7 @@ import javax.inject.Singleton
ContentProviderBuilderModule::class, ContentProviderBuilderModule::class,
UploadModule::class, UploadModule::class,
ContributionsModule::class, ContributionsModule::class,
ContributionsProvidesModule::class,
SearchModule::class, SearchModule::class,
DepictionModule::class, DepictionModule::class,
CategoriesModule::class CategoriesModule::class

View file

@ -426,7 +426,7 @@ object FilePicker : Constants {
fun onCanceled(source: ImageSource, type: Int) fun onCanceled(source: ImageSource, type: Int)
} }
interface HandleActivityResult { fun interface HandleActivityResult {
fun onHandleActivityResult(callbacks: Callbacks) fun onHandleActivityResult(callbacks: Callbacks)
} }
} }

View file

@ -31,8 +31,8 @@ class NavTabLayout : BottomNavigationView {
private fun setTabViews() { private fun setTabViews() {
val isLoginSkipped = (context as MainActivity) val isLoginSkipped = (context as MainActivity)
.applicationKvStore.getBoolean("login_skipped") .applicationKvStore?.getBoolean("login_skipped")
if (isLoginSkipped) { if (isLoginSkipped == true) {
for (i in 0 until NavTabLoggedOut.size()) { for (i in 0 until NavTabLoggedOut.size()) {
val navTab = NavTabLoggedOut.of(i) val navTab = NavTabLoggedOut.of(i)
menu.add(Menu.NONE, i, i, navTab.text()).setIcon(navTab.icon()) menu.add(Menu.NONE, i, i, navTab.text()).setIcon(navTab.icon())

View file

@ -28,8 +28,7 @@ import javax.inject.Named
/** /**
* The presenter class for PendingUploadsFragment and FailedUploadsFragment * The presenter class for PendingUploadsFragment and FailedUploadsFragment
*/ */ class PendingUploadsPresenter @Inject internal constructor(
class PendingUploadsPresenter @Inject internal constructor(
private val contributionBoundaryCallback: ContributionBoundaryCallback, private val contributionBoundaryCallback: ContributionBoundaryCallback,
private val contributionsRemoteDataSource: ContributionsRemoteDataSource, private val contributionsRemoteDataSource: ContributionsRemoteDataSource,
private val contributionsRepository: ContributionsRepository, private val contributionsRepository: ContributionsRepository,
@ -89,12 +88,16 @@ class PendingUploadsPresenter @Inject internal constructor(
* @param context The context in which the operation is being performed. * @param context The context in which the operation is being performed.
*/ */
override fun deleteUpload(contribution: Contribution?, context: Context?) { override fun deleteUpload(contribution: Contribution?, context: Context?) {
compositeDisposable.add( contribution?.let {
contributionsRepository contributionsRepository
.deleteContributionFromDB(contribution) .deleteContributionFromDB(it)
.subscribeOn(ioThreadScheduler) .subscribeOn(ioThreadScheduler)
.subscribe() .subscribe()
) }?.let {
compositeDisposable.add(
it
)
}
} }
/** /**