Skip to content

Commit 3779cfb

Browse files
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 <[email protected]>
1 parent 3690571 commit 3779cfb

16 files changed

+1184
-587
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 264 additions & 260 deletions
Large diffs are not rendered by default.

app/src/main/java/fr/free/nrw/commons/actions/PageEditClient.kt

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException
44
import io.reactivex.Observable
55
import io.reactivex.Single
66
import fr.free.nrw.commons.auth.csrf.CsrfTokenClient
7+
import timber.log.Timber
78

89
/**
910
* This class acts as a Client to facilitate wiki page editing
@@ -27,7 +28,41 @@ class PageEditClient(
2728
fun edit(pageTitle: String, text: String, summary: String): Observable<Boolean> {
2829
return try {
2930
pageEditInterface.postEdit(pageTitle, summary, text, csrfTokenClient.getTokenBlocking())
30-
.map { editResponse -> editResponse.edit()!!.editSucceeded() }
31+
.map { editResponse ->
32+
editResponse.edit()!!.editSucceeded()
33+
}
34+
} catch (throwable: Throwable) {
35+
if (throwable is InvalidLoginTokenException) {
36+
throw throwable
37+
} else {
38+
Observable.just(false)
39+
}
40+
}
41+
}
42+
43+
/**
44+
* Creates a new page with the given title, text, and summary.
45+
*
46+
* @param pageTitle The title of the page to be created.
47+
* @param text The content of the page in wikitext format.
48+
* @param summary The edit summary for the page creation.
49+
* @return An observable that emits true if the page creation succeeded, false otherwise.
50+
* @throws InvalidLoginTokenException If an invalid login token is encountered during the process.
51+
*/
52+
fun postCreate(pageTitle: String, text: String, summary: String): Observable<Boolean> {
53+
return try {
54+
pageEditInterface.postCreate(
55+
pageTitle,
56+
summary,
57+
text,
58+
"text/x-wiki",
59+
"wikitext",
60+
true,
61+
true,
62+
csrfTokenClient.getTokenBlocking()
63+
).map { editResponse ->
64+
editResponse.edit()!!.editSucceeded()
65+
}
3166
} catch (throwable: Throwable) {
3267
if (throwable is InvalidLoginTokenException) {
3368
throw throwable

app/src/main/java/fr/free/nrw/commons/actions/PageEditInterface.kt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,33 @@ interface PageEditInterface {
3636
@Field("token") token: String
3737
): Observable<Edit>
3838

39+
/**
40+
* This method creates or edits a page for nearby items.
41+
*
42+
* @param title Title of the page to edit. Cannot be used together with pageid.
43+
* @param summary Edit summary. Also used as the section title when section=new and sectiontitle is not set.
44+
* @param text Text of the page.
45+
* @param contentformat Format of the content (e.g., "text/x-wiki").
46+
* @param contentmodel Model of the content (e.g., "wikitext").
47+
* @param minor Whether the edit is a minor edit.
48+
* @param recreate Whether to recreate the page if it does not exist.
49+
* @param token A "csrf" token. This should always be sent as the last field of form data.
50+
*/
51+
@FormUrlEncoded
52+
@Headers("Cache-Control: no-cache")
53+
@POST(MW_API_PREFIX + "action=edit")
54+
fun postCreate(
55+
@Field("title") title: String,
56+
@Field("summary") summary: String,
57+
@Field("text") text: String,
58+
@Field("contentformat") contentformat: String,
59+
@Field("contentmodel") contentmodel: String,
60+
@Field("minor") minor: Boolean,
61+
@Field("recreate") recreate: Boolean,
62+
// NOTE: This csrf shold always be sent as the last field of form data
63+
@Field("token") token: String
64+
): Observable<Edit>
65+
3966
/**
4067
* This method posts such that the Content which the page
4168
* has will be appended with the value being passed to the

app/src/main/java/fr/free/nrw/commons/di/ActivityBuilderModule.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import fr.free.nrw.commons.explore.depictions.WikidataItemDetailsActivity;
1515
import fr.free.nrw.commons.explore.SearchActivity;
1616
import fr.free.nrw.commons.media.ZoomableActivity;
17+
import fr.free.nrw.commons.nearby.WikidataFeedback;
1718
import fr.free.nrw.commons.notification.NotificationActivity;
1819
import fr.free.nrw.commons.profile.ProfileActivity;
1920
import fr.free.nrw.commons.review.ReviewActivity;
@@ -79,4 +80,7 @@ public abstract class ActivityBuilderModule {
7980

8081
@ContributesAndroidInjector
8182
abstract ZoomableActivity bindZoomableActivity();
83+
84+
@ContributesAndroidInjector
85+
abstract WikidataFeedback bindWikiFeedback();
8286
}

app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
import com.google.gson.Gson;
44

5+
import fr.free.nrw.commons.actions.PageEditClient;
56
import fr.free.nrw.commons.explore.categories.CategoriesModule;
67
import fr.free.nrw.commons.navtab.MoreBottomSheetFragment;
78
import fr.free.nrw.commons.navtab.MoreBottomSheetLoggedOutFragment;
9+
import fr.free.nrw.commons.nearby.NearbyController;
810
import fr.free.nrw.commons.upload.worker.UploadWorker;
911
import javax.inject.Singleton;
1012

@@ -68,6 +70,9 @@ public interface CommonsApplicationComponent extends AndroidInjector<Application
6870

6971
void inject(PicOfDayAppWidget picOfDayAppWidget);
7072

73+
@Singleton
74+
void inject(NearbyController nearbyController);
75+
7176
Gson gson();
7277

7378
@Component.Builder

app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.java

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public class NetworkingModule {
6363
public static final String NAMED_LANGUAGE_WIKI_PEDIA_WIKI_SITE = "language-wikipedia-wikisite";
6464

6565
public static final String NAMED_COMMONS_CSRF = "commons-csrf";
66+
public static final String NAMED_WIKI_CSRF = "wiki-csrf";
6667

6768
@Provides
6869
@Singleton
@@ -128,10 +129,41 @@ public CommonsCookieJar provideCookieJar(CommonsCookieStorage storage) {
128129
@Provides
129130
@Singleton
130131
public CsrfTokenClient provideCommonsCsrfTokenClient(SessionManager sessionManager,
131-
CsrfTokenInterface tokenInterface, LoginClient loginClient, LogoutClient logoutClient) {
132+
@Named("commons-csrf-interface") CsrfTokenInterface tokenInterface, LoginClient loginClient, LogoutClient logoutClient) {
132133
return new CsrfTokenClient(sessionManager, tokenInterface, loginClient, logoutClient);
133134
}
134135

136+
/**
137+
* Provides a singleton instance of CsrfTokenClient for Wikidata.
138+
*
139+
* @param sessionManager The session manager to manage user sessions.
140+
* @param tokenInterface The interface for obtaining CSRF tokens.
141+
* @param loginClient The client for handling login operations.
142+
* @param logoutClient The client for handling logout operations.
143+
* @return A singleton instance of CsrfTokenClient.
144+
*/
145+
@Named(NAMED_WIKI_CSRF)
146+
@Provides
147+
@Singleton
148+
public CsrfTokenClient provideWikiCsrfTokenClient(SessionManager sessionManager,
149+
@Named("wikidata-csrf-interface") CsrfTokenInterface tokenInterface, LoginClient loginClient, LogoutClient logoutClient) {
150+
return new CsrfTokenClient(sessionManager, tokenInterface, loginClient, logoutClient);
151+
}
152+
153+
/**
154+
* Provides a singleton instance of CsrfTokenInterface for Wikidata.
155+
*
156+
* @param serviceFactory The factory used to create service interfaces.
157+
* @return A singleton instance of CsrfTokenInterface for Wikidata.
158+
*/
159+
@Named("wikidata-csrf-interface")
160+
@Provides
161+
@Singleton
162+
public CsrfTokenInterface provideWikidataCsrfTokenInterface(CommonsServiceFactory serviceFactory) {
163+
return serviceFactory.create(BuildConfig.WIKIDATA_URL, CsrfTokenInterface.class);
164+
}
165+
166+
@Named("commons-csrf-interface")
135167
@Provides
136168
@Singleton
137169
public CsrfTokenInterface provideCsrfTokenInterface(CommonsServiceFactory serviceFactory) {
@@ -230,6 +262,21 @@ public PageEditClient provideCommonsPageEditClient(@Named(NAMED_COMMONS_CSRF) Cs
230262
return new PageEditClient(csrfTokenClient, pageEditInterface);
231263
}
232264

265+
/**
266+
* Provides a singleton instance of PageEditClient for Wikidata.
267+
*
268+
* @param csrfTokenClient The client used to manage CSRF tokens.
269+
* @param pageEditInterface The interface for page edit operations.
270+
* @return A singleton instance of PageEditClient for Wikidata.
271+
*/
272+
@Named("wikidata-page-edit")
273+
@Provides
274+
@Singleton
275+
public PageEditClient provideWikidataPageEditClient(@Named(NAMED_WIKI_CSRF) CsrfTokenClient csrfTokenClient,
276+
@Named("wikidata-page-edit-service") PageEditInterface pageEditInterface) {
277+
return new PageEditClient(csrfTokenClient, pageEditInterface);
278+
}
279+
233280
@Provides
234281
@Singleton
235282
public MediaInterface provideMediaInterface(CommonsServiceFactory serviceFactory) {

app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111
import fr.free.nrw.commons.explore.depictions.DepictsClient;
1212
import fr.free.nrw.commons.location.LatLng;
1313
import fr.free.nrw.commons.nearby.Place;
14-
import fr.free.nrw.commons.nearby.model.PlaceBindings;
1514
import fr.free.nrw.commons.nearby.model.ItemsClass;
1615
import fr.free.nrw.commons.nearby.model.NearbyResponse;
1716
import fr.free.nrw.commons.nearby.model.NearbyResultItem;
17+
import fr.free.nrw.commons.nearby.model.PlaceBindings;
1818
import fr.free.nrw.commons.profile.achievements.FeaturedImages;
1919
import fr.free.nrw.commons.profile.achievements.FeedbackResponse;
2020
import fr.free.nrw.commons.profile.leaderboard.LeaderboardResponse;
@@ -476,7 +476,7 @@ public String getPlacesAsGPX(final LatLng leftLatLng, final LatLng rightLatLng)
476476
" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd\">"
477477
+ "\n<bounds minlat=\"$MIN_LATITUDE\" minlon=\"$MIN_LONGITUDE\" maxlat=\"$MAX_LATITUDE\" maxlon=\"$MAX_LONGITUDE\"/>";
478478

479-
List<PlaceBindings> placeBindings = runQuery(leftLatLng,rightLatLng);
479+
List<PlaceBindings> placeBindings = runQuery(leftLatLng, rightLatLng);
480480
if (placeBindings != null) {
481481
for (PlaceBindings item : placeBindings) {
482482
if (item.getItem() != null && item.getLabel() != null && item.getClas() != null) {
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package fr.free.nrw.commons.nearby
2+
3+
import android.content.Context
4+
import android.view.LayoutInflater
5+
import android.view.View
6+
import android.view.View.OnLongClickListener
7+
import android.view.ViewGroup
8+
import android.widget.ImageView
9+
import android.widget.TextView
10+
import androidx.annotation.NonNull
11+
import androidx.core.content.ContextCompat
12+
import androidx.recyclerview.widget.RecyclerView
13+
import fr.free.nrw.commons.R
14+
import fr.free.nrw.commons.nearby.model.BottomSheetItem
15+
16+
/**
17+
* RecyclerView Adapter for displaying items in a bottom sheet.
18+
*
19+
* @property context The context used for inflating layout resources.
20+
* @property itemList The list of BottomSheetItem objects to display.
21+
* @constructor Creates an instance of BottomSheetAdapter.
22+
*/
23+
class BottomSheetAdapter(context: Context?, private val itemList: List<BottomSheetItem>) :
24+
RecyclerView.Adapter<BottomSheetAdapter.ViewHolder>() {
25+
private val layoutInflater: LayoutInflater = LayoutInflater.from(context)
26+
private var itemClickListener: ItemClickListener? = null
27+
28+
@NonNull
29+
override fun onCreateViewHolder(@NonNull parent: ViewGroup, viewType: Int): ViewHolder {
30+
val view: View = layoutInflater.inflate(R.layout.bottom_sheet_item_layout, parent, false)
31+
return ViewHolder(view)
32+
}
33+
34+
override fun onBindViewHolder(@NonNull holder: ViewHolder, position: Int) {
35+
val item = itemList[position]
36+
holder.imageView.setImageDrawable(
37+
ContextCompat.getDrawable(
38+
getContext(),
39+
item.imageResourceId
40+
)
41+
)
42+
holder.title.setText(item.title)
43+
}
44+
45+
/**
46+
* Returns the total number of items in the data set held by the adapter.
47+
*
48+
* @return The total number of items in this adapter.
49+
*/
50+
override fun getItemCount(): Int {
51+
return itemList.size
52+
}
53+
54+
/**
55+
* Updates the icon for bookmark item.
56+
*
57+
* @param icon The resource ID of the new icon to set.
58+
*/
59+
fun updateBookmarkIcon(icon: Int) {
60+
itemList.forEachIndexed { index, item ->
61+
if (item.imageResourceId == R.drawable.ic_round_star_filled_24px || item.imageResourceId == R.drawable.ic_round_star_border_24px) {
62+
item.imageResourceId = icon
63+
this.notifyItemChanged(index)
64+
return
65+
}
66+
}
67+
}
68+
69+
inner class ViewHolder internal constructor(itemView: View) : RecyclerView.ViewHolder(itemView),
70+
View.OnClickListener, OnLongClickListener {
71+
var imageView: ImageView = itemView.findViewById(R.id.buttonImage)
72+
var title: TextView = itemView.findViewById(R.id.buttonText)
73+
74+
init {
75+
itemView.setOnClickListener(this)
76+
itemView.setOnLongClickListener(this)
77+
}
78+
79+
override fun onClick(view: View) {
80+
if (itemClickListener != null) itemClickListener!!.onBottomSheetItemClick(
81+
view,
82+
adapterPosition
83+
)
84+
}
85+
86+
override fun onLongClick(view: View): Boolean {
87+
if (itemClickListener != null) itemClickListener!!.onBottomSheetItemLongClick(
88+
view,
89+
adapterPosition
90+
)
91+
return true
92+
}
93+
}
94+
95+
fun setClickListener(itemClickListener: ItemClickListener?) {
96+
this.itemClickListener = itemClickListener
97+
}
98+
99+
fun getContext(): Context {
100+
return layoutInflater.context
101+
}
102+
103+
interface ItemClickListener {
104+
fun onBottomSheetItemClick(view: View?, position: Int)
105+
fun onBottomSheetItemLongClick(view: View?, position: Int)
106+
}
107+
}
108+

0 commit comments

Comments
 (0)