convert top level classes to kotlin (#6368)

* Converted welcome activity / pager to kotlin

* Removed unused interface

* Convert ViewPagerAdapter to kotlin and enforce that all tabs must have a title that comes from strings.xml

* Convert OkHttpConnectionFactory and remove an exception class nobody was using

* Convert MapController to kotlin along with fixing nullability in a few places
This commit is contained in:
Paul Hawke 2025-07-06 19:50:16 -05:00 committed by GitHub
parent 65f41beed8
commit 66395b9871
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 555 additions and 655 deletions

View file

@ -16,6 +16,7 @@
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" /> <option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
<option name="IMPORT_LAYOUT_TABLE"> <option name="IMPORT_LAYOUT_TABLE">
<value> <value>
<package name="" withSubpackages="true" static="false" module="true" />
<package name="" withSubpackages="true" static="true" /> <package name="" withSubpackages="true" static="true" />
<emptyLine /> <emptyLine />
<package name="" withSubpackages="true" static="false" /> <package name="" withSubpackages="true" static="false" />

View file

@ -1,30 +0,0 @@
package fr.free.nrw.commons;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.nearby.Place;
import java.util.List;
public abstract class MapController {
/**
* We pass this variable as a group of placeList and boundaryCoordinates
*/
public class NearbyPlacesInfo {
public List<Place> placeList; // List of nearby places
public LatLng[] boundaryCoordinates; // Corners of nearby area
public LatLng currentLatLng; // Current location when this places are populated
public LatLng searchLatLng; // Search location for finding this places
public List<Media> mediaList; // Search location for finding this places
}
/**
* We pass this variable as a group of placeList and boundaryCoordinates
*/
public class ExplorePlacesInfo {
public List<Place> explorePlaceList; // List of nearby places
public LatLng[] boundaryCoordinates; // Corners of nearby area
public LatLng currentLatLng; // Current location when this places are populated
public LatLng searchLatLng; // Search location for finding this places
public List<Media> mediaList; // Search location for finding this places
}
}

View file

@ -0,0 +1,46 @@
package fr.free.nrw.commons
import fr.free.nrw.commons.location.LatLng
import fr.free.nrw.commons.nearby.Place
abstract class MapController {
/**
* We pass this variable as a group of placeList and boundaryCoordinates
*/
inner class NearbyPlacesInfo {
@JvmField
var placeList: List<Place> = emptyList() // List of nearby places
@JvmField
var boundaryCoordinates: Array<LatLng> = emptyArray() // Corners of nearby area
@JvmField
var currentLatLng: LatLng? = null // Current location when this places are populated
@JvmField
var searchLatLng: LatLng? = null // Search location for finding this places
@JvmField
var mediaList: List<Media>? = null // Search location for finding this places
}
/**
* We pass this variable as a group of placeList and boundaryCoordinates
*/
inner class ExplorePlacesInfo {
@JvmField
var explorePlaceList: List<Place> = emptyList() // List of nearby places
@JvmField
var boundaryCoordinates: Array<LatLng> = emptyArray() // Corners of nearby area
@JvmField
var currentLatLng: LatLng? = null // Current location when this places are populated
@JvmField
var searchLatLng: LatLng? = null // Search location for finding this places
@JvmField
var mediaList: List<Media> = emptyList() // Search location for finding this places
}
}

View file

@ -1,154 +0,0 @@
package fr.free.nrw.commons;
import androidx.annotation.NonNull;
import fr.free.nrw.commons.wikidata.cookies.CommonsCookieJar;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import okhttp3.Cache;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.logging.HttpLoggingInterceptor;
import okhttp3.logging.HttpLoggingInterceptor.Level;
import timber.log.Timber;
public final class OkHttpConnectionFactory {
private static final String CACHE_DIR_NAME = "okhttp-cache";
private static final long NET_CACHE_SIZE = 64 * 1024 * 1024;
public static OkHttpClient CLIENT;
@NonNull public static OkHttpClient getClient(final CommonsCookieJar cookieJar) {
if (CLIENT == null) {
CLIENT = createClient(cookieJar);
}
return CLIENT;
}
@NonNull
private static OkHttpClient createClient(final CommonsCookieJar cookieJar) {
return new OkHttpClient.Builder()
.cookieJar(cookieJar)
.cache((CommonsApplication.getInstance()!=null) ? new Cache(new File(CommonsApplication.getInstance().getCacheDir(), CACHE_DIR_NAME), NET_CACHE_SIZE) : null)
.connectTimeout(120, TimeUnit.SECONDS)
.writeTimeout(120, TimeUnit.SECONDS)
.readTimeout(120, TimeUnit.SECONDS)
.addInterceptor(getLoggingInterceptor())
.addInterceptor(new UnsuccessfulResponseInterceptor())
.addInterceptor(new CommonHeaderRequestInterceptor())
.build();
}
private static HttpLoggingInterceptor getLoggingInterceptor() {
final HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor()
.setLevel(Level.BASIC);
httpLoggingInterceptor.redactHeader("Authorization");
httpLoggingInterceptor.redactHeader("Cookie");
return httpLoggingInterceptor;
}
private static class CommonHeaderRequestInterceptor implements Interceptor {
@Override
@NonNull
public Response intercept(@NonNull final Chain chain) throws IOException {
final Request request = chain.request().newBuilder()
.header("User-Agent", CommonsApplication.getInstance().getUserAgent())
.build();
return chain.proceed(request);
}
}
public static class UnsuccessfulResponseInterceptor implements Interceptor {
private static final String SUPPRESS_ERROR_LOG = "x-commons-suppress-error-log";
public static final String SUPPRESS_ERROR_LOG_HEADER = SUPPRESS_ERROR_LOG+": true";
private static final List<String> DO_NOT_INTERCEPT = Collections.singletonList(
"api.php?format=json&formatversion=2&errorformat=plaintext&action=upload&ignorewarnings=1");
private static final String ERRORS_PREFIX = "{\"error";
@Override
@NonNull
public Response intercept(@NonNull final Chain chain) throws IOException {
final Request rq = chain.request();
// If the request contains our special "suppress errors" header, make note of it
// but don't pass that on to the server.
final boolean suppressErrors = rq.headers().names().contains(SUPPRESS_ERROR_LOG);
final Request request = rq.newBuilder()
.removeHeader(SUPPRESS_ERROR_LOG)
.build();
final Response rsp = chain.proceed(request);
// Do not intercept certain requests and let the caller handle the errors
if(isExcludedUrl(chain.request())) {
return rsp;
}
if (rsp.isSuccessful()) {
try (final ResponseBody responseBody = rsp.peekBody(ERRORS_PREFIX.length())) {
if (ERRORS_PREFIX.equals(responseBody.string())) {
try (final ResponseBody body = rsp.body()) {
throw new IOException(body.string());
}
}
} catch (final IOException e) {
// Log the error as debug (and therefore, "expected") or at error level
if (suppressErrors) {
Timber.d(e, "Suppressed (known / expected) error");
} else {
Timber.e(e);
}
}
return rsp;
}
throw new HttpStatusException(rsp);
}
private boolean isExcludedUrl(final Request request) {
final String requestUrl = request.url().toString();
for(final String url: DO_NOT_INTERCEPT) {
if(requestUrl.contains(url)) {
return true;
}
}
return false;
}
}
private OkHttpConnectionFactory() {
}
public static class HttpStatusException extends IOException {
private final int code;
private final String url;
public HttpStatusException(@NonNull Response rsp) {
this.code = rsp.code();
this.url = rsp.request().url().uri().toString();
try {
if (rsp.body() != null && rsp.body().contentType() != null
&& rsp.body().contentType().toString().contains("json")) {
}
} catch (Exception e) {
// Log?
}
}
public int code() {
return code;
}
@Override
public String getMessage() {
String str = "Code: " + code + ", URL: " + url;
return str;
}
}
}

View file

@ -0,0 +1,122 @@
package fr.free.nrw.commons
import androidx.annotation.VisibleForTesting
import fr.free.nrw.commons.wikidata.cookies.CommonsCookieJar
import okhttp3.Cache
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.logging.HttpLoggingInterceptor
import timber.log.Timber
import java.io.File
import java.io.IOException
import java.util.concurrent.TimeUnit
object OkHttpConnectionFactory {
private const val CACHE_DIR_NAME = "okhttp-cache"
private const val NET_CACHE_SIZE = (64 * 1024 * 1024).toLong()
@VisibleForTesting
var CLIENT: OkHttpClient? = null
fun getClient(cookieJar: CommonsCookieJar): OkHttpClient {
if (CLIENT == null) {
CLIENT = createClient(cookieJar)
}
return CLIENT!!
}
private fun createClient(cookieJar: CommonsCookieJar): OkHttpClient {
return OkHttpClient.Builder()
.cookieJar(cookieJar)
.cache(
if (CommonsApplication.instance != null) Cache(
File(CommonsApplication.instance.cacheDir, CACHE_DIR_NAME),
NET_CACHE_SIZE
) else null
)
.connectTimeout(120, TimeUnit.SECONDS)
.writeTimeout(120, TimeUnit.SECONDS)
.readTimeout(120, TimeUnit.SECONDS)
.addInterceptor(HttpLoggingInterceptor().apply {
setLevel(HttpLoggingInterceptor.Level.BASIC)
redactHeader("Authorization")
redactHeader("Cookie")
})
.addInterceptor(UnsuccessfulResponseInterceptor())
.addInterceptor(CommonHeaderRequestInterceptor())
.build()
}
}
private class CommonHeaderRequestInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request().newBuilder()
.header("User-Agent", CommonsApplication.instance.userAgent)
.build()
return chain.proceed(request)
}
}
private const val SUPPRESS_ERROR_LOG = "x-commons-suppress-error-log"
const val SUPPRESS_ERROR_LOG_HEADER: String = "$SUPPRESS_ERROR_LOG: true"
private class UnsuccessfulResponseInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val rq = chain.request()
// If the request contains our special "suppress errors" header, make note of it
// but don't pass that on to the server.
val suppressErrors = rq.headers.names().contains(SUPPRESS_ERROR_LOG)
val request = rq.newBuilder()
.removeHeader(SUPPRESS_ERROR_LOG)
.build()
val rsp = chain.proceed(request)
// Do not intercept certain requests and let the caller handle the errors
if (isExcludedUrl(chain.request())) {
return rsp
}
if (rsp.isSuccessful) {
try {
rsp.peekBody(ERRORS_PREFIX.length.toLong()).use { responseBody ->
if (ERRORS_PREFIX == responseBody.string()) {
rsp.body.use { body ->
throw IOException(body!!.string())
}
}
}
} catch (e: IOException) {
// Log the error as debug (and therefore, "expected") or at error level
if (suppressErrors) {
Timber.d(e, "Suppressed (known / expected) error")
} else {
Timber.e(e)
}
}
return rsp
}
throw IOException("Unsuccessful response")
}
private fun isExcludedUrl(request: Request): Boolean {
val requestUrl = request.url.toString()
for (url in DO_NOT_INTERCEPT) {
if (requestUrl.contains(url)) {
return true
}
}
return false
}
companion object {
val DO_NOT_INTERCEPT = listOf(
"api.php?format=json&formatversion=2&errorformat=plaintext&action=upload&ignorewarnings=1"
)
const val ERRORS_PREFIX = "{\"error"
}
}

