Convert UploadCategoriesFragment to kotlin

This commit is contained in:
Paul Hawke 2024-12-19 21:44:44 -06:00
parent c963cd9ea4
commit 5540fe6ee5
2 changed files with 397 additions and 425 deletions

View file

@ -1,425 +0,0 @@
package fr.free.nrw.commons.upload.categories;
import static fr.free.nrw.commons.wikidata.WikidataConstants.SELECTED_NEARBY_PLACE_CATEGORY;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.Bundle;
import android.text.Editable;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.jakewharton.rxbinding2.view.RxView;
import com.jakewharton.rxbinding2.widget.RxTextView;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.category.CategoryItem;
import fr.free.nrw.commons.contributions.ContributionsFragment;
import fr.free.nrw.commons.databinding.UploadCategoriesFragmentBinding;
import fr.free.nrw.commons.media.MediaDetailFragment;
import fr.free.nrw.commons.upload.UploadActivity;
import fr.free.nrw.commons.upload.UploadBaseFragment;
import fr.free.nrw.commons.utils.DialogUtil;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import kotlin.Unit;
import timber.log.Timber;
public class UploadCategoriesFragment extends UploadBaseFragment implements CategoriesContract.View {
@Inject
CategoriesContract.UserActionListener presenter;
@Inject
SessionManager sessionManager;
private UploadCategoryAdapter adapter;
private Disposable subscribe;
/**
* Current media
*/
private Media media;
/**
* Progress Dialog for showing background process
*/
private ProgressDialog progressDialog;
/**
* WikiText from the server
*/
private String wikiText;
private String nearbyPlaceCategory;
private UploadCategoriesFragmentBinding binding;
@Nullable
@Override
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
binding = UploadCategoriesFragmentBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
final Bundle bundle = getArguments();
if (bundle != null) {
media = bundle.getParcelable("Existing_Categories");
wikiText = bundle.getString("WikiText");
nearbyPlaceCategory = bundle.getString(SELECTED_NEARBY_PLACE_CATEGORY);
}
init();
presenter.getCategories().observe(getViewLifecycleOwner(), this::setCategories);
}
private void init() {
if (binding == null) {
return;
}
if (media == null) {
if (callback != null) {
binding.tvTitle.setText(getString(R.string.step_count, callback.getIndexInViewFlipper(this) + 1,
callback.getTotalNumberOfSteps(), getString(R.string.categories_activity_title)));
}
} else {
binding.tvTitle.setText(R.string.edit_categories);
binding.tvSubtitle.setVisibility(View.GONE);
binding.btnNext.setText(R.string.menu_save_categories);
binding.btnPrevious.setText(R.string.menu_cancel_upload);
}
setTvSubTitle();
binding.tooltip.setOnClickListener(new OnClickListener() {
@Override
public void onClick(final View v) {
DialogUtil.showAlertDialog(requireActivity(),
getString(R.string.categories_activity_title),
getString(R.string.categories_tooltip),
getString(android.R.string.ok),
null);
}
});
if (media == null) {
presenter.onAttachView(this);
} else {
presenter.onAttachViewWithMedia(this, media);
}
binding.btnNext.setOnClickListener(v -> onNextButtonClicked());
binding.btnPrevious.setOnClickListener(v -> onPreviousButtonClicked());
initRecyclerView();
addTextChangeListenerToEtSearch();
}
private void addTextChangeListenerToEtSearch() {
if (binding == null) {
return;
}
subscribe = RxTextView.textChanges(binding.etSearch)
.doOnEach(v -> binding.tilContainerSearch.setError(null))
.takeUntil(RxView.detaches(binding.etSearch))
.debounce(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(filter -> searchForCategory(filter.toString()), Timber::e);
}
/**
* Removes the tv subtitle If the activity is the instance of [UploadActivity] and
* if multiple files aren't selected.
*/
private void setTvSubTitle() {
final Activity activity = getActivity();
if (activity instanceof UploadActivity) {
final boolean isMultipleFileSelected = ((UploadActivity) activity).getIsMultipleFilesSelected();
if (!isMultipleFileSelected) {
binding.tvSubtitle.setVisibility(View.GONE);
}
}
}
private void searchForCategory(final String query) {
presenter.searchForCategories(query);
}
private void initRecyclerView() {
adapter = new UploadCategoryAdapter(categoryItem -> {
presenter.onCategoryItemClicked(categoryItem);
return Unit.INSTANCE;
}, nearbyPlaceCategory);
if (binding!=null) {
binding.rvCategories.setLayoutManager(new LinearLayoutManager(getContext()));
binding.rvCategories.setAdapter(adapter);
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
presenter.onDetachView();
subscribe.dispose();
}
@Override
public void showProgress(final boolean shouldShow) {
if (binding != null) {
binding.pbCategories.setVisibility(shouldShow ? View.VISIBLE : View.GONE);
}
}
@Override
public void showError(final String error) {
if (binding != null) {
binding.tilContainerSearch.setError(error);
}
}
@Override
public void showError(final int stringResourceId) {
if (binding != null) {
binding.tilContainerSearch.setError(getString(stringResourceId));
}
}
@Override
public void setCategories(final List<CategoryItem> categories) {
if (categories == null) {
adapter.clear();
} else {
adapter.setItems(categories);
}
adapter.notifyDataSetChanged();
if (binding == null) {
return;
}
// Nested waiting for search result data to load into the category
// list and smoothly scroll to the top of the search result list.
binding.rvCategories.post(new Runnable() {
@Override
public void run() {
binding.rvCategories.smoothScrollToPosition(0);
binding.rvCategories.post(new Runnable() {
@Override
public void run() {
binding.rvCategories.smoothScrollToPosition(0);
}
});
}
});
}
@Override
public void goToNextScreen() {
if (callback != null){
callback.onNextButtonClicked(callback.getIndexInViewFlipper(this));
}
}
@Override
public void showNoCategorySelected() {
if (media == null) {
DialogUtil.showAlertDialog(requireActivity(),
getString(R.string.no_categories_selected),
getString(R.string.no_categories_selected_warning_desc),
getString(R.string.continue_message),
getString(R.string.cancel),
this::goToNextScreen,
null);
} else {
Toast.makeText(requireContext(), getString(R.string.no_categories_selected),
Toast.LENGTH_SHORT).show();
presenter.clearPreviousSelection();
goBackToPreviousScreen();
}
}
/**
* Gets existing categories from media
*/
@Override
public List<String> getExistingCategories() {
return (media == null) ? null : media.getCategories();
}
/**
* Returns required context
*/
@NonNull
@Override
public Context getFragmentContext() {
return requireContext();
}
/**
* Returns to previous fragment
*/
@Override
public void goBackToPreviousScreen() {
getFragmentManager().popBackStack();
}
/**
* Shows the progress dialog
*/
@Override
public void showProgressDialog() {
progressDialog = new ProgressDialog(requireContext());
progressDialog.setMessage(getString(R.string.please_wait));
progressDialog.show();
}
/**
* Hides the progress dialog
*/
@Override
public void dismissProgressDialog() {
if (progressDialog != null) {
progressDialog.dismiss();
}
}
/**
* Refreshes the categories
*/
@Override
public void refreshCategories() {
final MediaDetailFragment mediaDetailFragment = (MediaDetailFragment) getParentFragment();
assert mediaDetailFragment != null;
mediaDetailFragment.updateCategories();
}
/**
*
*/
@Override
public void navigateToLoginScreen() {
final String username = sessionManager.getUserName();
final CommonsApplication.BaseLogoutListener logoutListener = new CommonsApplication.BaseLogoutListener(
requireActivity(),
requireActivity().getString(R.string.invalid_login_message),
username
);
CommonsApplication.getInstance().clearApplicationData(
requireActivity(), logoutListener);
}
public void onNextButtonClicked() {
if (media != null) {
presenter.updateCategories(media, wikiText);
} else {
presenter.verifyCategories();
}
}
public void onPreviousButtonClicked() {
if (media != null) {
presenter.clearPreviousSelection();
adapter.setItems(null);
final MediaDetailFragment mediaDetailFragment = (MediaDetailFragment) getParentFragment();
assert mediaDetailFragment != null;
mediaDetailFragment.onResume();
goBackToPreviousScreen();
} else {
if (callback != null) {
callback.onPreviousButtonClicked(callback.getIndexInViewFlipper(this));
}
}
}
@Override
protected void onBecameVisible() {
super.onBecameVisible();
if (binding == null) {
return;
}
presenter.selectCategories();
final Editable text = binding.etSearch.getText();
if (text != null) {
presenter.searchForCategories(text.toString());
}
}
/**
* Hides the action bar while opening editing fragment
*/
@Override
public void onResume() {
super.onResume();
if (media != null) {
binding.etSearch.setOnKeyListener((v, keyCode, event) -> {
if (keyCode == KeyEvent.KEYCODE_BACK) {
binding.etSearch.clearFocus();
presenter.clearPreviousSelection();
final MediaDetailFragment mediaDetailFragment = (MediaDetailFragment) getParentFragment();
assert mediaDetailFragment != null;
mediaDetailFragment.onResume();
goBackToPreviousScreen();
return true;
}
return false;
});
requireView().setFocusableInTouchMode(true);
getView().requestFocus();
getView().setOnKeyListener((v, keyCode, event) -> {
if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
presenter.clearPreviousSelection();
final MediaDetailFragment mediaDetailFragment = (MediaDetailFragment) getParentFragment();
assert mediaDetailFragment != null;
mediaDetailFragment.onResume();
goBackToPreviousScreen();
return true;
}
return false;
});
Objects.requireNonNull(
((AppCompatActivity) requireActivity()).getSupportActionBar())
.hide();
if (getParentFragment().getParentFragment().getParentFragment()
instanceof ContributionsFragment) {
((ContributionsFragment) (getParentFragment()
.getParentFragment().getParentFragment())).binding.cardViewNearby
.setVisibility(View.GONE);
}
}
}
/**
* Shows the action bar while closing editing fragment
*/
@Override
public void onStop() {
super.onStop();
if (media != null) {
Objects.requireNonNull(
((AppCompatActivity) requireActivity()).getSupportActionBar())
.show();
}
}
@Override
public void onDestroy() {
super.onDestroy();
binding = null;
}
}

