diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 5c297a65e..ea0cb3b07 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -16,6 +16,7 @@ + diff --git a/app/src/main/java/fr/free/nrw/commons/MapController.java b/app/src/main/java/fr/free/nrw/commons/MapController.java deleted file mode 100644 index 72005fe83..000000000 --- a/app/src/main/java/fr/free/nrw/commons/MapController.java +++ /dev/null @@ -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 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 mediaList; // Search location for finding this places - } - - /** - * We pass this variable as a group of placeList and boundaryCoordinates - */ - public class ExplorePlacesInfo { - public List 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 mediaList; // Search location for finding this places - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/MapController.kt b/app/src/main/java/fr/free/nrw/commons/MapController.kt new file mode 100644 index 000000000..5888b3f5f --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/MapController.kt @@ -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 = emptyList() // List of nearby places + + @JvmField + var boundaryCoordinates: Array = 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? = 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 = emptyList() // List of nearby places + + @JvmField + var boundaryCoordinates: Array = 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 = emptyList() // Search location for finding this places + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/OkHttpConnectionFactory.java b/app/src/main/java/fr/free/nrw/commons/OkHttpConnectionFactory.java deleted file mode 100644 index a3cef1172..000000000 --- a/app/src/main/java/fr/free/nrw/commons/OkHttpConnectionFactory.java +++ /dev/null @@ -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 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; - } - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/OkHttpConnectionFactory.kt b/app/src/main/java/fr/free/nrw/commons/OkHttpConnectionFactory.kt new file mode 100644 index 000000000..d15c72f57 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/OkHttpConnectionFactory.kt @@ -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" + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/ViewHolder.java b/app/src/main/java/fr/free/nrw/commons/ViewHolder.java deleted file mode 100644 index 7181d85cc..000000000 --- a/app/src/main/java/fr/free/nrw/commons/ViewHolder.java +++ /dev/null @@ -1,7 +0,0 @@ -package fr.free.nrw.commons; - -import android.content.Context; - -public interface ViewHolder { - void bindModel(Context context, T model); -} diff --git a/app/src/main/java/fr/free/nrw/commons/ViewPagerAdapter.java b/app/src/main/java/fr/free/nrw/commons/ViewPagerAdapter.java deleted file mode 100644 index b887aaf99..000000000 --- a/app/src/main/java/fr/free/nrw/commons/ViewPagerAdapter.java +++ /dev/null @@ -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 fragmentList = new ArrayList<>(); - private List 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 fragmentList, List 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); - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/ViewPagerAdapter.kt b/app/src/main/java/fr/free/nrw/commons/ViewPagerAdapter.kt new file mode 100644 index 000000000..a8ce8c79a --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/ViewPagerAdapter.kt @@ -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 = emptyList() + private var fragmentTitleList: List = 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) { + // 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 + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/WelcomeActivity.java b/app/src/main/java/fr/free/nrw/commons/WelcomeActivity.java deleted file mode 100644 index c8cedfef1..000000000 --- a/app/src/main/java/fr/free/nrw/commons/WelcomeActivity.java +++ /dev/null @@ -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(); - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/WelcomeActivity.kt b/app/src/main/java/fr/free/nrw/commons/WelcomeActivity.kt new file mode 100644 index 000000000..439ed1e92 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/WelcomeActivity.kt @@ -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)) +} diff --git a/app/src/main/java/fr/free/nrw/commons/WelcomePagerAdapter.java b/app/src/main/java/fr/free/nrw/commons/WelcomePagerAdapter.java deleted file mode 100644 index a9b7381df..000000000 --- a/app/src/main/java/fr/free/nrw/commons/WelcomePagerAdapter.java +++ /dev/null @@ -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); - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/WelcomePagerAdapter.kt b/app/src/main/java/fr/free/nrw/commons/WelcomePagerAdapter.kt new file mode 100644 index 000000000..0cb88c48b --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/WelcomePagerAdapter.kt @@ -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(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(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 + ) + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.kt b/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.kt index e15d9baf3..8517db744 100644 --- a/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.kt @@ -59,7 +59,7 @@ class CategoryDetailsActivity : BaseActivity(), val view = binding.root setContentView(view) supportFragmentManager = getSupportFragmentManager() - viewPagerAdapter = ViewPagerAdapter(supportFragmentManager) + viewPagerAdapter = ViewPagerAdapter(this, supportFragmentManager) binding.viewPager.adapter = viewPagerAdapter binding.viewPager.offscreenPageLimit = 2 binding.tabLayout.setupWithViewPager(binding.viewPager) @@ -83,8 +83,6 @@ class CategoryDetailsActivity : BaseActivity(), * Set the fragments according to the tab selected in the viewPager. */ private fun setTabs() { - val fragmentList = mutableListOf() - val titleList = mutableListOf() categoriesMediaFragment = CategoriesMediaFragment() val subCategoryListFragment = SubCategoriesFragment() val parentCategoriesFragment = ParentCategoriesFragment() @@ -99,13 +97,12 @@ class CategoryDetailsActivity : BaseActivity(), viewModel.onCheckIfBookmarked(categoryName!!) } - fragmentList.add(categoriesMediaFragment) - titleList.add("MEDIA") - fragmentList.add(subCategoryListFragment) - titleList.add("SUBCATEGORIES") - fragmentList.add(parentCategoriesFragment) - titleList.add("PARENT CATEGORIES") - viewPagerAdapter.setTabData(fragmentList, titleList) + + viewPagerAdapter.setTabs( + R.string.title_for_media to categoriesMediaFragment, + R.string.title_for_subcategories to subCategoryListFragment, + R.string.title_for_parent_categories to parentCategoriesFragment + ) viewPagerAdapter.notifyDataSetChanged() } diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.kt b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.kt index a83532bdb..b9fa3e395 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/contributions/MainActivity.kt @@ -12,7 +12,6 @@ import androidx.fragment.app.FragmentManager import androidx.work.ExistingWorkPolicy import com.google.android.material.bottomnavigation.BottomNavigationView import fr.free.nrw.commons.R -import fr.free.nrw.commons.WelcomeActivity import fr.free.nrw.commons.auth.SessionManager import fr.free.nrw.commons.bookmarks.BookmarkFragment 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.quiz.QuizChecker 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.upload.UploadProgressActivity import fr.free.nrw.commons.upload.worker.WorkRequestHelper.Companion.makeOneTimeWorkRequest @@ -517,7 +517,7 @@ after opening the app. (!applicationKvStore!!.getBoolean("login_skipped")) ) { defaultKvStore.putBoolean("inAppCameraFirstRun", true) - WelcomeActivity.startYourself(this) + startWelcome() } retryAllFailedUploads() diff --git a/app/src/main/java/fr/free/nrw/commons/explore/ExploreFragment.java b/app/src/main/java/fr/free/nrw/commons/explore/ExploreFragment.java index b31c34b67..475d14287 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/ExploreFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/explore/ExploreFragment.java @@ -1,6 +1,7 @@ package fr.free.nrw.commons.explore; import static androidx.viewpager.widget.ViewPager.SCROLL_STATE_IDLE; +import static fr.free.nrw.commons.ViewPagerAdapter.pairOf; import android.os.Bundle; 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.utils.ActivityUtils; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Locale; import javax.inject.Inject; import javax.inject.Named; +import kotlin.Pair; public class ExploreFragment extends CommonsDaggerSupportFragment { @@ -70,7 +73,7 @@ public class ExploreFragment extends CommonsDaggerSupportFragment { loadNearbyMapData(); binding = FragmentExploreBinding.inflate(inflater, container, false); - viewPagerAdapter = new ViewPagerAdapter(getChildFragmentManager(), + viewPagerAdapter = new ViewPagerAdapter(requireContext(), getChildFragmentManager(), FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); binding.viewPager.setAdapter(viewPagerAdapter); @@ -111,9 +114,6 @@ public class ExploreFragment extends CommonsDaggerSupportFragment { * Sets the titles in the tabLayout and fragments in the viewPager */ public void setTabs() { - List fragmentList = new ArrayList<>(); - List titleList = new ArrayList<>(); - Bundle featuredArguments = new Bundle(); featuredArguments.putString("categoryName", FEATURED_IMAGES_CATEGORY); @@ -133,19 +133,15 @@ public class ExploreFragment extends CommonsDaggerSupportFragment { featuredRootFragment = new ExploreListRootFragment(featuredArguments); mobileRootFragment = new ExploreListRootFragment(mobileArguments); 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(); ((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(); } diff --git a/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.java b/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.java index 934bff6ec..1651c720c 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.java @@ -1,5 +1,7 @@ package fr.free.nrw.commons.explore; +import static fr.free.nrw.commons.ViewPagerAdapter.pairOf; + import android.os.Bundle; import android.text.TextUtils; import android.view.View; @@ -26,11 +28,13 @@ import fr.free.nrw.commons.utils.FragmentUtils; import fr.free.nrw.commons.utils.ViewUtil; import io.reactivex.android.schedulers.AndroidSchedulers; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.concurrent.TimeUnit; import javax.inject.Inject; +import kotlin.Pair; import timber.log.Timber; /** @@ -65,7 +69,7 @@ public class SearchActivity extends BaseActivity binding.toolbarSearch.setNavigationOnClickListener(v->onBackPressed()); supportFragmentManager = getSupportFragmentManager(); setSearchHistoryFragment(); - viewPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager()); + viewPagerAdapter = new ViewPagerAdapter(this, getSupportFragmentManager()); binding.viewPager.setAdapter(viewPagerAdapter); binding.viewPager.setOffscreenPageLimit(2); // Because we want all the fragments to be alive 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 */ public void setTabs() { - List fragmentList = new ArrayList<>(); - List titleList = new ArrayList<>(); searchMediaFragment = new SearchMediaFragment(); searchDepictionsFragment = new SearchDepictionsFragment(); 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(); getCompositeDisposable().add(RxSearchView.queryTextChanges(binding.searchBox) .takeUntil(RxView.detaches(binding.searchBox)) diff --git a/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.java b/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.java index 89593d07e..e4f7ce465 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/explore/depictions/WikidataItemDetailsActivity.java @@ -1,5 +1,6 @@ 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 android.content.Context; @@ -31,8 +32,10 @@ import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.schedulers.Schedulers; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import javax.inject.Inject; +import kotlin.Pair; /** * 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()); compositeDisposable = new CompositeDisposable(); supportFragmentManager = getSupportFragmentManager(); - viewPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager()); + viewPagerAdapter = new ViewPagerAdapter(this, getSupportFragmentManager()); binding.viewPager.setAdapter(viewPagerAdapter); binding.viewPager.setOffscreenPageLimit(2); 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. */ private void setTabs() { - List fragmentList = new ArrayList<>(); - List titleList = new ArrayList<>(); depictionImagesListFragment = new DepictedImagesFragment(); ChildDepictionsFragment childDepictionsFragment = new ChildDepictionsFragment(); ParentDepictionsFragment parentDepictionsFragment = new ParentDepictionsFragment(); @@ -120,13 +121,12 @@ public class WikidataItemDetailsActivity extends BaseActivity implements MediaDe parentDepictionsFragment.setArguments(arguments); childDepictionsFragment.setArguments(arguments); } - fragmentList.add(depictionImagesListFragment); - titleList.add(getResources().getString(R.string.title_for_media)); - fragmentList.add(childDepictionsFragment); - titleList.add(getResources().getString(R.string.title_for_child_classes)); - fragmentList.add(parentDepictionsFragment); - titleList.add(getResources().getString(R.string.title_for_parent_classes)); - viewPagerAdapter.setTabData(fragmentList, titleList); + + viewPagerAdapter.setTabs( + pairOf(R.string.title_for_media, depictionImagesListFragment), + pairOf(R.string.title_for_subcategories, childDepictionsFragment), + pairOf(R.string.title_for_parent_categories, parentDepictionsFragment) + ); binding.viewPager.setOffscreenPageLimit(2); viewPagerAdapter.notifyDataSetChanged(); diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapCalls.java b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapCalls.java index ec426942c..5e674dceb 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapCalls.java +++ b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapCalls.java @@ -1,8 +1,10 @@ package fr.free.nrw.commons.explore.map; +import androidx.annotation.NonNull; import fr.free.nrw.commons.Media; import fr.free.nrw.commons.location.LatLng; import fr.free.nrw.commons.media.MediaClient; +import java.util.Collections; import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; @@ -23,6 +25,7 @@ public class ExploreMapCalls { * @param currentLatLng coordinates of search location * @return list of places obtained */ + @NonNull List callCommonsQuery(final LatLng currentLatLng) { String coordinates = currentLatLng.getLatitude() + "|" + currentLatLng.getLongitude(); return mediaClient.getMediaListFromGeoSearch(coordinates).blockingGet(); diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapPresenter.java b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapPresenter.java index 45a11a422..70f785b40 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapPresenter.java +++ b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapPresenter.java @@ -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.location.LatLng; import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType; +import fr.free.nrw.commons.nearby.Place; import io.reactivex.Observable; import java.lang.reflect.Proxy; import java.util.List; @@ -182,7 +183,7 @@ public class ExploreMapPresenter exploreMapController .loadAttractionsFromLocationToBaseMarkerOptions(explorePlacesInfo.currentLatLng, // Curlatlang will be used to calculate distances - explorePlacesInfo.explorePlaceList, + (List) explorePlacesInfo.explorePlaceList, exploreMapFragmentView.getContext(), this, explorePlacesInfo); @@ -230,11 +231,7 @@ public class ExploreMapPresenter mylocation.setLongitude(exploreMapFragmentView.getLastMapFocus().getLongitude()); Float distance = mylocation.distanceTo(dest_location); - if (distance > 2000.0 * 3 / 4) { - return false; - } else { - return true; - } + return !(distance > 2000.0 * 3 / 4); } } diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaInterface.kt b/app/src/main/java/fr/free/nrw/commons/media/MediaInterface.kt index 643374e54..d6c87410a 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaInterface.kt +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaInterface.kt @@ -1,6 +1,6 @@ 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 io.reactivex.Single import retrofit2.http.GET diff --git a/app/src/main/java/fr/free/nrw/commons/navtab/MoreBottomSheetFragment.kt b/app/src/main/java/fr/free/nrw/commons/navtab/MoreBottomSheetFragment.kt index 3f7a196fe..2d92855fc 100644 --- a/app/src/main/java/fr/free/nrw/commons/navtab/MoreBottomSheetFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/navtab/MoreBottomSheetFragment.kt @@ -18,7 +18,6 @@ import fr.free.nrw.commons.BuildConfig import fr.free.nrw.commons.CommonsApplication import fr.free.nrw.commons.CommonsApplication.ActivityLogoutListener import fr.free.nrw.commons.R -import fr.free.nrw.commons.WelcomeActivity import fr.free.nrw.commons.actions.PageEditClient import fr.free.nrw.commons.databinding.FragmentMoreBottomSheetBinding 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.review.ReviewActivity import fr.free.nrw.commons.settings.SettingsActivity +import fr.free.nrw.commons.startWelcome import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers @@ -241,7 +241,7 @@ class MoreBottomSheetFragment : BottomSheetDialogFragment() { } fun onTutorialClicked() { - WelcomeActivity.startYourself(requireActivity()) + requireContext().startWelcome() } fun onSettingsClicked() { diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java index caae8ee45..3bb2f549f 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/NearbyPlaces.java @@ -1,10 +1,14 @@ package fr.free.nrw.commons.nearby; +import static java.util.Collections.emptyList; + import android.location.Location; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; 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.util.Collections; import java.util.List; import javax.inject.Inject; @@ -46,13 +50,14 @@ public class NearbyPlaces { * @param customQuery * @return list of places obtained */ + @NonNull List radiusExpander(final LatLng currentLatLng, final String lang, final boolean returnClosestResult, @Nullable final String customQuery) throws Exception { final int minResults; final double maxRadius; - List places = Collections.emptyList(); + List places = emptyList(); // If returnClosestResult is true, then this means that we are trying to get closest point // to use in cardView in Contributions fragment @@ -113,6 +118,7 @@ public class NearbyPlaces { * @return A list of places obtained from the Wikidata query. * @throws Exception If an error occurs during the retrieval process. */ + @NonNull public List getFromWikidataQuery( final fr.free.nrw.commons.location.LatLng centerPoint, final fr.free.nrw.commons.location.LatLng screenTopRight, @@ -120,11 +126,11 @@ public class NearbyPlaces { final boolean shouldQueryForMonuments, @Nullable final String customQuery) throws Exception { if (customQuery != null) { - return okHttpJsonApiClient - .getNearbyPlaces( - new NearbyQueryParams.Rectangular(screenTopRight, screenBottomLeft), lang, + final List nearbyPlaces = okHttpJsonApiClient.getNearbyPlaces( + new Rectangular(screenTopRight, screenBottomLeft), lang, shouldQueryForMonuments, customQuery); + return nearbyPlaces != null ? nearbyPlaces : emptyList(); } final int lowerLimit = 1000, upperLimit = 1500; @@ -141,9 +147,10 @@ public class NearbyPlaces { final int itemCount = okHttpJsonApiClient.getNearbyItemCount( new NearbyQueryParams.Rectangular(screenTopRight, screenBottomLeft)); if (itemCount < upperLimit) { - return okHttpJsonApiClient.getNearbyPlaces( - new NearbyQueryParams.Rectangular(screenTopRight, screenBottomLeft), lang, + final List nearbyPlaces = okHttpJsonApiClient.getNearbyPlaces( + new Rectangular(screenTopRight, screenBottomLeft), lang, shouldQueryForMonuments, null); + return nearbyPlaces != null ? nearbyPlaces : emptyList(); } } @@ -175,9 +182,10 @@ public class NearbyPlaces { maxRadius = targetRadius - 1; } } - return okHttpJsonApiClient.getNearbyPlaces( - new NearbyQueryParams.Radial(centerPoint, targetRadius / 100f), lang, shouldQueryForMonuments, + final List nearbyPlaces = okHttpJsonApiClient.getNearbyPlaces( + new Radial(centerPoint, targetRadius / 100f), lang, shouldQueryForMonuments, null); + return nearbyPlaces != null ? nearbyPlaces : emptyList(); } /** diff --git a/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.kt b/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.kt index 105cf1860..48e61051c 100644 --- a/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.kt @@ -11,6 +11,7 @@ import android.view.View import android.widget.ImageView import android.widget.TextView import androidx.core.content.FileProvider +import androidx.core.os.bundleOf import androidx.fragment.app.Fragment import fr.free.nrw.commons.R import fr.free.nrw.commons.ViewPagerAdapter @@ -71,7 +72,7 @@ class ProfileActivity : BaseActivity() { title = userName shouldShowContributions = intent.getBooleanExtra(KEY_SHOULD_SHOW_CONTRIBUTIONS, false) - viewPagerAdapter = ViewPagerAdapter(supportFragmentManager) + viewPagerAdapter = ViewPagerAdapter(this, supportFragmentManager) binding.viewPager.adapter = viewPagerAdapter binding.tabLayout.setupWithViewPager(binding.viewPager) setTabs() @@ -83,39 +84,23 @@ class ProfileActivity : BaseActivity() { } private fun setTabs() { - val fragmentList = mutableListOf() - val titleList = mutableListOf() - - // Add Achievements tab achievementsFragment = AchievementsFragment().apply { - arguments = Bundle().apply { - putString(KEY_USERNAME, userName) - } + arguments = bundleOf(KEY_USERNAME to userName) } - fragmentList.add(achievementsFragment) - titleList.add(resources.getString(R.string.achievements_tab_title).uppercase()) - // Add Leaderboard tab leaderboardFragment = LeaderboardFragment().apply { - arguments = Bundle().apply { - putString(KEY_USERNAME, userName) - } + arguments = bundleOf(KEY_USERNAME to userName) } - fragmentList.add(leaderboardFragment) - titleList.add(resources.getString(R.string.leaderboard_tab_title).uppercase(Locale.ROOT)) - // Add Contributions tab contributionsFragment = ContributionsFragment().apply { - arguments = Bundle().apply { - putString(KEY_USERNAME, userName) - } - } - contributionsFragment?.let { - fragmentList.add(it) - titleList.add(getString(R.string.contributions_fragment).uppercase(Locale.ROOT)) + arguments = bundleOf(KEY_USERNAME to userName) } - 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() } diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadProgressActivity.kt b/app/src/main/java/fr/free/nrw/commons/upload/UploadProgressActivity.kt index aeaefa302..5d98ebffb 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadProgressActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadProgressActivity.kt @@ -28,8 +28,6 @@ class UploadProgressActivity : BaseActivity() { @Inject lateinit var contributionDao: ContributionDao - val fragmentList: MutableList = ArrayList() - val titleList: MutableList = ArrayList() var isPaused = true var isPendingIconsVisible = true var isErrorIconsVisisble = false @@ -38,7 +36,7 @@ class UploadProgressActivity : BaseActivity() { super.onCreate(savedInstanceState) binding = ActivityUploadProgressBinding.inflate(layoutInflater) setContentView(binding.root) - viewPagerAdapter = ViewPagerAdapter(supportFragmentManager) + viewPagerAdapter = ViewPagerAdapter(this, supportFragmentManager) binding.uploadProgressViewPager.setAdapter(viewPagerAdapter) binding.uploadProgressViewPager.setId(R.id.upload_progress_view_pager) binding.uploadProgressTabLayout.setupWithViewPager(binding.uploadProgressViewPager) @@ -81,11 +79,10 @@ class UploadProgressActivity : BaseActivity() { pendingUploadsFragment = PendingUploadsFragment() failedUploadsFragment = FailedUploadsFragment() - fragmentList.add(pendingUploadsFragment!!) - titleList.add(getString(R.string.pending)) - fragmentList.add(failedUploadsFragment!!) - titleList.add(getString(R.string.failed)) - viewPagerAdapter!!.setTabData(fragmentList, titleList) + viewPagerAdapter!!.setTabs( + R.string.pending to pendingUploadsFragment!!, + R.string.failed to failedUploadsFragment!! + ) viewPagerAdapter!!.notifyDataSetChanged() } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c9a2d16c8..4ca714b6c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -617,6 +617,8 @@ Upload your first media by tapping on the add button. MEDIA CHILD CLASSES PARENT CLASSES + SUBCATEGORIES + PARENT CATEGORIES Nearby Place Found Are these pictures of %1$s? diff --git a/app/src/test/kotlin/fr/free/nrw/commons/MockWebServerTest.java b/app/src/test/kotlin/fr/free/nrw/commons/MockWebServerTest.java deleted file mode 100644 index d9c8ad4fb..000000000 --- a/app/src/test/kotlin/fr/free/nrw/commons/MockWebServerTest.java +++ /dev/null @@ -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 service(Class clazz) { - return service(clazz, server().getUrl()); - } - - @NonNull protected T service(Class 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 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(); - } - } -} diff --git a/app/src/test/kotlin/fr/free/nrw/commons/MockWebServerTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/MockWebServerTest.kt new file mode 100644 index 000000000..b5bd4aac2 --- /dev/null +++ b/app/src/test/kotlin/fr/free/nrw/commons/MockWebServerTest.kt @@ -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 service(clazz: Class): T { + return service(clazz, server().url) + } + + protected fun service(clazz: Class, 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 { + 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() + } + } +} diff --git a/app/src/test/kotlin/fr/free/nrw/commons/TestConnectionFactory.kt b/app/src/test/kotlin/fr/free/nrw/commons/TestConnectionFactory.kt index 3bea1dc26..222310802 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/TestConnectionFactory.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/TestConnectionFactory.kt @@ -1,6 +1,5 @@ package fr.free.nrw.commons -import fr.free.nrw.commons.OkHttpConnectionFactory.HttpStatusException import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Response @@ -39,6 +38,6 @@ private class UnsuccessfulResponseInterceptor : Interceptor { if (rsp.isSuccessful) { return rsp } - throw HttpStatusException(rsp) + throw IOException("Unsuccessful response") } } diff --git a/app/src/test/kotlin/fr/free/nrw/commons/auth/csrf/CsrfTokenClientTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/auth/csrf/CsrfTokenClientTest.kt index 2e502d21f..92195f5cc 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/auth/csrf/CsrfTokenClientTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/auth/csrf/CsrfTokenClientTest.kt @@ -2,7 +2,6 @@ package fr.free.nrw.commons.auth.csrf import com.google.gson.stream.MalformedJsonException 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.login.LoginClient 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.never import org.mockito.Mockito.verify +import java.io.IOException class CsrfTokenClientTest : MockWebServerTest() { private val cb = mock(CsrfTokenClient.Callback::class.java) @@ -53,7 +53,7 @@ class CsrfTokenClientTest : MockWebServerTest() { performRequest() verify(cb, never()).success(any(String::class.java)) - verify(cb).failure(isA(HttpStatusException::class.java)) + verify(cb).failure(isA(IOException::class.java)) } @Test