View file

@ -1,7 +0,0 @@
package fr.free.nrw.commons;
import android.content.Context;
public interface ViewHolder<T> {
void bindModel(Context context, T model);
}

View file

@ -1,68 +0,0 @@
package fr.free.nrw.commons;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import java.util.ArrayList;
import java.util.List;
/**
* This adapter will be used to display fragments in a ViewPager
*/
public class ViewPagerAdapter extends FragmentPagerAdapter {
private List<Fragment> fragmentList = new ArrayList<>();
private List<String> fragmentTitleList = new ArrayList<>();
public ViewPagerAdapter(FragmentManager manager) {
super(manager);
}
/**
* Constructs a ViewPagerAdapter with a specified Fragment Manager and Fragment resume behavior.
*
* @param manager The FragmentManager
* @param behavior An integer which represents the behavior of non visible fragments. See
* FragmentPagerAdapter.java for options.
*/
public ViewPagerAdapter(FragmentManager manager, int behavior) {
super(manager, behavior);
}
/**
* This method returns the fragment of the viewpager at a particular position
* @param position
*/
@Override
public Fragment getItem(int position) {
return fragmentList.get(position);
}
/**
* This method returns the total number of fragments in the viewpager.
* @return size
*/
@Override
public int getCount() {
return fragmentList.size();
}
/**
* This method sets the fragment and title list in the viewpager
* @param fragmentList List of all fragments to be displayed in the viewpager
* @param fragmentTitleList List of all titles of the fragments
*/
public void setTabData(List<Fragment> fragmentList, List<String> fragmentTitleList) {
this.fragmentList = fragmentList;
this.fragmentTitleList = fragmentTitleList;
}
/**
* This method returns the title of the page at a particular position
* @param position
*/
@Override
public CharSequence getPageTitle(int position) {
return fragmentTitleList.get(position);
}
}

View file

@ -0,0 +1,44 @@
package fr.free.nrw.commons
import android.content.Context
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter
import java.util.Locale
/**
* This adapter will be used to display fragments in a ViewPager
*/
class ViewPagerAdapter : FragmentPagerAdapter {
private val context: Context
private var fragmentList: List<Fragment> = emptyList()
private var fragmentTitleList: List<String> = emptyList()
constructor(context: Context, manager: FragmentManager) : super(manager) {
this.context = context
}
constructor(context: Context, manager: FragmentManager, behavior: Int) : super(manager, behavior) {
this.context = context
}
override fun getItem(position: Int): Fragment = fragmentList[position]
override fun getPageTitle(position: Int): CharSequence = fragmentTitleList[position]
override fun getCount(): Int = fragmentList.size
fun setTabs(vararg titlesToFragments: Pair<Int, Fragment>) {
// Enforce that every title must come from strings.xml and all will consistently be uppercase
fragmentTitleList = titlesToFragments.map {
context.getString(it.first).uppercase(Locale.ROOT)
}
fragmentList = titlesToFragments.map { it.second }
}
companion object {
// Convenience method for Java callers, can be removed when everything is migrated
@JvmStatic
fun pairOf(first: Int, second: Fragment) = first to second
}
}

View file