View file

@ -0,0 +1,397 @@
package fr.free.nrw.commons.upload.categories
import android.app.Activity
import android.app.ProgressDialog
import android.content.Context
import android.os.Bundle
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.jakewharton.rxbinding2.view.RxView
import com.jakewharton.rxbinding2.widget.RxTextView
import fr.free.nrw.commons.CommonsApplication
import fr.free.nrw.commons.CommonsApplication.Companion.instance
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.R
import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.category.CategoryItem
import fr.free.nrw.commons.contributions.ContributionsFragment
import fr.free.nrw.commons.databinding.UploadCategoriesFragmentBinding
import fr.free.nrw.commons.media.MediaDetailFragment
import fr.free.nrw.commons.upload.UploadActivity
import fr.free.nrw.commons.upload.UploadBaseFragment
import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog
import fr.free.nrw.commons.wikidata.WikidataConstants.SELECTED_NEARBY_PLACE_CATEGORY
import io.reactivex.Notification
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import timber.log.Timber
import java.util.Objects
import java.util.concurrent.TimeUnit
import javax.inject.Inject
class UploadCategoriesFragment : UploadBaseFragment(), CategoriesContract.View {
@JvmField
@Inject
var presenter: CategoriesContract.UserActionListener? = null
@JvmField
@Inject
var sessionManager: SessionManager? = null
private var adapter: UploadCategoryAdapter? = null
private var subscribe: Disposable? = null
/**
* Current media
*/
private var media: Media? = null
/**
* Progress Dialog for showing background process
*/
private var progressDialog: ProgressDialog? = null
/**
* WikiText from the server
*/
private var wikiText: String? = null
private var nearbyPlaceCategory: String? = null
private var binding: UploadCategoriesFragmentBinding? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = UploadCategoriesFragmentBinding.inflate(inflater, container, false)
return binding!!.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val bundle = arguments
if (bundle != null) {
media = bundle.getParcelable("Existing_Categories")
wikiText = bundle.getString("WikiText")
nearbyPlaceCategory = bundle.getString(SELECTED_NEARBY_PLACE_CATEGORY)
}
init()
presenter!!.getCategories().observe(
viewLifecycleOwner
) { categories: List<CategoryItem>? ->
this.setCategories(
categories
)
}
}
private fun init() {
if (binding == null) {
return
}
if (media == null) {
if (callback != null) {
binding!!.tvTitle.text = getString(
R.string.step_count, callback.getIndexInViewFlipper(
this
) + 1,
callback.totalNumberOfSteps, getString(R.string.categories_activity_title)
)
}
} else {
binding!!.tvTitle.setText(R.string.edit_categories)
binding!!.tvSubtitle.visibility = View.GONE
binding!!.btnNext.setText(R.string.menu_save_categories)
binding!!.btnPrevious.setText(R.string.menu_cancel_upload)
}
setTvSubTitle()
binding!!.tooltip.setOnClickListener {
showAlertDialog(
requireActivity(),
getString(R.string.categories_activity_title),
getString(R.string.categories_tooltip),
getString(android.R.string.ok),
null
)
}
if (media == null) {
presenter!!.onAttachView(this)
} else {
presenter!!.onAttachViewWithMedia(this, media!!)
}
binding!!.btnNext.setOnClickListener { v: View? -> onNextButtonClicked() }
binding!!.btnPrevious.setOnClickListener { v: View? -> onPreviousButtonClicked() }
initRecyclerView()
addTextChangeListenerToEtSearch()
}
private fun addTextChangeListenerToEtSearch() {
if (binding == null) {
return
}
subscribe = RxTextView.textChanges(binding!!.etSearch)
.doOnEach { v: Notification<CharSequence?>? ->
binding!!.tilContainerSearch.error =
null
}
.takeUntil(RxView.detaches(binding!!.etSearch))
.debounce(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ filter: CharSequence -> searchForCategory(filter.toString()) },
{ t: Throwable? -> Timber.e(t) })
}
/**
* Removes the tv subtitle If the activity is the instance of [UploadActivity] and
* if multiple files aren't selected.
*/
private fun setTvSubTitle() {
val activity: Activity? = activity
if (activity is UploadActivity) {
val isMultipleFileSelected = activity.isMultipleFilesSelected
if (!isMultipleFileSelected) {
binding!!.tvSubtitle.visibility = View.GONE
}
}
}
private fun searchForCategory(query: String) {
presenter!!.searchForCategories(query)
}
private fun initRecyclerView() {
adapter = UploadCategoryAdapter({ categoryItem: CategoryItem? ->
presenter!!.onCategoryItemClicked(categoryItem!!)
Unit
}, nearbyPlaceCategory)
if (binding != null) {
binding!!.rvCategories.layoutManager = LinearLayoutManager(context)
binding!!.rvCategories.adapter = adapter
}
}
override fun onDestroyView() {
super.onDestroyView()
presenter!!.onDetachView()
subscribe!!.dispose()
}
override fun showProgress(shouldShow: Boolean) {
binding?.pbCategories?.setVisibility(if (shouldShow) View.VISIBLE else View.GONE)
}
override fun showError(error: String?) {
binding?.tilContainerSearch?.error = error
}
override fun showError(stringResourceId: Int) {
binding?.tilContainerSearch?.error = getString(stringResourceId)
}
override fun setCategories(categories: List<CategoryItem>?) {
if (categories == null) {
adapter!!.clear()
} else {
adapter!!.items = categories
}
adapter!!.notifyDataSetChanged()
if (binding == null) {
return
}
// Nested waiting for search result data to load into the category
// list and smoothly scroll to the top of the search result list.
binding!!.rvCategories.post {
binding!!.rvCategories.smoothScrollToPosition(0)
binding!!.rvCategories.post {
binding!!.rvCategories.smoothScrollToPosition(
0
)
}
}
}
override fun goToNextScreen() {
callback?.onNextButtonClicked(callback.getIndexInViewFlipper(this))
}
override fun showNoCategorySelected() {
if (media == null) {
showAlertDialog(
requireActivity(),
getString(R.string.no_categories_selected),
getString(R.string.no_categories_selected_warning_desc),
getString(R.string.continue_message),
getString(R.string.cancel),
{ this.goToNextScreen() },
null
)
} else {
Toast.makeText(
requireContext(), getString(R.string.no_categories_selected),
Toast.LENGTH_SHORT
).show()
presenter!!.clearPreviousSelection()
goBackToPreviousScreen()
}
}
/**
* Gets existing categories from media
*/
override fun getExistingCategories(): List<String>? {
return media?.categories
}
/**
* Returns required context
*/
override fun getFragmentContext(): Context {
return requireContext()
}
/**
* Returns to previous fragment
*/
override fun goBackToPreviousScreen() {
fragmentManager?.popBackStack()
}
/**
* Shows the progress dialog
*/
override fun showProgressDialog() {
progressDialog = ProgressDialog(requireContext()).apply {
setMessage(getString(R.string.please_wait))
}.also {
it.show()
}
}
/**
* Hides the progress dialog
*/
override fun dismissProgressDialog() {
progressDialog?.dismiss()
}
/**
* Refreshes the categories
*/
override fun refreshCategories() {
(parentFragment as MediaDetailFragment?)?.updateCategories()
}
/**
*
*/
override fun navigateToLoginScreen() {
val username = sessionManager!!.userName
val logoutListener = CommonsApplication.BaseLogoutListener(
requireActivity(),
requireActivity().getString(R.string.invalid_login_message),
username
)
instance.clearApplicationData(
requireActivity(), logoutListener
)
}
fun onNextButtonClicked() {
if (media != null) {
presenter!!.updateCategories(media!!, wikiText!!)
} else {
presenter!!.verifyCategories()
}
}
fun onPreviousButtonClicked() {
if (media != null) {
presenter!!.clearPreviousSelection()
adapter!!.items = null
val mediaDetailFragment = checkNotNull(parentFragment as MediaDetailFragment?)
mediaDetailFragment.onResume()
goBackToPreviousScreen()
} else {
callback?.onPreviousButtonClicked(callback.getIndexInViewFlipper(this))
}
}
override fun onBecameVisible() {
super.onBecameVisible()
if (binding == null) {
return
}
presenter!!.selectCategories()
val text = binding!!.etSearch.text
if (text != null) {
presenter!!.searchForCategories(text.toString())
}
}
/**
* Hides the action bar while opening editing fragment
*/
override fun onResume() {
super.onResume()
if (media != null) {
binding!!.etSearch.setOnKeyListener { v: View?, keyCode: Int, event: KeyEvent? ->
if (keyCode == KeyEvent.KEYCODE_BACK) {
binding!!.etSearch.clearFocus()
presenter!!.clearPreviousSelection()
val mediaDetailFragment =
checkNotNull(parentFragment as MediaDetailFragment?)
mediaDetailFragment.onResume()
goBackToPreviousScreen()
return@setOnKeyListener true
}
false
}
requireView().isFocusableInTouchMode = true
requireView().requestFocus()
requireView().setOnKeyListener { v: View?, keyCode: Int, event: KeyEvent ->
if (event.action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
presenter!!.clearPreviousSelection()
val mediaDetailFragment =
checkNotNull(parentFragment as MediaDetailFragment?)
mediaDetailFragment.onResume()
goBackToPreviousScreen()
return@setOnKeyListener true
}
false
}
(requireActivity() as AppCompatActivity).supportActionBar?.hide()
if (parentFragment?.parentFragment?.parentFragment is ContributionsFragment) {
((parentFragment?.parentFragment?.parentFragment) as ContributionsFragment).binding.cardViewNearby.visibility = View.GONE
}
}
}
/**
* Shows the action bar while closing editing fragment
*/
override fun onStop() {
super.onStop()
if (media != null) {
(requireActivity() as AppCompatActivity).supportActionBar?.show()
}
}
override fun onDestroy() {
super.onDestroy()
binding = null
}
}