Added Wikitalk Page (#5740)

* Added wikitalk page and improved bottomsheet for landscape mode

* Improved wikitalk page

* Fixed italics

* Fixed little bug

* Improved the wiki talk page

* .

* changed commons url to wikidata url

* changed commons url to wikidata url + 1

* fixed bookmark issue

* Added kdoc and javadoc

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
This commit is contained in:
Kanahia 2024-07-08 11:43:57 +05:30 committed by GitHub
parent 36905711d0
commit 3779cfb6a5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 1142 additions and 545 deletions

View file

@ -1,262 +1,266 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.READ_SYNC_STATS" />
<uses-permission android:name="android.permission.REORDER_TASKS" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="com.google.android.apps.photos.permission.GOOGLE_PHOTOS" />
<uses-permission android:name="android.permission.SET_WALLPAPER"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION"/>
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.READ_SYNC_STATS" />
<uses-permission android:name="android.permission.REORDER_TASKS" />
<uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="com.google.android.apps.photos.permission.GOOGLE_PHOTOS" />
<uses-permission android:name="android.permission.SET_WALLPAPER"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION"/>
<queries>
<!-- Browser -->
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
<!-- Google Maps -->
<package android:name="com.google.android.apps.maps" />
</queries>
<queries>
<!-- Browser -->
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
<!-- Google Maps -->
<package android:name="com.google.android.apps.maps" />
</queries>
<!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
<uses-feature android:name="android.hardware.location.gps" />
<!-- Needed only if your app targets Android 5.0 (API level 21) or higher. -->
<uses-feature android:name="android.hardware.location.gps" />
<application
android:name=".CommonsApplication"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/LightAppTheme"
android:largeHeap="true"
android:supportsRtl="true"
tools:replace="android:appComponentFactory"
android:appComponentFactory="commons"
android:requestLegacyExternalStorage = "true"
tools:ignore="GoogleAppIndexingWarning">
<application
android:name=".CommonsApplication"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/LightAppTheme"
android:largeHeap="true"
android:supportsRtl="true"
tools:replace="android:appComponentFactory"
android:appComponentFactory="commons"
android:requestLegacyExternalStorage = "true"
tools:ignore="GoogleAppIndexingWarning">
<activity
android:theme="@style/EditActivityTheme"
android:name=".description.DescriptionEditActivity"
android:exported="true" />
<activity
android:name=".nearby.WikidataFeedback"
android:exported="false" />
<activity
android:name=".edit.EditActivity"
android:exported="false" />
<activity
android:theme="@style/EditActivityTheme"
android:name=".description.DescriptionEditActivity"
android:exported="true" />
<activity android:name="org.acra.dialog.CrashReportDialog"
android:process=":acra"
android:launchMode="singleInstance"
android:excludeFromRecents="true"
android:finishOnTaskLaunch="true" />
<activity
android:name=".edit.EditActivity"
android:exported="false" />
<activity
android:name=".media.ZoomableActivity"
android:label="Zoomable Activity"
android:configChanges="screenSize|keyboard|orientation"
android:parentActivityName=".customselector.ui.selector.CustomSelectorActivity" />
<activity android:name="org.acra.dialog.CrashReportDialog"
android:process=":acra"
android:launchMode="singleInstance"
android:excludeFromRecents="true"
android:finishOnTaskLaunch="true" />
<activity android:name=".auth.LoginActivity"
android:exported="true">
<intent-filter>
<category android:name="android.intent.category.LAUNCHER" />
<activity
android:name=".media.ZoomableActivity"
android:label="Zoomable Activity"
android:configChanges="screenSize|keyboard|orientation"
android:parentActivityName=".customselector.ui.selector.CustomSelectorActivity" />
<action android:name="android.intent.action.MAIN" />
</intent-filter>
<activity android:name=".auth.LoginActivity"
android:exported="true">
<intent-filter>
<category android:name="android.intent.category.LAUNCHER" />
<meta-data android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<activity android:name=".WelcomeActivity" />
<meta-data android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
<activity
android:hardwareAccelerated="false"
android:name=".upload.UploadActivity"
android:exported="true"
android:configChanges="orientation|screenSize|keyboard"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:windowSoftInputMode="adjustResize"
>
<intent-filter android:label="@string/intent_share_upload_label">
<action android:name="android.intent.action.SEND" />
</activity>
<activity android:name=".WelcomeActivity" />
<category android:name="android.intent.category.DEFAULT" />
<activity
android:hardwareAccelerated="false"
android:name=".upload.UploadActivity"
android:exported="true"
android:configChanges="orientation|screenSize|keyboard"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:windowSoftInputMode="adjustResize"
>
<intent-filter android:label="@string/intent_share_upload_label">
<action android:name="android.intent.action.SEND" />
<data android:mimeType="image/*" />
<data android:mimeType="audio/ogg" />
</intent-filter>
<intent-filter android:label="@string/intent_share_upload_label">
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
<data android:mimeType="audio/ogg" />
</intent-filter>
<intent-filter android:label="@string/intent_share_upload_label">
<action android:name="android.intent.action.SEND_MULTIPLE" />
<data android:mimeType="image/*" />
<data android:mimeType="audio/ogg" />
</intent-filter>
</activity>
<activity
android:name=".contributions.MainActivity"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:configChanges="screenSize|keyboard|orientation" />
<activity
android:name=".settings.SettingsActivity"
android:label="@string/title_activity_settings" />
<activity
android:name=".AboutActivity"
android:label="@string/title_activity_about"
android:parentActivityName=".contributions.MainActivity" />
<category android:name="android.intent.category.DEFAULT" />
<activity
android:name=".auth.SignupActivity"
android:configChanges="orientation|screenLayout|screenSize"
android:label="@string/title_activity_signup" />
<data android:mimeType="image/*" />
<data android:mimeType="audio/ogg" />
</intent-filter>
</activity>
<activity
android:name=".contributions.MainActivity"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:configChanges="screenSize|keyboard|orientation" />
<activity
android:name=".settings.SettingsActivity"
android:label="@string/title_activity_settings" />
<activity
android:name=".AboutActivity"
android:label="@string/title_activity_about"
android:parentActivityName=".contributions.MainActivity" />
<activity
android:name=".notification.NotificationActivity"
android:label="@string/navigation_item_notification" />
<activity
android:name=".auth.SignupActivity"
android:configChanges="orientation|screenLayout|screenSize"
android:label="@string/title_activity_signup" />
<activity android:name=".quiz.QuizActivity"
android:label="@string/quiz"/>
<activity
android:name=".notification.NotificationActivity"
android:label="@string/navigation_item_notification" />
<activity android:name=".quiz.QuizResultActivity"
android:label="@string/result"/>
<activity android:name=".quiz.QuizActivity"
android:label="@string/quiz"/>
<activity
android:name=".customselector.ui.selector.CustomSelectorActivity"
android:label="@string/title_activity_custom_selector"
android:configChanges="screenSize|keyboard|orientation"
android:parentActivityName=".contributions.MainActivity" />
<activity android:name=".quiz.QuizResultActivity"
android:label="@string/result"/>
<activity
android:name=".category.CategoryDetailsActivity"
android:label="@string/title_activity_featured_images"
android:configChanges="screenSize|keyboard|orientation"
android:parentActivityName=".contributions.MainActivity" />
<activity
android:name=".customselector.ui.selector.CustomSelectorActivity"
android:label="@string/title_activity_custom_selector"
android:configChanges="screenSize|keyboard|orientation"
android:parentActivityName=".contributions.MainActivity" />
<activity
android:name=".explore.depictions.WikidataItemDetailsActivity"
android:label="@string/title_activity_featured_images"
android:configChanges="screenSize|keyboard|orientation"
android:parentActivityName=".contributions.MainActivity" />
<activity
android:name=".category.CategoryDetailsActivity"
android:label="@string/title_activity_featured_images"
android:configChanges="screenSize|keyboard|orientation"
android:parentActivityName=".contributions.MainActivity" />
<activity
android:name=".explore.SearchActivity"
android:label="@string/title_activity_search"
android:launchMode="singleTop"
android:configChanges="orientation|keyboardHidden|screenSize"
android:parentActivityName=".contributions.MainActivity"
/>
<activity
android:name=".explore.depictions.WikidataItemDetailsActivity"
android:label="@string/title_activity_featured_images"
android:configChanges="screenSize|keyboard|orientation"
android:parentActivityName=".contributions.MainActivity" />
<activity
android:name=".profile.ProfileActivity"
android:configChanges="orientation|screenSize|keyboard"
android:label="@string/Profile" />
<activity
android:name=".explore.SearchActivity"
android:label="@string/title_activity_search"
android:launchMode="singleTop"
android:configChanges="orientation|keyboardHidden|screenSize"
android:parentActivityName=".contributions.MainActivity"
/>
<activity
android:name=".review.ReviewActivity"
android:label="@string/title_activity_review" />
<activity
android:name=".profile.ProfileActivity"
android:configChanges="orientation|screenSize|keyboard"
android:label="@string/Profile" />
<activity
android:name=".LocationPicker.LocationPickerActivity"
android:label="Location Picker" />
<activity
android:name=".review.ReviewActivity"
android:label="@string/title_activity_review" />
<service
android:name=".auth.WikiAccountAuthenticatorService"
android:exported="true"
android:process=":auth">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator" />
</service>
<activity
android:name=".LocationPicker.LocationPickerActivity"
android:label="Location Picker" />
<service
android:name="org.acra.sender.SenderService"
android:exported="false"
android:process=":acra" />
<service
android:name=".auth.WikiAccountAuthenticatorService"
android:exported="true"
android:process=":auth">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator" />
</service>
<provider
android:name=".filepicker.ExtendedFileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
<service
android:name="org.acra.sender.SenderService"
android:exported="false"
android:process=":acra" />
<provider
android:name=".category.CategoryContentProvider"
android:authorities="${applicationId}.categories.contentprovider"
android:exported="false"
android:label="@string/provider_categories"
android:syncable="false" />
<provider
android:name=".filepicker.ExtendedFileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
<provider
android:name=".explore.recentsearches.RecentSearchesContentProvider"
android:authorities="${applicationId}.explore.recentsearches.contentprovider"
android:exported="false"
android:label="@string/provider_searches"
android:syncable="false" />
<provider
android:name=".category.CategoryContentProvider"
android:authorities="${applicationId}.categories.contentprovider"
android:exported="false"
android:label="@string/provider_categories"
android:syncable="false" />
<provider
android:name=".recentlanguages.RecentLanguagesContentProvider"
android:authorities="${applicationId}.recentlanguages.contentprovider"
android:exported="false"
android:label="@string/provider_recent_languages"
android:syncable="false" />
<provider
android:name=".explore.recentsearches.RecentSearchesContentProvider"
android:authorities="${applicationId}.explore.recentsearches.contentprovider"
android:exported="false"
android:label="@string/provider_searches"
android:syncable="false" />
<provider
android:name=".bookmarks.pictures.BookmarkPicturesContentProvider"
android:authorities="${applicationId}.bookmarks.contentprovider"
android:exported="false"
android:label="@string/provider_bookmarks"
android:syncable="false" />
<provider
android:name=".recentlanguages.RecentLanguagesContentProvider"
android:authorities="${applicationId}.recentlanguages.contentprovider"
android:exported="false"
android:label="@string/provider_recent_languages"
android:syncable="false" />
<provider
android:name=".bookmarks.locations.BookmarkLocationsContentProvider"
android:authorities="${applicationId}.bookmarks.locations.contentprovider"
android:exported="false"
android:label="@string/provider_bookmarks_location"
android:syncable="false" />
<provider
android:name=".bookmarks.pictures.BookmarkPicturesContentProvider"
android:authorities="${applicationId}.bookmarks.contentprovider"
android:exported="false"
android:label="@string/provider_bookmarks"
android:syncable="false" />
<provider
android:name=".bookmarks.items.BookmarkItemsContentProvider"
android:authorities="${applicationId}.bookmarks.items.contentprovider"
android:exported="false"
android:label="@string/provider_bookmarks_location"
android:syncable="false" />
<provider
android:name=".bookmarks.locations.BookmarkLocationsContentProvider"
android:authorities="${applicationId}.bookmarks.locations.contentprovider"
android:exported="false"
android:label="@string/provider_bookmarks_location"
android:syncable="false" />
<receiver android:name=".widget.PicOfDayAppWidget"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<provider
android:name=".bookmarks.items.BookmarkItemsContentProvider"
android:authorities="${applicationId}.bookmarks.items.contentprovider"
android:exported="false"
android:label="@string/provider_bookmarks_location"
android:syncable="false" />
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/pic_of_day_app_widget_info" />
</receiver>
<receiver android:name=".widget.PicOfDayAppWidget"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<uses-library android:name="org.apache.http.legacy" android:required="false" />
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/pic_of_day_app_widget_info" />
</receiver>
</application>
<uses-library android:name="org.apache.http.legacy" android:required="false" />
</application>
</manifest>

View file

@ -4,6 +4,7 @@ import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException
import io.reactivex.Observable
import io.reactivex.Single
import fr.free.nrw.commons.auth.csrf.CsrfTokenClient
import timber.log.Timber
/**
* This class acts as a Client to facilitate wiki page editing
@ -27,7 +28,41 @@ class PageEditClient(
fun edit(pageTitle: String, text: String, summary: String): Observable<Boolean> {
return try {
pageEditInterface.postEdit(pageTitle, summary, text, csrfTokenClient.getTokenBlocking())
.map { editResponse -> editResponse.edit()!!.editSucceeded() }
.map { editResponse ->
editResponse.edit()!!.editSucceeded()
}
} catch (throwable: Throwable) {
if (throwable is InvalidLoginTokenException) {
throw throwable
} else {
Observable.just(false)
}
}
}
/**
* Creates a new page with the given title, text, and summary.
*
* @param pageTitle The title of the page to be created.
* @param text The content of the page in wikitext format.
* @param summary The edit summary for the page creation.
* @return An observable that emits true if the page creation succeeded, false otherwise.
* @throws InvalidLoginTokenException If an invalid login token is encountered during the process.
*/
fun postCreate(pageTitle: String, text: String, summary: String): Observable<Boolean> {
return try {
pageEditInterface.postCreate(
pageTitle,
summary,
text,
"text/x-wiki",
"wikitext",
true,
true,
csrfTokenClient.getTokenBlocking()
).map { editResponse ->
editResponse.edit()!!.editSucceeded()
}
} catch (throwable: Throwable) {
if (throwable is InvalidLoginTokenException) {
throw throwable

View file

@ -36,6 +36,33 @@ interface PageEditInterface {
@Field("token") token: String
): Observable<Edit>
/**
* This method creates or edits a page for nearby items.
*
* @param title Title of the page to edit. Cannot be used together with pageid.
* @param summary Edit summary. Also used as the section title when section=new and sectiontitle is not set.
* @param text Text of the page.
* @param contentformat Format of the content (e.g., "text/x-wiki").
* @param contentmodel Model of the content (e.g., "wikitext").
* @param minor Whether the edit is a minor edit.
* @param recreate Whether to recreate the page if it does not exist.
* @param token A "csrf" token. This should always be sent as the last field of form data.
*/
@FormUrlEncoded
@Headers("Cache-Control: no-cache")
@POST(MW_API_PREFIX + "action=edit")
fun postCreate(
@Field("title") title: String,
@Field("summary") summary: String,
@Field("text") text: String,
@Field("contentformat") contentformat: String,
@Field("contentmodel") contentmodel: String,
@Field("minor") minor: Boolean,
@Field("recreate") recreate: Boolean,
// NOTE: This csrf shold always be sent as the last field of form data
@Field("token") token: String
): Observable<Edit>
/**
* This method posts such that the Content which the page
* has will be appended with the value being passed to the

View file

@ -14,6 +14,7 @@ import fr.free.nrw.commons.description.DescriptionEditActivity;
import fr.free.nrw.commons.explore.depictions.WikidataItemDetailsActivity;
import fr.free.nrw.commons.explore.SearchActivity;
import fr.free.nrw.commons.media.ZoomableActivity;
import fr.free.nrw.commons.nearby.WikidataFeedback;
import fr.free.nrw.commons.notification.NotificationActivity;
import fr.free.nrw.commons.profile.ProfileActivity;
import fr.free.nrw.commons.review.ReviewActivity;
@ -79,4 +80,7 @@ public abstract class ActivityBuilderModule {
@ContributesAndroidInjector
abstract ZoomableActivity bindZoomableActivity();
@ContributesAndroidInjector
abstract WikidataFeedback bindWikiFeedback();
}

View file

@ -2,9 +2,11 @@ package fr.free.nrw.commons.di;
import com.google.gson.Gson;
import fr.free.nrw.commons.actions.PageEditClient;
import fr.free.nrw.commons.explore.categories.CategoriesModule;
import fr.free.nrw.commons.navtab.MoreBottomSheetFragment;
import fr.free.nrw.commons.navtab.MoreBottomSheetLoggedOutFragment;
import fr.free.nrw.commons.nearby.NearbyController;
import fr.free.nrw.commons.upload.worker.UploadWorker;
import javax.inject.Singleton;
@ -68,6 +70,9 @@ public interface CommonsApplicationComponent extends AndroidInjector<Application
void inject(PicOfDayAppWidget picOfDayAppWidget);
@Singleton
void inject(NearbyController nearbyController);
Gson gson();
@Component.Builder

View file

@ -63,6 +63,7 @@ public class NetworkingModule {
public static final String NAMED_LANGUAGE_WIKI_PEDIA_WIKI_SITE = "language-wikipedia-wikisite";
public static final String NAMED_COMMONS_CSRF = "commons-csrf";
public static final String NAMED_WIKI_CSRF = "wiki-csrf";
@Provides
@Singleton
@ -128,10 +129,41 @@ public class NetworkingModule {
@Provides
@Singleton
public CsrfTokenClient provideCommonsCsrfTokenClient(SessionManager sessionManager,
CsrfTokenInterface tokenInterface, LoginClient loginClient, LogoutClient logoutClient) {
@Named("commons-csrf-interface") CsrfTokenInterface tokenInterface, LoginClient loginClient, LogoutClient logoutClient) {
return new CsrfTokenClient(sessionManager, tokenInterface, loginClient, logoutClient);
}
/**
* Provides a singleton instance of CsrfTokenClient for Wikidata.
*
* @param sessionManager The session manager to manage user sessions.
* @param tokenInterface The interface for obtaining CSRF tokens.
* @param loginClient The client for handling login operations.
* @param logoutClient The client for handling logout operations.
* @return A singleton instance of CsrfTokenClient.
*/
@Named(NAMED_WIKI_CSRF)
@Provides
@Singleton
public CsrfTokenClient provideWikiCsrfTokenClient(SessionManager sessionManager,
@Named("wikidata-csrf-interface") CsrfTokenInterface tokenInterface, LoginClient loginClient, LogoutClient logoutClient) {
return new CsrfTokenClient(sessionManager, tokenInterface, loginClient, logoutClient);
}
/**
* Provides a singleton instance of CsrfTokenInterface for Wikidata.
*
* @param serviceFactory The factory used to create service interfaces.
* @return A singleton instance of CsrfTokenInterface for Wikidata.
*/
@Named("wikidata-csrf-interface")
@Provides
@Singleton
public CsrfTokenInterface provideWikidataCsrfTokenInterface(CommonsServiceFactory serviceFactory) {
return serviceFactory.create(BuildConfig.WIKIDATA_URL, CsrfTokenInterface.class);
}
@Named("commons-csrf-interface")
@Provides
@Singleton
public CsrfTokenInterface provideCsrfTokenInterface(CommonsServiceFactory serviceFactory) {
@ -230,6 +262,21 @@ public class NetworkingModule {
return new PageEditClient(csrfTokenClient, pageEditInterface);
}
/**
* Provides a singleton instance of PageEditClient for Wikidata.
*
* @param csrfTokenClient The client used to manage CSRF tokens.
* @param pageEditInterface The interface for page edit operations.
* @return A singleton instance of PageEditClient for Wikidata.
*/
@Named("wikidata-page-edit")
@Provides
@Singleton
public PageEditClient provideWikidataPageEditClient(@Named(NAMED_WIKI_CSRF) CsrfTokenClient csrfTokenClient,
@Named("wikidata-page-edit-service") PageEditInterface pageEditInterface) {
return new PageEditClient(csrfTokenClient, pageEditInterface);
}
@Provides
@Singleton
public MediaInterface provideMediaInterface(CommonsServiceFactory serviceFactory) {

View file

@ -11,10 +11,10 @@ import fr.free.nrw.commons.campaigns.CampaignResponseDTO;
import fr.free.nrw.commons.explore.depictions.DepictsClient;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.nearby.model.PlaceBindings;
import fr.free.nrw.commons.nearby.model.ItemsClass;
import fr.free.nrw.commons.nearby.model.NearbyResponse;
import fr.free.nrw.commons.nearby.model.NearbyResultItem;
import fr.free.nrw.commons.nearby.model.PlaceBindings;
import fr.free.nrw.commons.profile.achievements.FeaturedImages;
import fr.free.nrw.commons.profile.achievements.FeedbackResponse;
import fr.free.nrw.commons.profile.leaderboard.LeaderboardResponse;
@ -476,7 +476,7 @@ public class OkHttpJsonApiClient {
" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">"
+ "\n<bounds minlat=\"$MIN_LATITUDE\" minlon=\"$MIN_LONGITUDE\" maxlat=\"$MAX_LATITUDE\" maxlon=\"$MAX_LONGITUDE\"/>";
List<PlaceBindings> placeBindings = runQuery(leftLatLng,rightLatLng);
List<PlaceBindings> placeBindings = runQuery(leftLatLng, rightLatLng);
if (placeBindings != null) {
for (PlaceBindings item : placeBindings) {
if (item.getItem() != null && item.getLabel() != null && item.getClas() != null) {

View file

@ -0,0 +1,108 @@
package fr.free.nrw.commons.nearby
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.View.OnLongClickListener
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.NonNull
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import fr.free.nrw.commons.R
import fr.free.nrw.commons.nearby.model.BottomSheetItem
/**
* RecyclerView Adapter for displaying items in a bottom sheet.
*
* @property context The context used for inflating layout resources.
* @property itemList The list of BottomSheetItem objects to display.
* @constructor Creates an instance of BottomSheetAdapter.
*/
class BottomSheetAdapter(context: Context?, private val itemList: List<BottomSheetItem>) :
RecyclerView.Adapter<BottomSheetAdapter.ViewHolder>() {
private val layoutInflater: LayoutInflater = LayoutInflater.from(context)
private var itemClickListener: ItemClickListener? = null
@NonNull
override fun onCreateViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder {
val view: View = layoutInflater.inflate(R.layout.bottom_sheet_item_layout, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(@NonNull holder: ViewHolder, position: Int) {
val item = itemList[position]
holder.imageView.setImageDrawable(
ContextCompat.getDrawable(
getContext(),
item.imageResourceId
)
)
holder.title.setText(item.title)
}
/**
* Returns the total number of items in the data set held by the adapter.
*
* @return The total number of items in this adapter.
*/
override fun getItemCount(): Int {
return itemList.size
}
/**
* Updates the icon for bookmark item.
*
* @param icon The resource ID of the new icon to set.
*/
fun updateBookmarkIcon(icon: Int) {
itemList.forEachIndexed { index, item ->
if (item.imageResourceId == R.drawable.ic_round_star_filled_24px || item.imageResourceId == R.drawable.ic_round_star_border_24px) {
item.imageResourceId = icon
this.notifyItemChanged(index)
return
}
}
}
inner class ViewHolder internal constructor(itemView: View) : RecyclerView.ViewHolder(itemView),
View.OnClickListener, OnLongClickListener {
var imageView: ImageView = itemView.findViewById(R.id.buttonImage)
var title: TextView = itemView.findViewById(R.id.buttonText)
init {
itemView.setOnClickListener(this)
itemView.setOnLongClickListener(this)
}
override fun onClick(view: View) {
if (itemClickListener != null) itemClickListener!!.onBottomSheetItemClick(
view,
adapterPosition
)
}
override fun onLongClick(view: View): Boolean {
if (itemClickListener != null) itemClickListener!!.onBottomSheetItemLongClick(
view,
adapterPosition
)
return true
}
}
fun setClickListener(itemClickListener: ItemClickListener?) {
this.itemClickListener = itemClickListener
}
fun getContext(): Context {
return layoutInflater.context
}
interface ItemClickListener {
fun onBottomSheetItemClick(view: View?, position: Int)
fun onBottomSheetItemLongClick(view: View?, position: Int)
}
}

View file

@ -0,0 +1,164 @@
package fr.free.nrw.commons.nearby;
import static fr.free.nrw.commons.notification.NotificationHelper.NOTIFICATION_DELETE;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import androidx.appcompat.app.AlertDialog;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.actions.PageEditClient;
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException;
import fr.free.nrw.commons.notification.NotificationHelper;
import fr.free.nrw.commons.utils.ViewUtilWrapper;
import io.reactivex.Observable;
import io.reactivex.Single;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import timber.log.Timber;
/**
* Singleton class for making edits to wiki pages asynchronously using RxJava.
*
* @property notificationHelper A helper class for displaying notifications.
* @property pageEditClient A client for making page edit requests.
* @property viewUtil A utility class for common view operations.
* @property username The username used for page edits.
* @constructor Initializes the PageEditHelper with required dependencies.
*/
@Singleton
public class PageEditHelper {
private final NotificationHelper notificationHelper;
private final PageEditClient pageEditClient;
private final ViewUtilWrapper viewUtil;
private final String username;
private AlertDialog d;
private DialogInterface.OnMultiChoiceClickListener listener;
@Inject
public PageEditHelper(NotificationHelper notificationHelper,
@Named("wikidata-page-edit") PageEditClient pageEditClient,
ViewUtilWrapper viewUtil,
@Named("username") String username) {
this.notificationHelper = notificationHelper;
this.pageEditClient = pageEditClient;
this.viewUtil = viewUtil;
this.username = username;
}
/**
* Public interface to make a page edit request asynchronously.
*
* @param context The context for displaying messages.
* @param title The title of the page to edit.
* @param preText The existing content of the page.
* @param description The description of the issue to be fixed.
* @param details Additional details about the issue.
* @param lat The latitude of the location related to the page.
* @param lng The longitude of the location related to the page.
* @return A Single emitting true if the edit was successful, false otherwise.
*/
public Single<Boolean> makePageEdit(Context context, String title, String preText,
String description,
String details, Double lat, Double lng) {
viewUtil.showShortToast(context, "Trying to edit " + title);
return editPage(title, preText, description, details, lat, lng)
.flatMapSingle(result -> Single.just(showNotification(context, title, result)))
.firstOrError()
.onErrorResumeNext(throwable -> {
if (throwable instanceof InvalidLoginTokenException) {
return Single.error(throwable);
}
return Single.error(throwable);
});
}
/**
* Creates the text content for the page edit based on provided parameters.
*
* @param title The title of the page to edit.
* @param preText The existing content of the page.
* @param description The description of the issue to be fixed.
* @param details Additional details about the issue.
* @param lat The latitude of the location related to the page.
* @param lng The longitude of the location related to the page.
* @return An Observable emitting true if the edit was successful, false otherwise.
*/
private Observable<Boolean> editPage(String title, String preText, String description,
String details, Double lat, Double lng) {
Timber.d("thread is edit %s", Thread.currentThread().getName());
String summary = "Please fix this item";
String text = "";
String marker = "Please anyone fix the item accordingly, then reply to mark this section as fixed. Thanks a lot for your cooperation!";
int markerIndex = preText.indexOf(marker);
if (preText == "" || markerIndex == -1) {
text = "==Please fix this item==\n"
+ "Someone using the [[Commons:Mobile_app|Commons Android app]] went to this item's geographical location ("
+ lat + "," + lng
+ ") and noted the following problem(s):\n"
+ "* <i><nowiki>" + description + "</nowiki></i>\n"
+ "\n"
+ "Details: <i><nowiki>" + details + "</nowiki></i>\n"
+ "\n"
+ "Please anyone fix the item accordingly, then reply to mark this section as fixed. Thanks a lot for your cooperation!\n"
+ "\n"
+ "~~~~";
} else {
text = preText.substring(0, markerIndex);
text = text + "* <i><nowiki>" + description + "</nowiki></i>\n"
+ "\n"
+ "Details: <i><nowiki>" + details + "</nowiki></i>\n"
+ "\n"
+ "Please anyone fix the item accordingly, then reply to mark this section as fixed. Thanks a lot for your cooperation!\n"
+ "\n"
+ "~~~~";
}
return pageEditClient.postCreate(title, text, summary);
}
/**
* Displays a notification based on the result of the page edit.
*
* @param context The context for displaying the notification.
* @param title The title of the page edited.
* @param result The result of the edit operation.
* @return true if the edit was successful, false otherwise.
*/
private boolean showNotification(Context context, String title, boolean result) {
String message;
if (result) {
message = title + " Edited Successfully";
} else {
message = context.getString(R.string.delete_helper_show_deletion_message_else);
}
String url = BuildConfig.WIKIDATA_URL + "/wiki/" + title;
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
notificationHelper.showNotification(context, title, message, NOTIFICATION_DELETE,
browserIntent);
return result;
}
/**
* returns the instance of shown AlertDialog, used for taking reference during unit test
*/
public AlertDialog getDialog() {
return d;
}
/**
* returns the instance of shown DialogInterface.OnMultiChoiceClickListener, used for taking
* reference during unit test
*/
public DialogInterface.OnMultiChoiceClickListener getListener() {
return listener;
}
}

View file

@ -0,0 +1,96 @@
package fr.free.nrw.commons.nearby
import android.annotation.SuppressLint
import android.os.Bundle
import android.widget.RadioButton
import android.widget.Toast
import fr.free.nrw.commons.R
import fr.free.nrw.commons.databinding.ActivityWikidataFeedbackBinding
import fr.free.nrw.commons.theme.BaseActivity
import io.reactivex.Single
import io.reactivex.SingleSource
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import java.util.concurrent.Callable
import javax.inject.Inject
/**
* Activity for providing feedback about Wikidata items.
*/
class WikidataFeedback : BaseActivity() {
private lateinit var binding: ActivityWikidataFeedbackBinding
var place: String = ""
var wikidataQId: String = ""
var pageTitle: String = ""
var preText: String = ""
var lat: Double = 0.0
var lng: Double = 0.0
@Inject
lateinit var pageEditHelper: PageEditHelper
@Inject
lateinit var nearbyController: NearbyController
@SuppressLint("CheckResult")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityWikidataFeedbackBinding.inflate(layoutInflater)
setContentView(binding.root)
lat = intent.getDoubleExtra("lat", 0.0)
lng = intent.getDoubleExtra("lng", 0.0)
place = intent.getStringExtra("place") ?: ""
wikidataQId = intent.getStringExtra("qid") ?: ""
pageTitle = getString(R.string.talk) + ":" + wikidataQId
binding.toolbarBinding.toolbar.title = pageTitle
binding.textHeader.text =
getString(R.string.write_something_about_the) + "'$place'" + getString(R.string.item_it_will_be_publicly_visible)
binding.radioButton1.setText(
getString(
R.string.does_not_exist_anymore_no_picture_can_ever_be_taken_of_it,
place
))
binding.radioButton2.setText(
getString(
R.string.is_at_a_different_place_please_specify_the_correct_place_below_if_possible_tell_us_the_correct_latitude_longitude,
place
))
binding.radioButton3.setText(getString(R.string.other_problem_or_information_please_explain_below))
setSupportActionBar(binding.toolbarBinding.toolbar)
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
binding.appCompatButton.setOnClickListener {
var desc = findViewById<RadioButton>(binding.radioGroup.checkedRadioButtonId).text
var det = binding.detailsEditText.text.toString()
if (binding.radioGroup.checkedRadioButtonId == R.id.radioButton3 && binding.detailsEditText.text.isNullOrEmpty()) {
Toast.makeText(
this,
getString(R.string.please_enter_some_comments), Toast.LENGTH_SHORT
).show()
} else {
binding.radioGroup.clearCheck()
binding.detailsEditText.setText("")
Single.defer<Boolean?>(Callable<SingleSource<Boolean?>> {
pageEditHelper.makePageEdit(
this, pageTitle, preText,
desc.toString(),
det, lat, lng
)
} as Callable<SingleSource<Boolean?>>)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ aBoolean: Boolean? ->
}, { throwable: Throwable? ->
Timber.e(throwable!!)
})
}
}
}
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
}

View file

@ -48,6 +48,7 @@ import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.snackbar.Snackbar;
@ -70,6 +71,7 @@ import fr.free.nrw.commons.location.LocationPermissionsHelper;
import fr.free.nrw.commons.location.LocationPermissionsHelper.LocationPermissionCallback;
import fr.free.nrw.commons.location.LocationServiceManager;
import fr.free.nrw.commons.location.LocationUpdateListener;
import fr.free.nrw.commons.nearby.BottomSheetAdapter;
import fr.free.nrw.commons.nearby.CheckBoxTriStates;
import fr.free.nrw.commons.nearby.Label;
import fr.free.nrw.commons.nearby.MarkerPlaceGroup;
@ -77,8 +79,10 @@ import fr.free.nrw.commons.nearby.NearbyController;
import fr.free.nrw.commons.nearby.NearbyFilterSearchRecyclerViewAdapter;
import fr.free.nrw.commons.nearby.NearbyFilterState;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.nearby.WikidataFeedback;
import fr.free.nrw.commons.nearby.contract.NearbyParentFragmentContract;
import fr.free.nrw.commons.nearby.fragments.AdvanceQueryFragment.Callback;
import fr.free.nrw.commons.nearby.model.BottomSheetItem;
import fr.free.nrw.commons.nearby.presenter.NearbyParentFragmentPresenter;
import fr.free.nrw.commons.upload.FileUtils;
import fr.free.nrw.commons.utils.DialogUtil;
@ -131,7 +135,7 @@ import timber.log.Timber;
public class NearbyParentFragment extends CommonsDaggerSupportFragment
implements NearbyParentFragmentContract.View,
WikidataEditListener.WikidataP18EditListener, LocationUpdateListener,
LocationPermissionCallback {
LocationPermissionCallback, BottomSheetAdapter.ItemClickListener {
FragmentNearbyParentBinding binding;
@ -189,6 +193,9 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
private NearbyParentFragmentInstanceReadyCallback nearbyParentFragmentInstanceReadyCallback;
private boolean isAdvancedQueryFragmentVisible = false;
private Place nearestPlace;
private GridLayoutManager gridLayoutManager;
private List<BottomSheetItem> dataList;
private BottomSheetAdapter bottomSheetAdapter;
private ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher = registerForActivityResult(
new ActivityResultContracts.RequestMultiplePermissions(),
new ActivityResultCallback<Map<String, Boolean>>() {
@ -203,7 +210,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
controller.locationPermissionCallback.onLocationPermissionGranted();
} else {
if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) {
controller.handleShowRationaleFlowCameraLocation(getActivity(), inAppCameraLocationPermissionLauncher);
controller.handleShowRationaleFlowCameraLocation(getActivity(),
inAppCameraLocationPermissionLauncher);
} else {
controller.locationPermissionCallback.onLocationPermissionDenied(
getActivity().getString(
@ -434,10 +442,10 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
moveCameraToPosition(lastMapFocus);
initRvNearbyList();
onResume();
binding.tvAttribution.setText(Html.fromHtml(getString(R.string.map_attribution)));
binding.tvAttribution.setMovementMethod(LinkMovementMethod.getInstance());
binding.tvAttribution.setText(Html.fromHtml(getString(R.string.map_attribution)));
binding.tvAttribution.setMovementMethod(LinkMovementMethod.getInstance());
binding.nearbyFilterList.btnAdvancedOptions.setOnClickListener(v -> {
binding.nearbyFilter.searchViewLayout.searchView.clearFocus();
binding.nearbyFilter.searchViewLayout.searchView.clearFocus();
showHideAdvancedQueryFragment(true);
final AdvanceQueryFragment fragment = new AdvanceQueryFragment();
final Bundle bundle = new Bundle();
@ -473,7 +481,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
.commit();
});
binding.tvLearnMore.setOnClickListener(v ->onLearnMoreClicked());
binding.tvLearnMore.setOnClickListener(v -> onLearnMoreClicked());
binding.nearbyFilter.ivToggleChips.setOnClickListener(v -> onToggleChipsClicked());
if (!locationPermissionsHelper.checkLocationPermission(getActivity())) {
@ -510,7 +518,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
}
private void initRvNearbyList() {
binding.bottomSheetNearby.rvNearbyList.setLayoutManager(new LinearLayoutManager(getContext()));
binding.bottomSheetNearby.rvNearbyList.setLayoutManager(
new LinearLayoutManager(getContext()));
adapter = new PlaceAdapter(bookmarkLocationDao,
place -> {
moveCameraToPosition(
@ -566,7 +575,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
locationManager.requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER);
setProgressBarVisibility(true);
} else {
locationPermissionsHelper.showLocationOffDialog(getActivity(), R.string.ask_to_turn_location_on_text);
locationPermissionsHelper.showLocationOffDialog(getActivity(),
R.string.ask_to_turn_location_on_text);
}
presenter.onMapReady();
registerUnregisterLocationListener(false);
@ -591,7 +601,6 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
* Starts the map without GPS and without permission By default it points to 51.50550,-0.07520
* coordinates, other than that it points to the last known location which can be get by the key
* "LastLocation" from applicationKvStore
*
*/
private void startMapWithoutPermission() {
if (applicationKvStore.getString("LastLocation") != null) {
@ -649,7 +658,6 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
initBottomSheets();
loadAnimations();
setBottomSheetCallbacks();
decideButtonVisibilities();
addActionToTitle();
if (!Utils.isMonumentsEnabled(new Date())) {
NearbyFilterState.setWlmSelected(false);
@ -673,26 +681,46 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
bottomSheetListBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
}
/**
* Determines the number of spans (columns) in the RecyclerView based on device orientation
* and adapter item count.
*
* @return The number of spans to be used in the RecyclerView.
*/
private int getSpanCount() {
int orientation = getResources().getConfiguration().orientation;
if (bottomSheetAdapter != null) {
return (orientation == Configuration.ORIENTATION_PORTRAIT) ? 3
: bottomSheetAdapter.getItemCount();
} else {
return (orientation == Configuration.ORIENTATION_PORTRAIT) ? 3 : 6;
}
}
public void initNearbyFilter() {
binding.nearbyFilterList.getRoot().setVisibility(View.GONE);
hideBottomSheet();
binding.nearbyFilter.searchViewLayout.searchView.setOnQueryTextFocusChangeListener((v, hasFocus) -> {
LayoutUtils.setLayoutHeightAllignedToWidth(1.25, binding.nearbyFilterList.getRoot());
if (hasFocus) {
binding.nearbyFilterList.getRoot().setVisibility(View.VISIBLE);
presenter.searchViewGainedFocus();
} else {
binding.nearbyFilterList.getRoot().setVisibility(View.GONE);
}
});
binding.nearbyFilterList.searchListView.setHasFixedSize(true);
binding.nearbyFilterList.searchListView.addItemDecoration(new DividerItemDecoration(getContext(),
DividerItemDecoration.VERTICAL));
binding.nearbyFilter.searchViewLayout.searchView.setOnQueryTextFocusChangeListener(
(v, hasFocus) -> {
LayoutUtils.setLayoutHeightAllignedToWidth(1.25,
binding.nearbyFilterList.getRoot());
if (hasFocus) {
binding.nearbyFilterList.getRoot().setVisibility(View.VISIBLE);
presenter.searchViewGainedFocus();
} else {
binding.nearbyFilterList.getRoot().setVisibility(View.GONE);
}
});
binding.nearbyFilterList.searchListView.setHasFixedSize(true);
binding.nearbyFilterList.searchListView.addItemDecoration(
new DividerItemDecoration(getContext(),
DividerItemDecoration.VERTICAL));
final LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity());
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
binding.nearbyFilterList.searchListView.setLayoutManager(linearLayoutManager);
binding.nearbyFilterList.searchListView.setLayoutManager(linearLayoutManager);
nearbyFilterSearchRecyclerViewAdapter = new NearbyFilterSearchRecyclerViewAdapter(
getContext(), new ArrayList<>(Label.valuesAsList()), binding.nearbyFilterList.searchListView);
getContext(), new ArrayList<>(Label.valuesAsList()),
binding.nearbyFilterList.searchListView);
nearbyFilterSearchRecyclerViewAdapter.setCallback(
new NearbyFilterSearchRecyclerViewAdapter.Callback() {
@Override
@ -711,18 +739,20 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
return isDarkTheme;
}
});
binding.nearbyFilterList.getRoot().getLayoutParams().width = (int) LayoutUtils.getScreenWidth(getActivity(),
binding.nearbyFilterList.getRoot()
.getLayoutParams().width = (int) LayoutUtils.getScreenWidth(getActivity(),
0.75);
binding.nearbyFilterList.searchListView.setAdapter(nearbyFilterSearchRecyclerViewAdapter);
binding.nearbyFilterList.searchListView.setAdapter(nearbyFilterSearchRecyclerViewAdapter);
LayoutUtils.setLayoutHeightAllignedToWidth(1.25, binding.nearbyFilterList.getRoot());
compositeDisposable.add(RxSearchView.queryTextChanges( binding.nearbyFilter.searchViewLayout.searchView)
.takeUntil(RxView.detaches(binding.nearbyFilter.searchViewLayout.searchView))
.debounce(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(query -> {
((NearbyFilterSearchRecyclerViewAdapter) binding.nearbyFilterList.searchListView.getAdapter()).getFilter()
.filter(query.toString());
}));
compositeDisposable.add(
RxSearchView.queryTextChanges(binding.nearbyFilter.searchViewLayout.searchView)
.takeUntil(RxView.detaches(binding.nearbyFilter.searchViewLayout.searchView))
.debounce(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(query -> {
((NearbyFilterSearchRecyclerViewAdapter) binding.nearbyFilterList.searchListView.getAdapter()).getFilter()
.filter(query.toString());
}));
initFilterChips();
}
@ -739,9 +769,12 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
@Override
public void setFilterState() {
binding.nearbyFilter.chipView.choiceChipNeedsPhoto.setChecked(NearbyFilterState.getInstance().isNeedPhotoSelected());
binding.nearbyFilter.chipView.choiceChipExists.setChecked(NearbyFilterState.getInstance().isExistsSelected());
binding.nearbyFilter.chipView.choiceChipWlm.setChecked(NearbyFilterState.getInstance().isWlmSelected());
binding.nearbyFilter.chipView.choiceChipNeedsPhoto.setChecked(
NearbyFilterState.getInstance().isNeedPhotoSelected());
binding.nearbyFilter.chipView.choiceChipExists.setChecked(
NearbyFilterState.getInstance().isExistsSelected());
binding.nearbyFilter.chipView.choiceChipWlm.setChecked(
NearbyFilterState.getInstance().isWlmSelected());
if (NearbyController.currentLocation != null) {
presenter.filterByMarkerType(nearbyFilterSearchRecyclerViewAdapter.selectedLabels,
binding.nearbyFilterList.checkboxTriStates.getState(), true, false);
@ -749,44 +782,53 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
}
private void initFilterChips() {
binding.nearbyFilter.chipView.choiceChipNeedsPhoto.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (NearbyController.currentLocation != null) {
binding.nearbyFilterList.checkboxTriStates.setState(CheckBoxTriStates.CHECKED);
NearbyFilterState.setNeedPhotoSelected(isChecked);
presenter.filterByMarkerType(nearbyFilterSearchRecyclerViewAdapter.selectedLabels,
binding.nearbyFilterList.checkboxTriStates.getState(), true, true);
updatePlaceList( binding.nearbyFilter.chipView.choiceChipNeedsPhoto.isChecked(),
binding.nearbyFilter.chipView.choiceChipExists.isChecked(), binding.nearbyFilter.chipView.choiceChipWlm.isChecked());
} else {
binding.nearbyFilter.chipView.choiceChipNeedsPhoto.setChecked(!isChecked);
}
});
binding.nearbyFilter.chipView.choiceChipNeedsPhoto.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
if (NearbyController.currentLocation != null) {
binding.nearbyFilterList.checkboxTriStates.setState(CheckBoxTriStates.CHECKED);
NearbyFilterState.setNeedPhotoSelected(isChecked);
presenter.filterByMarkerType(
nearbyFilterSearchRecyclerViewAdapter.selectedLabels,
binding.nearbyFilterList.checkboxTriStates.getState(), true, true);
updatePlaceList(binding.nearbyFilter.chipView.choiceChipNeedsPhoto.isChecked(),
binding.nearbyFilter.chipView.choiceChipExists.isChecked(),
binding.nearbyFilter.chipView.choiceChipWlm.isChecked());
} else {
binding.nearbyFilter.chipView.choiceChipNeedsPhoto.setChecked(!isChecked);
}
});
binding.nearbyFilter.chipView.choiceChipExists.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (NearbyController.currentLocation != null) {
binding.nearbyFilterList.checkboxTriStates.setState(CheckBoxTriStates.CHECKED);
NearbyFilterState.setExistsSelected(isChecked);
presenter.filterByMarkerType(nearbyFilterSearchRecyclerViewAdapter.selectedLabels,
binding.nearbyFilterList.checkboxTriStates.getState(), true, true);
updatePlaceList( binding.nearbyFilter.chipView.choiceChipNeedsPhoto.isChecked(),
binding.nearbyFilter.chipView.choiceChipExists.isChecked(), binding.nearbyFilter.chipView.choiceChipWlm.isChecked());
} else {
binding.nearbyFilter.chipView.choiceChipExists.setChecked(!isChecked);
}
});
binding.nearbyFilter.chipView.choiceChipExists.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
if (NearbyController.currentLocation != null) {
binding.nearbyFilterList.checkboxTriStates.setState(CheckBoxTriStates.CHECKED);
NearbyFilterState.setExistsSelected(isChecked);
presenter.filterByMarkerType(
nearbyFilterSearchRecyclerViewAdapter.selectedLabels,
binding.nearbyFilterList.checkboxTriStates.getState(), true, true);
updatePlaceList(binding.nearbyFilter.chipView.choiceChipNeedsPhoto.isChecked(),
binding.nearbyFilter.chipView.choiceChipExists.isChecked(),
binding.nearbyFilter.chipView.choiceChipWlm.isChecked());
} else {
binding.nearbyFilter.chipView.choiceChipExists.setChecked(!isChecked);
}
});
binding.nearbyFilter.chipView.choiceChipWlm.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (NearbyController.currentLocation != null) {
binding.nearbyFilterList.checkboxTriStates.setState(CheckBoxTriStates.CHECKED);
NearbyFilterState.setWlmSelected(isChecked);
presenter.filterByMarkerType(nearbyFilterSearchRecyclerViewAdapter.selectedLabels,
binding.nearbyFilterList.checkboxTriStates.getState(), true, true);
updatePlaceList( binding.nearbyFilter.chipView.choiceChipNeedsPhoto.isChecked(),
binding.nearbyFilter.chipView.choiceChipExists.isChecked(), binding.nearbyFilter.chipView.choiceChipWlm.isChecked());
} else {
binding.nearbyFilter.chipView.choiceChipWlm.setChecked(!isChecked);
}
});
binding.nearbyFilter.chipView.choiceChipWlm.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
if (NearbyController.currentLocation != null) {
binding.nearbyFilterList.checkboxTriStates.setState(CheckBoxTriStates.CHECKED);
NearbyFilterState.setWlmSelected(isChecked);
presenter.filterByMarkerType(
nearbyFilterSearchRecyclerViewAdapter.selectedLabels,
binding.nearbyFilterList.checkboxTriStates.getState(), true, true);
updatePlaceList(binding.nearbyFilter.chipView.choiceChipNeedsPhoto.isChecked(),
binding.nearbyFilter.chipView.choiceChipExists.isChecked(),
binding.nearbyFilter.chipView.choiceChipWlm.isChecked());
} else {
binding.nearbyFilter.chipView.choiceChipWlm.setChecked(!isChecked);
}
});
}
/**
@ -838,7 +880,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
}
adapter.setItems(updatedPlaces);
binding.bottomSheetNearby.noResultsMessage.setVisibility(updatedPlaces.isEmpty() ? View.VISIBLE : View.GONE);
binding.bottomSheetNearby.noResultsMessage.setVisibility(
updatedPlaces.isEmpty() ? View.VISIBLE : View.GONE);
}
/**
@ -867,8 +910,9 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
}
});
binding.bottomSheetNearby.bottomSheet.getLayoutParams().height = getActivity().getWindowManager()
.getDefaultDisplay().getHeight() / 16 * 9;
binding.bottomSheetNearby.bottomSheet.getLayoutParams().height =
getActivity().getWindowManager()
.getDefaultDisplay().getHeight() / 16 * 9;
bottomSheetListBehavior = BottomSheetBehavior.from(binding.bottomSheetNearby.bottomSheet);
bottomSheetListBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
bottomSheetListBehavior.setBottomSheetCallback(new BottomSheetBehavior
@ -897,26 +941,13 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
rotate_backward = AnimationUtils.loadAnimation(getActivity(), R.anim.rotate_backward);
}
/**
* Fits buttons according to our layout
*/
private void decideButtonVisibilities() {
// Remove button text if they exceed 1 line or if internal layout has not been built
// Only need to check for directions button because it is the longest
if ( binding.bottomSheetDetails.directionsButtonText.getLineCount() > 1 || binding.bottomSheetDetails.directionsButtonText.getLineCount() == 0) {
binding.bottomSheetDetails.wikipediaButtonText.setVisibility(View.GONE);
binding.bottomSheetDetails.wikidataButtonText.setVisibility(View.GONE);
binding.bottomSheetDetails.commonsButtonText.setVisibility(View.GONE);
binding.bottomSheetDetails.directionsButtonText.setVisibility(View.GONE);
}
}
/**
*
*/
private void addActionToTitle() {
binding.bottomSheetDetails.title.setOnLongClickListener(view -> {
Utils.copy("place", binding.bottomSheetDetails.title.getText().toString(), getContext());
Utils.copy("place", binding.bottomSheetDetails.title.getText().toString(),
getContext());
Toast.makeText(getContext(), R.string.text_copy, Toast.LENGTH_SHORT).show();
return true;
});
@ -963,7 +994,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
public void updateListFragment(final List<Place> placeList) {
places = placeList;
adapter.setItems(placeList);
binding.bottomSheetNearby.noResultsMessage.setVisibility(placeList.isEmpty() ? View.VISIBLE : View.GONE);
binding.bottomSheetNearby.noResultsMessage.setVisibility(
placeList.isEmpty() ? View.VISIBLE : View.GONE);
}
@Override
@ -1006,7 +1038,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
@Override
public fr.free.nrw.commons.location.LatLng getMapFocus() {
fr.free.nrw.commons.location.LatLng mapFocusedLatLng = new fr.free.nrw.commons.location.LatLng(
binding.map.getMapCenter().getLatitude(), binding.map.getMapCenter().getLongitude(), 100);
binding.map.getMapCenter().getLatitude(), binding.map.getMapCenter().getLongitude(),
100);
return mapFocusedLatLng;
}
@ -1077,8 +1110,10 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
@Override
public void populatePlaces(final fr.free.nrw.commons.location.LatLng currentLatLng) {
IGeoPoint screenTopRight = binding.map.getProjection().fromPixels(binding.map.getWidth(), 0);
IGeoPoint screenBottomLeft = binding.map.getProjection().fromPixels(0, binding.map.getHeight());
IGeoPoint screenTopRight = binding.map.getProjection()
.fromPixels(binding.map.getWidth(), 0);
IGeoPoint screenBottomLeft = binding.map.getProjection()
.fromPixels(0, binding.map.getHeight());
fr.free.nrw.commons.location.LatLng screenTopRightLatLng = new fr.free.nrw.commons.location.LatLng(
screenBottomLeft.getLatitude(), screenBottomLeft.getLongitude(), 0);
fr.free.nrw.commons.location.LatLng screenBottomLeftLatLng = new fr.free.nrw.commons.location.LatLng(
@ -1135,8 +1170,10 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
populatePlaces(currentLatLng);
return;
}
IGeoPoint screenTopRight = binding.map.getProjection().fromPixels(binding.map.getWidth(), 0);
IGeoPoint screenBottomLeft = binding.map.getProjection().fromPixels(0, binding.map.getHeight());
IGeoPoint screenTopRight = binding.map.getProjection()
.fromPixels(binding.map.getWidth(), 0);
IGeoPoint screenBottomLeft = binding.map.getProjection()
.fromPixels(0, binding.map.getHeight());
fr.free.nrw.commons.location.LatLng screenTopRightLatLng = new fr.free.nrw.commons.location.LatLng(
screenBottomLeft.getLatitude(), screenBottomLeft.getLongitude(), 0);
fr.free.nrw.commons.location.LatLng screenBottomLeftLatLng = new fr.free.nrw.commons.location.LatLng(
@ -1250,7 +1287,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
(isGPX) ? getString(R.string.do_you_want_to_open_gpx_file)
: getString(R.string.do_you_want_to_open_kml_file);
Runnable runnable = () -> openFile(context, fileName, isGPX);
DialogUtil.showAlertDialog(getActivity(), title, message, runnable,() -> {});
DialogUtil.showAlertDialog(getActivity(), title, message, runnable, () -> {
});
}
private void openFile(Context context, String fileName, Boolean isGPX) {
@ -1418,9 +1456,9 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
@Override
public void setProgressBarVisibility(final boolean isVisible) {
if (isVisible) {
binding.mapProgressBar.setVisibility(View.VISIBLE);
binding.mapProgressBar.setVisibility(View.VISIBLE);
} else {
binding.mapProgressBar.setVisibility(View.GONE);
binding.mapProgressBar.setVisibility(View.GONE);
}
}
@ -1444,7 +1482,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
}
private void showFABs() {
NearbyFABUtils.addAnchorToBigFABs(binding.fabPlus, binding.bottomSheetDetails.getRoot().getId());
NearbyFABUtils.addAnchorToBigFABs(binding.fabPlus,
binding.bottomSheetDetails.getRoot().getId());
binding.fabPlus.show();
NearbyFABUtils.addAnchorToSmallFABs(binding.fabGallery,
getView().findViewById(R.id.empty_view).getId());
@ -1576,17 +1615,17 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
@Override
public void setFABRecenterAction(final View.OnClickListener onClickListener) {
binding.fabRecenter.setOnClickListener(onClickListener);
binding.fabRecenter.setOnClickListener(onClickListener);
}
@Override
public void disableFABRecenter() {
binding.fabRecenter.setEnabled(false);
binding.fabRecenter.setEnabled(false);
}
@Override
public void enableFABRecenter() {
binding.fabRecenter.setEnabled(true);
binding.fabRecenter.setEnabled(true);
}
/**
@ -1865,7 +1904,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
}
/**
* Extracts text between the first occurrence of '(' and its corresponding ')' in the input string.
* Extracts text between the first occurrence of '(' and its corresponding ')' in the input
* string.
*
* @param input The input string from which to extract text between parentheses.
* @return The text between parentheses if found, or {@code null} if no parentheses are found.
@ -1890,14 +1930,17 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
return input.contains("(") || input.contains(")");
}
private void removeMarker(Place place){
private void removeMarker(Place place) {
List<Overlay> overlays = binding.map.getOverlays();
for (int i = 0; i < overlays.size();i++){
if (overlays.get(i) instanceof ItemizedOverlayWithFocus){
ItemizedOverlayWithFocus item = (ItemizedOverlayWithFocus)overlays.get(i);
for (int i = 0; i < overlays.size(); i++) {
if (overlays.get(i) instanceof ItemizedOverlayWithFocus) {
ItemizedOverlayWithFocus item = (ItemizedOverlayWithFocus) overlays.get(i);
OverlayItem overlayItem = item.getItem(0);
fr.free.nrw.commons.location.LatLng diffLatLang = new fr.free.nrw.commons.location.LatLng(overlayItem.getPoint().getLatitude(),overlayItem.getPoint().getLongitude(),100);
if (place.location.getLatitude() == overlayItem.getPoint().getLatitude() && place.location.getLongitude() == overlayItem.getPoint().getLongitude()){
fr.free.nrw.commons.location.LatLng diffLatLang = new fr.free.nrw.commons.location.LatLng(
overlayItem.getPoint().getLatitude(), overlayItem.getPoint().getLongitude(),
100);
if (place.location.getLatitude() == overlayItem.getPoint().getLatitude()
&& place.location.getLongitude() == overlayItem.getPoint().getLongitude()) {
binding.map.getOverlays().remove(i);
binding.map.invalidate();
break;
@ -2020,50 +2063,33 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
*/
private void passInfoToSheet(final Place place) {
selectedPlace = place;
dataList = new ArrayList<>();
// TODO: Decide button text for fitting in the screen
dataList.add(new BottomSheetItem(R.drawable.ic_round_star_border_24px, ""));
dataList.add(new BottomSheetItem(R.drawable.ic_directions_black_24dp,
getResources().getString(R.string.nearby_directions)));
if (place.hasWikidataLink()) {
dataList.add(new BottomSheetItem(R.drawable.ic_wikidata_logo_24dp,
getResources().getString(R.string.nearby_wikidata)));
}
dataList.add(new BottomSheetItem(R.drawable.ic_feedback_black_24dp,
getResources().getString(R.string.nearby_wikitalk)));
if (place.hasWikipediaLink()) {
dataList.add(new BottomSheetItem(R.drawable.ic_wikipedia_logo_24dp,
getResources().getString(R.string.nearby_wikipedia)));
}
if (selectedPlace.hasCommonsLink()) {
dataList.add(new BottomSheetItem(R.drawable.ic_commons_icon_vector,
getResources().getString(R.string.nearby_commons)));
}
int spanCount = getSpanCount();
gridLayoutManager = new GridLayoutManager(this.getContext(), spanCount);
binding.bottomSheetDetails.bottomSheetRecyclerView.setLayoutManager(gridLayoutManager);
bottomSheetAdapter = new BottomSheetAdapter(this.getContext(), dataList);
bottomSheetAdapter.setClickListener(this);
binding.bottomSheetDetails.bottomSheetRecyclerView.setAdapter(bottomSheetAdapter);
updateBookmarkButtonImage(selectedPlace);
binding.bottomSheetDetails.bookmarkButton.setOnClickListener(view -> {
final boolean isBookmarked = bookmarkLocationDao.updateBookmarkLocation(selectedPlace);
updateBookmarkButtonImage(selectedPlace);
updateMarker(isBookmarked, selectedPlace, locationManager.getLastLocation());
binding.map.invalidate();
});
binding.bottomSheetDetails.bookmarkButton.setOnLongClickListener(view -> {
Toast.makeText(getContext(), R.string.menu_bookmark, Toast.LENGTH_SHORT).show();
return true;
});
binding.bottomSheetDetails.wikipediaButton.setVisibility(place.hasWikipediaLink() ? View.VISIBLE : View.GONE);
binding.bottomSheetDetails.wikipediaButton.setOnClickListener(
view -> Utils.handleWebUrl(getContext(), selectedPlace.siteLinks.getWikipediaLink()));
binding.bottomSheetDetails.wikipediaButton.setOnLongClickListener(view -> {
Toast.makeText(getContext(), R.string.nearby_wikipedia, Toast.LENGTH_SHORT).show();
return true;
});
binding.bottomSheetDetails.wikidataButton.setVisibility(place.hasWikidataLink() ? View.VISIBLE : View.GONE);
binding.bottomSheetDetails.wikidataButton.setOnClickListener(
view -> Utils.handleWebUrl(getContext(), selectedPlace.siteLinks.getWikidataLink()));
binding.bottomSheetDetails.wikidataButton.setOnLongClickListener(view -> {
Toast.makeText(getContext(), R.string.nearby_wikidata, Toast.LENGTH_SHORT).show();
return true;
});
binding.bottomSheetDetails.directionsButton.setOnClickListener(view -> Utils.handleGeoCoordinates(getActivity(),
selectedPlace.getLocation()));
binding.bottomSheetDetails.directionsButton.setOnLongClickListener(view -> {
Toast.makeText(getContext(), R.string.nearby_directions, Toast.LENGTH_SHORT).show();
return true;
});
binding.bottomSheetDetails.commonsButton.setVisibility(selectedPlace.hasCommonsLink() ? View.VISIBLE : View.GONE);
binding.bottomSheetDetails.commonsButton.setOnClickListener(
view -> Utils.handleWebUrl(getContext(), selectedPlace.siteLinks.getCommonsLink()));
binding.bottomSheetDetails.commonsButton.setOnLongClickListener(view -> {
Toast.makeText(getContext(), R.string.nearby_commons, Toast.LENGTH_SHORT).show();
return true;
});
binding.bottomSheetDetails.icon.setImageResource(selectedPlace.getLabel().getIcon());
binding.bottomSheetDetails.title.setText(selectedPlace.name);
@ -2074,7 +2100,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
descriptionText = (descriptionText.equals(selectedPlace.getLongDescription())
? descriptionText : descriptionText.replaceFirst(".$", ""));
// Set the short description after we remove place name from long description
binding.bottomSheetDetails.description.setText(descriptionText);
binding.bottomSheetDetails.description.setText(descriptionText);
binding.fabCamera.setOnClickListener(view -> {
if (binding.fabCamera.isShown()) {
@ -2088,7 +2114,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
if (binding.fabGallery.isShown()) {
Timber.d("Gallery button tapped. Place: %s", selectedPlace.toString());
storeSharedPrefs(selectedPlace);
controller.initiateGalleryPick(getActivity(), binding.nearbyFilter.chipView.choiceChipWlm.isChecked());
controller.initiateGalleryPick(getActivity(),
binding.nearbyFilter.chipView.choiceChipWlm.isChecked());
}
});
@ -2115,9 +2142,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
} else {
bookmarkIcon = R.drawable.ic_round_star_border_24px;
}
if ( binding.bottomSheetDetails.bookmarkButtonImage != null) {
binding.bottomSheetDetails.bookmarkButtonImage.setImageResource(bookmarkIcon);
}
bottomSheetAdapter.updateBookmarkIcon(bookmarkIcon);
}
@Override
@ -2295,6 +2320,83 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
binding.map.getController().animateTo(geoPoint);
}
@Override
public void onBottomSheetItemClick(@Nullable View view, int position) {
BottomSheetItem item = dataList.get(position);
boolean isBookmarked = bookmarkLocationDao.findBookmarkLocation(selectedPlace);
switch (item.getImageResourceId()) {
case R.drawable.ic_round_star_border_24px:
bookmarkLocationDao.updateBookmarkLocation(selectedPlace);
updateBookmarkButtonImage(selectedPlace);
isBookmarked = bookmarkLocationDao.findBookmarkLocation(selectedPlace);
updateMarker(isBookmarked, selectedPlace, locationManager.getLastLocation());
binding.map.invalidate();
break;
case R.drawable.ic_round_star_filled_24px:
bookmarkLocationDao.updateBookmarkLocation(selectedPlace);
updateBookmarkButtonImage(selectedPlace);
isBookmarked = bookmarkLocationDao.findBookmarkLocation(selectedPlace);
updateMarker(isBookmarked, selectedPlace, locationManager.getLastLocation());
binding.map.invalidate();
break;
case R.drawable.ic_directions_black_24dp:
Utils.handleGeoCoordinates(this.getContext(), selectedPlace.getLocation());
break;
case R.drawable.ic_wikidata_logo_24dp:
Utils.handleWebUrl(this.getContext(), selectedPlace.siteLinks.getWikidataLink());
break;
case R.drawable.ic_feedback_black_24dp:
Intent intent = new Intent(this.getContext(), WikidataFeedback.class);
intent.putExtra("lat", selectedPlace.location.getLatitude());
intent.putExtra("lng", selectedPlace.location.getLongitude());
intent.putExtra("place", selectedPlace.name);
intent.putExtra("qid", selectedPlace.getWikiDataEntityId());
startActivity(intent);
break;
case R.drawable.ic_wikipedia_logo_24dp:
Utils.handleWebUrl(this.getContext(), selectedPlace.siteLinks.getWikipediaLink());
break;
case R.drawable.ic_commons_icon_vector:
Utils.handleWebUrl(this.getContext(), selectedPlace.siteLinks.getCommonsLink());
break;
default:
break;
}
}
@Override
public void onBottomSheetItemLongClick(@Nullable View view, int position) {
BottomSheetItem item = dataList.get(position);
String message;
switch (item.getImageResourceId()) {
case R.drawable.ic_round_star_border_24px:
message = getString(R.string.menu_bookmark);
break;
case R.drawable.ic_round_star_filled_24px:
message = getString(R.string.menu_bookmark);
break;
case R.drawable.ic_directions_black_24dp:
message = getString(R.string.nearby_directions);
break;
case R.drawable.ic_wikidata_logo_24dp:
message = getString(R.string.nearby_wikidata);
break;
case R.drawable.ic_feedback_black_24dp:
message = getString(R.string.nearby_wikitalk);
break;
case R.drawable.ic_wikipedia_logo_24dp:
message = getString(R.string.nearby_wikipedia);
break;
case R.drawable.ic_commons_icon_vector:
message = getString(R.string.nearby_commons);
break;
default:
message = "Long click";
break;
}
Toast.makeText(this.getContext(), message, Toast.LENGTH_SHORT).show();
}
public interface NearbyParentFragmentInstanceReadyCallback {
void onReady();
@ -2312,9 +2414,12 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
rlBottomSheetLayoutParams.height =
getActivity().getWindowManager().getDefaultDisplay().getHeight() / 16 * 9;
binding.bottomSheetNearby.bottomSheet.setLayoutParams(rlBottomSheetLayoutParams);
int spanCount = getSpanCount();
if (gridLayoutManager != null) {
gridLayoutManager.setSpanCount(spanCount);
}
}
public void onLearnMoreClicked() {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(WLM_URL));
@ -2322,11 +2427,12 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
}
public void onToggleChipsClicked() {
if ( binding.nearbyFilter.chipView.getRoot().getVisibility() == View.VISIBLE) {
binding.nearbyFilter.chipView.getRoot().setVisibility(View.GONE);
if (binding.nearbyFilter.chipView.getRoot().getVisibility() == View.VISIBLE) {
binding.nearbyFilter.chipView.getRoot().setVisibility(View.GONE);
} else {
binding.nearbyFilter.chipView.getRoot().setVisibility(View.VISIBLE);
binding.nearbyFilter.chipView.getRoot().setVisibility(View.VISIBLE);
}
binding.nearbyFilter.ivToggleChips.setRotation(binding.nearbyFilter.ivToggleChips.getRotation() + 180);
binding.nearbyFilter.ivToggleChips.setRotation(
binding.nearbyFilter.ivToggleChips.getRotation() + 180);
}
}

View file

@ -0,0 +1,3 @@
package fr.free.nrw.commons.nearby.model
class BottomSheetItem(var imageResourceId: Int, val title: String)

View file

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".nearby.WikidataFeedback">
<LinearLayout
android:id="@+id/toolbarLayout"
android:layout_width="0dp"
android:layout_height="?attr/actionBarSize"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<include
android:id="@+id/toolbarBinding"
layout="@layout/toolbar" />
</LinearLayout>
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/activity_layout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbarLayout">
<RadioGroup
android:id="@+id/radioGroup"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@+id/detailsEditText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<RadioButton
android:id="@+id/radioButton1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp" />
<RadioButton
android:id="@+id/radioButton2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp" />
<RadioButton
android:id="@+id/radioButton3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"/>
</RadioGroup>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/textHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="6dp"
android:text="Write something about the item. It will be publicly visible."
android:textSize="@dimen/normal_text"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="@+id/radioGroup"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent" />
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/detailsEditText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:hint="Comments"
android:singleLine="false"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="@+id/appCompatButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/appCompatButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="SEND"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/textHeader"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="@+id/textHeader" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -53,169 +53,21 @@
android:layout_width="match_parent"
android:layout_height="@dimen/tiny_height"
android:layout_marginTop="@dimen/small_height"
android:layout_marginBottom="@dimen/activity_margin_horizontal"
android:background="@android:color/darker_gray"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
android:layout_marginBottom="@dimen/activity_margin_horizontal"
android:background="@android:color/darker_gray" />
<LinearLayout
android:id="@+id/bookmarkButton"
android:layout_width="@dimen/dimen_0"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="@dimen/standard_gap"
android:clickable="true"
android:orientation="vertical"
android:background="@drawable/button_background_selector"
>
<ImageView
android:id="@+id/bookmarkButtonImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
app:srcCompat="@drawable/ic_round_star_border_24px"
android:tint="?attr/rowButtonColor"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/bookmarkButtonText"
android:paddingTop="@dimen/activity_margin_horizontal"
android:layout_gravity="center_horizontal"
android:text="CAMERA"
android:visibility="gone"
/>
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/bottom_sheet_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<LinearLayout
android:id="@+id/directionsButton"
android:layout_width="@dimen/dimen_0"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="@dimen/standard_gap"
android:clickable="true"
android:background="@drawable/button_background_selector"
android:orientation="vertical"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:duplicateParentState="true"
app:srcCompat="@drawable/ic_directions_black_24dp"
android:tint="?attr/rowButtonColor"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/directionsButtonText"
android:paddingTop="@dimen/activity_margin_horizontal"
android:duplicateParentState="true"
android:textColor="@color/text_color_selector"
android:layout_gravity="center_horizontal"
android:text="@string/nearby_directions"
android:textAllCaps="true"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/wikidataButton"
android:layout_width="@dimen/dimen_0"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="@dimen/standard_gap"
android:clickable="true"
android:background="@drawable/button_background_selector"
android:orientation="vertical"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:duplicateParentState="true"
app:srcCompat="@drawable/ic_wikidata_logo_24dp"
android:tint="?attr/rowButtonColor"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/wikidataButtonText"
android:paddingTop="@dimen/activity_margin_horizontal"
android:duplicateParentState="true"
android:textColor="@color/text_color_selector"
android:layout_gravity="center_horizontal"
android:text="@string/nearby_wikidata"
android:textAllCaps="true"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/wikipediaButton"
android:layout_width="@dimen/dimen_0"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="@dimen/standard_gap"
android:clickable="true"
android:background="@drawable/button_background_selector"
android:orientation="vertical"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:duplicateParentState="true"
app:srcCompat="@drawable/ic_wikipedia_logo_24dp"
android:tint="?attr/rowButtonColor"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/wikipediaButtonText"
android:paddingTop="@dimen/activity_margin_horizontal"
android:duplicateParentState="true"
android:textColor="@color/text_color_selector"
android:layout_gravity="center_horizontal"
android:text="@string/nearby_wikipedia"
android:textAllCaps="true"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/commonsButton"
android:layout_width="@dimen/dimen_0"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="@dimen/standard_gap"
android:clickable="true"
android:background="@drawable/button_background_selector"
android:orientation="vertical"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:duplicateParentState="true"
app:srcCompat="@drawable/ic_commons_icon_vector"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/commonsButtonText"
android:paddingTop="@dimen/activity_margin_horizontal"
android:duplicateParentState="true"
android:textColor="@color/text_color_selector"
android:layout_gravity="center_horizontal"
android:text="@string/nearby_commons"
android:textAllCaps="true"
/>
</LinearLayout>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="@dimen/tiny_height"
android:layout_marginTop="@dimen/small_height"
android:layout_marginBottom="@dimen/activity_margin_horizontal"
android:background="@android:color/darker_gray"/>
android:layout_width="match_parent"
android:layout_height="@dimen/tiny_height"
android:layout_marginTop="@dimen/small_height"
android:layout_marginBottom="@dimen/activity_margin_horizontal"
android:background="@android:color/darker_gray" />
<TextView
android:id="@+id/description"
android:layout_width="match_parent"

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/bookmarkButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_columnWeight="1"
android:background="@drawable/button_background_selector"
android:clickable="true"
android:orientation="vertical"
android:padding="@dimen/standard_gap">
<ImageView
android:id="@+id/buttonImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:tint="?attr/rowButtonColor" />
<TextView
android:id="@+id/buttonText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:paddingTop="@dimen/activity_margin_horizontal"
android:text="CAMERA"
android:visibility="gone" />
</LinearLayout>

View file

@ -818,5 +818,13 @@ Upload your first media by tapping on the add button.</string>
</plurals>
<string name="multiple_files_depiction">Please remember that all images in a multi-upload get the same categories and depictions. If the images do not share depictions and categories, please perform several separate uploads.</string>
<string name="multiple_files_depiction_header">Note about multi-uploads</string>
<string name="nearby_wikitalk">Report a problem about this item to Wikidata</string>
<string name="please_enter_some_comments">Please enter some comments</string>
<string name="talk">Talk</string>
<string name="write_something_about_the">"Write something about the "</string>
<string name="item_it_will_be_publicly_visible">" item. It will be publicly visible."</string>
<string name="does_not_exist_anymore_no_picture_can_ever_be_taken_of_it">\'%1$s\' does not exist anymore, no picture can ever be taken of it.</string>
<string name="is_at_a_different_place_please_specify_the_correct_place_below_if_possible_tell_us_the_correct_latitude_longitude">\'%1$s\' is at a different place (please specify the correct place below, if possible tell us the correct latitude/longitude).</string>
<string name="other_problem_or_information_please_explain_below">Other problem or information (please explain below).</string>
<string name="feedback_destination_note">Your feedback gets posted to the following wiki page: <![CDATA[ <a href="https://commons.wikimedia.org/wiki/Commons:Mobile_app/Feedback">Commons:Mobile app/Feedback</a> ]]></string>
</resources>