@ -1,109 +0,0 @@
package fr.free.nrw.commons;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import fr.free.nrw.commons.databinding.ActivityWelcomeBinding;
import fr.free.nrw.commons.databinding.PopupForCopyrightBinding;
import fr.free.nrw.commons.quiz.QuizActivity;
import fr.free.nrw.commons.theme.BaseActivity;
import fr.free.nrw.commons.utils.ConfigUtils;
public class WelcomeActivity extends BaseActivity {
private ActivityWelcomeBinding binding;
private PopupForCopyrightBinding copyrightBinding;
private final WelcomePagerAdapter adapter = new WelcomePagerAdapter();
private boolean isQuiz;
private AlertDialog.Builder dialogBuilder;
private AlertDialog dialog;
/**
* Initialises exiting fields and dependencies
*
* @param savedInstanceState WelcomeActivity bundled data
*/
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityWelcomeBinding.inflate(getLayoutInflater());
final View view = binding.getRoot();
setContentView(view);
if (getIntent() != null) {
final Bundle bundle = getIntent().getExtras();
if (bundle != null) {
isQuiz = bundle.getBoolean("isQuiz");
}
} else {
isQuiz = false;
}
// Enable skip button if beta flavor
if (ConfigUtils.isBetaFlavour()) {
binding.finishTutorialButton.setVisibility(View.VISIBLE);
dialogBuilder = new AlertDialog.Builder(this);
copyrightBinding = PopupForCopyrightBinding.inflate(getLayoutInflater());
final View contactPopupView = copyrightBinding.getRoot();
dialogBuilder.setView(contactPopupView);
dialogBuilder.setCancelable(false);
dialog = dialogBuilder.create();
dialog.show();
copyrightBinding.buttonOk.setOnClickListener(v -> dialog.dismiss());
}
binding.welcomePager.setAdapter(adapter);
binding.welcomePagerIndicator.setViewPager(binding.welcomePager);
binding.finishTutorialButton.setOnClickListener(v -> finishTutorial());
}
/**
* References WelcomePageAdapter to null before the activity is destroyed
*/
@Override
public void onDestroy() {
if (isQuiz) {
final Intent i = new Intent(this, QuizActivity.class);
startActivity(i);
}
super.onDestroy();
}
/**
* Creates a way to change current activity to WelcomeActivity
*
* @param context Activity context
*/
public static void startYourself(final Context context) {
final Intent welcomeIntent = new Intent(context, WelcomeActivity.class);
context.startActivity(welcomeIntent);
}
/**
* Override onBackPressed() to go to previous tutorial 'pages' if not on first page
*/
@Override
public void onBackPressed() {
if (binding.welcomePager.getCurrentItem() != 0) {
binding.welcomePager.setCurrentItem(binding.welcomePager.getCurrentItem() - 1, true);
} else {
if (defaultKvStore.getBoolean("firstrun", true)) {
finishAffinity();
} else {
super.onBackPressed();
}
}
}
public void finishTutorial() {
defaultKvStore.putBoolean("firstrun", false);
finish();
}
}

View file

@ -0,0 +1,78 @@
package fr.free.nrw.commons
import android.app.AlertDialog
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import fr.free.nrw.commons.databinding.ActivityWelcomeBinding
import fr.free.nrw.commons.databinding.PopupForCopyrightBinding
import fr.free.nrw.commons.quiz.QuizActivity
import fr.free.nrw.commons.theme.BaseActivity
import fr.free.nrw.commons.utils.ConfigUtils.isBetaFlavour
class WelcomeActivity : BaseActivity() {
private var binding: ActivityWelcomeBinding? = null
private var isQuiz = false
/**
* Initialises exiting fields and dependencies
*
* @param savedInstanceState WelcomeActivity bundled data
*/
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityWelcomeBinding.inflate(layoutInflater)
setContentView(binding!!.root)
isQuiz = intent?.extras?.getBoolean("isQuiz", false) ?: false
// Enable skip button if beta flavor
if (isBetaFlavour) {
binding!!.finishTutorialButton.visibility = View.VISIBLE
val copyrightBinding = PopupForCopyrightBinding.inflate(layoutInflater)
val dialog = AlertDialog.Builder(this)
.setView(copyrightBinding.root)
.setCancelable(false)
.create()
dialog.show()
copyrightBinding.buttonOk.setOnClickListener { v: View? -> dialog.dismiss() }
}
val adapter = WelcomePagerAdapter()
binding!!.welcomePager.adapter = adapter
binding!!.welcomePagerIndicator.setViewPager(binding!!.welcomePager)
binding!!.finishTutorialButton.setOnClickListener { v: View? -> finishTutorial() }
}
public override fun onDestroy() {
if (isQuiz) {
startActivity(Intent(this, QuizActivity::class.java))
}
super.onDestroy()
}
override fun onBackPressed() {
if (binding!!.welcomePager.currentItem != 0) {
binding!!.welcomePager.setCurrentItem(binding!!.welcomePager.currentItem - 1, true)
} else {
if (defaultKvStore.getBoolean("firstrun", true)) {
finishAffinity()
} else {
super.onBackPressed()
}
}
}
fun finishTutorial() {
defaultKvStore.putBoolean("firstrun", false)
finish()
}
}
fun Context.startWelcome() {
startActivity(Intent(this, WelcomeActivity::class.java))
}

View file

@ -1,77 +0,0 @@
package fr.free.nrw.commons;
import static fr.free.nrw.commons.utils.UrlUtilsKt.handleWebUrl;
import android.net.Uri;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.viewpager.widget.PagerAdapter;
import fr.free.nrw.commons.utils.UnderlineUtils;
public class WelcomePagerAdapter extends PagerAdapter {
private static final int[] PAGE_LAYOUTS = new int[]{
R.layout.welcome_wikipedia,
R.layout.welcome_do_upload,
R.layout.welcome_dont_upload,
R.layout.welcome_image_example,
R.layout.welcome_final
};
/**
* Gets total number of layouts
* @return Number of layouts
*/
@Override
public int getCount() {
return PAGE_LAYOUTS.length;
}
/**
* Compares given view with provided object
* @param view Adapter view
* @param object Adapter object
* @return Equality between view and object
*/
@Override
public boolean isViewFromObject(View view, Object object) {
return (view == object);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
LayoutInflater inflater = LayoutInflater.from(container.getContext());
ViewGroup layout = (ViewGroup) inflater.inflate(PAGE_LAYOUTS[position], container, false);
// If final page
if (position == PAGE_LAYOUTS.length - 1) {
// Add link to more information
TextView moreInfo = layout.findViewById(R.id.welcomeInfo);
UnderlineUtils.setUnderlinedText(moreInfo, R.string.welcome_help_button_text);
moreInfo.setOnClickListener(view -> handleWebUrl(
container.getContext(),
Uri.parse("https://commons.wikimedia.org/wiki/Help:Contents")
));
// Handle click of finishTutorialButton ("YES!" button) inside layout
layout.findViewById(R.id.finishTutorialButton)
.setOnClickListener(view -> ((WelcomeActivity) container.getContext()).finishTutorial());
}
container.addView(layout);
return layout;
}
/**
* Provides a way to remove an item from container
* @param container Adapter view group container
* @param position Index of item
* @param obj Adapter object
*/
@Override
public void destroyItem(ViewGroup container, int position, Object obj) {
container.removeView((View) obj);
}
}

View file

@ -0,0 +1,70 @@
package fr.free.nrw.commons
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.net.toUri
import androidx.viewpager.widget.PagerAdapter
import fr.free.nrw.commons.utils.UnderlineUtils.setUnderlinedText
import fr.free.nrw.commons.utils.handleWebUrl
class WelcomePagerAdapter : PagerAdapter() {
/**
* Gets total number of layouts
* @return Number of layouts
*/
override fun getCount(): Int = PAGE_LAYOUTS.size
/**
* Compares given view with provided object
* @param view Adapter view
* @param obj Adapter object
* @return Equality between view and object
*/
override fun isViewFromObject(view: View, obj: Any): Boolean = (view === obj)
/**
* Provides a way to remove an item from container
* @param container Adapter view group container
* @param position Index of item
* @param obj Adapter object
*/
override fun destroyItem(container: ViewGroup, position: Int, obj: Any) =
container.removeView(obj as View)
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val inflater = LayoutInflater.from(container.context)
val layout = inflater.inflate(PAGE_LAYOUTS[position], container, false) as ViewGroup
// If final page
if (position == PAGE_LAYOUTS.size - 1) {
// Add link to more information
val moreInfo = layout.findViewById<TextView>(R.id.welcomeInfo)
setUnderlinedText(moreInfo, R.string.welcome_help_button_text)
moreInfo.setOnClickListener {
handleWebUrl(
container.context,
"https://commons.wikimedia.org/wiki/Help:Contents".toUri()
)
}
// Handle click of finishTutorialButton ("YES!" button) inside layout
layout.findViewById<View>(R.id.finishTutorialButton)
.setOnClickListener { view: View? -> (container.context as WelcomeActivity).finishTutorial() }
}
container.addView(layout)
return layout
}
companion object {
private val PAGE_LAYOUTS = intArrayOf(
R.layout.welcome_wikipedia,
R.layout.welcome_do_upload,
R.layout.welcome_dont_upload,
R.layout.welcome_image_example,
R.layout.welcome_final
)
}
}

View file

@ -59,7 +59,7 @@ class CategoryDetailsActivity : BaseActivity(),
val view = binding.root val view = binding.root
setContentView(view) setContentView(view)
supportFragmentManager = getSupportFragmentManager() supportFragmentManager = getSupportFragmentManager()
viewPagerAdapter = ViewPagerAdapter(supportFragmentManager) viewPagerAdapter = ViewPagerAdapter(this, supportFragmentManager)
binding.viewPager.adapter = viewPagerAdapter binding.viewPager.adapter = viewPagerAdapter
binding.viewPager.offscreenPageLimit = 2 binding.viewPager.offscreenPageLimit = 2
binding.tabLayout.setupWithViewPager(binding.viewPager) binding.tabLayout.setupWithViewPager(binding.viewPager)
@ -83,8 +83,6 @@ class CategoryDetailsActivity : BaseActivity(),
* Set the fragments according to the tab selected in the viewPager. * Set the fragments according to the tab selected in the viewPager.
*/ */
private fun setTabs() { private fun setTabs() {
val fragmentList = mutableListOf<Fragment>()
val titleList = mutableListOf<String>()
categoriesMediaFragment = CategoriesMediaFragment() categoriesMediaFragment = CategoriesMediaFragment()
val subCategoryListFragment = SubCategoriesFragment() val subCategoryListFragment = SubCategoriesFragment()
val parentCategoriesFragment = ParentCategoriesFragment() val parentCategoriesFragment = ParentCategoriesFragment()
@ -99,13 +97,12 @@ class CategoryDetailsActivity : BaseActivity(),
viewModel.onCheckIfBookmarked(categoryName!!) viewModel.onCheckIfBookmarked(categoryName!!)
} }
fragmentList.add(categoriesMediaFragment)
titleList.add("MEDIA") viewPagerAdapter.setTabs(
fragmentList.add(subCategoryListFragment) R.string.title_for_media to categoriesMediaFragment,
titleList.add("SUBCATEGORIES") R.string.title_for_subcategories to subCategoryListFragment,
fragmentList.add(parentCategoriesFragment) R.string.title_for_parent_categories to parentCategoriesFragment
titleList.add("PARENT CATEGORIES") )
viewPagerAdapter.setTabData(fragmentList, titleList)
viewPagerAdapter.notifyDataSetChanged() viewPagerAdapter.notifyDataSetChanged()
} }

View file

@ -12,7 +12,6 @@ import androidx.fragment.app.FragmentManager
import androidx.work.ExistingWorkPolicy import androidx.work.ExistingWorkPolicy
import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomnavigation.BottomNavigationView
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.WelcomeActivity
import fr.free.nrw.commons.auth.SessionManager import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.bookmarks.BookmarkFragment import fr.free.nrw.commons.bookmarks.BookmarkFragment
import fr.free.nrw.commons.contributions.ContributionsFragment.Companion.newInstance import fr.free.nrw.commons.contributions.ContributionsFragment.Companion.newInstance
@ -33,6 +32,7 @@ import fr.free.nrw.commons.notification.NotificationActivity.Companion.startYour
import fr.free.nrw.commons.notification.NotificationController import fr.free.nrw.commons.notification.NotificationController
import fr.free.nrw.commons.quiz.QuizChecker import fr.free.nrw.commons.quiz.QuizChecker
import fr.free.nrw.commons.settings.SettingsFragment import fr.free.nrw.commons.settings.SettingsFragment
import fr.free.nrw.commons.startWelcome
import fr.free.nrw.commons.theme.BaseActivity import fr.free.nrw.commons.theme.BaseActivity
import fr.free.nrw.commons.upload.UploadProgressActivity import fr.free.nrw.commons.upload.UploadProgressActivity
import fr.free.nrw.commons.upload.worker.WorkRequestHelper.Companion.makeOneTimeWorkRequest import fr.free.nrw.commons.upload.worker.WorkRequestHelper.Companion.makeOneTimeWorkRequest
@ -517,7 +517,7 @@ after opening the app.
(!applicationKvStore!!.getBoolean("login_skipped")) (!applicationKvStore!!.getBoolean("login_skipped"))
) { ) {
defaultKvStore.putBoolean("inAppCameraFirstRun", true) defaultKvStore.putBoolean("inAppCameraFirstRun", true)
WelcomeActivity.startYourself(this) startWelcome()
} }
retryAllFailedUploads() retryAllFailedUploads()

View file

@ -1,6 +1,7 @@
package fr.free.nrw.commons.explore; package fr.free.nrw.commons.explore;
import static androidx.viewpager.widget.ViewPager.SCROLL_STATE_IDLE; import static androidx.viewpager.widget.ViewPager.SCROLL_STATE_IDLE;
import static fr.free.nrw.commons.ViewPagerAdapter.pairOf;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -23,10 +24,12 @@ import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.theme.BaseActivity; import fr.free.nrw.commons.theme.BaseActivity;
import fr.free.nrw.commons.utils.ActivityUtils; import fr.free.nrw.commons.utils.ActivityUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import kotlin.Pair;
public class ExploreFragment extends CommonsDaggerSupportFragment { public class ExploreFragment extends CommonsDaggerSupportFragment {
@ -70,7 +73,7 @@ public class ExploreFragment extends CommonsDaggerSupportFragment {
loadNearbyMapData(); loadNearbyMapData();
binding = FragmentExploreBinding.inflate(inflater, container, false); binding = FragmentExploreBinding.inflate(inflater, container, false);
viewPagerAdapter = new ViewPagerAdapter(getChildFragmentManager(), viewPagerAdapter = new ViewPagerAdapter(requireContext(), getChildFragmentManager(),
FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
binding.viewPager.setAdapter(viewPagerAdapter); binding.viewPager.setAdapter(viewPagerAdapter);
@ -111,9 +114,6 @@ public class ExploreFragment extends CommonsDaggerSupportFragment {
* Sets the titles in the tabLayout and fragments in the viewPager * Sets the titles in the tabLayout and fragments in the viewPager
*/ */
public void setTabs() { public void setTabs() {
List<Fragment> fragmentList = new ArrayList<>();
List<String> titleList = new ArrayList<>();
Bundle featuredArguments = new Bundle(); Bundle featuredArguments = new Bundle();
featuredArguments.putString("categoryName", FEATURED_IMAGES_CATEGORY); featuredArguments.putString("categoryName", FEATURED_IMAGES_CATEGORY);
@ -133,19 +133,15 @@ public class ExploreFragment extends CommonsDaggerSupportFragment {
featuredRootFragment = new ExploreListRootFragment(featuredArguments); featuredRootFragment = new ExploreListRootFragment(featuredArguments);
mobileRootFragment = new ExploreListRootFragment(mobileArguments); mobileRootFragment = new ExploreListRootFragment(mobileArguments);
mapRootFragment = new ExploreMapRootFragment(mapArguments); mapRootFragment = new ExploreMapRootFragment(mapArguments);
fragmentList.add(featuredRootFragment);
titleList.add(getString(R.string.explore_tab_title_featured).toUpperCase(Locale.ROOT));
fragmentList.add(mobileRootFragment);
titleList.add(getString(R.string.explore_tab_title_mobile).toUpperCase(Locale.ROOT));
fragmentList.add(mapRootFragment);
titleList.add(getString(R.string.explore_tab_title_map).toUpperCase(Locale.ROOT));
((MainActivity) getActivity()).showTabs(); ((MainActivity) getActivity()).showTabs();
((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false); ((BaseActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);
viewPagerAdapter.setTabData(fragmentList, titleList); viewPagerAdapter.setTabs(
pairOf(R.string.explore_tab_title_featured, featuredRootFragment),
pairOf(R.string.explore_tab_title_mobile, mobileRootFragment),
pairOf(R.string.explore_tab_title_map, mapRootFragment)
);
viewPagerAdapter.notifyDataSetChanged(); viewPagerAdapter.notifyDataSetChanged();
} }

View file

@ -1,5 +1,7 @@
package fr.free.nrw.commons.explore; package fr.free.nrw.commons.explore;
import static fr.free.nrw.commons.ViewPagerAdapter.pairOf;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.View; import android.view.View;
@ -26,11 +28,13 @@ import fr.free.nrw.commons.utils.FragmentUtils;
import fr.free.nrw.commons.utils.ViewUtil; import fr.free.nrw.commons.utils.ViewUtil;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.inject.Inject; import javax.inject.Inject;
import kotlin.Pair;
import timber.log.Timber; import timber.log.Timber;
/** /**
@ -65,7 +69,7 @@ public class SearchActivity extends BaseActivity
binding.toolbarSearch.setNavigationOnClickListener(v->onBackPressed()); binding.toolbarSearch.setNavigationOnClickListener(v->onBackPressed());
supportFragmentManager = getSupportFragmentManager(); supportFragmentManager = getSupportFragmentManager();
setSearchHistoryFragment(); setSearchHistoryFragment();
viewPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager()); viewPagerAdapter = new ViewPagerAdapter(this, getSupportFragmentManager());
binding.viewPager.setAdapter(viewPagerAdapter); binding.viewPager.setAdapter(viewPagerAdapter);
binding.viewPager.setOffscreenPageLimit(2); // Because we want all the fragments to be alive binding.viewPager.setOffscreenPageLimit(2); // Because we want all the fragments to be alive
binding.tabLayout.setupWithViewPager(binding.viewPager); binding.tabLayout.setupWithViewPager(binding.viewPager);
@ -90,19 +94,15 @@ public class SearchActivity extends BaseActivity
* Sets the titles in the tabLayout and fragments in the viewPager * Sets the titles in the tabLayout and fragments in the viewPager
*/ */
public void setTabs() { public void setTabs() {
List<Fragment> fragmentList = new ArrayList<>();
List<String> titleList = new ArrayList<>();
searchMediaFragment = new SearchMediaFragment(); searchMediaFragment = new SearchMediaFragment();
searchDepictionsFragment = new SearchDepictionsFragment(); searchDepictionsFragment = new SearchDepictionsFragment();
searchCategoryFragment= new SearchCategoryFragment(); searchCategoryFragment= new SearchCategoryFragment();
fragmentList.add(searchMediaFragment);
titleList.add(getResources().getString(R.string.search_tab_title_media).toUpperCase(Locale.ROOT));
fragmentList.add(searchCategoryFragment);
titleList.add(getResources().getString(R.string.search_tab_title_categories).toUpperCase(Locale.ROOT));
fragmentList.add(searchDepictionsFragment);
titleList.add(getResources().getString(R.string.search_tab_title_depictions).toUpperCase(Locale.ROOT));
viewPagerAdapter.setTabData(fragmentList, titleList); viewPagerAdapter.setTabs(
pairOf(R.string.search_tab_title_media, searchMediaFragment),
pairOf(R.string.search_tab_title_categories, searchCategoryFragment),
pairOf(R.string.search_tab_title_depictions, searchDepictionsFragment)
);
viewPagerAdapter.notifyDataSetChanged(); viewPagerAdapter.notifyDataSetChanged();
getCompositeDisposable().add(RxSearchView.queryTextChanges(binding.searchBox) getCompositeDisposable().add(RxSearchView.queryTextChanges(binding.searchBox)
.takeUntil(RxView.detaches(binding.searchBox)) .takeUntil(RxView.detaches(binding.searchBox))

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.explore.depictions; package fr.free.nrw.commons.explore.depictions;
import static fr.free.nrw.commons.ViewPagerAdapter.pairOf;
import static fr.free.nrw.commons.utils.UrlUtilsKt.handleWebUrl; import static fr.free.nrw.commons.utils.UrlUtilsKt.handleWebUrl;
import android.content.Context; import android.content.Context;
@ -31,8 +32,10 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import kotlin.Pair;
/** /**
* Activity to show depiction media, parent classes and child classes of depicted items in Explore * Activity to show depiction media, parent classes and child classes of depicted items in Explore
@ -66,7 +69,7 @@ public class WikidataItemDetailsActivity extends BaseActivity implements MediaDe
setContentView(binding.getRoot()); setContentView(binding.getRoot());
compositeDisposable = new CompositeDisposable(); compositeDisposable = new CompositeDisposable();
supportFragmentManager = getSupportFragmentManager(); supportFragmentManager = getSupportFragmentManager();
viewPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager()); viewPagerAdapter = new ViewPagerAdapter(this, getSupportFragmentManager());
binding.viewPager.setAdapter(viewPagerAdapter); binding.viewPager.setAdapter(viewPagerAdapter);
binding.viewPager.setOffscreenPageLimit(2); binding.viewPager.setOffscreenPageLimit(2);
binding.tabLayout.setupWithViewPager(binding.viewPager); binding.tabLayout.setupWithViewPager(binding.viewPager);
@ -105,8 +108,6 @@ public class WikidataItemDetailsActivity extends BaseActivity implements MediaDe
* Set the fragments according to the tab selected in the viewPager. * Set the fragments according to the tab selected in the viewPager.
*/ */
private void setTabs() { private void setTabs() {
List<Fragment> fragmentList = new ArrayList<>();
List<String> titleList = new ArrayList<>();
depictionImagesListFragment = new DepictedImagesFragment(); depictionImagesListFragment = new DepictedImagesFragment();
ChildDepictionsFragment childDepictionsFragment = new ChildDepictionsFragment(); ChildDepictionsFragment childDepictionsFragment = new ChildDepictionsFragment();
ParentDepictionsFragment parentDepictionsFragment = new ParentDepictionsFragment(); ParentDepictionsFragment parentDepictionsFragment = new ParentDepictionsFragment();
@ -120,13 +121,12 @@ public class WikidataItemDetailsActivity extends BaseActivity implements MediaDe
parentDepictionsFragment.setArguments(arguments); parentDepictionsFragment.setArguments(arguments);
childDepictionsFragment.setArguments(arguments); childDepictionsFragment.setArguments(arguments);
} }
fragmentList.add(depictionImagesListFragment);
titleList.add(getResources().getString(R.string.title_for_media)); viewPagerAdapter.setTabs(
fragmentList.add(childDepictionsFragment); pairOf(R.string.title_for_media, depictionImagesListFragment),
titleList.add(getResources().getString(R.string.title_for_child_classes)); pairOf(R.string.title_for_subcategories, childDepictionsFragment),
fragmentList.add(parentDepictionsFragment); pairOf(R.string.title_for_parent_categories, parentDepictionsFragment)
titleList.add(getResources().getString(R.string.title_for_parent_classes)); );
viewPagerAdapter.setTabData(fragmentList, titleList);
binding.viewPager.setOffscreenPageLimit(2); binding.viewPager.setOffscreenPageLimit(2);
viewPagerAdapter.notifyDataSetChanged(); viewPagerAdapter.notifyDataSetChanged();

View file

@ -1,8 +1,10 @@
package fr.free.nrw.commons.explore.map; package fr.free.nrw.commons.explore.map;
import androidx.annotation.NonNull;
import fr.free.nrw.commons.Media; import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.location.LatLng; import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.media.MediaClient; import fr.free.nrw.commons.media.MediaClient;
import java.util.Collections;
import java.util.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@ -23,6 +25,7 @@ public class ExploreMapCalls {
* @param currentLatLng coordinates of search location * @param currentLatLng coordinates of search location
* @return list of places obtained * @return list of places obtained
*/ */
@NonNull
List<Media> callCommonsQuery(final LatLng currentLatLng) { List<Media> callCommonsQuery(final LatLng currentLatLng) {
String coordinates = currentLatLng.getLatitude() + "|" + currentLatLng.getLongitude(); String coordinates = currentLatLng.getLatitude() + "|" + currentLatLng.getLongitude();
return mediaClient.getMediaListFromGeoSearch(coordinates).blockingGet(); return mediaClient.getMediaListFromGeoSearch(coordinates).blockingGet();

View file

@ -14,6 +14,7 @@ import fr.free.nrw.commons.explore.map.ExploreMapController.NearbyBaseMarkerThum
import fr.free.nrw.commons.kvstore.JsonKvStore; import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.location.LatLng; import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType; import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType;
import fr.free.nrw.commons.nearby.Place;
import io.reactivex.Observable; import io.reactivex.Observable;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
import java.util.List; import java.util.List;
@ -182,7 +183,7 @@ public class ExploreMapPresenter
exploreMapController exploreMapController
.loadAttractionsFromLocationToBaseMarkerOptions(explorePlacesInfo.currentLatLng, .loadAttractionsFromLocationToBaseMarkerOptions(explorePlacesInfo.currentLatLng,
// Curlatlang will be used to calculate distances // Curlatlang will be used to calculate distances
explorePlacesInfo.explorePlaceList, (List<Place>) explorePlacesInfo.explorePlaceList,
exploreMapFragmentView.getContext(), exploreMapFragmentView.getContext(),
this, this,
explorePlacesInfo); explorePlacesInfo);
@ -230,11 +231,7 @@ public class ExploreMapPresenter
mylocation.setLongitude(exploreMapFragmentView.getLastMapFocus().getLongitude()); mylocation.setLongitude(exploreMapFragmentView.getLastMapFocus().getLongitude());
Float distance = mylocation.distanceTo(dest_location); Float distance = mylocation.distanceTo(dest_location);
if (distance > 2000.0 * 3 / 4) { return !(distance > 2000.0 * 3 / 4);
return false;
} else {
return true;
}
} }
} }

View file

@ -1,6 +1,6 @@
package fr.free.nrw.commons.media package fr.free.nrw.commons.media
import fr.free.nrw.commons.OkHttpConnectionFactory.UnsuccessfulResponseInterceptor.SUPPRESS_ERROR_LOG_HEADER import fr.free.nrw.commons.SUPPRESS_ERROR_LOG_HEADER
import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse import fr.free.nrw.commons.wikidata.mwapi.MwQueryResponse
import io.reactivex.Single import io.reactivex.Single
import retrofit2.http.GET import retrofit2.http.GET

View file

@ -18,7 +18,6 @@ import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.CommonsApplication import fr.free.nrw.commons.CommonsApplication
import fr.free.nrw.commons.CommonsApplication.ActivityLogoutListener import fr.free.nrw.commons.CommonsApplication.ActivityLogoutListener
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.WelcomeActivity
import fr.free.nrw.commons.actions.PageEditClient import fr.free.nrw.commons.actions.PageEditClient
import fr.free.nrw.commons.databinding.FragmentMoreBottomSheetBinding import fr.free.nrw.commons.databinding.FragmentMoreBottomSheetBinding
import fr.free.nrw.commons.di.ApplicationlessInjection import fr.free.nrw.commons.di.ApplicationlessInjection
@ -32,6 +31,7 @@ import fr.free.nrw.commons.logging.CommonsLogSender
import fr.free.nrw.commons.profile.ProfileActivity import fr.free.nrw.commons.profile.ProfileActivity
import fr.free.nrw.commons.review.ReviewActivity import fr.free.nrw.commons.review.ReviewActivity
import fr.free.nrw.commons.settings.SettingsActivity import fr.free.nrw.commons.settings.SettingsActivity
import fr.free.nrw.commons.startWelcome
import io.reactivex.Single import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
@ -241,7 +241,7 @@ class MoreBottomSheetFragment : BottomSheetDialogFragment() {
} }
fun onTutorialClicked() { fun onTutorialClicked() {
WelcomeActivity.startYourself(requireActivity()) requireContext().startWelcome()
} }
fun onSettingsClicked() { fun onSettingsClicked() {

View file

@ -1,10 +1,14 @@
package fr.free.nrw.commons.nearby; package fr.free.nrw.commons.nearby;
import static java.util.Collections.emptyList;
import android.location.Location; import android.location.Location;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import fr.free.nrw.commons.nearby.model.NearbyQueryParams; import fr.free.nrw.commons.nearby.model.NearbyQueryParams;
import fr.free.nrw.commons.nearby.model.NearbyQueryParams.Radial;
import fr.free.nrw.commons.nearby.model.NearbyQueryParams.Rectangular;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
import java.util.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
@ -46,13 +50,14 @@ public class NearbyPlaces {
* @param customQuery * @param customQuery
* @return list of places obtained * @return list of places obtained
*/ */
@NonNull
List<Place> radiusExpander(final LatLng currentLatLng, final String lang, List<Place> radiusExpander(final LatLng currentLatLng, final String lang,
final boolean returnClosestResult, @Nullable final String customQuery) throws Exception { final boolean returnClosestResult, @Nullable final String customQuery) throws Exception {
final int minResults; final int minResults;
final double maxRadius; final double maxRadius;
List<Place> places = Collections.emptyList(); List<Place> places = emptyList();
// If returnClosestResult is true, then this means that we are trying to get closest point // If returnClosestResult is true, then this means that we are trying to get closest point
// to use in cardView in Contributions fragment // to use in cardView in Contributions fragment
@ -113,6 +118,7 @@ public class NearbyPlaces {
* @return A list of places obtained from the Wikidata query. * @return A list of places obtained from the Wikidata query.
* @throws Exception If an error occurs during the retrieval process. * @throws Exception If an error occurs during the retrieval process.
*/ */
@NonNull
public List<Place> getFromWikidataQuery( public List<Place> getFromWikidataQuery(
final fr.free.nrw.commons.location.LatLng centerPoint, final fr.free.nrw.commons.location.LatLng centerPoint,
final fr.free.nrw.commons.location.LatLng screenTopRight, final fr.free.nrw.commons.location.LatLng screenTopRight,
@ -120,11 +126,11 @@ public class NearbyPlaces {
final boolean shouldQueryForMonuments, final boolean shouldQueryForMonuments,
@Nullable final String customQuery) throws Exception { @Nullable final String customQuery) throws Exception {
if (customQuery != null) { if (customQuery != null) {
return okHttpJsonApiClient final List<Place> nearbyPlaces = okHttpJsonApiClient.getNearbyPlaces(
.getNearbyPlaces( new Rectangular(screenTopRight, screenBottomLeft), lang,
new NearbyQueryParams.Rectangular(screenTopRight, screenBottomLeft), lang,
shouldQueryForMonuments, shouldQueryForMonuments,
customQuery); customQuery);
return nearbyPlaces != null ? nearbyPlaces : emptyList();
} }
final int lowerLimit = 1000, upperLimit = 1500; final int lowerLimit = 1000, upperLimit = 1500;
@ -141,9 +147,10 @@ public class NearbyPlaces {
final int itemCount = okHttpJsonApiClient.getNearbyItemCount( final int itemCount = okHttpJsonApiClient.getNearbyItemCount(
new NearbyQueryParams.Rectangular(screenTopRight, screenBottomLeft)); new NearbyQueryParams.Rectangular(screenTopRight, screenBottomLeft));
if (itemCount < upperLimit) { if (itemCount < upperLimit) {
return okHttpJsonApiClient.getNearbyPlaces( final List<Place> nearbyPlaces = okHttpJsonApiClient.getNearbyPlaces(
new NearbyQueryParams.Rectangular(screenTopRight, screenBottomLeft), lang, new Rectangular(screenTopRight, screenBottomLeft), lang,
shouldQueryForMonuments, null); shouldQueryForMonuments, null);
return nearbyPlaces != null ? nearbyPlaces : emptyList();
} }
} }
@ -175,9 +182,10 @@ public class NearbyPlaces {
maxRadius = targetRadius - 1; maxRadius = targetRadius - 1;
} }
} }
return okHttpJsonApiClient.getNearbyPlaces( final List<Place> nearbyPlaces = okHttpJsonApiClient.getNearbyPlaces(
new NearbyQueryParams.Radial(centerPoint, targetRadius / 100f), lang, shouldQueryForMonuments, new Radial(centerPoint, targetRadius / 100f), lang, shouldQueryForMonuments,
null); null);
return nearbyPlaces != null ? nearbyPlaces : emptyList();
} }
/** /**

View file

@ -11,6 +11,7 @@ import android.view.View
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.ViewPagerAdapter import fr.free.nrw.commons.ViewPagerAdapter
@ -71,7 +72,7 @@ class ProfileActivity : BaseActivity() {
title = userName title = userName
shouldShowContributions = intent.getBooleanExtra(KEY_SHOULD_SHOW_CONTRIBUTIONS, false) shouldShowContributions = intent.getBooleanExtra(KEY_SHOULD_SHOW_CONTRIBUTIONS, false)
viewPagerAdapter = ViewPagerAdapter(supportFragmentManager) viewPagerAdapter = ViewPagerAdapter(this, supportFragmentManager)
binding.viewPager.adapter = viewPagerAdapter binding.viewPager.adapter = viewPagerAdapter
binding.tabLayout.setupWithViewPager(binding.viewPager) binding.tabLayout.setupWithViewPager(binding.viewPager)
setTabs() setTabs()
@ -83,39 +84,23 @@ class ProfileActivity : BaseActivity() {
} }
private fun setTabs() { private fun setTabs() {
val fragmentList = mutableListOf<Fragment>()
val titleList = mutableListOf<String>()
// Add Achievements tab
achievementsFragment = AchievementsFragment().apply { achievementsFragment = AchievementsFragment().apply {
arguments = Bundle().apply { arguments = bundleOf(KEY_USERNAME to userName)
putString(KEY_USERNAME, userName)
} }
}
fragmentList.add(achievementsFragment)
titleList.add(resources.getString(R.string.achievements_tab_title).uppercase())
// Add Leaderboard tab
leaderboardFragment = LeaderboardFragment().apply { leaderboardFragment = LeaderboardFragment().apply {
arguments = Bundle().apply { arguments = bundleOf(KEY_USERNAME to userName)
putString(KEY_USERNAME, userName)
} }
}
fragmentList.add(leaderboardFragment)
titleList.add(resources.getString(R.string.leaderboard_tab_title).uppercase(Locale.ROOT))
// Add Contributions tab
contributionsFragment = ContributionsFragment().apply { contributionsFragment = ContributionsFragment().apply {
arguments = Bundle().apply { arguments = bundleOf(KEY_USERNAME to userName)
putString(KEY_USERNAME, userName)
}
}
contributionsFragment?.let {
fragmentList.add(it)
titleList.add(getString(R.string.contributions_fragment).uppercase(Locale.ROOT))
} }
viewPagerAdapter.setTabData(fragmentList, titleList) viewPagerAdapter.setTabs(
R.string.achievements_tab_title to achievementsFragment,
R.string.leaderboard_tab_title to leaderboardFragment,
R.string.contributions_fragment to contributionsFragment!!
)
viewPagerAdapter.notifyDataSetChanged() viewPagerAdapter.notifyDataSetChanged()
} }

View file

@ -28,8 +28,6 @@ class UploadProgressActivity : BaseActivity() {
@Inject @Inject
lateinit var contributionDao: ContributionDao lateinit var contributionDao: ContributionDao
val fragmentList: MutableList<Fragment> = ArrayList()
val titleList: MutableList<String> = ArrayList()
var isPaused = true var isPaused = true
var isPendingIconsVisible = true var isPendingIconsVisible = true
var isErrorIconsVisisble = false var isErrorIconsVisisble = false
@ -38,7 +36,7 @@ class UploadProgressActivity : BaseActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityUploadProgressBinding.inflate(layoutInflater) binding = ActivityUploadProgressBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
viewPagerAdapter = ViewPagerAdapter(supportFragmentManager) viewPagerAdapter = ViewPagerAdapter(this, supportFragmentManager)
binding.uploadProgressViewPager.setAdapter(viewPagerAdapter) binding.uploadProgressViewPager.setAdapter(viewPagerAdapter)
binding.uploadProgressViewPager.setId(R.id.upload_progress_view_pager) binding.uploadProgressViewPager.setId(R.id.upload_progress_view_pager)
binding.uploadProgressTabLayout.setupWithViewPager(binding.uploadProgressViewPager) binding.uploadProgressTabLayout.setupWithViewPager(binding.uploadProgressViewPager)
@ -81,11 +79,10 @@ class UploadProgressActivity : BaseActivity() {
pendingUploadsFragment = PendingUploadsFragment() pendingUploadsFragment = PendingUploadsFragment()
failedUploadsFragment = FailedUploadsFragment() failedUploadsFragment = FailedUploadsFragment()
fragmentList.add(pendingUploadsFragment!!) viewPagerAdapter!!.setTabs(
titleList.add(getString(R.string.pending)) R.string.pending to pendingUploadsFragment!!,
fragmentList.add(failedUploadsFragment!!) R.string.failed to failedUploadsFragment!!
titleList.add(getString(R.string.failed)) )
viewPagerAdapter!!.setTabData(fragmentList, titleList)
viewPagerAdapter!!.notifyDataSetChanged() viewPagerAdapter!!.notifyDataSetChanged()
} }

View file

@ -617,6 +617,8 @@ Upload your first media by tapping on the add button.</string>
<string name="title_for_media">MEDIA</string> <string name="title_for_media">MEDIA</string>
<string name="title_for_child_classes">CHILD CLASSES</string> <string name="title_for_child_classes">CHILD CLASSES</string>
<string name="title_for_parent_classes">PARENT CLASSES</string> <string name="title_for_parent_classes">PARENT CLASSES</string>
<string name="title_for_subcategories">SUBCATEGORIES</string>
<string name="title_for_parent_categories">PARENT CATEGORIES</string>
<string name="upload_nearby_place_found_title">Nearby Place Found</string> <string name="upload_nearby_place_found_title">Nearby Place Found</string>
<string name="upload_nearby_place_found_description_plural">Are these pictures of %1$s?</string> <string name="upload_nearby_place_found_description_plural">Are these pictures of %1$s?</string>

View file

@ -1,110 +0,0 @@
package fr.free.nrw.commons;
import static fr.free.nrw.commons.TestConnectionFactoryKt.createTestClient;
import androidx.annotation.NonNull;
import java.util.List;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import okhttp3.Dispatcher;
import okhttp3.OkHttpClient;
import okhttp3.mockwebserver.MockResponse;
import org.junit.After;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import fr.free.nrw.commons.wikidata.GsonUtil;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
@RunWith(RobolectricTestRunner.class)
public abstract class MockWebServerTest {
private OkHttpClient okHttpClient;
private final TestWebServer server = new TestWebServer();
@Before public void setUp() throws Throwable {
OkHttpConnectionFactory.CLIENT = createTestClient();
okHttpClient = OkHttpConnectionFactory.CLIENT.newBuilder()
.dispatcher(new Dispatcher(new ImmediateExecutorService())).build();
server.setUp();
}
@After public void tearDown() throws Throwable {
server.tearDown();
}
@NonNull protected TestWebServer server() {
return server;
}
protected void enqueueFromFile(@NonNull String filename) throws Throwable {
String json = TestFileUtil.readRawFile(filename);
server.enqueue(json);
}
protected void enqueue404() {
final int code = 404;
server.enqueue(new MockResponse().setResponseCode(code).setBody("Not Found"));
}
protected void enqueueMalformed() {
server.enqueue("(╯°□°)╯︵ ┻━┻");
}
protected void enqueueEmptyJson() {
server.enqueue(new MockResponse().setBody("{}"));
}
@NonNull protected OkHttpClient okHttpClient() {
return okHttpClient;
}
@NonNull protected <T> T service(Class<T> clazz) {
return service(clazz, server().getUrl());
}
@NonNull protected <T> T service(Class<T> clazz, @NonNull String url) {
return new Retrofit.Builder()
.baseUrl(url)
.callbackExecutor(new ImmediateExecutor())
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(GsonUtil.INSTANCE.getDefaultGson()))
.build()
.create(clazz);
}
public final class ImmediateExecutorService extends AbstractExecutorService {
@Override public void shutdown() {
throw new UnsupportedOperationException();
}
@NonNull @Override public List<Runnable> shutdownNow() {
throw new UnsupportedOperationException();
}
@Override public boolean isShutdown() {
throw new UnsupportedOperationException();
}
@Override public boolean isTerminated() {
throw new UnsupportedOperationException();
}
@Override public boolean awaitTermination(long l, @NonNull TimeUnit timeUnit)
throws InterruptedException {
throw new UnsupportedOperationException();
}
@Override public void execute(@NonNull Runnable runnable) {
runnable.run();
}
}
public class ImmediateExecutor implements Executor {
@Override
public void execute(@NonNull Runnable runnable) {
runnable.run();
}
}
}

View file

@ -0,0 +1,110 @@
package fr.free.nrw.commons
import fr.free.nrw.commons.wikidata.GsonUtil.defaultGson
import okhttp3.Dispatcher
import okhttp3.OkHttpClient
import okhttp3.mockwebserver.MockResponse
import org.junit.After
import org.junit.Before
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.AbstractExecutorService
import java.util.concurrent.Executor
import java.util.concurrent.TimeUnit
@RunWith(RobolectricTestRunner::class)
abstract class MockWebServerTest {
private var okHttpClient: OkHttpClient? = null
private val server = TestWebServer()
@Before
@Throws(Throwable::class)
open fun setUp() {
OkHttpConnectionFactory.CLIENT = createTestClient()
okHttpClient = OkHttpConnectionFactory.CLIENT!!.newBuilder()
.dispatcher(Dispatcher(ImmediateExecutorService())).build()
server.setUp()
}
@After
@Throws(Throwable::class)
fun tearDown() {
server.tearDown()
}
protected fun server(): TestWebServer {
return server
}
@Throws(Throwable::class)
protected fun enqueueFromFile(filename: String) {
val json = TestFileUtil.readRawFile(filename)
server.enqueue(json)
}
protected fun enqueue404() {
val code = 404
server.enqueue(MockResponse().setResponseCode(code).setBody("Not Found"))
}
protected fun enqueueMalformed() {
server.enqueue("(╯°□°)╯︵ ┻━┻")
}
protected fun enqueueEmptyJson() {
server.enqueue(MockResponse().setBody("{}"))
}
protected fun okHttpClient(): OkHttpClient {
return okHttpClient!!
}
protected fun <T> service(clazz: Class<T>): T {
return service(clazz, server().url)
}
protected fun <T> service(clazz: Class<T>, url: String): T {
return Retrofit.Builder()
.baseUrl(url)
.callbackExecutor(ImmediateExecutor())
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(defaultGson))
.build()
.create(clazz)
}
inner class ImmediateExecutorService : AbstractExecutorService() {
override fun shutdown() {
throw UnsupportedOperationException()
}
override fun shutdownNow(): List<Runnable> {
throw UnsupportedOperationException()
}
override fun isShutdown(): Boolean {
throw UnsupportedOperationException()
}
override fun isTerminated(): Boolean {
throw UnsupportedOperationException()
}
@Throws(InterruptedException::class)
override fun awaitTermination(l: Long, timeUnit: TimeUnit): Boolean {
throw UnsupportedOperationException()
}
override fun execute(runnable: Runnable) {
runnable.run()
}
}
inner class ImmediateExecutor : Executor {
override fun execute(runnable: Runnable) {
runnable.run()
}
}
}

View file

@ -1,6 +1,5 @@
package fr.free.nrw.commons package fr.free.nrw.commons
import fr.free.nrw.commons.OkHttpConnectionFactory.HttpStatusException
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Response import okhttp3.Response
@ -39,6 +38,6 @@ private class UnsuccessfulResponseInterceptor : Interceptor {
if (rsp.isSuccessful) { if (rsp.isSuccessful) {
return rsp return rsp
} }
throw HttpStatusException(rsp) throw IOException("Unsuccessful response")
} }
} }

View file

@ -2,7 +2,6 @@ package fr.free.nrw.commons.auth.csrf
import com.google.gson.stream.MalformedJsonException import com.google.gson.stream.MalformedJsonException
import fr.free.nrw.commons.MockWebServerTest import fr.free.nrw.commons.MockWebServerTest
import fr.free.nrw.commons.OkHttpConnectionFactory.HttpStatusException
import fr.free.nrw.commons.auth.SessionManager import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.auth.login.LoginClient import fr.free.nrw.commons.auth.login.LoginClient
import fr.free.nrw.commons.wikidata.mwapi.MwException import fr.free.nrw.commons.wikidata.mwapi.MwException
@ -13,6 +12,7 @@ import org.mockito.ArgumentMatchers.isA
import org.mockito.Mockito.mock import org.mockito.Mockito.mock
import org.mockito.Mockito.never import org.mockito.Mockito.never
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
import java.io.IOException
class CsrfTokenClientTest : MockWebServerTest() { class CsrfTokenClientTest : MockWebServerTest() {
private val cb = mock(CsrfTokenClient.Callback::class.java) private val cb = mock(CsrfTokenClient.Callback::class.java)
@ -53,7 +53,7 @@ class CsrfTokenClientTest : MockWebServerTest() {
performRequest() performRequest()
verify(cb, never()).success(any(String::class.java)) verify(cb, never()).success(any(String::class.java))
verify(cb).failure(isA(HttpStatusException::class.java)) verify(cb).failure(isA(IOException::class.java))
} }
@Test @Test