Merge branch 'main' into media_migration

This commit is contained in:
Nicolas Raoul 2025-03-08 22:39:56 +09:00 committed by GitHub
commit 53c7fd8507
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
59 changed files with 1265 additions and 1112 deletions

View file

@ -2,11 +2,6 @@ name: Android CI
on: [push, pull_request, workflow_dispatch] on: [push, pull_request, workflow_dispatch]
permissions:
pull-requests: write
contents: read
actions: read
concurrency: concurrency:
group: build-${{ github.event.pull_request.number || github.ref }} group: build-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true cancel-in-progress: true
@ -17,17 +12,17 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Set up JDK - name: Set up JDK
uses: actions/setup-java@v3 uses: actions/setup-java@v4
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: '17' java-version: '17'
- name: Cache packages - name: Cache packages
id: cache-packages id: cache-packages
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: | path: |
~/.gradle/caches ~/.gradle/caches
@ -42,7 +37,7 @@ jobs:
- name: AVD cache - name: AVD cache
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
uses: actions/cache@v3 uses: actions/cache@v4
id: avd-cache id: avd-cache
with: with:
path: | path: |
@ -108,63 +103,12 @@ jobs:
name: prodDebugAPK name: prodDebugAPK
path: app/build/outputs/apk/prod/debug/app-*.apk path: app/build/outputs/apk/prod/debug/app-*.apk
- name: Comment on PR with APK download links - name: Create and PR number artifact
if: github.event_name == 'pull_request' run: |
uses: actions/github-script@v6 echo "{\"pr_number\": ${{ github.event.pull_request.number || 'null' }}}" > pr_number.json
- name: Upload PR number artifact
uses: actions/upload-artifact@v4
with: with:
github-token: ${{secrets.GITHUB_TOKEN}} name: pr_number
script: | path: ./pr_number.json
try {
const token = process.env.GITHUB_TOKEN;
if (!token) {
throw new Error('GITHUB_TOKEN is not set. Please check workflow permissions.');
}
const { data: { artifacts } } = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.runId
});
if (!artifacts || artifacts.length === 0) {
console.log('No artifacts found for this workflow run.');
return;
}
const betaArtifact = artifacts.find(artifact => artifact.name === "betaDebugAPK");
const prodArtifact = artifacts.find(artifact => artifact.name === "prodDebugAPK");
if (!betaArtifact || !prodArtifact) {
console.log('Could not find both Beta and Prod APK artifacts.');
console.log('Available artifacts:', artifacts.map(a => a.name).join(', '));
return;
}
const betaDownloadUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/suites/${context.runId}/artifacts/${betaArtifact.id}`;
const prodDownloadUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/suites/${context.runId}/artifacts/${prodArtifact.id}`;
const commentBody = `
📱 **APK for pull request is ready to see the changes** 📱
- [Download Beta APK](${betaDownloadUrl})
- [Download Prod APK](${prodDownloadUrl})
`;
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: commentBody
});
console.log('Successfully posted comment with APK download links');
} catch (error) {
console.error('Error in PR comment creation:', error);
if (error.message.includes('GITHUB_TOKEN')) {
core.setFailed('Missing or invalid GITHUB_TOKEN. Please check repository secrets configuration.');
} else if (error.status === 403) {
core.setFailed('Permission denied. Please check workflow permissions in repository settings.');
} else {
core.setFailed(`Workflow failed: ${error.message}`);
}
}

View file

@ -8,9 +8,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: set up JDK 17 - name: set up JDK 17
uses: actions/setup-java@v3 uses: actions/setup-java@v4
with: with:
java-version: '17' java-version: '17'
distribution: 'temurin' distribution: 'temurin'

View file

@ -0,0 +1,96 @@
name: Comment Artifacts on PR
on:
workflow_run:
workflows: [ "Android CI" ]
types: [ completed ]
permissions:
pull-requests: write
contents: read
concurrency:
group: comment-${{ github.event.workflow_run.id }}
cancel-in-progress: true
jobs:
comment:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request' }}
steps:
- name: Download and process artifacts
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require('fs');
const runId = context.payload.workflow_run.id;
const allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: runId,
});
const prNumberArtifact = allArtifacts.data.artifacts.find(artifact => artifact.name === "pr_number");
if (!prNumberArtifact) {
console.log("pr_number artifact not found.");
return;
}
const download = await github.rest.actions.downloadArtifact({
owner: context.repo.owner,
repo: context.repo.repo,
artifact_id: prNumberArtifact.id,
archive_format: 'zip',
});
fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/pr_number.zip`, Buffer.from(download.data));
const { execSync } = require('child_process');
execSync('unzip -q pr_number.zip -d ./pr_number/');
fs.unlinkSync('pr_number.zip');
const prData = JSON.parse(fs.readFileSync('./pr_number/pr_number.json', 'utf8'));
const prNumber = prData.pr_number;
if (!prNumber || prNumber === 'null') {
console.log("No valid PR number found in pr_number.json. Skipping.");
return;
}
const artifactsToLink = allArtifacts.data.artifacts.filter(artifact => artifact.name !== "pr_number");
if (artifactsToLink.length === 0) {
console.log("No artifacts to link found.");
return;
}
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: Number(prNumber),
});
const oldComments = comments.data.filter(comment =>
comment.body.startsWith("✅ Generated APK variants!")
);
for (const comment of oldComments) {
await github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: comment.id,
});
console.log(`Deleted old comment ID: ${comment.id}`);
};
const commentBody = `✅ Generated APK variants!\n` +
artifactsToLink.map(artifact => {
const artifactUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}/artifacts/${artifact.id}`;
return `- 🤖 [Download ${artifact.name}](${artifactUrl})`;
}).join('\n');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: Number(prNumber),
body: commentBody
});

View file

@ -1,6 +1,6 @@
# Wikimedia Commons Android app # Wikimedia Commons Android app
![GitHub issue custom search](https://img.shields.io/github/issues-search?label=%22good%20first%20issue%22%20issues&query=repo%3Acommons-app%2Fapps-android-commons%20is%3Aissue%20is%3Aopen%20label%3A%22good%20first%20issue%22) ![GitHub issue custom search](https://img.shields.io/github/issues-search?label=%22good%20first%20issue%22%20issues&query=repo%3Acommons-app%2Fapps-android-commons%20is%3Aissue%20is%3Aopen%20label%3A%22good%20first%20issue%22)
[![Build status](https://github.com/commons-app/apps-android-commons/actions/workflows/android.yml/badge.svg?branch=master)](https://github.com/commons-app/apps-android-commons/actions?query=branch%3Amaster) [![Build status](https://github.com/commons-app/apps-android-commons/actions/workflows/android.yml/badge.svg?branch=main)](https://github.com/commons-app/apps-android-commons/actions?query=branch%3Amain)
[![Preview the app](https://img.shields.io/badge/Preview-Appetize.io-orange.svg)](https://appetize.io/app/8ywtpe9f8tb8h6bey11c92vkcw) [![Preview the app](https://img.shields.io/badge/Preview-Appetize.io-orange.svg)](https://appetize.io/app/8ywtpe9f8tb8h6bey11c92vkcw)
[![codecov](https://codecov.io/gh/commons-app/apps-android-commons/branch/master/graph/badge.svg)](https://codecov.io/gh/commons-app/apps-android-commons) [![codecov](https://codecov.io/gh/commons-app/apps-android-commons/branch/master/graph/badge.svg)](https://codecov.io/gh/commons-app/apps-android-commons)
@ -45,7 +45,7 @@ This software is open source, licensed under the [Apache License 2.0][10].
[1]: https://play.google.com/store/apps/details?id=fr.free.nrw.commons [1]: https://play.google.com/store/apps/details?id=fr.free.nrw.commons
[2]: https://commons-app.github.io/ [2]: https://commons-app.github.io/
[3]: https://github.com/commons-app/apps-android-commons/issues [3]: https://github.com/commons-app/apps-android-commons/issues?q=is%3Aopen+is%3Aissue+no%3Aassignee+-label%3Adebated+label%3Abug+-label%3A%22low+priority%22+-label%3Aupstream
[4]: https://github.com/commons-app/commons-app-documentation/blob/master/android/README.md#-android-documentation [4]: https://github.com/commons-app/commons-app-documentation/blob/master/android/README.md#-android-documentation
[5]: https://github.com/commons-app/commons-app-documentation/blob/master/android/README.md#-user-documentation [5]: https://github.com/commons-app/commons-app-documentation/blob/master/android/README.md#-user-documentation

View file

View file

@ -175,8 +175,8 @@ dependencies {
testImplementation "androidx.work:work-testing:$work_version" testImplementation "androidx.work:work-testing:$work_version"
//Glide //Glide
implementation 'com.github.bumptech.glide:glide:4.12.0' implementation 'com.github.bumptech.glide:glide:4.16.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
kaptTest "androidx.databinding:databinding-compiler:8.0.2" kaptTest "androidx.databinding:databinding-compiler:8.0.2"
kaptAndroidTest "androidx.databinding:databinding-compiler:8.0.2" kaptAndroidTest "androidx.databinding:databinding-compiler:8.0.2"
@ -212,8 +212,8 @@ android {
defaultConfig { defaultConfig {
//applicationId 'fr.free.nrw.commons' //applicationId 'fr.free.nrw.commons'
versionCode 1043 versionCode 1046
versionName '5.1.2' versionName '5.1.3'
setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName()) setProperty("archivesBaseName", "app-commons-v$versionName-" + getBranchName())
minSdkVersion 21 minSdkVersion 21

View file

@ -232,12 +232,6 @@
android:exported="false" android:exported="false"
android:label="@string/provider_bookmarks" android:label="@string/provider_bookmarks"
android:syncable="false" /> 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 <provider
android:name=".bookmarks.items.BookmarkItemsContentProvider" android:name=".bookmarks.items.BookmarkItemsContentProvider"
android:authorities="${applicationId}.bookmarks.items.contentprovider" android:authorities="${applicationId}.bookmarks.items.contentprovider"

View file

@ -247,13 +247,17 @@ class CommonsApplication : MultiDexApplication() {
DBOpenHelper.CONTRIBUTIONS_TABLE DBOpenHelper.CONTRIBUTIONS_TABLE
) //Delete the contributions table in the existing db on older versions ) //Delete the contributions table in the existing db on older versions
dbOpenHelper.deleteTable(
db,
DBOpenHelper.BOOKMARKS_LOCATIONS
)
try { try {
contributionDao.deleteAll() contributionDao.deleteAll()
} catch (e: SQLiteException) { } catch (e: SQLiteException) {
Timber.e(e) Timber.e(e)
} }
BookmarkPicturesDao.Table.onDelete(db) BookmarkPicturesDao.Table.onDelete(db)
BookmarkLocationsDao.Table.onDelete(db)
BookmarkItemsDao.Table.onDelete(db) BookmarkItemsDao.Table.onDelete(db)
} }

View file

@ -90,6 +90,54 @@ class Media constructor(
captions = captions, captions = captions,
) )
constructor(
captions: Map<String, String>,
categories: List<String>?,
filename: String?,
fallbackDescription: String?,
author: String?,
user: String?,
dateUploaded: Date? = Date(),
license: String? = null,
licenseUrl: String? = null,
imageUrl: String? = null,
thumbUrl: String? = null,
coordinates: LatLng? = null,
descriptions: Map<String, String> = emptyMap(),
depictionIds: List<String> = emptyList(),
categoriesHiddenStatus: Map<String, Boolean> = emptyMap()
) : this(
pageId = UUID.randomUUID().toString(),
filename = filename,
fallbackDescription = fallbackDescription,
dateUploaded = dateUploaded,
author = author,
user = user,
categories = categories,
captions = captions,
license = license,
licenseUrl = licenseUrl,
imageUrl = imageUrl,
thumbUrl = thumbUrl,
coordinates = coordinates,
descriptions = descriptions,
depictionIds = depictionIds,
categoriesHiddenStatus = categoriesHiddenStatus
)
/**
* Returns Author if it's not null or empty, otherwise
* returns user
* @return Author or User
*/
fun getAuthorOrUser(): String? {
return if (!author.isNullOrEmpty()) {
author
} else{
user
}
}
/** /**
* Gets media display title * Gets media display title
* @return Media title * @return Media title

View file

@ -1,119 +0,0 @@
package fr.free.nrw.commons.bookmarks.locations;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
// We can get uri using java.Net.Uri, but andoid implimentation is faster (but it's forgiving with handling exceptions though)
import android.net.Uri;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import javax.inject.Inject;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.data.DBOpenHelper;
import fr.free.nrw.commons.di.CommonsDaggerContentProvider;
import timber.log.Timber;
import static fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_NAME;
import static fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.TABLE_NAME;
/**
* Handles private storage for Bookmark locations
*/
public class BookmarkLocationsContentProvider extends CommonsDaggerContentProvider {
private static final String BASE_PATH = "bookmarksLocations";
public static final Uri BASE_URI = Uri.parse("content://" + BuildConfig.BOOKMARK_LOCATIONS_AUTHORITY + "/" + BASE_PATH);
/**
* Append bookmark locations name to the base uri
*/
public static Uri uriForName(String name) {
return Uri.parse(BASE_URI.toString() + "/" + name);
}
@Inject DBOpenHelper dbOpenHelper;
@Override
public String getType(@NonNull Uri uri) {
return null;
}
/**
* Queries the SQLite database for the bookmark locations
* @param uri : contains the uri for bookmark locations
* @param projection
* @param selection : handles Where
* @param selectionArgs : the condition of Where clause
* @param sortOrder : ascending or descending
*/
@SuppressWarnings("ConstantConditions")
@Override
public Cursor query(@NonNull Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(TABLE_NAME);
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
/**
* Handles the update query of local SQLite Database
* @param uri : contains the uri for bookmark locations
* @param contentValues : new values to be entered to db
* @param selection : handles Where
* @param selectionArgs : the condition of Where clause
*/
@SuppressWarnings("ConstantConditions")
@Override
public int update(@NonNull Uri uri, ContentValues contentValues, String selection,
String[] selectionArgs) {
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
int rowsUpdated;
if (TextUtils.isEmpty(selection)) {
int id = Integer.valueOf(uri.getLastPathSegment());
rowsUpdated = sqlDB.update(TABLE_NAME,
contentValues,
COLUMN_NAME + " = ?",
new String[]{String.valueOf(id)});
} else {
throw new IllegalArgumentException(
"Parameter `selection` should be empty when updating an ID");
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsUpdated;
}
/**
* Handles the insertion of new bookmark locations record to local SQLite Database
*/
@SuppressWarnings("ConstantConditions")
@Override
public Uri insert(@NonNull Uri uri, ContentValues contentValues) {
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
long id = sqlDB.insert(BookmarkLocationsDao.Table.TABLE_NAME, null, contentValues);
getContext().getContentResolver().notifyChange(uri, null);
return Uri.parse(BASE_URI + "/" + id);
}
@SuppressWarnings("ConstantConditions")
@Override
public int delete(@NonNull Uri uri, String s, String[] strings) {
int rows;
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
Timber.d("Deleting bookmark name %s", uri.getLastPathSegment());
rows = db.delete(TABLE_NAME,
"location_name = ?",
new String[]{uri.getLastPathSegment()}
);
getContext().getContentResolver().notifyChange(uri, null);
return rows;
}
}

View file

@ -1,26 +0,0 @@
package fr.free.nrw.commons.bookmarks.locations;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Singleton;
import fr.free.nrw.commons.nearby.Place;
@Singleton
public class BookmarkLocationsController {
@Inject
BookmarkLocationsDao bookmarkLocationDao;
@Inject
public BookmarkLocationsController() {}
/**
* Load from DB the bookmarked locations
* @return a list of Place objects.
*/
public List<Place> loadFavoritesLocations() {
return bookmarkLocationDao.getAllBookmarksLocations();
}
}

View file

@ -0,0 +1,20 @@
package fr.free.nrw.commons.bookmarks.locations
import fr.free.nrw.commons.nearby.Place
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class BookmarkLocationsController @Inject constructor(
private val bookmarkLocationDao: BookmarkLocationsDao
) {
/**
* Load bookmarked locations from the database.
* @return a list of Place objects.
*/
suspend fun loadFavoritesLocations(): List<Place> =
bookmarkLocationDao.getAllBookmarksLocationsPlace()
}

View file

@ -1,313 +0,0 @@
package fr.free.nrw.commons.bookmarks.locations;
import android.annotation.SuppressLint;
import android.content.ContentProviderClient;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.os.RemoteException;
import androidx.annotation.NonNull;
import fr.free.nrw.commons.nearby.NearbyController;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.nearby.Label;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.nearby.Sitelinks;
import timber.log.Timber;
import static fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsContentProvider.BASE_URI;
public class BookmarkLocationsDao {
private final Provider<ContentProviderClient> clientProvider;
@Inject
public BookmarkLocationsDao(@Named("bookmarksLocation") Provider<ContentProviderClient> clientProvider) {
this.clientProvider = clientProvider;
}
/**
* Find all persisted locations bookmarks on database
*
* @return list of Place
*/
@NonNull
public List<Place> getAllBookmarksLocations() {
List<Place> items = new ArrayList<>();
Cursor cursor = null;
ContentProviderClient db = clientProvider.get();
try {
cursor = db.query(
BookmarkLocationsContentProvider.BASE_URI,
Table.ALL_FIELDS,
null,
new String[]{},
null);
while (cursor != null && cursor.moveToNext()) {
items.add(fromCursor(cursor));
}
} catch (RemoteException e) {
throw new RuntimeException(e);
} finally {
if (cursor != null) {
cursor.close();
}
db.release();
}
return items;
}
/**
* Look for a place in bookmarks table in order to insert or delete it
*
* @param bookmarkLocation : Place object
* @return is Place now fav ?
*/
public boolean updateBookmarkLocation(Place bookmarkLocation) {
boolean bookmarkExists = findBookmarkLocation(bookmarkLocation);
if (bookmarkExists) {
deleteBookmarkLocation(bookmarkLocation);
NearbyController.updateMarkerLabelListBookmark(bookmarkLocation, false);
} else {
addBookmarkLocation(bookmarkLocation);
NearbyController.updateMarkerLabelListBookmark(bookmarkLocation, true);
}
return !bookmarkExists;
}
/**
* Add a Place to bookmarks table
*
* @param bookmarkLocation : Place to add
*/
private void addBookmarkLocation(Place bookmarkLocation) {
ContentProviderClient db = clientProvider.get();
try {
db.insert(BASE_URI, toContentValues(bookmarkLocation));
} catch (RemoteException e) {
throw new RuntimeException(e);
} finally {
db.release();
}
}
/**
* Delete a Place from bookmarks table
*
* @param bookmarkLocation : Place to delete
*/
private void deleteBookmarkLocation(Place bookmarkLocation) {
ContentProviderClient db = clientProvider.get();
try {
db.delete(BookmarkLocationsContentProvider.uriForName(bookmarkLocation.name), null, null);
} catch (RemoteException e) {
throw new RuntimeException(e);
} finally {
db.release();
}
}
/**
* Find a Place from database based on its name
*
* @param bookmarkLocation : Place to find
* @return boolean : is Place in database ?
*/
public boolean findBookmarkLocation(Place bookmarkLocation) {
Cursor cursor = null;
ContentProviderClient db = clientProvider.get();
try {
cursor = db.query(
BookmarkLocationsContentProvider.BASE_URI,
Table.ALL_FIELDS,
Table.COLUMN_NAME + "=?",
new String[]{bookmarkLocation.name},
null);
if (cursor != null && cursor.moveToFirst()) {
return true;
}
} catch (RemoteException e) {
// This feels lazy, but to hell with checked exceptions. :)
throw new RuntimeException(e);
} finally {
if (cursor != null) {
cursor.close();
}
db.release();
}
return false;
}
@SuppressLint("Range")
@NonNull
Place fromCursor(final Cursor cursor) {
final LatLng location = new LatLng(cursor.getDouble(cursor.getColumnIndex(Table.COLUMN_LAT)),
cursor.getDouble(cursor.getColumnIndex(Table.COLUMN_LONG)), 1F);
final Sitelinks.Builder builder = new Sitelinks.Builder();
builder.setWikipediaLink(cursor.getString(cursor.getColumnIndex(Table.COLUMN_WIKIPEDIA_LINK)));
builder.setWikidataLink(cursor.getString(cursor.getColumnIndex(Table.COLUMN_WIKIDATA_LINK)));
builder.setCommonsLink(cursor.getString(cursor.getColumnIndex(Table.COLUMN_COMMONS_LINK)));
return new Place(
cursor.getString(cursor.getColumnIndex(Table.COLUMN_LANGUAGE)),
cursor.getString(cursor.getColumnIndex(Table.COLUMN_NAME)),
Label.fromText((cursor.getString(cursor.getColumnIndex(Table.COLUMN_LABEL_TEXT)))),
cursor.getString(cursor.getColumnIndex(Table.COLUMN_DESCRIPTION)),
location,
cursor.getString(cursor.getColumnIndex(Table.COLUMN_CATEGORY)),
builder.build(),
cursor.getString(cursor.getColumnIndex(Table.COLUMN_PIC)),
Boolean.parseBoolean(cursor.getString(cursor.getColumnIndex(Table.COLUMN_EXISTS)))
);
}
private ContentValues toContentValues(Place bookmarkLocation) {
ContentValues cv = new ContentValues();
cv.put(BookmarkLocationsDao.Table.COLUMN_NAME, bookmarkLocation.getName());
cv.put(BookmarkLocationsDao.Table.COLUMN_LANGUAGE, bookmarkLocation.getLanguage());
cv.put(BookmarkLocationsDao.Table.COLUMN_DESCRIPTION, bookmarkLocation.getLongDescription());
cv.put(BookmarkLocationsDao.Table.COLUMN_CATEGORY, bookmarkLocation.getCategory());
cv.put(BookmarkLocationsDao.Table.COLUMN_LABEL_TEXT, bookmarkLocation.getLabel()!=null ? bookmarkLocation.getLabel().getText() : "");
cv.put(BookmarkLocationsDao.Table.COLUMN_LABEL_ICON, bookmarkLocation.getLabel()!=null ? bookmarkLocation.getLabel().getIcon() : null);
cv.put(BookmarkLocationsDao.Table.COLUMN_WIKIPEDIA_LINK, bookmarkLocation.siteLinks.getWikipediaLink().toString());
cv.put(BookmarkLocationsDao.Table.COLUMN_WIKIDATA_LINK, bookmarkLocation.siteLinks.getWikidataLink().toString());
cv.put(BookmarkLocationsDao.Table.COLUMN_COMMONS_LINK, bookmarkLocation.siteLinks.getCommonsLink().toString());
cv.put(BookmarkLocationsDao.Table.COLUMN_LAT, bookmarkLocation.location.getLatitude());
cv.put(BookmarkLocationsDao.Table.COLUMN_LONG, bookmarkLocation.location.getLongitude());
cv.put(BookmarkLocationsDao.Table.COLUMN_PIC, bookmarkLocation.pic);
cv.put(BookmarkLocationsDao.Table.COLUMN_EXISTS, bookmarkLocation.exists.toString());
return cv;
}
public static class Table {
public static final String TABLE_NAME = "bookmarksLocations";
static final String COLUMN_NAME = "location_name";
static final String COLUMN_LANGUAGE = "location_language";
static final String COLUMN_DESCRIPTION = "location_description";
static final String COLUMN_LAT = "location_lat";
static final String COLUMN_LONG = "location_long";
static final String COLUMN_CATEGORY = "location_category";
static final String COLUMN_LABEL_TEXT = "location_label_text";
static final String COLUMN_LABEL_ICON = "location_label_icon";
static final String COLUMN_IMAGE_URL = "location_image_url";
static final String COLUMN_WIKIPEDIA_LINK = "location_wikipedia_link";
static final String COLUMN_WIKIDATA_LINK = "location_wikidata_link";
static final String COLUMN_COMMONS_LINK = "location_commons_link";
static final String COLUMN_PIC = "location_pic";
static final String COLUMN_EXISTS = "location_exists";
// NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES.
public static final String[] ALL_FIELDS = {
COLUMN_NAME,
COLUMN_LANGUAGE,
COLUMN_DESCRIPTION,
COLUMN_CATEGORY,
COLUMN_LABEL_TEXT,
COLUMN_LABEL_ICON,
COLUMN_LAT,
COLUMN_LONG,
COLUMN_IMAGE_URL,
COLUMN_WIKIPEDIA_LINK,
COLUMN_WIKIDATA_LINK,
COLUMN_COMMONS_LINK,
COLUMN_PIC,
COLUMN_EXISTS,
};
static final String DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS " + TABLE_NAME;
static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " ("
+ COLUMN_NAME + " STRING PRIMARY KEY,"
+ COLUMN_LANGUAGE + " STRING,"
+ COLUMN_DESCRIPTION + " STRING,"
+ COLUMN_CATEGORY + " STRING,"
+ COLUMN_LABEL_TEXT + " STRING,"
+ COLUMN_LABEL_ICON + " INTEGER,"
+ COLUMN_LAT + " DOUBLE,"
+ COLUMN_LONG + " DOUBLE,"
+ COLUMN_IMAGE_URL + " STRING,"
+ COLUMN_WIKIPEDIA_LINK + " STRING,"
+ COLUMN_WIKIDATA_LINK + " STRING,"
+ COLUMN_COMMONS_LINK + " STRING,"
+ COLUMN_PIC + " STRING,"
+ COLUMN_EXISTS + " STRING"
+ ");";
public static void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE_STATEMENT);
}
public static void onDelete(SQLiteDatabase db) {
db.execSQL(DROP_TABLE_STATEMENT);
onCreate(db);
}
public static void onUpdate(final SQLiteDatabase db, int from, final int to) {
Timber.d("bookmarksLocations db is updated from:"+from+", to:"+to);
if (from == to) {
return;
}
if (from < 7) {
// doesn't exist yet
from++;
onUpdate(db, from, to);
return;
}
if (from == 7) {
// table added in version 8
onCreate(db);
from++;
onUpdate(db, from, to);
return;
}
if (from < 10) {
from++;
onUpdate(db, from, to);
return;
}
if (from == 10) {
//This is safe, and can be called clean, as we/I do not remember the appropriate version for this
//We are anyways switching to room, these things won't be necessary then
try {
db.execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_pic STRING;");
}catch (SQLiteException exception){
Timber.e(exception);//
}
return;
}
if (from >= 12) {
try {
db.execSQL(
"ALTER TABLE bookmarksLocations ADD COLUMN location_destroyed STRING;");
} catch (SQLiteException exception) {
Timber.e(exception);
}
}
if (from >= 13){
try {
db.execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_language STRING;");
} catch (SQLiteException exception){
Timber.e(exception);
}
}
if (from >= 14){
try {
db.execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_exists STRING;");
} catch (SQLiteException exception){
Timber.e(exception);
}
}
}
}
}

View file

@ -0,0 +1,65 @@
package fr.free.nrw.commons.bookmarks.locations
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import fr.free.nrw.commons.nearby.NearbyController
import fr.free.nrw.commons.nearby.Place
/**
* DAO for managing bookmark locations in the database.
*/
@Dao
abstract class BookmarkLocationsDao {
/**
* Adds or updates a bookmark location in the database.
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun addBookmarkLocation(bookmarkLocation: BookmarksLocations)
/**
* Fetches all bookmark locations from the database.
*/
@Query("SELECT * FROM bookmarks_locations")
abstract suspend fun getAllBookmarksLocations(): List<BookmarksLocations>
/**
* Checks if a bookmark location exists by name.
*/
@Query("SELECT EXISTS (SELECT 1 FROM bookmarks_locations WHERE location_name = :name)")
abstract suspend fun findBookmarkLocation(name: String): Boolean
/**
* Deletes a bookmark location from the database.
*/
@Delete
abstract suspend fun deleteBookmarkLocation(bookmarkLocation: BookmarksLocations)
/**
* Adds or removes a bookmark location and updates markers.
* @return `true` if added, `false` if removed.
*/
suspend fun updateBookmarkLocation(bookmarkLocation: Place): Boolean {
val exists = findBookmarkLocation(bookmarkLocation.name)
if (exists) {
deleteBookmarkLocation(bookmarkLocation.toBookmarksLocations())
NearbyController.updateMarkerLabelListBookmark(bookmarkLocation, false)
} else {
addBookmarkLocation(bookmarkLocation.toBookmarksLocations())
NearbyController.updateMarkerLabelListBookmark(bookmarkLocation, true)
}
return !exists
}
/**
* Fetches all bookmark locations as `Place` objects.
*/
suspend fun getAllBookmarksLocationsPlace(): List<Place> {
return getAllBookmarksLocations().map { it.toPlace() }
}
}

View file

@ -1,137 +0,0 @@
package fr.free.nrw.commons.bookmarks.locations;
import android.Manifest.permission;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;
import dagger.android.support.DaggerFragment;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.ContributionController;
import fr.free.nrw.commons.databinding.FragmentBookmarksLocationsBinding;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.nearby.fragments.CommonPlaceClickActions;
import fr.free.nrw.commons.nearby.fragments.PlaceAdapter;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import kotlin.Unit;
public class BookmarkLocationsFragment extends DaggerFragment {
public FragmentBookmarksLocationsBinding binding;
@Inject BookmarkLocationsController controller;
@Inject ContributionController contributionController;
@Inject BookmarkLocationsDao bookmarkLocationDao;
@Inject CommonPlaceClickActions commonPlaceClickActions;
private PlaceAdapter adapter;
private final ActivityResultLauncher<Intent> cameraPickLauncherForResult =
registerForActivityResult(new StartActivityForResult(),
result -> {
contributionController.handleActivityResultWithCallback(requireActivity(), callbacks -> {
contributionController.onPictureReturnedFromCamera(result, requireActivity(), callbacks);
});
});
private final ActivityResultLauncher<Intent> galleryPickLauncherForResult =
registerForActivityResult(new StartActivityForResult(),
result -> {
contributionController.handleActivityResultWithCallback(requireActivity(), callbacks -> {
contributionController.onPictureReturnedFromGallery(result, requireActivity(), callbacks);
});
});
private ActivityResultLauncher<String[]> inAppCameraLocationPermissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback<Map<String, Boolean>>() {
@Override
public void onActivityResult(Map<String, Boolean> result) {
boolean areAllGranted = true;
for(final boolean b : result.values()) {
areAllGranted = areAllGranted && b;
}
if (areAllGranted) {
contributionController.locationPermissionCallback.onLocationPermissionGranted();
} else {
if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) {
contributionController.handleShowRationaleFlowCameraLocation(getActivity(), inAppCameraLocationPermissionLauncher, cameraPickLauncherForResult);
} else {
contributionController.locationPermissionCallback.onLocationPermissionDenied(getActivity().getString(R.string.in_app_camera_location_permission_denied));
}
}
}
});
/**
* Create an instance of the fragment with the right bundle parameters
* @return an instance of the fragment
*/
public static BookmarkLocationsFragment newInstance() {
return new BookmarkLocationsFragment();
}
@Override
public View onCreateView(
@NonNull LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState
) {
binding = FragmentBookmarksLocationsBinding.inflate(inflater, container, false);
return binding.getRoot();
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
binding.loadingImagesProgressBar.setVisibility(View.VISIBLE);
binding.listView.setLayoutManager(new LinearLayoutManager(getContext()));
adapter = new PlaceAdapter(bookmarkLocationDao,
place -> Unit.INSTANCE,
(place, isBookmarked) -> {
adapter.remove(place);
return Unit.INSTANCE;
},
commonPlaceClickActions,
inAppCameraLocationPermissionLauncher,
galleryPickLauncherForResult,
cameraPickLauncherForResult
);
binding.listView.setAdapter(adapter);
}
@Override
public void onResume() {
super.onResume();
initList();
}
/**
* Initialize the recycler view with bookmarked locations
*/
private void initList() {
List<Place> places = controller.loadFavoritesLocations();
adapter.setItems(places);
binding.loadingImagesProgressBar.setVisibility(View.GONE);
if (places.size() <= 0) {
binding.statusMessage.setText(R.string.bookmark_empty);
binding.statusMessage.setVisibility(View.VISIBLE);
} else {
binding.statusMessage.setVisibility(View.GONE);
}
}
@Override
public void onDestroy() {
super.onDestroy();
binding = null;
}
}

View file

@ -0,0 +1,161 @@
package fr.free.nrw.commons.bookmarks.locations
import android.Manifest.permission
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.LinearLayoutManager
import dagger.android.support.DaggerFragment
import fr.free.nrw.commons.R
import fr.free.nrw.commons.contributions.ContributionController
import fr.free.nrw.commons.databinding.FragmentBookmarksLocationsBinding
import fr.free.nrw.commons.filepicker.FilePicker
import fr.free.nrw.commons.nearby.Place
import fr.free.nrw.commons.nearby.fragments.CommonPlaceClickActions
import fr.free.nrw.commons.nearby.fragments.PlaceAdapter
import kotlinx.coroutines.launch
import javax.inject.Inject
class BookmarkLocationsFragment : DaggerFragment() {
private var binding: FragmentBookmarksLocationsBinding? = null
@Inject lateinit var controller: BookmarkLocationsController
@Inject lateinit var contributionController: ContributionController
@Inject lateinit var bookmarkLocationDao: BookmarkLocationsDao
@Inject lateinit var commonPlaceClickActions: CommonPlaceClickActions
private lateinit var inAppCameraLocationPermissionLauncher:
ActivityResultLauncher<Array<String>>
private lateinit var adapter: PlaceAdapter
private val cameraPickLauncherForResult =
registerForActivityResult(StartActivityForResult()) { result ->
contributionController.handleActivityResultWithCallback(
requireActivity(),
object: FilePicker.HandleActivityResult {
override fun onHandleActivityResult(callbacks: FilePicker.Callbacks) {
contributionController.onPictureReturnedFromCamera(
result,
requireActivity(),
callbacks
)
}
}
)
}
private val galleryPickLauncherForResult =
registerForActivityResult(StartActivityForResult()) { result ->
contributionController.handleActivityResultWithCallback(
requireActivity(),
object: FilePicker.HandleActivityResult {
override fun onHandleActivityResult(callbacks: FilePicker.Callbacks) {
contributionController.onPictureReturnedFromGallery(
result,
requireActivity(),
callbacks
)
}
}
)
}
companion object {
fun newInstance(): BookmarkLocationsFragment {
return BookmarkLocationsFragment()
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentBookmarksLocationsBinding.inflate(inflater, container, false)
return binding?.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.loadingImagesProgressBar?.visibility = View.VISIBLE
binding?.listView?.layoutManager = LinearLayoutManager(context)
inAppCameraLocationPermissionLauncher =
registerForActivityResult(RequestMultiplePermissions()) { result ->
val areAllGranted = result.values.all { it }
if (areAllGranted) {
contributionController.locationPermissionCallback?.onLocationPermissionGranted()
} else {
if (shouldShowRequestPermissionRationale(permission.ACCESS_FINE_LOCATION)) {
contributionController.handleShowRationaleFlowCameraLocation(
requireActivity(),
inAppCameraLocationPermissionLauncher,
cameraPickLauncherForResult
)
} else {
contributionController.locationPermissionCallback
?.onLocationPermissionDenied(
getString(R.string.in_app_camera_location_permission_denied)
)
}
}
}
adapter = PlaceAdapter(
bookmarkLocationDao,
lifecycleScope,
{ },
{ place, _ ->
adapter.remove(place)
},
commonPlaceClickActions,
inAppCameraLocationPermissionLauncher,
galleryPickLauncherForResult,
cameraPickLauncherForResult
)
binding?.listView?.adapter = adapter
}
override fun onResume() {
super.onResume()
initList()
}
fun initList() {
var places: List<Place>
if(view != null) {
viewLifecycleOwner.lifecycleScope.launch {
places = controller.loadFavoritesLocations()
updateUIList(places)
}
}
}
private fun updateUIList(places: List<Place>) {
adapter.items = places
binding?.loadingImagesProgressBar?.visibility = View.GONE
if (places.isEmpty()) {
binding?.statusMessage?.text = getString(R.string.bookmark_empty)
binding?.statusMessage?.visibility = View.VISIBLE
} else {
binding?.statusMessage?.visibility = View.GONE
}
}
override fun onDestroy() {
super.onDestroy()
// Make sure to null out the binding to avoid memory leaks
binding = null
}
}

View file

@ -0,0 +1,15 @@
package fr.free.nrw.commons.bookmarks.locations
import androidx.lifecycle.ViewModel
import fr.free.nrw.commons.nearby.Place
import kotlinx.coroutines.flow.Flow
class BookmarkLocationsViewModel(
private val bookmarkLocationsDao: BookmarkLocationsDao
): ViewModel() {
// fun getAllBookmarkLocations(): List<Place> {
// return bookmarkLocationsDao.getAllBookmarksLocationsPlace()
// }
}

View file

@ -0,0 +1,72 @@
package fr.free.nrw.commons.bookmarks.locations
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import fr.free.nrw.commons.location.LatLng
import fr.free.nrw.commons.nearby.Label
import fr.free.nrw.commons.nearby.Place
import fr.free.nrw.commons.nearby.Sitelinks
@Entity(tableName = "bookmarks_locations")
data class BookmarksLocations(
@PrimaryKey @ColumnInfo(name = "location_name") val locationName: String,
@ColumnInfo(name = "location_language") val locationLanguage: String,
@ColumnInfo(name = "location_description") val locationDescription: String,
@ColumnInfo(name = "location_lat") val locationLat: Double,
@ColumnInfo(name = "location_long") val locationLong: Double,
@ColumnInfo(name = "location_category") val locationCategory: String,
@ColumnInfo(name = "location_label_text") val locationLabelText: String,
@ColumnInfo(name = "location_label_icon") val locationLabelIcon: Int?,
@ColumnInfo(name = "location_image_url") val locationImageUrl: String,
@ColumnInfo(name = "location_wikipedia_link") val locationWikipediaLink: String,
@ColumnInfo(name = "location_wikidata_link") val locationWikidataLink: String,
@ColumnInfo(name = "location_commons_link") val locationCommonsLink: String,
@ColumnInfo(name = "location_pic") val locationPic: String,
@ColumnInfo(name = "location_exists") val locationExists: Boolean
)
fun BookmarksLocations.toPlace(): Place {
val location = LatLng(
locationLat,
locationLong,
1F
)
val builder = Sitelinks.Builder().apply {
setWikipediaLink(locationWikipediaLink)
setWikidataLink(locationWikidataLink)
setCommonsLink(locationCommonsLink)
}
return Place(
locationLanguage,
locationName,
Label.fromText(locationLabelText),
locationDescription,
location,
locationCategory,
builder.build(),
locationPic,
locationExists
)
}
fun Place.toBookmarksLocations(): BookmarksLocations {
return BookmarksLocations(
locationName = name,
locationLanguage = language,
locationDescription = longDescription,
locationCategory = category,
locationLat = location.latitude,
locationLong = location.longitude,
locationLabelText = label?.text ?: "",
locationLabelIcon = label?.icon,
locationImageUrl = pic,
locationWikipediaLink = siteLinks.wikipediaLink.toString(),
locationWikidataLink = siteLinks.wikidataLink.toString(),
locationCommonsLink = siteLinks.commonsLink.toString(),
locationPic = pic,
locationExists = exists
)
}

View file

@ -98,14 +98,9 @@ class GridViewAdapter(
*/ */
@SuppressLint("StringFormatInvalid") @SuppressLint("StringFormatInvalid")
private fun setUploaderView(item: Media, uploader: TextView) { private fun setUploaderView(item: Media, uploader: TextView) {
if (!item.author.isNullOrEmpty()) {
uploader.visibility = View.VISIBLE
uploader.text = context.getString( uploader.text = context.getString(
R.string.image_uploaded_by, R.string.image_uploaded_by,
item.user item.getAuthorOrUser()
) )
} else {
uploader.visibility = View.GONE
}
} }
} }

View file

@ -54,7 +54,7 @@ an upload might take a dozen seconds. */
this.contribution = contribution this.contribution = contribution
this.position = position this.position = position
binding.contributionTitle.text = contribution.media.mostRelevantCaption binding.contributionTitle.text = contribution.media.mostRelevantCaption
binding.authorView.text = contribution.media.author binding.authorView.text = contribution.media.getAuthorOrUser()
//Removes flicker of loading image. //Removes flicker of loading image.
binding.contributionImage.hierarchy.fadeDuration = 0 binding.contributionImage.hierarchy.fadeDuration = 0

View file

@ -10,7 +10,7 @@ interface ContributionsContract {
interface View { interface View {
fun showMessage(localizedMessage: String) fun showMessage(localizedMessage: String)
fun getContext(): Context fun getContext(): Context?
} }
interface UserActionListener : BasePresenter<View> { interface UserActionListener : BasePresenter<View> {

View file

@ -74,12 +74,9 @@ import java.util.Date
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Named import javax.inject.Named
class ContributionsFragment class ContributionsFragment : CommonsDaggerSupportFragment(), FragmentManager.OnBackStackChangedListener,
: CommonsDaggerSupportFragment(), FragmentManager.OnBackStackChangedListener,
LocationUpdateListener, MediaDetailProvider, SensorEventListener, ICampaignsView, LocationUpdateListener, MediaDetailProvider, SensorEventListener, ICampaignsView,
ContributionsContract.View, ContributionsContract.View, ContributionsListFragment.Callback {
ContributionsListFragment.Callback {
@JvmField @JvmField
@Inject @Inject
@Named("default_preferences") @Named("default_preferences")
@ -307,11 +304,13 @@ class ContributionsFragment
} }
} }
notification.setOnClickListener { view: View? -> notification.setOnClickListener { view: View? ->
context?.let {
startYourself( startYourself(
context, "unread" it, "unread"
) )
} }
} }
}
@SuppressLint("CheckResult") @SuppressLint("CheckResult")
fun setNotificationCount() { fun setNotificationCount() {
@ -500,7 +499,7 @@ class ContributionsFragment
private fun setUploadCount() { private fun setUploadCount() {
okHttpJsonApiClient okHttpJsonApiClient
?.getUploadCount((activity as MainActivity).sessionManager?.currentAccount!!.name) ?.getUploadCount(sessionManager?.currentAccount!!.name)
?.subscribeOn(Schedulers.io()) ?.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())?.let { ?.observeOn(AndroidSchedulers.mainThread())?.let {
compositeDisposable.add( compositeDisposable.add(
@ -889,7 +888,8 @@ class ContributionsFragment
* this function updates the number of contributions * this function updates the number of contributions
*/ */
fun upDateUploadCount() { fun upDateUploadCount() {
WorkManager.getInstance(context) context?.let {
WorkManager.getInstance(it)
.getWorkInfosForUniqueWorkLiveData(UploadWorker::class.java.simpleName).observe( .getWorkInfosForUniqueWorkLiveData(UploadWorker::class.java.simpleName).observe(
viewLifecycleOwner viewLifecycleOwner
) { workInfos: List<WorkInfo?> -> ) { workInfos: List<WorkInfo?> ->
@ -898,6 +898,7 @@ class ContributionsFragment
} }
} }
} }
}
/** /**
@ -953,7 +954,7 @@ class ContributionsFragment
Timber.d("Skipping re-upload for non-failed %s", contribution.toString()) Timber.d("Skipping re-upload for non-failed %s", contribution.toString())
} }
} else { } else {
showLongToast(context, R.string.this_function_needs_network_connection) context?.let { showLongToast(it, R.string.this_function_needs_network_connection) }
} }
} }

View file

@ -80,9 +80,11 @@ class ContributionsPresenter @Inject internal constructor(
.save(contribution) .save(contribution)
.subscribeOn(ioThreadScheduler) .subscribeOn(ioThreadScheduler)
.subscribe { .subscribe {
view!!.getContext()?.applicationContext?.let {
makeOneTimeWorkRequest( makeOneTimeWorkRequest(
view!!.getContext().applicationContext, ExistingWorkPolicy.KEEP it, ExistingWorkPolicy.KEEP
) )
}
}) })
} }
} }

View file

@ -18,8 +18,9 @@ class DBOpenHelper(
companion object { companion object {
private const val DATABASE_NAME = "commons.db" private const val DATABASE_NAME = "commons.db"
private const val DATABASE_VERSION = 21 private const val DATABASE_VERSION = 22
const val CONTRIBUTIONS_TABLE = "contributions" const val CONTRIBUTIONS_TABLE = "contributions"
const val BOOKMARKS_LOCATIONS = "bookmarksLocations"
private const val DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS %s" private const val DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS %s"
} }
@ -30,7 +31,6 @@ class DBOpenHelper(
override fun onCreate(db: SQLiteDatabase) { override fun onCreate(db: SQLiteDatabase) {
CategoryDao.Table.onCreate(db) CategoryDao.Table.onCreate(db)
BookmarkPicturesDao.Table.onCreate(db) BookmarkPicturesDao.Table.onCreate(db)
BookmarkLocationsDao.Table.onCreate(db)
BookmarkItemsDao.Table.onCreate(db) BookmarkItemsDao.Table.onCreate(db)
RecentSearchesDao.Table.onCreate(db) RecentSearchesDao.Table.onCreate(db)
RecentLanguagesDao.Table.onCreate(db) RecentLanguagesDao.Table.onCreate(db)
@ -39,11 +39,11 @@ class DBOpenHelper(
override fun onUpgrade(db: SQLiteDatabase, from: Int, to: Int) { override fun onUpgrade(db: SQLiteDatabase, from: Int, to: Int) {
CategoryDao.Table.onUpdate(db, from, to) CategoryDao.Table.onUpdate(db, from, to)
BookmarkPicturesDao.Table.onUpdate(db, from, to) BookmarkPicturesDao.Table.onUpdate(db, from, to)
BookmarkLocationsDao.Table.onUpdate(db, from, to)
BookmarkItemsDao.Table.onUpdate(db, from, to) BookmarkItemsDao.Table.onUpdate(db, from, to)
RecentSearchesDao.Table.onUpdate(db, from, to) RecentSearchesDao.Table.onUpdate(db, from, to)
RecentLanguagesDao.Table.onUpdate(db, from, to) RecentLanguagesDao.Table.onUpdate(db, from, to)
deleteTable(db, CONTRIBUTIONS_TABLE) deleteTable(db, CONTRIBUTIONS_TABLE)
deleteTable(db, BOOKMARKS_LOCATIONS)
} }
/** /**

View file

@ -1,10 +1,16 @@
package fr.free.nrw.commons.db package fr.free.nrw.commons.db
import android.content.Context
import androidx.room.Database import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import androidx.room.TypeConverters import androidx.room.TypeConverters
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import fr.free.nrw.commons.bookmarks.category.BookmarkCategoriesDao import fr.free.nrw.commons.bookmarks.category.BookmarkCategoriesDao
import fr.free.nrw.commons.bookmarks.category.BookmarksCategoryModal import fr.free.nrw.commons.bookmarks.category.BookmarksCategoryModal
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao
import fr.free.nrw.commons.bookmarks.locations.BookmarksLocations
import fr.free.nrw.commons.contributions.Contribution import fr.free.nrw.commons.contributions.Contribution
import fr.free.nrw.commons.contributions.ContributionDao import fr.free.nrw.commons.contributions.ContributionDao
import fr.free.nrw.commons.customselector.database.NotForUploadStatus import fr.free.nrw.commons.customselector.database.NotForUploadStatus
@ -23,8 +29,8 @@ import fr.free.nrw.commons.upload.depicts.DepictsDao
* *
*/ */
@Database( @Database(
entities = [Contribution::class, Depicts::class, UploadedStatus::class, NotForUploadStatus::class, ReviewEntity::class, Place::class, BookmarksCategoryModal::class], entities = [Contribution::class, Depicts::class, UploadedStatus::class, NotForUploadStatus::class, ReviewEntity::class, Place::class, BookmarksCategoryModal::class, BookmarksLocations::class],
version = 19, version = 20,
exportSchema = false, exportSchema = false,
) )
@TypeConverters(Converters::class) @TypeConverters(Converters::class)
@ -42,4 +48,6 @@ abstract class AppDatabase : RoomDatabase() {
abstract fun ReviewDao(): ReviewDao abstract fun ReviewDao(): ReviewDao
abstract fun bookmarkCategoriesDao(): BookmarkCategoriesDao abstract fun bookmarkCategoriesDao(): BookmarkCategoriesDao
abstract fun bookmarkLocationsDao(): BookmarkLocationsDao
} }

View file

@ -111,7 +111,7 @@ class DeleteHelper @Inject constructor(
val userPageString = "\n{{subst:idw|${media.filename}}} ~~~~" val userPageString = "\n{{subst:idw|${media.filename}}} ~~~~"
val creator = media.author val creator = media.getAuthorOrUser()
?: throw RuntimeException("Failed to nominate for deletion") ?: throw RuntimeException("Failed to nominate for deletion")
return pageEditClient.prependEdit( return pageEditClient.prependEdit(

View file

@ -4,6 +4,7 @@ import android.app.Activity
import android.content.ContentProviderClient import android.content.ContentProviderClient
import android.content.ContentResolver import android.content.ContentResolver
import android.content.Context import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import androidx.collection.LruCache import androidx.collection.LruCache
import androidx.room.Room.databaseBuilder import androidx.room.Room.databaseBuilder
@ -16,6 +17,7 @@ import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.auth.SessionManager import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.bookmarks.category.BookmarkCategoriesDao import fr.free.nrw.commons.bookmarks.category.BookmarkCategoriesDao
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao
import fr.free.nrw.commons.contributions.ContributionDao import fr.free.nrw.commons.contributions.ContributionDao
import fr.free.nrw.commons.customselector.database.NotForUploadStatusDao import fr.free.nrw.commons.customselector.database.NotForUploadStatusDao
import fr.free.nrw.commons.customselector.database.UploadedStatusDao import fr.free.nrw.commons.customselector.database.UploadedStatusDao
@ -36,6 +38,7 @@ import fr.free.nrw.commons.wikidata.WikidataEditListenerImpl
import io.reactivex.Scheduler import io.reactivex.Scheduler
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import java.util.Objects import java.util.Objects
import javax.inject.Named import javax.inject.Named
import javax.inject.Singleton import javax.inject.Singleton
@ -49,6 +52,11 @@ import javax.inject.Singleton
@Module @Module
@Suppress("unused") @Suppress("unused")
open class CommonsApplicationModule(private val applicationContext: Context) { open class CommonsApplicationModule(private val applicationContext: Context) {
init {
appContext = applicationContext
}
@Provides @Provides
fun providesImageFileLoader(context: Context): ImageFileLoader = fun providesImageFileLoader(context: Context): ImageFileLoader =
ImageFileLoader(context) ImageFileLoader(context)
@ -110,11 +118,6 @@ open class CommonsApplicationModule(private val applicationContext: Context) {
fun provideBookmarkContentProviderClient(context: Context): ContentProviderClient? = fun provideBookmarkContentProviderClient(context: Context): ContentProviderClient? =
context.contentResolver.acquireContentProviderClient(BuildConfig.BOOKMARK_AUTHORITY) context.contentResolver.acquireContentProviderClient(BuildConfig.BOOKMARK_AUTHORITY)
@Provides
@Named("bookmarksLocation")
fun provideBookmarkLocationContentProviderClient(context: Context): ContentProviderClient? =
context.contentResolver.acquireContentProviderClient(BuildConfig.BOOKMARK_LOCATIONS_AUTHORITY)
@Provides @Provides
@Named("bookmarksItem") @Named("bookmarksItem")
fun provideBookmarkItemContentProviderClient(context: Context): ContentProviderClient? = fun provideBookmarkItemContentProviderClient(context: Context): ContentProviderClient? =
@ -196,7 +199,10 @@ open class CommonsApplicationModule(private val applicationContext: Context) {
applicationContext, applicationContext,
AppDatabase::class.java, AppDatabase::class.java,
"commons_room.db" "commons_room.db"
).addMigrations(MIGRATION_1_2).fallbackToDestructiveMigration().build() ).addMigrations(
MIGRATION_1_2,
MIGRATION_19_TO_20
).fallbackToDestructiveMigration().build()
@Provides @Provides
fun providesContributionsDao(appDatabase: AppDatabase): ContributionDao = fun providesContributionsDao(appDatabase: AppDatabase): ContributionDao =
@ -206,6 +212,10 @@ open class CommonsApplicationModule(private val applicationContext: Context) {
fun providesPlaceDao(appDatabase: AppDatabase): PlaceDao = fun providesPlaceDao(appDatabase: AppDatabase): PlaceDao =
appDatabase.PlaceDao() appDatabase.PlaceDao()
@Provides
fun providesBookmarkLocationsDao(appDatabase: AppDatabase): BookmarkLocationsDao =
appDatabase.bookmarkLocationsDao()
@Provides @Provides
fun providesDepictDao(appDatabase: AppDatabase): DepictsDao = fun providesDepictDao(appDatabase: AppDatabase): DepictsDao =
appDatabase.DepictsDao() appDatabase.DepictsDao()
@ -239,6 +249,9 @@ open class CommonsApplicationModule(private val applicationContext: Context) {
const val IO_THREAD: String = "io_thread" const val IO_THREAD: String = "io_thread"
const val MAIN_THREAD: String = "main_thread" const val MAIN_THREAD: String = "main_thread"
lateinit var appContext: Context
private set
val MIGRATION_1_2: Migration = object : Migration(1, 2) { val MIGRATION_1_2: Migration = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) { override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL( db.execSQL(
@ -246,5 +259,101 @@ open class CommonsApplicationModule(private val applicationContext: Context) {
) )
} }
} }
private val MIGRATION_19_TO_20 = object : Migration(19, 20) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL(
"""
CREATE TABLE IF NOT EXISTS bookmarks_locations (
location_name TEXT NOT NULL PRIMARY KEY,
location_language TEXT NOT NULL,
location_description TEXT NOT NULL,
location_lat REAL NOT NULL,
location_long REAL NOT NULL,
location_category TEXT NOT NULL,
location_label_text TEXT NOT NULL,
location_label_icon INTEGER,
location_image_url TEXT NOT NULL DEFAULT '',
location_wikipedia_link TEXT NOT NULL,
location_wikidata_link TEXT NOT NULL,
location_commons_link TEXT NOT NULL,
location_pic TEXT NOT NULL,
location_exists INTEGER NOT NULL CHECK(location_exists IN (0, 1))
)
"""
)
val oldDbPath = appContext.getDatabasePath("commons.db").path
val oldDb = SQLiteDatabase
.openDatabase(oldDbPath, null, SQLiteDatabase.OPEN_READONLY)
val cursor = oldDb.rawQuery("SELECT * FROM bookmarksLocations", null)
while (cursor.moveToNext()) {
val locationName =
cursor.getString(cursor.getColumnIndexOrThrow("location_name"))
val locationLanguage =
cursor.getString(cursor.getColumnIndexOrThrow("location_language"))
val locationDescription =
cursor.getString(cursor.getColumnIndexOrThrow("location_description"))
val locationCategory =
cursor.getString(cursor.getColumnIndexOrThrow("location_category"))
val locationLabelText =
cursor.getString(cursor.getColumnIndexOrThrow("location_label_text"))
val locationLabelIcon =
cursor.getInt(cursor.getColumnIndexOrThrow("location_label_icon"))
val locationLat =
cursor.getDouble(cursor.getColumnIndexOrThrow("location_lat"))
val locationLong =
cursor.getDouble(cursor.getColumnIndexOrThrow("location_long"))
// Handle NULL values safely
val locationImageUrl =
cursor.getString(
cursor.getColumnIndexOrThrow("location_image_url")
) ?: ""
val locationWikipediaLink =
cursor.getString(
cursor.getColumnIndexOrThrow("location_wikipedia_link")
) ?: ""
val locationWikidataLink =
cursor.getString(
cursor.getColumnIndexOrThrow("location_wikidata_link")
) ?: ""
val locationCommonsLink =
cursor.getString(
cursor.getColumnIndexOrThrow("location_commons_link")
) ?: ""
val locationPic =
cursor.getString(
cursor.getColumnIndexOrThrow("location_pic")
) ?: ""
val locationExists =
cursor.getInt(
cursor.getColumnIndexOrThrow("location_exists")
)
db.execSQL(
"""
INSERT OR REPLACE INTO bookmarks_locations (
location_name, location_language, location_description, location_category,
location_label_text, location_label_icon, location_lat, location_long,
location_image_url, location_wikipedia_link, location_wikidata_link,
location_commons_link, location_pic, location_exists
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
arrayOf(
locationName, locationLanguage, locationDescription, locationCategory,
locationLabelText, locationLabelIcon, locationLat, locationLong,
locationImageUrl, locationWikipediaLink, locationWikidataLink,
locationCommonsLink, locationPic, locationExists
)
)
}
cursor.close()
oldDb.close()
}
}
} }
} }

View file

@ -63,9 +63,4 @@ abstract class CommonsDaggerSupportFragment : Fragment(), HasSupportFragmentInje
return getInstance(activity.applicationContext) return getInstance(activity.applicationContext)
} }
// Ensure getContext() returns a non-null Context
override fun getContext(): Context {
return super.getContext() ?: throw IllegalStateException("Context is null")
}
} }

View file

@ -3,7 +3,6 @@ package fr.free.nrw.commons.di
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsContentProvider import fr.free.nrw.commons.bookmarks.items.BookmarkItemsContentProvider
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsContentProvider
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider
import fr.free.nrw.commons.category.CategoryContentProvider import fr.free.nrw.commons.category.CategoryContentProvider
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesContentProvider import fr.free.nrw.commons.explore.recentsearches.RecentSearchesContentProvider
@ -26,9 +25,6 @@ abstract class ContentProviderBuilderModule {
@ContributesAndroidInjector @ContributesAndroidInjector
abstract fun bindBookmarkContentProvider(): BookmarkPicturesContentProvider abstract fun bindBookmarkContentProvider(): BookmarkPicturesContentProvider
@ContributesAndroidInjector
abstract fun bindBookmarkLocationContentProvider(): BookmarkLocationsContentProvider
@ContributesAndroidInjector @ContributesAndroidInjector
abstract fun bindBookmarkItemContentProvider(): BookmarkItemsContentProvider abstract fun bindBookmarkItemContentProvider(): BookmarkItemsContentProvider

View file

@ -39,7 +39,7 @@ class MediaConverter
metadata.licenseShortName(), metadata.licenseShortName(),
metadata.prefixedLicenseUrl, metadata.prefixedLicenseUrl,
getAuthor(metadata), getAuthor(metadata),
getAuthor(metadata), imageInfo.getUser(),
MediaDataExtractorUtil.extractCategoriesFromList(metadata.categories()), MediaDataExtractorUtil.extractCategoriesFromList(metadata.categories()),
metadata.latLng, metadata.latLng,
entity.labels().mapValues { it.value.value() }, entity.labels().mapValues { it.value.value() },

View file

@ -52,12 +52,7 @@ class SearchImagesViewHolder(
binding.categoryImageView.setOnClickListener { onImageClicked(item.second) } binding.categoryImageView.setOnClickListener { onImageClicked(item.second) }
binding.categoryImageTitle.text = media.mostRelevantCaption binding.categoryImageTitle.text = media.mostRelevantCaption
binding.categoryImageView.setImageURI(media.thumbUrl) binding.categoryImageView.setImageURI(media.thumbUrl)
if (media.author?.isNotEmpty() == true) {
binding.categoryImageAuthor.visibility = View.VISIBLE
binding.categoryImageAuthor.text = binding.categoryImageAuthor.text =
containerView.context.getString(R.string.image_uploaded_by, media.user) containerView.context.getString(R.string.image_uploaded_by, media.getAuthorOrUser())
} else {
binding.categoryImageAuthor.visibility = View.GONE
}
} }
} }

View file

@ -16,6 +16,7 @@ import android.view.KeyEvent
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.view.ViewTreeObserver.OnGlobalLayoutListener import android.view.ViewTreeObserver.OnGlobalLayoutListener
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.Button import android.widget.Button
@ -406,8 +407,13 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
* of the picture. * of the picture.
*/ */
view.post{ view.post{
val width = binding.mediaDetailScrollView.width
if (width > 0) {
frameLayoutHeight = binding.mediaDetailFrameLayout.measuredHeight frameLayoutHeight = binding.mediaDetailFrameLayout.measuredHeight
updateAspectRatio(binding.mediaDetailScrollView.width) updateAspectRatio(width)
} else {
view.postDelayed({ updateAspectRatio(binding.root.width) }, 1)
}
} }
return view return view

View file

@ -185,10 +185,12 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
* or a fragment * or a fragment
*/ */
private void initProvider() { private void initProvider() {
if (getParentFragment() != null) { if (getParentFragment() instanceof MediaDetailProvider) {
provider = (MediaDetailProvider) getParentFragment(); provider = (MediaDetailProvider) getParentFragment();
} else { } else if (getActivity() instanceof MediaDetailProvider) {
provider = (MediaDetailProvider) getActivity(); provider = (MediaDetailProvider) getActivity();
} else {
throw new ClassCastException("Parent must implement MediaDetailProvider");
} }
} }
@ -326,7 +328,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
.append("\n\n"); .append("\n\n");
builder.append("User that you want to report: ") builder.append("User that you want to report: ")
.append(media.getAuthor()) .append(media.getUser())
.append("\n\n"); .append("\n\n");
if (sessionManager.getUserName() != null) { if (sessionManager.getUserName() != null) {
@ -421,7 +423,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
// Initialize bookmark object // Initialize bookmark object
bookmark = new Bookmark( bookmark = new Bookmark(
m.getFilename(), m.getFilename(),
m.getAuthor(), m.getAuthorOrUser(),
BookmarkPicturesContentProvider.uriForName(m.getFilename()) BookmarkPicturesContentProvider.uriForName(m.getFilename())
); );
updateBookmarkState(menu.findItem(R.id.menu_bookmark_current_image)); updateBookmarkState(menu.findItem(R.id.menu_bookmark_current_image));

View file

@ -68,7 +68,21 @@ class BottomSheetAdapter(
item.imageResourceId == R.drawable.ic_round_star_border_24px item.imageResourceId == R.drawable.ic_round_star_border_24px
) { ) {
item.imageResourceId = icon item.imageResourceId = icon
this.notifyItemChanged(index) notifyItemChanged(index)
return
}
}
}
fun toggleBookmarkIcon() {
itemList.forEachIndexed { index, item ->
if(item.imageResourceId == R.drawable.ic_round_star_filled_24px) {
item.imageResourceId = R.drawable.ic_round_star_border_24px
notifyItemChanged(index)
return
} else if(item.imageResourceId == R.drawable.ic_round_star_border_24px){
item.imageResourceId = R.drawable.ic_round_star_filled_24px
notifyItemChanged(index)
return return
} }
} }

View file

@ -0,0 +1,28 @@
package fr.free.nrw.commons.nearby
import android.util.Log
import androidx.lifecycle.LifecycleCoroutineScope
import fr.free.nrw.commons.R
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao
import kotlinx.coroutines.launch
import timber.log.Timber
object NearbyUtil {
fun getBookmarkLocationExists(
bookmarksLocationsDao: BookmarkLocationsDao,
name: String,
scope: LifecycleCoroutineScope?,
bottomSheetAdapter: BottomSheetAdapter,
) {
scope?.launch {
val isBookmarked = bookmarksLocationsDao.findBookmarkLocation(name)
Timber.i("isBookmarked: $isBookmarked")
if (isBookmarked) {
bottomSheetAdapter.updateBookmarkIcon(R.drawable.ic_round_star_filled_24px)
} else {
bottomSheetAdapter.updateBookmarkIcon(R.drawable.ic_round_star_border_24px)
}
}
}
}

View file

@ -7,6 +7,7 @@ import android.view.View.INVISIBLE
import android.view.View.VISIBLE import android.view.View.VISIBLE
import android.widget.RelativeLayout import android.widget.RelativeLayout
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.transition.TransitionManager import androidx.transition.TransitionManager
@ -16,9 +17,11 @@ import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao
import fr.free.nrw.commons.databinding.ItemPlaceBinding import fr.free.nrw.commons.databinding.ItemPlaceBinding
import kotlinx.coroutines.launch
fun placeAdapterDelegate( fun placeAdapterDelegate(
bookmarkLocationDao: BookmarkLocationsDao, bookmarkLocationDao: BookmarkLocationsDao,
scope: LifecycleCoroutineScope?,
onItemClick: ((Place) -> Unit)? = null, onItemClick: ((Place) -> Unit)? = null,
onCameraClicked: (Place, ActivityResultLauncher<Array<String>>, ActivityResultLauncher<Intent>) -> Unit, onCameraClicked: (Place, ActivityResultLauncher<Array<String>>, ActivityResultLauncher<Intent>) -> Unit,
onCameraLongPressed: () -> Boolean, onCameraLongPressed: () -> Boolean,
@ -61,7 +64,10 @@ fun placeAdapterDelegate(
nearbyButtonLayout.galleryButton.setOnClickListener { onGalleryClicked(item, galleryPickLauncherForResult) } nearbyButtonLayout.galleryButton.setOnClickListener { onGalleryClicked(item, galleryPickLauncherForResult) }
nearbyButtonLayout.galleryButton.setOnLongClickListener { onGalleryLongPressed() } nearbyButtonLayout.galleryButton.setOnLongClickListener { onGalleryLongPressed() }
bookmarkButtonImage.setOnClickListener { bookmarkButtonImage.setOnClickListener {
val isBookmarked = bookmarkLocationDao.updateBookmarkLocation(item) var isBookmarked = false
scope?.launch {
isBookmarked = bookmarkLocationDao.updateBookmarkLocation(item)
}
bookmarkButtonImage.setImageResource( bookmarkButtonImage.setImageResource(
if (isBookmarked) R.drawable.ic_round_star_filled_24px else R.drawable.ic_round_star_border_24px, if (isBookmarked) R.drawable.ic_round_star_filled_24px else R.drawable.ic_round_star_border_24px,
) )
@ -93,14 +99,16 @@ fun placeAdapterDelegate(
GONE GONE
} }
scope?.launch {
bookmarkButtonImage.setImageResource( bookmarkButtonImage.setImageResource(
if (bookmarkLocationDao.findBookmarkLocation(item)) { if (bookmarkLocationDao.findBookmarkLocation(item.name)) {
R.drawable.ic_round_star_filled_24px R.drawable.ic_round_star_filled_24px
} else { } else {
R.drawable.ic_round_star_border_24px R.drawable.ic_round_star_border_24px
}, },
) )
} }
}
nearbyButtonLayout.directionsButton.setOnLongClickListener { onDirectionsLongPressed() } nearbyButtonLayout.directionsButton.setOnLongClickListener { onDirectionsLongPressed() }
} }
} }

View file

@ -134,7 +134,7 @@ public interface NearbyParentFragmentContract {
void setAdvancedQuery(String query); void setAdvancedQuery(String query);
void toggleBookmarkedStatus(Place place); void toggleBookmarkedStatus(Place place, LifecycleCoroutineScope scope);
void handleMapScrolled(LifecycleCoroutineScope scope, boolean isNetworkAvailable); void handleMapScrolled(LifecycleCoroutineScope scope, boolean isNetworkAvailable);
} }

View file

@ -39,11 +39,16 @@ import androidx.appcompat.app.AlertDialog
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.fragment.app.Fragment
import androidx.lifecycle.LifecycleCoroutineScope import androidx.lifecycle.LifecycleCoroutineScope
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
@ -51,6 +56,7 @@ import com.jakewharton.rxbinding2.view.RxView
import com.jakewharton.rxbinding3.appcompat.queryTextChanges import com.jakewharton.rxbinding3.appcompat.queryTextChanges
import fr.free.nrw.commons.CommonsApplication import fr.free.nrw.commons.CommonsApplication
import fr.free.nrw.commons.MapController.NearbyPlacesInfo import fr.free.nrw.commons.MapController.NearbyPlacesInfo
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.Utils import fr.free.nrw.commons.Utils
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao
@ -67,6 +73,10 @@ import fr.free.nrw.commons.location.LocationPermissionsHelper.LocationPermission
import fr.free.nrw.commons.location.LocationServiceManager import fr.free.nrw.commons.location.LocationServiceManager
import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType
import fr.free.nrw.commons.location.LocationUpdateListener import fr.free.nrw.commons.location.LocationUpdateListener
import fr.free.nrw.commons.media.MediaClient
import fr.free.nrw.commons.media.MediaDetailPagerFragment
import fr.free.nrw.commons.media.MediaDetailPagerFragment.MediaDetailProvider
import fr.free.nrw.commons.navtab.NavTab
import fr.free.nrw.commons.nearby.BottomSheetAdapter import fr.free.nrw.commons.nearby.BottomSheetAdapter
import fr.free.nrw.commons.nearby.BottomSheetAdapter.ItemClickListener import fr.free.nrw.commons.nearby.BottomSheetAdapter.ItemClickListener
import fr.free.nrw.commons.nearby.CheckBoxTriStates import fr.free.nrw.commons.nearby.CheckBoxTriStates
@ -75,6 +85,7 @@ import fr.free.nrw.commons.nearby.MarkerPlaceGroup
import fr.free.nrw.commons.nearby.NearbyController import fr.free.nrw.commons.nearby.NearbyController
import fr.free.nrw.commons.nearby.NearbyFilterSearchRecyclerViewAdapter import fr.free.nrw.commons.nearby.NearbyFilterSearchRecyclerViewAdapter
import fr.free.nrw.commons.nearby.NearbyFilterState import fr.free.nrw.commons.nearby.NearbyFilterState
import fr.free.nrw.commons.nearby.NearbyUtil
import fr.free.nrw.commons.nearby.Place import fr.free.nrw.commons.nearby.Place
import fr.free.nrw.commons.nearby.PlacesRepository import fr.free.nrw.commons.nearby.PlacesRepository
import fr.free.nrw.commons.nearby.WikidataFeedback import fr.free.nrw.commons.nearby.WikidataFeedback
@ -118,17 +129,26 @@ import timber.log.Timber
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.IOException import java.io.IOException
import java.net.URLDecoder
import java.nio.charset.StandardCharsets
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
import java.util.UUID
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Named import javax.inject.Named
import javax.sql.DataSource
import kotlin.concurrent.Volatile import kotlin.concurrent.Volatile
class NearbyParentFragment : CommonsDaggerSupportFragment(), NearbyParentFragmentContract.View, class NearbyParentFragment : CommonsDaggerSupportFragment(),
WikidataP18EditListener, LocationUpdateListener, LocationPermissionCallback, ItemClickListener { NearbyParentFragmentContract.View,
WikidataP18EditListener,
LocationUpdateListener,
LocationPermissionCallback,
ItemClickListener,
MediaDetailPagerFragment.MediaDetailProvider {
var binding: FragmentNearbyParentBinding? = null var binding: FragmentNearbyParentBinding? = null
val mapEventsOverlay: MapEventsOverlay = MapEventsOverlay(object : MapEventsReceiver { val mapEventsOverlay: MapEventsOverlay = MapEventsOverlay(object : MapEventsReceiver {
@ -163,6 +183,13 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(), NearbyParentFragmen
@Named("default_preferences") @Named("default_preferences")
lateinit var applicationKvStore: JsonKvStore lateinit var applicationKvStore: JsonKvStore
@Inject
lateinit var mediaClient: MediaClient
lateinit var mediaDetails: MediaDetailPagerFragment
lateinit var media: Media
@Inject @Inject
lateinit var bookmarkLocationDao: BookmarkLocationsDao lateinit var bookmarkLocationDao: BookmarkLocationsDao
@ -196,7 +223,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(), NearbyParentFragmen
private var view: View? = null private var view: View? = null
private var scope: LifecycleCoroutineScope? = null private var scope: LifecycleCoroutineScope? = null
private var presenter: NearbyParentFragmentPresenter? = null private var presenter: NearbyParentFragmentPresenter? = null
private var isDarkTheme = false private var _isDarkTheme = false
private var isFABsExpanded = false private var isFABsExpanded = false
private var selectedPlace: Place? = null private var selectedPlace: Place? = null
private var clickedMarker: Marker? = null private var clickedMarker: Marker? = null
@ -434,7 +461,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(), NearbyParentFragmen
} }
} }
} }
isDarkTheme = systemThemeUtils?.isDeviceInNightMode() == true _isDarkTheme = systemThemeUtils?.isDeviceInNightMode() == true
if (Utils.isMonumentsEnabled(Date())) { if (Utils.isMonumentsEnabled(Date())) {
binding?.rlContainerWlmMonthMessage?.visibility = View.VISIBLE binding?.rlContainerWlmMonthMessage?.visibility = View.VISIBLE
} else { } else {
@ -604,7 +631,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(), NearbyParentFragmen
* another refactor * another refactor
*/ */
private fun initThemePreferences() { private fun initThemePreferences() {
if (isDarkTheme) { if (_isDarkTheme) {
binding!!.bottomSheetNearby.rvNearbyList.setBackgroundColor( binding!!.bottomSheetNearby.rvNearbyList.setBackgroundColor(
requireContext().resources.getColor(fr.free.nrw.commons.R.color.contributionListDarkBackground) requireContext().resources.getColor(fr.free.nrw.commons.R.color.contributionListDarkBackground)
) )
@ -638,21 +665,23 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(), NearbyParentFragmen
private fun initRvNearbyList() { private fun initRvNearbyList() {
binding!!.bottomSheetNearby.rvNearbyList.layoutManager = LinearLayoutManager(context) binding!!.bottomSheetNearby.rvNearbyList.layoutManager = LinearLayoutManager(context)
adapter = PlaceAdapter( adapter = PlaceAdapter(
bookmarkLocationDao!!, bookmarkLocationsDao = bookmarkLocationDao,
{ place: Place -> scope = scope,
onPlaceClicked = { place: Place ->
moveCameraToPosition( moveCameraToPosition(
GeoPoint(place.location.latitude, place.location.longitude) GeoPoint(
place.location.latitude,
place.location.longitude
)
) )
Unit
}, },
{ place: Place?, isBookmarked: Boolean? -> onBookmarkClicked = { place: Place?, _: Boolean? ->
presenter!!.toggleBookmarkedStatus(place) presenter!!.toggleBookmarkedStatus(place, scope)
Unit
}, },
commonPlaceClickActions!!, commonPlaceClickActions = commonPlaceClickActions,
inAppCameraLocationPermissionLauncher, inAppCameraLocationPermissionLauncher = inAppCameraLocationPermissionLauncher,
galleryPickLauncherForResult, galleryPickLauncherForResult = galleryPickLauncherForResult,
cameraPickLauncherForResult cameraPickLauncherForResult = cameraPickLauncherForResult
) )
binding!!.bottomSheetNearby.rvNearbyList.adapter = adapter binding!!.bottomSheetNearby.rvNearbyList.adapter = adapter
} }
@ -716,6 +745,10 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(), NearbyParentFragmen
presenter?.attachView(this) presenter?.attachView(this)
registerNetworkReceiver() registerNetworkReceiver()
binding?.coordinatorLayout?.visibility = View.VISIBLE
binding?.map?.setMultiTouchControls(true)
binding?.map?.isClickable = true
if (isResumed && (activity as? MainActivity)?.activeFragment == ActiveFragment.NEARBY) { if (isResumed && (activity as? MainActivity)?.activeFragment == ActiveFragment.NEARBY) {
if (activity?.let { locationPermissionsHelper?.checkLocationPermission(it) } == true) { if (activity?.let { locationPermissionsHelper?.checkLocationPermission(it) } == true) {
locationPermissionGranted() locationPermissionGranted()
@ -882,7 +915,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(), NearbyParentFragmen
} }
override fun isDarkTheme(): Boolean { override fun isDarkTheme(): Boolean {
return isDarkTheme return _isDarkTheme
} }
}) })
binding!!.nearbyFilterList.root binding!!.nearbyFilterList.root
@ -1853,7 +1886,31 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(), NearbyParentFragmen
} }
fun backButtonClicked(): Boolean { fun backButtonClicked(): Boolean {
return presenter!!.backButtonClicked() if (::mediaDetails.isInitialized && mediaDetails.isVisible) {
removeFragment(mediaDetails)
binding?.coordinatorLayout?.visibility = View.VISIBLE
binding?.map?.setMultiTouchControls(true)
binding?.map?.isClickable = true
val transaction = childFragmentManager.beginTransaction()
val fragmentContainer = childFragmentManager.findFragmentById(R.id.coordinator_layout)
if (fragmentContainer != null) {
transaction.show(fragmentContainer)
}
transaction.commit()
childFragmentManager.executePendingTransactions()
(activity as? MainActivity)?.showTabs()
(activity as? MainActivity)?.supportActionBar?.setDisplayHomeAsUpEnabled(false)
return true
} else {
(activity as? MainActivity)?.setSelectedItemId(NavTab.NEARBY.code())
}
return presenter?.backButtonClicked() ?: false
} }
override fun onLocationPermissionDenied(toastMessage: String) { override fun onLocationPermissionDenied(toastMessage: String) {
@ -2249,34 +2306,34 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(), NearbyParentFragmen
// TODO: Decide button text for fitting in the screen // TODO: Decide button text for fitting in the screen
(dataList as ArrayList<BottomSheetItem>).add( (dataList as ArrayList<BottomSheetItem>).add(
BottomSheetItem( BottomSheetItem(
fr.free.nrw.commons.R.drawable.ic_round_star_border_24px, R.drawable.ic_round_star_border_24px,
"" ""
) )
) )
(dataList as ArrayList<BottomSheetItem>).add( (dataList as ArrayList<BottomSheetItem>).add(
BottomSheetItem( BottomSheetItem(
fr.free.nrw.commons.R.drawable.ic_directions_black_24dp, R.drawable.ic_directions_black_24dp,
resources.getString(fr.free.nrw.commons.R.string.nearby_directions) resources.getString(fr.free.nrw.commons.R.string.nearby_directions)
) )
) )
if (place.hasWikidataLink()) { if (place.hasWikidataLink()) {
(dataList as ArrayList<BottomSheetItem>).add( (dataList as ArrayList<BottomSheetItem>).add(
BottomSheetItem( BottomSheetItem(
fr.free.nrw.commons.R.drawable.ic_wikidata_logo_24dp, R.drawable.ic_wikidata_logo_24dp,
resources.getString(fr.free.nrw.commons.R.string.nearby_wikidata) resources.getString(fr.free.nrw.commons.R.string.nearby_wikidata)
) )
) )
} }
(dataList as ArrayList<BottomSheetItem>).add( (dataList as ArrayList<BottomSheetItem>).add(
BottomSheetItem( BottomSheetItem(
fr.free.nrw.commons.R.drawable.ic_feedback_black_24dp, R.drawable.ic_feedback_black_24dp,
resources.getString(fr.free.nrw.commons.R.string.nearby_wikitalk) resources.getString(fr.free.nrw.commons.R.string.nearby_wikitalk)
) )
) )
if (place.hasWikipediaLink()) { if (place.hasWikipediaLink()) {
(dataList as ArrayList<BottomSheetItem>).add( (dataList as ArrayList<BottomSheetItem>).add(
BottomSheetItem( BottomSheetItem(
fr.free.nrw.commons.R.drawable.ic_wikipedia_logo_24dp, R.drawable.ic_wikipedia_logo_24dp,
resources.getString(fr.free.nrw.commons.R.string.nearby_wikipedia) resources.getString(fr.free.nrw.commons.R.string.nearby_wikipedia)
) )
) )
@ -2284,7 +2341,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(), NearbyParentFragmen
if (selectedPlace!!.hasCommonsLink()) { if (selectedPlace!!.hasCommonsLink()) {
(dataList as ArrayList<BottomSheetItem>).add( (dataList as ArrayList<BottomSheetItem>).add(
BottomSheetItem( BottomSheetItem(
fr.free.nrw.commons.R.drawable.ic_commons_icon_vector, R.drawable.ic_commons_icon_vector,
resources.getString(fr.free.nrw.commons.R.string.nearby_commons) resources.getString(fr.free.nrw.commons.R.string.nearby_commons)
) )
) )
@ -2299,7 +2356,61 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(), NearbyParentFragmen
bottomSheetAdapter!!.setClickListener(this) bottomSheetAdapter!!.setClickListener(this)
binding!!.bottomSheetDetails.bottomSheetRecyclerView.adapter = bottomSheetAdapter binding!!.bottomSheetDetails.bottomSheetRecyclerView.adapter = bottomSheetAdapter
updateBookmarkButtonImage(selectedPlace!!) updateBookmarkButtonImage(selectedPlace!!)
selectedPlace?.pic?.substringAfterLast("/")?.takeIf { it.isNotEmpty() }?.let { imageName ->
Glide.with(binding!!.bottomSheetDetails.icon.context)
.clear(binding!!.bottomSheetDetails.icon)
val loadingDrawable = ContextCompat.getDrawable(
binding!!.bottomSheetDetails.icon.context,
R.drawable.loading_icon
)
val animation = AnimationUtils.loadAnimation(
binding!!.bottomSheetDetails.icon.context,
R.anim.rotate
)
Glide.with(binding!!.bottomSheetDetails.icon.context)
.load("https://commons.wikimedia.org/wiki/Special:Redirect/file/$imageName?width=25")
.placeholder(loadingDrawable)
.error(selectedPlace!!.label.icon)
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>,
isFirstResource: Boolean
): Boolean {
binding!!.bottomSheetDetails.icon.clearAnimation()
return false
}
override fun onResourceReady(
resource: Drawable,
model: Any,
target: Target<Drawable>?,
dataSource: com.bumptech.glide.load.DataSource,
isFirstResource: Boolean
): Boolean {
binding!!.bottomSheetDetails.icon.clearAnimation()
return false
}
})
.into(binding!!.bottomSheetDetails.icon)
if (binding!!.bottomSheetDetails.icon.drawable != null && binding!!.bottomSheetDetails.icon.drawable.constantState == loadingDrawable?.constantState) {
binding!!.bottomSheetDetails.icon.startAnimation(animation)
} else {
binding!!.bottomSheetDetails.icon.clearAnimation()
}
binding!!.bottomSheetDetails.icon.setOnClickListener {
handleMediaClick(imageName)
}
} ?: run {
binding!!.bottomSheetDetails.icon.setImageResource(selectedPlace!!.label.icon) binding!!.bottomSheetDetails.icon.setImageResource(selectedPlace!!.label.icon)
}
binding!!.bottomSheetDetails.title.text = selectedPlace!!.name binding!!.bottomSheetDetails.title.text = selectedPlace!!.name
binding!!.bottomSheetDetails.category.text = selectedPlace!!.distance binding!!.bottomSheetDetails.category.text = selectedPlace!!.distance
// Remove label since it is double information // Remove label since it is double information
@ -2354,6 +2465,101 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(), NearbyParentFragmen
} }
} }
private fun handleMediaClick(imageName: String) {
val decodedImageName = URLDecoder.decode(imageName, StandardCharsets.UTF_8.toString())
mediaClient.getMedia("File:$decodedImageName")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ mediaResponse ->
if (mediaResponse != null) {
// Create a Media object from the response
media = Media(
pageId = mediaResponse.pageId ?: UUID.randomUUID().toString(),
thumbUrl = mediaResponse.thumbUrl,
imageUrl = mediaResponse.imageUrl,
filename = mediaResponse.filename,
fallbackDescription = mediaResponse.fallbackDescription,
dateUploaded = mediaResponse.dateUploaded,
license = mediaResponse.license,
licenseUrl = mediaResponse.licenseUrl,
author = mediaResponse.author,
user = mediaResponse.user,
categories = mediaResponse.categories,
coordinates = mediaResponse.coordinates,
captions = mediaResponse.captions ?: emptyMap(),
descriptions = mediaResponse.descriptions ?: emptyMap(),
depictionIds = mediaResponse.depictionIds ?: emptyList(),
categoriesHiddenStatus = mediaResponse.categoriesHiddenStatus ?: emptyMap()
)
// Remove existing fragment before showing new details
if (::mediaDetails.isInitialized && mediaDetails.isAdded) {
removeFragment(mediaDetails)
}
showMediaDetails()
} else {
Timber.e("Fetched media is null for image: $decodedImageName")
}
}, { throwable ->
Timber.e(throwable, "Error fetching media for image: $decodedImageName")
})
}
private fun showMediaDetails() {
binding?.map?.setMultiTouchControls(false)
binding?.map?.isClickable = false
mediaDetails = MediaDetailPagerFragment.newInstance(false, true)
val transaction = childFragmentManager.beginTransaction()
val fragmentContainer = childFragmentManager.findFragmentById(R.id.coordinator_layout)
if (fragmentContainer != null) {
transaction.hide(fragmentContainer)
}
// Replace instead of add to ensure new fragment is used
transaction.replace(R.id.coordinator_layout, mediaDetails, "MediaDetailFragmentTag")
transaction.addToBackStack("Nearby_Parent_Fragment_Tag").commit()
childFragmentManager.executePendingTransactions()
(activity as? MainActivity)?.supportActionBar?.setDisplayHomeAsUpEnabled(true)
if (mediaDetails.isAdded) {
mediaDetails.showImage(0)
} else {
Timber.e("Error: MediaDetailPagerFragment is NOT added")
}
}
override fun getMediaAtPosition(i: Int): Media? {
return media
}
override fun getTotalMediaCount(): Int {
return 2
}
override fun getContributionStateAt(position: Int): Int? {
return null
}
override fun refreshNominatedMedia(index: Int) {
if (this::mediaDetails.isInitialized && !binding?.map?.isClickable!! == true) {
removeFragment(mediaDetails)
showMediaDetails()
}
}
private fun removeFragment(fragment: Fragment) {
childFragmentManager
.beginTransaction()
.remove(fragment)
.commit()
childFragmentManager.executePendingTransactions()
}
private fun storeSharedPrefs(selectedPlace: Place) { private fun storeSharedPrefs(selectedPlace: Place) {
applicationKvStore!!.putJson<Place>(WikidataConstants.PLACE_OBJECT, selectedPlace) applicationKvStore!!.putJson<Place>(WikidataConstants.PLACE_OBJECT, selectedPlace)
val place = val place =
@ -2363,12 +2569,16 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(), NearbyParentFragmen
} }
private fun updateBookmarkButtonImage(place: Place) { private fun updateBookmarkButtonImage(place: Place) {
val bookmarkIcon = if (bookmarkLocationDao!!.findBookmarkLocation(place)) { NearbyUtil.getBookmarkLocationExists(
fr.free.nrw.commons.R.drawable.ic_round_star_filled_24px bookmarkLocationDao,
} else { place.getName(),
fr.free.nrw.commons.R.drawable.ic_round_star_border_24px scope,
bottomSheetAdapter!!
)
} }
bottomSheetAdapter!!.updateBookmarkIcon(bookmarkIcon)
private fun toggleBookmarkButtonImage() {
bottomSheetAdapter?.toggleBookmarkIcon()
} }
override fun onAttach(context: Context) { override fun onAttach(context: Context) {
@ -2546,26 +2756,31 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(), NearbyParentFragmen
override fun onBottomSheetItemClick(view: View?, position: Int) { override fun onBottomSheetItemClick(view: View?, position: Int) {
val item = dataList?.get(position) ?: return // Null check for dataList val item = dataList?.get(position) ?: return // Null check for dataList
when (item.imageResourceId) { when (item.imageResourceId) {
fr.free.nrw.commons.R.drawable.ic_round_star_border_24px, R.drawable.ic_round_star_border_24px -> {
fr.free.nrw.commons.R.drawable.ic_round_star_filled_24px -> { presenter?.toggleBookmarkedStatus(selectedPlace, scope)
presenter?.toggleBookmarkedStatus(selectedPlace) toggleBookmarkButtonImage()
}
R.drawable.ic_round_star_filled_24px -> {
presenter?.toggleBookmarkedStatus(selectedPlace, scope)
toggleBookmarkButtonImage()
selectedPlace?.let { updateBookmarkButtonImage(it) } selectedPlace?.let { updateBookmarkButtonImage(it) }
} }
fr.free.nrw.commons.R.drawable.ic_directions_black_24dp -> { R.drawable.ic_directions_black_24dp -> {
selectedPlace?.let { selectedPlace?.let {
Utils.handleGeoCoordinates(this.context, it.getLocation()) Utils.handleGeoCoordinates(this.context, it.getLocation())
binding?.map?.zoomLevelDouble ?: 0.0 binding?.map?.zoomLevelDouble ?: 0.0
} }
} }
fr.free.nrw.commons.R.drawable.ic_wikidata_logo_24dp -> { R.drawable.ic_wikidata_logo_24dp -> {
selectedPlace?.siteLinks?.wikidataLink?.let { selectedPlace?.siteLinks?.wikidataLink?.let {
Utils.handleWebUrl(this.context, it) Utils.handleWebUrl(this.context, it)
} }
} }
fr.free.nrw.commons.R.drawable.ic_feedback_black_24dp -> { R.drawable.ic_feedback_black_24dp -> {
selectedPlace?.let { selectedPlace?.let {
val intent = Intent(this.context, WikidataFeedback::class.java).apply { val intent = Intent(this.context, WikidataFeedback::class.java).apply {
putExtra("lat", it.location.latitude) putExtra("lat", it.location.latitude)
@ -2577,13 +2792,13 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(), NearbyParentFragmen
} }
} }
fr.free.nrw.commons.R.drawable.ic_wikipedia_logo_24dp -> { R.drawable.ic_wikipedia_logo_24dp -> {
selectedPlace?.siteLinks?.wikipediaLink?.let { selectedPlace?.siteLinks?.wikipediaLink?.let {
Utils.handleWebUrl(this.context, it) Utils.handleWebUrl(this.context, it)
} }
} }
fr.free.nrw.commons.R.drawable.ic_commons_icon_vector -> { R.drawable.ic_commons_icon_vector -> {
selectedPlace?.siteLinks?.commonsLink?.let { selectedPlace?.siteLinks?.commonsLink?.let {
Utils.handleWebUrl(this.context, it) Utils.handleWebUrl(this.context, it)
} }
@ -2597,13 +2812,13 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(), NearbyParentFragmen
override fun onBottomSheetItemLongClick(view: View?, position: Int) { override fun onBottomSheetItemLongClick(view: View?, position: Int) {
val item = dataList!![position] val item = dataList!![position]
val message = when (item.imageResourceId) { val message = when (item.imageResourceId) {
fr.free.nrw.commons.R.drawable.ic_round_star_border_24px -> getString(fr.free.nrw.commons.R.string.menu_bookmark) R.drawable.ic_round_star_border_24px -> getString(fr.free.nrw.commons.R.string.menu_bookmark)
fr.free.nrw.commons.R.drawable.ic_round_star_filled_24px -> getString(fr.free.nrw.commons.R.string.menu_bookmark) R.drawable.ic_round_star_filled_24px -> getString(fr.free.nrw.commons.R.string.menu_bookmark)
fr.free.nrw.commons.R.drawable.ic_directions_black_24dp -> getString(fr.free.nrw.commons.R.string.nearby_directions) R.drawable.ic_directions_black_24dp -> getString(fr.free.nrw.commons.R.string.nearby_directions)
fr.free.nrw.commons.R.drawable.ic_wikidata_logo_24dp -> getString(fr.free.nrw.commons.R.string.nearby_wikidata) R.drawable.ic_wikidata_logo_24dp -> getString(fr.free.nrw.commons.R.string.nearby_wikidata)
fr.free.nrw.commons.R.drawable.ic_feedback_black_24dp -> getString(fr.free.nrw.commons.R.string.nearby_wikitalk) R.drawable.ic_feedback_black_24dp -> getString(fr.free.nrw.commons.R.string.nearby_wikitalk)
fr.free.nrw.commons.R.drawable.ic_wikipedia_logo_24dp -> getString(fr.free.nrw.commons.R.string.nearby_wikipedia) R.drawable.ic_wikipedia_logo_24dp -> getString(fr.free.nrw.commons.R.string.nearby_wikipedia)
fr.free.nrw.commons.R.drawable.ic_commons_icon_vector -> getString(fr.free.nrw.commons.R.string.nearby_commons) R.drawable.ic_commons_icon_vector -> getString(fr.free.nrw.commons.R.string.nearby_commons)
else -> "Long click" else -> "Long click"
} }
Toast.makeText(this.context, message, Toast.LENGTH_SHORT).show() Toast.makeText(this.context, message, Toast.LENGTH_SHORT).show()

View file

@ -2,6 +2,7 @@ package fr.free.nrw.commons.nearby.fragments
import android.content.Intent import android.content.Intent
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.lifecycle.LifecycleCoroutineScope
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao
import fr.free.nrw.commons.nearby.Place import fr.free.nrw.commons.nearby.Place
import fr.free.nrw.commons.nearby.placeAdapterDelegate import fr.free.nrw.commons.nearby.placeAdapterDelegate
@ -9,6 +10,7 @@ import fr.free.nrw.commons.upload.categories.BaseDelegateAdapter
class PlaceAdapter( class PlaceAdapter(
bookmarkLocationsDao: BookmarkLocationsDao, bookmarkLocationsDao: BookmarkLocationsDao,
scope: LifecycleCoroutineScope? = null,
onPlaceClicked: ((Place) -> Unit)? = null, onPlaceClicked: ((Place) -> Unit)? = null,
onBookmarkClicked: (Place, Boolean) -> Unit, onBookmarkClicked: (Place, Boolean) -> Unit,
commonPlaceClickActions: CommonPlaceClickActions, commonPlaceClickActions: CommonPlaceClickActions,
@ -18,6 +20,7 @@ class PlaceAdapter(
) : BaseDelegateAdapter<Place>( ) : BaseDelegateAdapter<Place>(
placeAdapterDelegate( placeAdapterDelegate(
bookmarkLocationsDao, bookmarkLocationsDao,
scope,
onPlaceClicked, onPlaceClicked,
commonPlaceClickActions.onCameraClicked(), commonPlaceClickActions.onCameraClicked(),
commonPlaceClickActions.onCameraLongPressed(), commonPlaceClickActions.onCameraLongPressed(),

View file

@ -27,6 +27,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.ensureActive import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.job import kotlinx.coroutines.job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.internal.wait import okhttp3.internal.wait
import timber.log.Timber import timber.log.Timber
@ -136,9 +137,14 @@ class NearbyParentFragmentPresenter
* @param place The place whose bookmarked status is to be toggled. If the place is `null`, * @param place The place whose bookmarked status is to be toggled. If the place is `null`,
* the operation is skipped. * the operation is skipped.
*/ */
override fun toggleBookmarkedStatus(place: Place?) { override fun toggleBookmarkedStatus(
place: Place?,
scope: LifecycleCoroutineScope?
) {
if (place == null) return if (place == null) return
val nowBookmarked = bookmarkLocationDao.updateBookmarkLocation(place) var nowBookmarked: Boolean
scope?.launch {
nowBookmarked = bookmarkLocationDao.updateBookmarkLocation(place)
bookmarkChangedPlaces.add(place) bookmarkChangedPlaces.add(place)
val placeIndex = val placeIndex =
NearbyController.markerLabelList.indexOfFirst { it.place.location == place.location } NearbyController.markerLabelList.indexOfFirst { it.place.location == place.location }
@ -148,13 +154,14 @@ class NearbyParentFragmentPresenter
) )
nearbyParentFragmentView.setFilterState() nearbyParentFragmentView.setFilterState()
} }
}
override fun attachView(view: NearbyParentFragmentContract.View) { override fun attachView(view: NearbyParentFragmentContract.View) {
this.nearbyParentFragmentView = view nearbyParentFragmentView = view
} }
override fun detachView() { override fun detachView() {
this.nearbyParentFragmentView = DUMMY nearbyParentFragmentView = DUMMY
} }
override fun removeNearbyPreferences(applicationKvStore: JsonKvStore) { override fun removeNearbyPreferences(applicationKvStore: JsonKvStore) {
@ -337,7 +344,7 @@ class NearbyParentFragmentPresenter
for (i in 0..updatedGroups.lastIndex) { for (i in 0..updatedGroups.lastIndex) {
val repoPlace = placesRepository.fetchPlace(updatedGroups[i].place.entityID) val repoPlace = placesRepository.fetchPlace(updatedGroups[i].place.entityID)
if (repoPlace != null && repoPlace.name != null && repoPlace.name != ""){ if (repoPlace != null && repoPlace.name != null && repoPlace.name != ""){
updatedGroups[i].isBookmarked = bookmarkLocationDao.findBookmarkLocation(repoPlace) updatedGroups[i].isBookmarked = bookmarkLocationDao.findBookmarkLocation(repoPlace.name)
updatedGroups[i].place.apply { updatedGroups[i].place.apply {
name = repoPlace.name name = repoPlace.name
isMonument = repoPlace.isMonument isMonument = repoPlace.isMonument
@ -375,7 +382,7 @@ class NearbyParentFragmentPresenter
collectResults.send( collectResults.send(
fetchedPlaces.mapIndexed { index, place -> fetchedPlaces.mapIndexed { index, place ->
Pair(indices[index], MarkerPlaceGroup( Pair(indices[index], MarkerPlaceGroup(
bookmarkLocationDao.findBookmarkLocation(place), bookmarkLocationDao.findBookmarkLocation(place.name),
place place
)) ))
} }
@ -393,7 +400,10 @@ class NearbyParentFragmentPresenter
onePlaceBatch.add(Pair(i, MarkerPlaceGroup( onePlaceBatch.add(Pair(i, MarkerPlaceGroup(
bookmarkLocationDao.findBookmarkLocation( bookmarkLocationDao.findBookmarkLocation(
fetchedPlace[0]),fetchedPlace[0]))) fetchedPlace[0].name
),
fetchedPlace[0]
)))
} catch (e: Exception) { } catch (e: Exception) {
Timber.tag("NearbyPinDetails").e(e) Timber.tag("NearbyPinDetails").e(e)
onePlaceBatch.add(Pair(i, updatedGroups[i])) onePlaceBatch.add(Pair(i, updatedGroups[i]))
@ -457,7 +467,7 @@ class NearbyParentFragmentPresenter
if (bookmarkChangedPlacesBacklog.containsKey(group.place.location)) { if (bookmarkChangedPlacesBacklog.containsKey(group.place.location)) {
updatedGroups[index] = MarkerPlaceGroup( updatedGroups[index] = MarkerPlaceGroup(
bookmarkLocationDao bookmarkLocationDao
.findBookmarkLocation(updatedGroups[index].place), .findBookmarkLocation(updatedGroups[index].place.name),
updatedGroups[index].place updatedGroups[index].place
) )
} }
@ -565,7 +575,7 @@ class NearbyParentFragmentPresenter
).sortedBy { it.getDistanceInDouble(mapFocus) }.take(NearbyController.MAX_RESULTS) ).sortedBy { it.getDistanceInDouble(mapFocus) }.take(NearbyController.MAX_RESULTS)
.map { .map {
MarkerPlaceGroup( MarkerPlaceGroup(
bookmarkLocationDao.findBookmarkLocation(it), it bookmarkLocationDao.findBookmarkLocation(it.name), it
) )
} }
ensureActive() ensureActive()

View file

@ -16,6 +16,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.AdapterView import android.widget.AdapterView
import android.widget.AdapterView.OnItemClickListener import android.widget.AdapterView.OnItemClickListener
import android.widget.Button
import android.widget.EditText import android.widget.EditText
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.ImageView import android.widget.ImageView
@ -330,6 +331,9 @@ class UploadMediaDetailAdapter : RecyclerView.Adapter<UploadMediaDetailAdapter.V
listView.adapter = languagesAdapter listView.adapter = languagesAdapter
dialog.findViewById<Button>(R.id.cancel_button)
.setOnClickListener { v: View? -> dialog.dismiss() }
editText.addTextChangedListener(object : TextWatcher { editText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) = override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) =
hideRecentLanguagesSection() hideRecentLanguagesSection()

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<rotate
xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:toDegrees="360"
android:pivotX="50%"
android:pivotY="50%"
android:duration="500"
android:repeatCount="infinite"
android:interpolator="@android:anim/linear_interpolator"/>

View file

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<vector
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,4 A8,8 0 1,1 4,12 A8,8 0 1,5 19.42 ,15"
android:strokeWidth="2"
android:strokeColor="#2196F3"
android:fillColor="#00000000"
android:trimPathStart="0.2"
android:trimPathEnd="1.0"/>
</vector>
</item>
<item>
<vector
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,4 A8,8 0 1,1 4,12 A8,8 0 1,1 12,4"
android:strokeWidth="2"
android:strokeColor="#FFFFFF"
android:fillColor="#00000000"
android:trimPathStart="0.0"
android:trimPathEnd="0.2"/>
</vector>
</item>
<item android:right="12dp">
<rotate
android:fromDegrees="0"
android:toDegrees="360"
android:pivotX="50%"
android:pivotY="50%">
<shape android:shape="ring">
<size
android:width="12dp"
android:height="2dp"/>
<solid android:color="#2196F3"/>
</shape>
</rotate>
</item>
</layer-list>

View file

@ -3,6 +3,7 @@
* 1917 Ekim Devrimi * 1917 Ekim Devrimi
* Envlh * Envlh
* Gambollar * Gambollar
* GolyatGeri
* Gorizon * Gorizon
* Gırd * Gırd
* Marmase * Marmase
@ -199,7 +200,7 @@
<string name="navigation_item_review">Çım berze cı</string> <string name="navigation_item_review">Çım berze cı</string>
<string name="no_description_found">İzahat nêvineya</string> <string name="no_description_found">İzahat nêvineya</string>
<string name="nearby_info_menu_commons_article">Pela dosyay commonsi</string> <string name="nearby_info_menu_commons_article">Pela dosyay commonsi</string>
<string name="nearby_info_menu_wikidata_article">Pbcey Wikidata</string> <string name="nearby_info_menu_wikidata_article">Pbcey Wikidayıt</string>
<string name="nearby_info_menu_wikipedia_article">Meqaley Wikipedia</string> <string name="nearby_info_menu_wikipedia_article">Meqaley Wikipedia</string>
<string name="upload_problem_exist">Muhtemel problemê nê resımi</string> <string name="upload_problem_exist">Muhtemel problemê nê resımi</string>
<string name="upload_problem_image_dark">Resım zehf tariyo.</string> <string name="upload_problem_image_dark">Resım zehf tariyo.</string>
@ -216,7 +217,7 @@
<string name="skip_login">Ravêre</string> <string name="skip_login">Ravêre</string>
<string name="navigation_item_login">Cı kewe</string> <string name="navigation_item_login">Cı kewe</string>
<string name="nearby_directions">Telimati</string> <string name="nearby_directions">Telimati</string>
<string name="nearby_wikidata">Wikidata</string> <string name="nearby_wikidata">Wikidayıt</string>
<string name="nearby_wikipedia">Wikipediya</string> <string name="nearby_wikipedia">Wikipediya</string>
<string name="nearby_commons">Commons</string> <string name="nearby_commons">Commons</string>
<string name="about_rate_us">Rey bı</string> <string name="about_rate_us">Rey bı</string>
@ -246,7 +247,7 @@
<string name="explore_tab_title_featured">Weçinaye</string> <string name="explore_tab_title_featured">Weçinaye</string>
<string name="explore_tab_title_mobile">Raya mobiliya biyo bar</string> <string name="explore_tab_title_mobile">Raya mobiliya biyo bar</string>
<string name="explore_tab_title_map">Xerita</string> <string name="explore_tab_title_map">Xerita</string>
<string name="successful_wikidata_edit">Resım , Wikidata dê biyo obcey %1$s miyan!</string> <string name="successful_wikidata_edit">Resım , Wikidayıt dê biyo obcey %1$s miyan!</string>
<string name="menu_set_wallpaper">Wallpaper eyar kerê</string> <string name="menu_set_wallpaper">Wallpaper eyar kerê</string>
<string name="wallpaper_set_successfully">Wallpaper eyar biyo!</string> <string name="wallpaper_set_successfully">Wallpaper eyar biyo!</string>
<string name="quiz">Quiz</string> <string name="quiz">Quiz</string>

View file

@ -136,8 +136,8 @@
<string name="menu_about">परिचय</string> <string name="menu_about">परिचय</string>
<string name="about_license">विकिमीडिया कॉमन्स एप्प एक मुक्त स्रोत एप्प है जो कि विकिमीडिया समुदाय के अनुदानप्राप्तकर्ताओं व स्वयंसेवकों द्वारा निर्मित एवं प्रबंधित है। विकिमीडिया फॉऊण्डेशन इस एप्प के निर्माण, विकास व प्रबंधन में किसी प्रकार से भी संलग्न नहीं है।</string> <string name="about_license">विकिमीडिया कॉमन्स एप्प एक मुक्त स्रोत एप्प है जो कि विकिमीडिया समुदाय के अनुदानप्राप्तकर्ताओं व स्वयंसेवकों द्वारा निर्मित एवं प्रबंधित है। विकिमीडिया फॉऊण्डेशन इस एप्प के निर्माण, विकास व प्रबंधन में किसी प्रकार से भी संलग्न नहीं है।</string>
<string name="about_improve">त्रुटि की सूचना और सुझावों के लिए &lt;a href=\"%1$s\"&gt; GitHub समस्या &lt;/a&gt; बनाएं</string> <string name="about_improve">त्रुटि की सूचना और सुझावों के लिए &lt;a href=\"%1$s\"&gt; GitHub समस्या &lt;/a&gt; बनाएं</string>
<string name="about_privacy_policy" fuzzy="true">&lt;u&gt;गोपनीयता नीति&lt;/u&gt;</string> <string name="about_privacy_policy">गोपनीयता नीति</string>
<string name="about_credits" fuzzy="true">&lt;u&gt;श्रेय&lt;/u&gt;</string> <string name="about_credits">श्रेय</string>
<string name="title_activity_about">परिचय</string> <string name="title_activity_about">परिचय</string>
<string name="menu_feedback">प्रतिक्रिया दें (ईमेल द्वारा)</string> <string name="menu_feedback">प्रतिक्रिया दें (ईमेल द्वारा)</string>
<string name="no_email_client">कोई ईमेल साधन स्थापित नहीं</string> <string name="no_email_client">कोई ईमेल साधन स्थापित नहीं</string>
@ -150,7 +150,7 @@
<string name="menu_download">डाउनलोड</string> <string name="menu_download">डाउनलोड</string>
<string name="preference_license">डिफॉल्ट लाइसेन्स</string> <string name="preference_license">डिफॉल्ट लाइसेन्स</string>
<string name="use_previous" fuzzy="true">पिछले शीर्षक/विवरण का उपयोग करें</string> <string name="use_previous" fuzzy="true">पिछले शीर्षक/विवरण का उपयोग करें</string>
<string name="preference_theme" fuzzy="true">रात्रि मोड</string> <string name="preference_theme">थीम</string>
<string name="license_name_cc_by_sa_four">एट्रीब्यूशन-शेयरअलाइक 4.0</string> <string name="license_name_cc_by_sa_four">एट्रीब्यूशन-शेयरअलाइक 4.0</string>
<string name="license_name_cc_by_four">एट्रिब्यूशन 4.0</string> <string name="license_name_cc_by_four">एट्रिब्यूशन 4.0</string>
<string name="license_name_cc_by_sa">एट्रीब्यूशन-शेयरअलाइक 3.0</string> <string name="license_name_cc_by_sa">एट्रीब्यूशन-शेयरअलाइक 3.0</string>
@ -263,12 +263,12 @@
<string name="nearby_wikipedia" fuzzy="true">विकीपीडिया</string> <string name="nearby_wikipedia" fuzzy="true">विकीपीडिया</string>
<string name="nearby_commons">कॉमन्स</string> <string name="nearby_commons">कॉमन्स</string>
<string name="about_rate_us" fuzzy="true">&lt;u&gt;हमें रेट करें&lt;/u&gt;</string> <string name="about_rate_us" fuzzy="true">&lt;u&gt;हमें रेट करें&lt;/u&gt;</string>
<string name="about_faq" fuzzy="true">&lt;u&gt;अक्सर पूछे जाने वाले प्रश्न&lt;/u&gt;</string> <string name="about_faq">अक्सर पूछे जाने वाले प्रश्न</string>
<string name="welcome_skip_button">प्रशिक्षण छोड़ें</string> <string name="welcome_skip_button">प्रशिक्षण छोड़ें</string>
<string name="no_internet">इंटरनेट उपलब्ध नहीं</string> <string name="no_internet">इंटरनेट उपलब्ध नहीं</string>
<string name="error_notifications">सूचनाएं लाने में त्रुटि</string> <string name="error_notifications">सूचनाएं लाने में त्रुटि</string>
<string name="no_notifications">कोई सूचनाएँ नहीं मिलीं</string> <string name="no_notifications">कोई सूचनाएँ नहीं मिलीं</string>
<string name="about_translate" fuzzy="true">&lt;u&gt;अनुवाद&lt;/u&gt;</string> <string name="about_translate">अनुवाद</string>
<string name="about_translate_title">भाषाएँ</string> <string name="about_translate_title">भाषाएँ</string>
<string name="about_translate_proceed">आगे बढ़ें</string> <string name="about_translate_proceed">आगे बढ़ें</string>
<string name="about_translate_cancel">रद्द करें</string> <string name="about_translate_cancel">रद्द करें</string>
@ -284,7 +284,7 @@
<string name="search_recent_header">हाल की खोजें</string> <string name="search_recent_header">हाल की खोजें</string>
<string name="provider_searches">हाल में खोजे गये प्रश्न</string> <string name="provider_searches">हाल में खोजे गये प्रश्न</string>
<string name="error_loading_categories">श्रेणी लोड करते समय त्रुटि उत्पन्न हुई।</string> <string name="error_loading_categories">श्रेणी लोड करते समय त्रुटि उत्पन्न हुई।</string>
<string name="search_tab_title_media" fuzzy="true">मीडिया</string> <string name="search_tab_title_media">मीडिया</string>
<string name="search_tab_title_categories">श्रेणियाँ</string> <string name="search_tab_title_categories">श्रेणियाँ</string>
<string name="explore_tab_title_featured">निर्वाचित</string> <string name="explore_tab_title_featured">निर्वाचित</string>
<string name="explore_tab_title_map">नक्शा</string> <string name="explore_tab_title_map">नक्शा</string>
@ -315,7 +315,7 @@
<string name="statistics">सांख्यिकी</string> <string name="statistics">सांख्यिकी</string>
<string name="statistics_thanks">धन्यवाद प्राप्त किया</string> <string name="statistics_thanks">धन्यवाद प्राप्त किया</string>
<string name="statistics_featured">निर्वाचित चित्र</string> <string name="statistics_featured">निर्वाचित चित्र</string>
<string name="level" fuzzy="true">स्तर</string> <string name="level">स्तर %d</string>
<string name="images_uploaded">चित्र अपलोड हुआ</string> <string name="images_uploaded">चित्र अपलोड हुआ</string>
<string name="image_reverts">चित्रों को वापस नहीं किया गया</string> <string name="image_reverts">चित्रों को वापस नहीं किया गया</string>
<string name="images_used_by_wiki">उपयोग हुए चित्र</string> <string name="images_used_by_wiki">उपयोग हुए चित्र</string>

View file

@ -4,6 +4,7 @@
* Akmaie Ajam * Akmaie Ajam
* Arifin.wijaya * Arifin.wijaya
* Birusian * Birusian
* Boesenbergia
* DARMAS BUDI SANTOSO * DARMAS BUDI SANTOSO
* Daud I.F. Argana * Daud I.F. Argana
* Fafau06 * Fafau06
@ -295,7 +296,7 @@
<string name="error_notifications">Galat penyampaian pemberitahuan</string> <string name="error_notifications">Galat penyampaian pemberitahuan</string>
<string name="error_review">Galat mengambil gambar untuk ditinjau. Tekan segarkan untuk coba lagi.</string> <string name="error_review">Galat mengambil gambar untuk ditinjau. Tekan segarkan untuk coba lagi.</string>
<string name="no_notifications">Pemberitahuan tidak ditemukan</string> <string name="no_notifications">Pemberitahuan tidak ditemukan</string>
<string name="about_translate">Terjemahkan</string> <string name="about_translate">Pertalaghi</string>
<string name="about_translate_title">Bahasa</string> <string name="about_translate_title">Bahasa</string>
<string name="about_translate_message">Pilih bahasa untuk terjemahan yang ingin Anda kirimkan</string> <string name="about_translate_message">Pilih bahasa untuk terjemahan yang ingin Anda kirimkan</string>
<string name="about_translate_proceed">Lanjutkan</string> <string name="about_translate_proceed">Lanjutkan</string>

View file

@ -181,6 +181,7 @@
<string name="no">Neen</string> <string name="no">Neen</string>
<string name="media_detail_caption">Beschrëftung</string> <string name="media_detail_caption">Beschrëftung</string>
<string name="media_detail_title">Titel</string> <string name="media_detail_title">Titel</string>
<string name="media_detail_depiction">Motiven</string>
<string name="media_detail_description">Beschreiwung</string> <string name="media_detail_description">Beschreiwung</string>
<string name="media_detail_discussion">Diskussioun</string> <string name="media_detail_discussion">Diskussioun</string>
<string name="media_detail_author">Auteur</string> <string name="media_detail_author">Auteur</string>

View file

@ -406,10 +406,10 @@
<string name="preference_author_name_toggle_summary">Gebruik een aangepaste auteursnaam in plaats van uw gebruikersnaam tijdens het uploaden van foto\'s</string> <string name="preference_author_name_toggle_summary">Gebruik een aangepaste auteursnaam in plaats van uw gebruikersnaam tijdens het uploaden van foto\'s</string>
<string name="preference_author_name">Aangepaste auteursnaam</string> <string name="preference_author_name">Aangepaste auteursnaam</string>
<string name="contributions_fragment">Bijdragen</string> <string name="contributions_fragment">Bijdragen</string>
<string name="nearby_fragment">Dichtbij</string> <string name="nearby_fragment">In de buurt</string>
<string name="notifications">Meldingen</string> <string name="notifications">Meldingen</string>
<string name="read_notifications">Meldingen (gelezen)</string> <string name="read_notifications">Meldingen (gelezen)</string>
<string name="display_nearby_notification">Meldingen dichtbij weergeven</string> <string name="display_nearby_notification">Melding in de buurt weergeven</string>
<string name="display_nearby_notification_summary">Toon in-app-melding voor de dichtstbijzijnde plaats die foto\'s nodig heeft</string> <string name="display_nearby_notification_summary">Toon in-app-melding voor de dichtstbijzijnde plaats die foto\'s nodig heeft</string>
<string name="list_sheet">Lijst</string> <string name="list_sheet">Lijst</string>
<string name="storage_permission">Toestemming om op te slaan</string> <string name="storage_permission">Toestemming om op te slaan</string>
@ -615,7 +615,7 @@
<string name="recommend_high_accuracy_mode">Kies voor de beste resultaten de modus van hoge nauwkeurigheid.</string> <string name="recommend_high_accuracy_mode">Kies voor de beste resultaten de modus van hoge nauwkeurigheid.</string>
<string name="ask_to_turn_location_on">Locatie inschakelen?</string> <string name="ask_to_turn_location_on">Locatie inschakelen?</string>
<string name="ask_to_turn_location_on_text">Schakel locatiediensten in zodat de app uw huidige locatie toont</string> <string name="ask_to_turn_location_on_text">Schakel locatiediensten in zodat de app uw huidige locatie toont</string>
<string name="nearby_needs_location">In de Buurt heeft locatie nodig om correct te werken</string> <string name="nearby_needs_location">In de buurt heeft locatietoegang nodig om correct te werken</string>
<string name="explore_map_needs_location">Voor de verkenningskaart is locatietoestemming nodig om afbeeldingen in de buurt weer te geven</string> <string name="explore_map_needs_location">Voor de verkenningskaart is locatietoestemming nodig om afbeeldingen in de buurt weer te geven</string>
<string name="upload_map_location_access">U moet locatietoestemming geven om de locatie automatisch in te stellen.</string> <string name="upload_map_location_access">U moet locatietoestemming geven om de locatie automatisch in te stellen.</string>
<string name="use_location_from_similar_image">Heeft u deze twee foto\'s op dezelfde plek gemaakt? Wilt u de breedtegraad/lengtegraad van de afbeelding rechts gebruiken?</string> <string name="use_location_from_similar_image">Heeft u deze twee foto\'s op dezelfde plek gemaakt? Wilt u de breedtegraad/lengtegraad van de afbeelding rechts gebruiken?</string>
@ -657,7 +657,7 @@
<string name="leaderboard_weekly">Wekelijks</string> <string name="leaderboard_weekly">Wekelijks</string>
<string name="leaderboard_all_time">Alle tijden</string> <string name="leaderboard_all_time">Alle tijden</string>
<string name="leaderboard_upload">Uploaden</string> <string name="leaderboard_upload">Uploaden</string>
<string name="leaderboard_nearby">In de Buurt</string> <string name="leaderboard_nearby">In de buurt</string>
<string name="leaderboard_used">Gebruikt</string> <string name="leaderboard_used">Gebruikt</string>
<string name="leaderboard_my_rank_button_text">Mijn ranking</string> <string name="leaderboard_my_rank_button_text">Mijn ranking</string>
<string name="limited_connection_enabled">Beperkte verbindingsmodus ingeschakeld!</string> <string name="limited_connection_enabled">Beperkte verbindingsmodus ingeschakeld!</string>
@ -724,7 +724,7 @@
<string name="edit_depictions">Wijzig items</string> <string name="edit_depictions">Wijzig items</string>
<string name="edit_categories">Categorieën bewerken</string> <string name="edit_categories">Categorieën bewerken</string>
<string name="advanced_options">Geavanceerde opties</string> <string name="advanced_options">Geavanceerde opties</string>
<string name="advanced_query_info_text">U kunt de zoekopdracht Dichtbij aanpassen. Als u fouten krijgt, kunt u opnieuw instellen en toepassen.</string> <string name="advanced_query_info_text">U kunt de zoekopdracht In de buurt aanpassen. Als u fouten krijgt, kunt u opnieuw instellen en toepassen.</string>
<string name="apply">Toepassen</string> <string name="apply">Toepassen</string>
<string name="reset">Opnieuw instellen</string> <string name="reset">Opnieuw instellen</string>
<string name="location_message">Locatiegegevens helpen wiki-bewerkers om uw foto te vinden, waardoor deze veel nuttiger wordt.\nUw recente uploads hebben geen locatie.\nWe raden u aan om de locatie in de instellingen van uw camera-app in te schakelen.\nBedankt voor het uploaden!</string> <string name="location_message">Locatiegegevens helpen wiki-bewerkers om uw foto te vinden, waardoor deze veel nuttiger wordt.\nUw recente uploads hebben geen locatie.\nWe raden u aan om de locatie in de instellingen van uw camera-app in te schakelen.\nBedankt voor het uploaden!</string>
@ -835,4 +835,10 @@
<string name="account">Account</string> <string name="account">Account</string>
<string name="vanish_account">Account laten verdwijnen</string> <string name="vanish_account">Account laten verdwijnen</string>
<string name="account_vanish_request_confirm_title">Waarschuwing verwijdering account</string> <string name="account_vanish_request_confirm_title">Waarschuwing verwijdering account</string>
<string name="account_vanish_request_confirm">Verdwijnen is een &lt;b&gt;laatste redmiddel&lt;/b&gt; en moet &lt;b&gt;alleen worden gebruikt als u voor altijd wilt stoppen met bewerken&lt;/b&gt; en om zoveel mogelijk van uw voorgaande relaties te verbergen.&lt;br/&gt;&lt;br/&gt;Accountverwijdering op Wikipedia gebeurt door uw accountnaam te wijzigen opdat anderen uw bijdragen niet meer kunnen herkennen. De procedure wordt accountverdwijning genoemd. &lt;b&gt;Door verdwijnen wordt geen volledige anonimiteit gegarandeerd en worden geen bijdragen aan de projecten verwijderd.&lt;/b&gt;</string>
<string name="caption">Bijschrift</string>
<string name="caption_copied_to_clipboard">Bijschrift gekopieerd naar klembord</string>
<string name="congratulations_all_pictures_in_this_album_have_been_either_uploaded_or_marked_as_not_for_upload">Gefeliciteerd, alle fotos in dit album zijn ofwel geüpload ofwel gemarkeerd als niet om te uploaden.</string>
<string name="show_in_explore">Weergeven in Verkennen</string>
<string name="show_in_nearby">Weergeven in In de buurt</string>
</resources> </resources>

View file

@ -10,8 +10,14 @@
--> -->
<resources> <resources>
<string name="commons_logo">ਕਾਮਨਜ਼ ਮਾਰਕਾ</string> <string name="commons_logo">ਕਾਮਨਜ਼ ਮਾਰਕਾ</string>
<string name="submit">ਹਵਾਲੇ ਕਰੋ</string>
<string name="add_another_description">ਇੱਕ ਹੋਰ ਵੇਰਵਾ ਸ਼ਾਮਲ ਕਰੋ</string> <string name="add_another_description">ਇੱਕ ਹੋਰ ਵੇਰਵਾ ਸ਼ਾਮਲ ਕਰੋ</string>
<string name="add_new_contribution">ਨਵਾਂ ਯੋਗਦਾਨ ਸ਼ਾਮਲ ਕਰੋ</string> <string name="add_new_contribution">ਨਵਾਂ ਯੋਗਦਾਨ ਸ਼ਾਮਲ ਕਰੋ</string>
<string name="add_contribution_from_camera">ਕੈਮਰੇ ਰਾਹੀਂ ਯੋਗਦਾਨ ਸ਼ਾਮਲ ਕਰੋ</string>
<string name="add_contribution_from_photos">ਤਸਵੀਰਾਂ ਰਾਹੀਂ ਯੋਗਦਾਨ ਸ਼ਾਮਲ ਕਰੋ</string>
<string name="show_captions">ਸੁਰਖੀ</string>
<string name="row_item_language_description">ਭਾਸ਼ਾ ਦਾ ਵੇਰਵਾ</string>
<string name="row_item_caption">ਸੁਰਖੀ</string>
<string name="show_captions_description">ਵੇਰਵਾ</string> <string name="show_captions_description">ਵੇਰਵਾ</string>
<string name="nearby_row_image">ਤਸਵੀਰ</string> <string name="nearby_row_image">ਤਸਵੀਰ</string>
<string name="nearby_all">ਸਾਰੇ</string> <string name="nearby_all">ਸਾਰੇ</string>
@ -37,14 +43,19 @@
<string name="navigation_item_explore">ਪੜਚੋਲ ਕਰੋ</string> <string name="navigation_item_explore">ਪੜਚੋਲ ਕਰੋ</string>
<string name="preference_category_appearance">ਦਿੱਖ</string> <string name="preference_category_appearance">ਦਿੱਖ</string>
<string name="preference_category_general">ਆਮ</string> <string name="preference_category_general">ਆਮ</string>
<string name="preference_category_feedback">ਸੁਝਾਅ</string>
<string name="preference_category_privacy">ਪਰਦੇਦਾਰੀ</string>
<string name="app_name" fuzzy="true">ਵਿਕੀਮੀਡੀਆ ਕਾਮਨਜ਼</string> <string name="app_name" fuzzy="true">ਵਿਕੀਮੀਡੀਆ ਕਾਮਨਜ਼</string>
<string name="menu_settings">ਪਸੰਦਾਂ</string> <string name="menu_settings">ਪਸੰਦਾਂ</string>
<string name="upload_in_progress">ਚੜ੍ਹਾਉਣਾ ਜਾਰੀ ਐ</string>
<string name="username">ਵਰਤੋਂਕਾਰ ਨਾਂ</string> <string name="username">ਵਰਤੋਂਕਾਰ ਨਾਂ</string>
<string name="password">ਲੰਘ-ਸ਼ਬਦ</string> <string name="password">ਲੰਘ-ਸ਼ਬਦ</string>
<string name="login">ਦਾਖ਼ਲ ਹੋਵੋ</string> <string name="login">ਦਾਖ਼ਲ ਹੋਵੋ</string>
<string name="forgot_password">ਪਾਰਸ਼ਬਦ ਭੁੱਲ ਗਏ?</string> <string name="forgot_password">ਪਾਰਸ਼ਬਦ ਭੁੱਲ ਗਏ?</string>
<string name="signup">ਖਾਤਾ ਬਣਾਓ</string>
<string name="logging_in_title">ਦਾਖ਼ਲਾ ਹੋ ਰਿਹਾ ਹੈ</string> <string name="logging_in_title">ਦਾਖ਼ਲਾ ਹੋ ਰਿਹਾ ਹੈ</string>
<string name="logging_in_message">ਉਡੀਕੋ ਜੀ…</string> <string name="logging_in_message">ਉਡੀਕੋ ਜੀ…</string>
<string name="updating_caption_title">ਸੁਰਖੀਆਂ ਅਤੇ ਵੇਰਵੇ ਨਵਿਆਏ ਜਾ ਰਹੇ ਹਨ</string>
<string name="updating_caption_message">ਕਿਰਪਾ ਕਰਕੇ ਉਡੀਕੋ...</string> <string name="updating_caption_message">ਕਿਰਪਾ ਕਰਕੇ ਉਡੀਕੋ...</string>
<string name="login_success">ਦਾਖ਼ਲ ਹੋਣਾ ਸਫ਼ਲ!</string> <string name="login_success">ਦਾਖ਼ਲ ਹੋਣਾ ਸਫ਼ਲ!</string>
<string name="login_failed">ਦਾਖ਼ਲ ਹੋਣਾ ਅਸਫ਼ਲ!</string> <string name="login_failed">ਦਾਖ਼ਲ ਹੋਣਾ ਅਸਫ਼ਲ!</string>
@ -53,11 +64,13 @@
<string name="uploading_started">ਅੱਪਲੋਡ ਸ਼ੁਰੂ ਹੋਇਆ!</string> <string name="uploading_started">ਅੱਪਲੋਡ ਸ਼ੁਰੂ ਹੋਇਆ!</string>
<string name="upload_completed_notification_title">%1$s ਅੱਪਲੋਡ ਹੋ ਗਏ!</string> <string name="upload_completed_notification_title">%1$s ਅੱਪਲੋਡ ਹੋ ਗਏ!</string>
<string name="upload_completed_notification_text">ਆਪਣਾ ਅੱਪਲੋਡ ਵੇਖਣ ਲਈ ਥਪੇੜੋ</string> <string name="upload_completed_notification_text">ਆਪਣਾ ਅੱਪਲੋਡ ਵੇਖਣ ਲਈ ਥਪੇੜੋ</string>
<string name="upload_progress_notification_title_start" fuzzy="true">%1$s ਅੱਪਲੋਡ ਸ਼ੁਰੂ ਕੀਤੇ ਜਾ ਰਹੇ ਹਨ</string> <string name="upload_progress_notification_title_start">ਫਾਈਲ ਚੜ੍ਹਾਈ ਜਾ ਰਹੀ ਐ: %s</string>
<string name="upload_progress_notification_title_in_progress">%1$s ਅੱਪਲੋਡ ਕੀਤੇ ਜਾ ਰਹੇ ਹਨ</string> <string name="upload_progress_notification_title_in_progress">%1$s ਅੱਪਲੋਡ ਕੀਤੇ ਜਾ ਰਹੇ ਹਨ</string>
<string name="upload_progress_notification_title_finishing">%1$s ਦਾ ਅੱਪਲੋਡ ਖ਼ਤਮ ਹੋ ਰਿਹਾ ਹੈ</string> <string name="upload_progress_notification_title_finishing">%1$s ਦਾ ਅੱਪਲੋਡ ਖ਼ਤਮ ਹੋ ਰਿਹਾ ਹੈ</string>
<string name="upload_failed_notification_title" fuzzy="true">%1$s ਦਾ ਅੱਪਲੋਡ ਫੇਲ੍ਹ ਹੋਇਆ</string> <string name="upload_failed_notification_title">%1$s ਨੂੰ ਚੜ੍ਹਾਉਣ ਵਿੱਚ ਨਾਕਾਮ</string>
<string name="upload_paused_notification_title">%1$s ਚੜ੍ਹਾਉਣਾ ਰੋਕਿਆ ਗਿਆ</string>
<string name="upload_failed_notification_subtitle">ਵੇਖਣ ਲਈ ਥਪੇੜੋ</string> <string name="upload_failed_notification_subtitle">ਵੇਖਣ ਲਈ ਥਪੇੜੋ</string>
<string name="upload_paused_notification_subtitle">ਵੇਖਣ ਲਈ ਥਪੇੜੋ</string>
<string name="title_activity_contributions" fuzzy="true">ਮੇਰੇ ਅੱਪਲੋਡ</string> <string name="title_activity_contributions" fuzzy="true">ਮੇਰੇ ਅੱਪਲੋਡ</string>
<string name="contribution_state_queued">ਕਤਾਰ ਵਿਚ</string> <string name="contribution_state_queued">ਕਤਾਰ ਵਿਚ</string>
<string name="contribution_state_failed">ਫੇਲ੍ਹ ਹੋਇਆ</string> <string name="contribution_state_failed">ਫੇਲ੍ਹ ਹੋਇਆ</string>
@ -68,12 +81,17 @@
<string name="menu_nearby">ਨੇੜੇ-ਤੇੜੇ</string> <string name="menu_nearby">ਨੇੜੇ-ਤੇੜੇ</string>
<string name="provider_contributions">ਮੇਰੇ ਅੱਪਲੋਡ</string> <string name="provider_contributions">ਮੇਰੇ ਅੱਪਲੋਡ</string>
<string name="menu_copy_link">ਕੜੀ ਦੀ ਨਕਲ ਕਰੋ</string> <string name="menu_copy_link">ਕੜੀ ਦੀ ਨਕਲ ਕਰੋ</string>
<string name="menu_link_copied">ਕੜੀ ਨੂੰ ਚੂੰਢੀ-ਤਖਤੀ ਉੱਤੇ ਨਕਲ ਕੀਤਾ ਗਿਆ ਐ</string>
<string name="menu_share">ਸਾਂਝਾ ਕਰੋ</string> <string name="menu_share">ਸਾਂਝਾ ਕਰੋ</string>
<string name="menu_view_file_page">ਫਾਇਲ ਸਫ਼ਾ ਵੇਖੋ</string>
<string name="share_title_hint">ਸੁਰਖੀ (ਲੋੜੀਂਦੀ)</string> <string name="share_title_hint">ਸੁਰਖੀ (ਲੋੜੀਂਦੀ)</string>
<string name="add_caption_toast">ਕਿਰਪਾ ਕਰਕੇ ਇਸ ਫਾਈਲ ਲਈ ਇੱਕ ਸੁਰਖੀ ਦਿਓ</string>
<string name="share_description_hint">ਵੇਰਵਾ</string> <string name="share_description_hint">ਵੇਰਵਾ</string>
<string name="share_caption_hint">ਸੁਰਖੀ</string>
<string name="login_failed_network">ਦਾਖ਼ਲ ਹੋਣ ਵਿੱਚ ਅਸਮਰੱਥ - ਨੈੱਟਵਰਕ ਫੇਲ੍ਹ ਹੋਇਆ ਹੈ</string> <string name="login_failed_network">ਦਾਖ਼ਲ ਹੋਣ ਵਿੱਚ ਅਸਮਰੱਥ - ਨੈੱਟਵਰਕ ਫੇਲ੍ਹ ਹੋਇਆ ਹੈ</string>
<string name="login_failed_throttled">ਬਹੁਤ ਸਾਰੀਆਂ ਅਸਫ਼ਲ ਕੋਸ਼ਿਸ਼ਾਂ। ਥੋੜ੍ਹੀ ਦੇਰ ਬਾਅਦ ਕੋਸ਼ਿਸ਼ ਕਰੋ ਜੀ।</string> <string name="login_failed_throttled">ਬਹੁਤ ਸਾਰੀਆਂ ਅਸਫ਼ਲ ਕੋਸ਼ਿਸ਼ਾਂ। ਥੋੜ੍ਹੀ ਦੇਰ ਬਾਅਦ ਕੋਸ਼ਿਸ਼ ਕਰੋ ਜੀ।</string>
<string name="login_failed_blocked">ਅਫ਼ਸੋਸ, ਇਹ ਵਰਤੋਂਕਾਰ ਨੂੰ ਕਾਮਨਜ਼ ਤੇ ਰੋਕ ਲਾਈ ਗਈ ਹੈ।</string> <string name="login_failed_blocked">ਅਫ਼ਸੋਸ, ਇਹ ਵਰਤੋਂਕਾਰ ਨੂੰ ਕਾਮਨਜ਼ ਤੇ ਰੋਕ ਲਾਈ ਗਈ ਹੈ।</string>
<string name="login_failed_2fa_needed">ਤੁਹਾਨੂੰ ਆਪਣਾ ਦੋ-ਕਾਰਕ ਪ੍ਰਮਾਣੀਕਰਨ ਕੋਡ ਦੇਣਾ ਪਵੇਗਾ।</string>
<string name="login_failed_generic">ਦਾਖ਼ਲ ਹੋਣਾ ਅਸਫ਼ਲ!</string> <string name="login_failed_generic">ਦਾਖ਼ਲ ਹੋਣਾ ਅਸਫ਼ਲ!</string>
<string name="share_upload_button">ਚੜ੍ਹਾਉ</string> <string name="share_upload_button">ਚੜ੍ਹਾਉ</string>
<string name="multiple_share_base_title">ਇਸ ਸੈੱਟ ਨੂੰ ਨਾਂ ਦਿਓ</string> <string name="multiple_share_base_title">ਇਸ ਸੈੱਟ ਨੂੰ ਨਾਂ ਦਿਓ</string>
@ -85,10 +103,12 @@
<string name="display_list_button">ਸੂਚੀ</string> <string name="display_list_button">ਸੂਚੀ</string>
<string name="contributions_subtitle_zero" fuzzy="true">ਫ਼ਿਲਹਾਲ ਕੋਈ ਅੱਪਲੋਡ ਨਹੀਂ</string> <string name="contributions_subtitle_zero" fuzzy="true">ਫ਼ਿਲਹਾਲ ਕੋਈ ਅੱਪਲੋਡ ਨਹੀਂ</string>
<string name="categories_not_found">%1$s ਨਾਲ਼ ਮੇਲ ਖਾਂਦੀ ਕੋਈ ਸ਼੍ਰੇਣੀ ਨਹੀਂ ਲੱਭੀ</string> <string name="categories_not_found">%1$s ਨਾਲ਼ ਮੇਲ ਖਾਂਦੀ ਕੋਈ ਸ਼੍ਰੇਣੀ ਨਹੀਂ ਲੱਭੀ</string>
<string name="depictions_not_found">%1$s ਨਾਲ ਮੇਲ ਖਾਂਦੀਆਂ ਕੋਈ ਵਿਕੀਡਾਟਾ ਚੀਜ਼ਾਂ ਨਹੀਂ ਲੱਭਿਆਂ।</string>
<string name="categories_skip_explanation" fuzzy="true">ਆਪਣੀਆਂ ਤਸਵੀਰਾਂ ਨੂੰ ਵਿਕੀਮੀਡੀਆ ਕਾਮਨਜ਼ ਵਿਚ ਜ਼ਿਆਦਾ ਲੱਭਣਯੋਗ ਬਣਾਉਣ ਲਈ ਸ਼੍ਰੇਣੀਆਂ ਜੋੜੋ।\n\nਸ਼੍ਰੇਣੀਆਂ ਜੋੜਨ ਲਈ ਟਾਈਪ ਕਰਨ ਅਰੰਭ ਕਰੋ।\nਇਸ ਕਾਰਜ ਨੂੰ ਅਣਡਿੱਠਾ ਕਰਨ ਲਈ ਇਹ ਸੁਨੇਹਾ ਥਪੇੜੋ (ਜਾਂ ਵਾਪਸੀ ਬਟਨ ਦਬਾਓ)।</string> <string name="categories_skip_explanation" fuzzy="true">ਆਪਣੀਆਂ ਤਸਵੀਰਾਂ ਨੂੰ ਵਿਕੀਮੀਡੀਆ ਕਾਮਨਜ਼ ਵਿਚ ਜ਼ਿਆਦਾ ਲੱਭਣਯੋਗ ਬਣਾਉਣ ਲਈ ਸ਼੍ਰੇਣੀਆਂ ਜੋੜੋ।\n\nਸ਼੍ਰੇਣੀਆਂ ਜੋੜਨ ਲਈ ਟਾਈਪ ਕਰਨ ਅਰੰਭ ਕਰੋ।\nਇਸ ਕਾਰਜ ਨੂੰ ਅਣਡਿੱਠਾ ਕਰਨ ਲਈ ਇਹ ਸੁਨੇਹਾ ਥਪੇੜੋ (ਜਾਂ ਵਾਪਸੀ ਬਟਨ ਦਬਾਓ)।</string>
<string name="categories_activity_title">ਸ਼੍ਰੇਣੀਆਂ</string> <string name="categories_activity_title">ਸ਼੍ਰੇਣੀਆਂ</string>
<string name="title_activity_settings">ਪਸੰਦਾਂ</string> <string name="title_activity_settings">ਪਸੰਦਾਂ</string>
<string name="title_activity_signup">ਖਾਤਾ ਬਣਾਓ</string> <string name="title_activity_signup">ਖਾਤਾ ਬਣਾਓ</string>
<string name="title_activity_featured_images">ਵਿਸ਼ੇਸ਼ ਤਸਵੀਰ</string>
<string name="title_activity_category_details">ਸ਼੍ਰੇਣੀ</string> <string name="title_activity_category_details">ਸ਼੍ਰੇਣੀ</string>
<string name="menu_about">ਇਸ ਬਾਰੇ</string> <string name="menu_about">ਇਸ ਬਾਰੇ</string>
<string name="about_license" fuzzy="true">ਅਜ਼ਾਦ ਸਰੋਤ ਸਾਫ਼ਟਵੇਅਰ ਨੂੰ &lt;a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\"&gt;Apache License v2&lt;/a&gt; ਅਧੀਨ ਜਾਰੀ ਕੀਤਾ ਗਿਆ ਹੈ</string> <string name="about_license" fuzzy="true">ਅਜ਼ਾਦ ਸਰੋਤ ਸਾਫ਼ਟਵੇਅਰ ਨੂੰ &lt;a href=\"https://github.com/commons-app/apps-android-commons/blob/master/COPYING\"&gt;Apache License v2&lt;/a&gt; ਅਧੀਨ ਜਾਰੀ ਕੀਤਾ ਗਿਆ ਹੈ</string>
@ -105,9 +125,9 @@
<string name="menu_cancel_upload">ਰੱਦ ਕਰੋ</string> <string name="menu_cancel_upload">ਰੱਦ ਕਰੋ</string>
<string name="media_upload_policy">ਇਹ ਤਸਵੀਰ ਅੱਪਲੋਡ ਕਰਨ ਨਾਲ ਹੀ ਮੈਂ ਦਾਅਵਾ ਕਰਦਾ ਹਾਂ/ਕਰਦੀ ਹਾਂ ਕਿ ਇਹ ਮੇਰਾ ਆਪਣਾ ਕਾਰਜ ਹੈ, ਕਿ ਇਸ ਤਹਿਤ ਕੋਈ ਕਾਪੀਰਾਈਟ ਉਲੰਘਣਾ ਨਹੀਂ ਕੀਤੀ ਗਈ ਅਤੇ &lt;a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\"&gt;ਵਿਕੀਮੀਡੀਆ ਕਾਮਨਜ਼ ਨੀਤੀਆਂ&lt;/a&gt; ਮੁਤਾਬਿਕ ਇਹ ਠੀਕ ਹੈ।</string> <string name="media_upload_policy">ਇਹ ਤਸਵੀਰ ਅੱਪਲੋਡ ਕਰਨ ਨਾਲ ਹੀ ਮੈਂ ਦਾਅਵਾ ਕਰਦਾ ਹਾਂ/ਕਰਦੀ ਹਾਂ ਕਿ ਇਹ ਮੇਰਾ ਆਪਣਾ ਕਾਰਜ ਹੈ, ਕਿ ਇਸ ਤਹਿਤ ਕੋਈ ਕਾਪੀਰਾਈਟ ਉਲੰਘਣਾ ਨਹੀਂ ਕੀਤੀ ਗਈ ਅਤੇ &lt;a href=\"https://commons.wikimedia.org/wiki/Commons:Policies_and_guidelines\"&gt;ਵਿਕੀਮੀਡੀਆ ਕਾਮਨਜ਼ ਨੀਤੀਆਂ&lt;/a&gt; ਮੁਤਾਬਿਕ ਇਹ ਠੀਕ ਹੈ।</string>
<string name="menu_download">ਡਾਊਨਲੋਡ</string> <string name="menu_download">ਡਾਊਨਲੋਡ</string>
<string name="preference_license" fuzzy="true">ਲਸੰਸ</string> <string name="preference_license">ਮੂਲ ਲਸੰਸ</string>
<string name="use_previous" fuzzy="true">ਪਹਿਲਾਂ ਵਾਲਾ ਸਿਰਲੇਖ/ਜਾਣਕਾਰੀ ਵਰਤ</string> <string name="use_previous">ਪਿਛਲੇ ਸਿਰਲੇਖ ਅਤੇ ਵੇਰਵੇ ਦੀ ਵਰਤੋਂ ਕਰ</string>
<string name="preference_theme" fuzzy="true">ਰਾਤ ਦਾ ਅੰਦਾਜ਼</string> <string name="preference_theme">ਵਿਸ਼ਾ-ਵਸਤੂ</string>
<string name="license_name_cc_by_sa_four"> Attribution-ShareAlike 4.0</string> <string name="license_name_cc_by_sa_four"> Attribution-ShareAlike 4.0</string>
<string name="license_name_cc_by_four"> Attribution 4.0</string> <string name="license_name_cc_by_four"> Attribution 4.0</string>
<string name="license_name_cc_by_sa">CC Attribution-ShareAlike 3.0</string> <string name="license_name_cc_by_sa">CC Attribution-ShareAlike 3.0</string>
@ -120,8 +140,14 @@
<string name="tutorial_1_text">ਵਿਕੀਮੀਡੀਆ ਕਾਮਨਜ਼ ਜ਼ਿਆਦਾਤਰ ਉਹ ਤਸਵੀਰਾਂ ਦਾ ਭੰਡਾਰ ਹੈ ਜੋ ਵਿਕੀਪੀਡੀਆ \'ਤੇ ਵਰਤੀਆਂ ਜਾਂਦੀਆਂ ਹਨ।</string> <string name="tutorial_1_text">ਵਿਕੀਮੀਡੀਆ ਕਾਮਨਜ਼ ਜ਼ਿਆਦਾਤਰ ਉਹ ਤਸਵੀਰਾਂ ਦਾ ਭੰਡਾਰ ਹੈ ਜੋ ਵਿਕੀਪੀਡੀਆ \'ਤੇ ਵਰਤੀਆਂ ਜਾਂਦੀਆਂ ਹਨ।</string>
<string name="tutorial_1_subtext">ਤੁਹਾਡੀਆਂ ਤਸਵੀਰਾਂ ਵਿਸ਼ਵ ਦੇ ਬਾਕੀ ਲੋਕਾਂ ਨੂੰ ਸਿੱਖਿਅਤ ਕਰਨ ਲਈ ਸਹਾਈ ਹਨ!</string> <string name="tutorial_1_subtext">ਤੁਹਾਡੀਆਂ ਤਸਵੀਰਾਂ ਵਿਸ਼ਵ ਦੇ ਬਾਕੀ ਲੋਕਾਂ ਨੂੰ ਸਿੱਖਿਅਤ ਕਰਨ ਲਈ ਸਹਾਈ ਹਨ!</string>
<string name="tutorial_2_text">ਕਿਰਪਾ ਕਰਕੇ ਉਹ ਤਸਵੀਰਾਂ ਨੂੰ ਚੜ੍ਹਾਉ ਜੋ ਤੁਹਾਡੇ ਵੱਲੋਂ ਲਈਆਂ ਗਈਆਂ ਹਨ ਜਾਂ ਬਣਾਈਆਂ ਗਈਆਂ ਹਨ:</string> <string name="tutorial_2_text">ਕਿਰਪਾ ਕਰਕੇ ਉਹ ਤਸਵੀਰਾਂ ਨੂੰ ਚੜ੍ਹਾਉ ਜੋ ਤੁਹਾਡੇ ਵੱਲੋਂ ਲਈਆਂ ਗਈਆਂ ਹਨ ਜਾਂ ਬਣਾਈਆਂ ਗਈਆਂ ਹਨ:</string>
<string name="tutorial_2_subtext_1">ਕੁਦਰਤੀ ਵਸਤੂਆਂ (ਫੁੱਲ, ਜਾਨਵਰ, ਪਹਾੜ)</string>
<string name="tutorial_2_subtext_2">ਲਾਹੇਵੰਦ ਵਸਤੂਆਂ (ਸੈਕਲ, ਰੇਲ ਅੱਡਾ)</string>
<string name="tutorial_2_subtext_3">ਮਸ਼ਹੂਰ ਲੋਕ (ਤੁਹਾਡੇ mayor, ਓਲੰਪਿਕ ਖਿਡਾਰੀ ਜਿਨ੍ਹਾਂ ਨੂੰ ਤੁਸੀਂ ਮਿਲੇ ਸੀ)</string>
<string name="tutorial_3_text">ਕਿਰਪਾ ਕਰਕੇ ਅਪਲੋਡ ਨਾ ਕਰੋ:</string> <string name="tutorial_3_text">ਕਿਰਪਾ ਕਰਕੇ ਅਪਲੋਡ ਨਾ ਕਰੋ:</string>
<string name="tutorial_3_subtext_1">ਤੁਹਾਡੇ ਦੋਸਤਾਂ ਦੀਆਂ ਸੈਲਫ਼ੀਆਂ ਜਾਂ ਤਸਵੀਰਾਂ</string>
<string name="tutorial_4_text">ਉਦਾਹਰਣ ਵਜੋਂ ਇਹ ਅਪਲੋਡ:</string> <string name="tutorial_4_text">ਉਦਾਹਰਣ ਵਜੋਂ ਇਹ ਅਪਲੋਡ:</string>
<string name="tutorial_4_subtext_1">ਸਿਰਲੇਖ: ਸਿਡਨੀ ਓਪੇਰਾ ਹਾਊਸ</string>
<string name="tutorial_4_subtext_2">ਵੇਰਵਾ: ਸਿਡਨੀ ਓਪੇਰਾ ਹਾਊਸ ਜਿਵੇਂ ਖਾੜੀ ਦੇ ਪਾਰ ਤੋਂ ਦਿਖਦਾ ਐ</string>
<string name="welcome_wikipedia_text">ਆਪਣੀਆਂ ਤਸਵੀਰਾਂ ਦਾ ਯੋਗਦਾਨ ਪਾਓ। ਵਿਕੀਪੀਡੀਆ ਲੇਖਾਂ ਨੂੰ ਸੁਰਜੀਤ ਕਰ ਦਿਓ!</string> <string name="welcome_wikipedia_text">ਆਪਣੀਆਂ ਤਸਵੀਰਾਂ ਦਾ ਯੋਗਦਾਨ ਪਾਓ। ਵਿਕੀਪੀਡੀਆ ਲੇਖਾਂ ਨੂੰ ਸੁਰਜੀਤ ਕਰ ਦਿਓ!</string>
<string name="welcome_wikipedia_subtext">ਵਿਕੀਪੀਡੀਆ ਉਤਲੀਆਂ ਤਸਵੀਰਾਂ ਵਿਕੀਮੀਡੀਆ ਕਾਮਨਜ਼ ਤੋਂ ਆਉਂਦੀਆਂ ਹਨ</string> <string name="welcome_wikipedia_subtext">ਵਿਕੀਪੀਡੀਆ ਉਤਲੀਆਂ ਤਸਵੀਰਾਂ ਵਿਕੀਮੀਡੀਆ ਕਾਮਨਜ਼ ਤੋਂ ਆਉਂਦੀਆਂ ਹਨ</string>
<string name="welcome_copyright_text">ਤੁਹਾਡੀਆਂ ਤਸਵੀਰਾਂ ਦੁਨੀਆਂ ਭਰ ਦੇ ਲੋਕਾਂ ਨੂੰ ਪੜ੍ਹਨ ਵਿਚ ਮਦਦ ਕਰਦੀਆਂ ਹਨ।</string> <string name="welcome_copyright_text">ਤੁਹਾਡੀਆਂ ਤਸਵੀਰਾਂ ਦੁਨੀਆਂ ਭਰ ਦੇ ਲੋਕਾਂ ਨੂੰ ਪੜ੍ਹਨ ਵਿਚ ਮਦਦ ਕਰਦੀਆਂ ਹਨ।</string>
@ -132,6 +158,7 @@
<string name="detail_panel_cats_label">ਸ਼੍ਰੇਣੀਆਂ</string> <string name="detail_panel_cats_label">ਸ਼੍ਰੇਣੀਆਂ</string>
<string name="detail_panel_cats_loading">ਲੱਦ ਰਿਹਾ ਹੈ...</string> <string name="detail_panel_cats_loading">ਲੱਦ ਰਿਹਾ ਹੈ...</string>
<string name="detail_panel_cats_none">ਕੋਈ ਵੀ ਨਹੀਂ ਚੁਣਿਆ</string> <string name="detail_panel_cats_none">ਕੋਈ ਵੀ ਨਹੀਂ ਚੁਣਿਆ</string>
<string name="detail_caption_empty">ਕੋਈ ਸੁਰਖੀ ਨਹੀਂ</string>
<string name="detail_description_empty">ਕੋਈ ਵੇਰਵਾ ਨਹੀਂ</string> <string name="detail_description_empty">ਕੋਈ ਵੇਰਵਾ ਨਹੀਂ</string>
<string name="detail_discussion_empty">ਕੋਈ ਗੱਲਬਾਤ ਨਹੀਂ</string> <string name="detail_discussion_empty">ਕੋਈ ਗੱਲਬਾਤ ਨਹੀਂ</string>
<string name="detail_license_empty">ਅਣਜਾਣ ਲਸੰਸ</string> <string name="detail_license_empty">ਅਣਜਾਣ ਲਸੰਸ</string>
@ -139,8 +166,10 @@
<string name="read_storage_permission_rationale" fuzzy="true">ਆਗਿਆ ਚਾਹੀਦੀ ਹੈ: ਬਾਹਰੀ ਸਟੋਰੇਜ ਬਾਰੇ। ਇਸ ਤੋਂ ਬਿਨਾਂ ਐਪ ਕਾਰਜ ਨਹੀਂ ਕਰ ਸਕੇਗੀ।</string> <string name="read_storage_permission_rationale" fuzzy="true">ਆਗਿਆ ਚਾਹੀਦੀ ਹੈ: ਬਾਹਰੀ ਸਟੋਰੇਜ ਬਾਰੇ। ਇਸ ਤੋਂ ਬਿਨਾਂ ਐਪ ਕਾਰਜ ਨਹੀਂ ਕਰ ਸਕੇਗੀ।</string>
<string name="ok">ਠੀਕ ਹੈ</string> <string name="ok">ਠੀਕ ਹੈ</string>
<string name="warning">ਖ਼ਬਰਦਾਰ</string> <string name="warning">ਖ਼ਬਰਦਾਰ</string>
<string name="upload">ਚੜ੍ਹਾਉ</string>
<string name="yes">ਹਾਂ</string> <string name="yes">ਹਾਂ</string>
<string name="no">ਨਹੀਂ</string> <string name="no">ਨਹੀਂ</string>
<string name="media_detail_caption">ਸੁਰਖੀ</string>
<string name="media_detail_title">ਸਿਰਲੇਖ</string> <string name="media_detail_title">ਸਿਰਲੇਖ</string>
<string name="media_detail_description">ਵੇਰਵਾ</string> <string name="media_detail_description">ਵੇਰਵਾ</string>
<string name="media_detail_discussion">ਗੱਲਬਾਤ</string> <string name="media_detail_discussion">ਗੱਲਬਾਤ</string>

View file

@ -56,7 +56,7 @@
<string name="menu_settings">امستنې</string> <string name="menu_settings">امستنې</string>
<string name="intent_share_upload_label">خونديځ ته راپورته کول</string> <string name="intent_share_upload_label">خونديځ ته راپورته کول</string>
<string name="upload_in_progress">راپورته کول جريان لري</string> <string name="upload_in_progress">راپورته کول جريان لري</string>
<string name="username">کارننوم</string> <string name="username">کارننوم</string>
<string name="password">پټنوم</string> <string name="password">پټنوم</string>
<string name="login_credential">خپل خونديځ بېټا ګڼون ته ورننوځئ</string> <string name="login_credential">خپل خونديځ بېټا ګڼون ته ورننوځئ</string>
<string name="login">ننوتل</string> <string name="login">ننوتل</string>

View file

@ -3,6 +3,7 @@
* A100Star * A100Star
* Ajeje Brazorf * Ajeje Brazorf
* Amire80 * Amire80
* Annick green
* Cabal * Cabal
* Googology * Googology
* LeGuyanaisPure * LeGuyanaisPure
@ -27,6 +28,7 @@
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="all"> <resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="all">
<string name="submit">{{Identical|Submit}}</string> <string name="submit">{{Identical|Submit}}</string>
<string name="nearby_all">{{identical|All}}</string> <string name="nearby_all">{{identical|All}}</string>
<string name="nearby_filter_search">Reba ishakiro</string>
<string name="uploads_pending_notification_indicator">Status text about number of uploads left.\n* %1$d represents number of uploads left, including current one</string> <string name="uploads_pending_notification_indicator">Status text about number of uploads left.\n* %1$d represents number of uploads left, including current one</string>
<string name="contributions_subtitle">See the current issue [https://phabricator.wikimedia.org/T267142 T267142] tracked in Phabricator about the &lt;code&gt;&lt;nowiki&gt;|zero=&lt;/nowiki&gt;&lt;/code&gt; option currently not supported on Translatewiki.net with the custom &lt;code&gt;&lt;nowiki&gt;{{PLURAL}}&lt;/nowiki&gt;&lt;/code&gt; rules used by this project for Android, using a non-MediaWiki syntax.</string> <string name="contributions_subtitle">See the current issue [https://phabricator.wikimedia.org/T267142 T267142] tracked in Phabricator about the &lt;code&gt;&lt;nowiki&gt;|zero=&lt;/nowiki&gt;&lt;/code&gt; option currently not supported on Translatewiki.net with the custom &lt;code&gt;&lt;nowiki&gt;{{PLURAL}}&lt;/nowiki&gt;&lt;/code&gt; rules used by this project for Android, using a non-MediaWiki syntax.</string>
<string name="multiple_uploads_title">{{Identical|Upload}}</string> <string name="multiple_uploads_title">{{Identical|Upload}}</string>
@ -190,6 +192,7 @@
<string name="depictions_edit_helper_edit_message_else">{{Doc-commons-app-depicts}}</string> <string name="depictions_edit_helper_edit_message_else">{{Doc-commons-app-depicts}}</string>
<string name="title_app_shortcut_bookmark">{{identical|Bookmark}}</string> <string name="title_app_shortcut_bookmark">{{identical|Bookmark}}</string>
<string name="theme_default_name">Option to make the app\'s theme follow the global system setting.</string> <string name="theme_default_name">Option to make the app\'s theme follow the global system setting.</string>
<string name="nearby_needs_location">Nearby should be translated as in {{msg-wm|Commons-android-strings-navigation item nearby}}</string>
<string name="more">{{Identical|More}}</string> <string name="more">{{Identical|More}}</string>
<string name="map_attribution">{{Optional}}\n&lt;code&gt;&amp;amp;#169;&lt;/code&gt; is the copyright symbol (©).</string> <string name="map_attribution">{{Optional}}\n&lt;code&gt;&amp;amp;#169;&lt;/code&gt; is the copyright symbol (©).</string>
<string name="categories_tooltip">{{Doc-commons-app-depicts}}</string> <string name="categories_tooltip">{{Doc-commons-app-depicts}}</string>
@ -202,9 +205,12 @@
<string name="done">{{identical|Done}}</string> <string name="done">{{identical|Done}}</string>
<string name="edit_depictions">{{Doc-commons-app-depicts}}</string> <string name="edit_depictions">{{Doc-commons-app-depicts}}</string>
<string name="advanced_options">{{Identical|Advanced options}}</string> <string name="advanced_options">{{Identical|Advanced options}}</string>
<string name="advanced_query_info_text">Nearby should be translated as in {{msg-wm|Commons-android-strings-navigation item nearby}}</string>
<string name="explore_map_details">{{Identical|Detail}}</string> <string name="explore_map_details">{{Identical|Detail}}</string>
<string name="set_up_avatar_toast_string">\"Set as avatar\" should be translated the same as {{msg-wm|Commons-android-strings-menu set avatar}}.</string> <string name="set_up_avatar_toast_string">\"Set as avatar\" should be translated the same as {{msg-wm|Commons-android-strings-menu set avatar}}.</string>
<string name="multiple_files_depiction">{{Doc-commons-app-depicts}}</string> <string name="multiple_files_depiction">{{Doc-commons-app-depicts}}</string>
<string name="custom_selector_delete">An answer to the question in {{msg-wm|Commons-android-strings-custom selector confirm deletion message}}.</string> <string name="custom_selector_delete">An answer to the question in {{msg-wm|Commons-android-strings-custom selector confirm deletion message}}.</string>
<string name="bullet_point">{{optional}}</string> <string name="bullet_point">{{optional}}</string>
<string name="show_in_explore">“Explore” should be translated as in {{msg-wm|Commons-android-strings-navigation item explore}}</string>
<string name="show_in_nearby">Nearby should be translated as in {{msg-wm|Commons-android-strings-navigation item nearby}}</string>
</resources> </resources>

View file

@ -876,6 +876,7 @@
<string name="account">Учётная запись</string> <string name="account">Учётная запись</string>
<string name="vanish_account">Удалить учётную запись</string> <string name="vanish_account">Удалить учётную запись</string>
<string name="account_vanish_request_confirm_title">Предупреждение об удалении учётной записи</string> <string name="account_vanish_request_confirm_title">Предупреждение об удалении учётной записи</string>
<string name="account_vanish_request_confirm">Удаление — это &lt;b&gt;крайняя мера&lt;/b&gt;, и её следует &lt;b&gt;использовать только в том случае, если вы хотите навсегда прекратить редактирование&lt;/b&gt;, а также скрыть как можно больше связанных с вами действий.&lt;br/&gt;&lt;br/&gt; Удаление вашей учётной записи на Викискладе осуществляется путём изменения её имени, чтобы другие не могли определить ваши действия. &lt;b&gt;Удаление не гарантирует полной анонимности или удаления вклада в проектах&lt;/b&gt;.</string>
<string name="caption">Подпись</string> <string name="caption">Подпись</string>
<string name="caption_copied_to_clipboard">Подпись скопирована в буфер обмена</string> <string name="caption_copied_to_clipboard">Подпись скопирована в буфер обмена</string>
<string name="congratulations_all_pictures_in_this_album_have_been_either_uploaded_or_marked_as_not_for_upload">Поздравляем, все фотографии в этом альбоме либо загружены, либо помечены как не предназначенные для загрузки.</string> <string name="congratulations_all_pictures_in_this_album_have_been_either_uploaded_or_marked_as_not_for_upload">Поздравляем, все фотографии в этом альбоме либо загружены, либо помечены как не предназначенные для загрузки.</string>

View file

@ -821,4 +821,6 @@
<string name="caption">Bildtext</string> <string name="caption">Bildtext</string>
<string name="caption_copied_to_clipboard">Bildtext kopierades till urklipp</string> <string name="caption_copied_to_clipboard">Bildtext kopierades till urklipp</string>
<string name="congratulations_all_pictures_in_this_album_have_been_either_uploaded_or_marked_as_not_for_upload">Grattis! Alla bilder i detta album har antingen laddats upp eller markerats för att inte laddas upp.</string> <string name="congratulations_all_pictures_in_this_album_have_been_either_uploaded_or_marked_as_not_for_upload">Grattis! Alla bilder i detta album har antingen laddats upp eller markerats för att inte laddas upp.</string>
<string name="show_in_explore">Visa i \"Utforska\"</string>
<string name="show_in_nearby">Visa i \"I närheten\"</string>
</resources> </resources>

View file

@ -66,7 +66,7 @@
<string name="upload_progress_notification_title_start">%s ಅಪ್ಲೋಡ್ ಸುರು ಆವೊಂದುಂಡು</string> <string name="upload_progress_notification_title_start">%s ಅಪ್ಲೋಡ್ ಸುರು ಆವೊಂದುಂಡು</string>
<string name="upload_progress_notification_title_in_progress">%1$s ಅಪ್ಲೋಡ್ ಆವೊಂದುಂಡು</string> <string name="upload_progress_notification_title_in_progress">%1$s ಅಪ್ಲೋಡ್ ಆವೊಂದುಂಡು</string>
<string name="upload_progress_notification_title_finishing">%1$s ಅಪ್ಲೋಡ್ ಕೈದ್ ಆವೊಂದುಂಡು.</string> <string name="upload_progress_notification_title_finishing">%1$s ಅಪ್ಲೋಡ್ ಕೈದ್ ಆವೊಂದುಂಡು.</string>
<string name="upload_failed_notification_title" fuzzy="true">%1$s ಅಪ್ಲೋಡ್ ಸರಿ ಆತಿಜಿ</string> <string name="upload_failed_notification_title">%1$s ಅಪ್ಲೋಡ್ ಆತಿಜಿ</string>
<string name="upload_failed_notification_subtitle">ತುಯಾರ ಮೆಲ್ಲ ಒತ್ತುಲೆ</string> <string name="upload_failed_notification_subtitle">ತುಯಾರ ಮೆಲ್ಲ ಒತ್ತುಲೆ</string>
<string name="title_activity_contributions">ಎನ್ನ ದಿಂಜಯೀನಾ ವಿಚಾರೊಳು</string> <string name="title_activity_contributions">ಎನ್ನ ದಿಂಜಯೀನಾ ವಿಚಾರೊಳು</string>
<string name="contribution_state_queued">ದಿಂಜೊಂತುಂಡು</string> <string name="contribution_state_queued">ದಿಂಜೊಂತುಂಡು</string>

View file

@ -7,6 +7,8 @@ import android.database.MatrixCursor
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import android.net.Uri import android.net.Uri
import android.os.RemoteException import android.os.RemoteException
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import com.nhaarman.mockitokotlin2.any import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.anyOrNull import com.nhaarman.mockitokotlin2.anyOrNull
import com.nhaarman.mockitokotlin2.argumentCaptor import com.nhaarman.mockitokotlin2.argumentCaptor
@ -18,36 +20,20 @@ import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.TestCommonsApplication import fr.free.nrw.commons.TestCommonsApplication
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsContentProvider.BASE_URI import fr.free.nrw.commons.db.AppDatabase
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_CATEGORY
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_COMMONS_LINK
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_DESCRIPTION
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_EXISTS
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_IMAGE_URL
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_LABEL_ICON
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_LABEL_TEXT
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_LANGUAGE
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_LAT
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_LONG
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_NAME
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_PIC
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_WIKIDATA_LINK
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.COLUMN_WIKIPEDIA_LINK
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.CREATE_TABLE_STATEMENT
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.DROP_TABLE_STATEMENT
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.onCreate
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.onDelete
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao.Table.onUpdate
import fr.free.nrw.commons.location.LatLng import fr.free.nrw.commons.location.LatLng
import fr.free.nrw.commons.nearby.Label import fr.free.nrw.commons.nearby.Label
import fr.free.nrw.commons.nearby.Place import fr.free.nrw.commons.nearby.Place
import fr.free.nrw.commons.nearby.Sitelinks import fr.free.nrw.commons.nearby.Sitelinks
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verifyNoInteractions import org.mockito.Mockito.verifyNoInteractions
import org.robolectric.RobolectricTestRunner import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config import org.robolectric.annotation.Config
@ -55,28 +41,11 @@ import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class) @RunWith(RobolectricTestRunner::class)
@Config(sdk = [21], application = TestCommonsApplication::class) @Config(sdk = [21], application = TestCommonsApplication::class)
class BookMarkLocationDaoTest { class BookMarkLocationDaoTest {
private val columns =
arrayOf(
COLUMN_NAME,
COLUMN_LANGUAGE,
COLUMN_DESCRIPTION,
COLUMN_CATEGORY,
COLUMN_LABEL_TEXT,
COLUMN_LABEL_ICON,
COLUMN_IMAGE_URL,
COLUMN_WIKIPEDIA_LINK,
COLUMN_WIKIDATA_LINK,
COLUMN_COMMONS_LINK,
COLUMN_LAT,
COLUMN_LONG,
COLUMN_PIC,
COLUMN_EXISTS,
)
private val client: ContentProviderClient = mock()
private val database: SQLiteDatabase = mock()
private val captor = argumentCaptor<ContentValues>()
private lateinit var testObject: BookmarkLocationsDao private lateinit var bookmarkLocationsDao: BookmarkLocationsDao
private lateinit var database: AppDatabase
private lateinit var examplePlaceBookmark: Place private lateinit var examplePlaceBookmark: Place
private lateinit var exampleLabel: Label private lateinit var exampleLabel: Label
private lateinit var exampleUri: Uri private lateinit var exampleUri: Uri
@ -89,10 +58,18 @@ class BookMarkLocationDaoTest {
exampleUri = Uri.parse("wikimedia/uri") exampleUri = Uri.parse("wikimedia/uri")
exampleLocation = LatLng(40.0, 51.4, 1f) exampleLocation = LatLng(40.0, 51.4, 1f)
builder = Sitelinks.Builder() database = Room.inMemoryDatabaseBuilder(
builder.setWikipediaLink("wikipediaLink") ApplicationProvider.getApplicationContext(),
builder.setWikidataLink("wikidataLink") AppDatabase::class.java
builder.setCommonsLink("commonsLink") ).allowMainThreadQueries().build()
bookmarkLocationsDao = database.bookmarkLocationsDao()
builder = Sitelinks.Builder().apply {
setWikipediaLink("wikipediaLink")
setWikidataLink("wikidataLink")
setCommonsLink("commonsLink")
}
examplePlaceBookmark = examplePlaceBookmark =
Place( Place(
@ -106,236 +83,75 @@ class BookMarkLocationDaoTest {
"picName", "picName",
false, false,
) )
testObject = BookmarkLocationsDao { client } }
@After
fun tearDown() {
database.close()
} }
@Test @Test
fun createTable() { fun testForAddAndGetAllBookmarkLocations() = runBlocking {
onCreate(database) bookmarkLocationsDao.addBookmarkLocation(examplePlaceBookmark.toBookmarksLocations())
verify(database).execSQL(CREATE_TABLE_STATEMENT)
val bookmarks = bookmarkLocationsDao.getAllBookmarksLocations()
assertEquals(1, bookmarks.size)
val retrievedBookmark = bookmarks.first()
assertEquals(examplePlaceBookmark.name, retrievedBookmark.locationName)
assertEquals(examplePlaceBookmark.language, retrievedBookmark.locationLanguage)
} }
@Test @Test
fun deleteTable() { fun testFindBookmarkByNameForTrue() = runBlocking {
onDelete(database) bookmarkLocationsDao.addBookmarkLocation(examplePlaceBookmark.toBookmarksLocations())
inOrder(database) {
verify(database).execSQL(DROP_TABLE_STATEMENT) val exists = bookmarkLocationsDao.findBookmarkLocation(examplePlaceBookmark.name)
verify(database).execSQL(CREATE_TABLE_STATEMENT) assertTrue(exists)
}
} }
@Test @Test
fun createFromCursor() { fun testFindBookmarkByNameForFalse() = runBlocking {
createCursor(1).let { cursor -> bookmarkLocationsDao.addBookmarkLocation(examplePlaceBookmark.toBookmarksLocations())
cursor.moveToFirst()
testObject.fromCursor(cursor).let { val exists = bookmarkLocationsDao.findBookmarkLocation("xyz")
assertEquals("en", it.language) assertFalse(exists)
assertEquals("placeName", it.name)
assertEquals(Label.FOREST, it.label)
assertEquals("placeDescription", it.longDescription)
assertEquals(40.0, it.location.latitude, 0.001)
assertEquals(51.4, it.location.longitude, 0.001)
assertEquals("placeCategory", it.category)
assertEquals(builder.build().wikipediaLink, it.siteLinks.wikipediaLink)
assertEquals(builder.build().wikidataLink, it.siteLinks.wikidataLink)
assertEquals(builder.build().commonsLink, it.siteLinks.commonsLink)
assertEquals("picName", it.pic)
assertEquals(false, it.exists)
}
}
} }
@Test @Test
fun getAllLocationBookmarks() { fun testDeleteBookmark() = runBlocking {
whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(createCursor(14)) val bookmarkLocation = examplePlaceBookmark.toBookmarksLocations()
bookmarkLocationsDao.addBookmarkLocation(bookmarkLocation)
var result = testObject.allBookmarksLocations bookmarkLocationsDao.deleteBookmarkLocation(bookmarkLocation)
assertEquals(14, result.size) val bookmarks = bookmarkLocationsDao.getAllBookmarksLocations()
} assertTrue(bookmarks.isEmpty())
@Test(expected = RuntimeException::class)
fun getAllLocationBookmarksTranslatesExceptions() {
whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenThrow(RemoteException(""))
testObject.allBookmarksLocations
} }
@Test @Test
fun getAllLocationBookmarksReturnsEmptyList_emptyCursor() { fun testUpdateBookmarkForTrue() = runBlocking {
whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(createCursor(0)) val exists = bookmarkLocationsDao.updateBookmarkLocation(examplePlaceBookmark)
assertTrue(testObject.allBookmarksLocations.isEmpty())
assertTrue(exists)
} }
@Test @Test
fun getAllLocationBookmarksReturnsEmptyList_nullCursor() { fun testUpdateBookmarkForFalse() = runBlocking {
whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(null) val newBookmark = examplePlaceBookmark.toBookmarksLocations()
assertTrue(testObject.allBookmarksLocations.isEmpty()) bookmarkLocationsDao.addBookmarkLocation(newBookmark)
val exists = bookmarkLocationsDao.updateBookmarkLocation(examplePlaceBookmark)
assertFalse(exists)
} }
@Test @Test
fun cursorsAreClosedAfterGetAllLocationBookmarksQuery() { fun testGetAllBookmarksLocationsPlace() = runBlocking {
val mockCursor: Cursor = mock() val bookmarkLocation = examplePlaceBookmark.toBookmarksLocations()
whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(mockCursor) bookmarkLocationsDao.addBookmarkLocation(bookmarkLocation)
whenever(mockCursor.moveToFirst()).thenReturn(false)
testObject.allBookmarksLocations val bookmarks = bookmarkLocationsDao.getAllBookmarksLocationsPlace()
assertEquals(1, bookmarks.size)
verify(mockCursor).close() assertEquals(examplePlaceBookmark.name, bookmarks.first().name)
}
@Test
fun updateNewLocationBookmark() {
whenever(client.insert(any(), any())).thenReturn(Uri.EMPTY)
whenever(client.query(any(), any(), any(), any(), anyOrNull())).thenReturn(null)
assertTrue(testObject.updateBookmarkLocation(examplePlaceBookmark))
verify(client).insert(eq(BASE_URI), captor.capture())
captor.firstValue.let { cv ->
assertEquals(13, cv.size())
assertEquals(examplePlaceBookmark.name, cv.getAsString(COLUMN_NAME))
assertEquals(examplePlaceBookmark.language, cv.getAsString(COLUMN_LANGUAGE))
assertEquals(examplePlaceBookmark.longDescription, cv.getAsString(COLUMN_DESCRIPTION))
assertEquals(examplePlaceBookmark.label.text, cv.getAsString(COLUMN_LABEL_TEXT))
assertEquals(examplePlaceBookmark.category, cv.getAsString(COLUMN_CATEGORY))
assertEquals(examplePlaceBookmark.location.latitude, cv.getAsDouble(COLUMN_LAT), 0.001)
assertEquals(examplePlaceBookmark.location.longitude, cv.getAsDouble(COLUMN_LONG), 0.001)
assertEquals(examplePlaceBookmark.siteLinks.wikipediaLink.toString(), cv.getAsString(COLUMN_WIKIPEDIA_LINK))
assertEquals(examplePlaceBookmark.siteLinks.wikidataLink.toString(), cv.getAsString(COLUMN_WIKIDATA_LINK))
assertEquals(examplePlaceBookmark.siteLinks.commonsLink.toString(), cv.getAsString(COLUMN_COMMONS_LINK))
assertEquals(examplePlaceBookmark.pic, cv.getAsString(COLUMN_PIC))
assertEquals(examplePlaceBookmark.exists.toString(), cv.getAsString(COLUMN_EXISTS))
}
}
@Test
fun updateExistingLocationBookmark() {
whenever(client.delete(isA(), isNull(), isNull())).thenReturn(1)
whenever(client.query(any(), any(), any(), any(), anyOrNull())).thenReturn(createCursor(1))
assertFalse(testObject.updateBookmarkLocation(examplePlaceBookmark))
verify(client).delete(eq(BookmarkLocationsContentProvider.uriForName(examplePlaceBookmark.name)), isNull(), isNull())
}
@Test
fun findExistingLocationBookmark() {
whenever(client.query(any(), any(), any(), any(), anyOrNull())).thenReturn(createCursor(1))
assertTrue(testObject.findBookmarkLocation(examplePlaceBookmark))
}
@Test(expected = RuntimeException::class)
fun findLocationBookmarkTranslatesExceptions() {
whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenThrow(RemoteException(""))
testObject.findBookmarkLocation(examplePlaceBookmark)
}
@Test
fun findNotExistingLocationBookmarkReturnsNull_emptyCursor() {
whenever(client.query(any(), any(), any(), any(), anyOrNull())).thenReturn(createCursor(0))
assertFalse(testObject.findBookmarkLocation(examplePlaceBookmark))
}
@Test
fun findNotExistingLocationBookmarkReturnsNull_nullCursor() {
whenever(client.query(any(), any(), any(), any(), anyOrNull())).thenReturn(null)
assertFalse(testObject.findBookmarkLocation(examplePlaceBookmark))
}
@Test
fun cursorsAreClosedAfterFindLocationBookmarkQuery() {
val mockCursor: Cursor = mock()
whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(mockCursor)
whenever(mockCursor.moveToFirst()).thenReturn(false)
testObject.findBookmarkLocation(examplePlaceBookmark)
verify(mockCursor).close()
}
@Test
fun migrateTableVersionFrom_v1_to_v2() {
onUpdate(database, 1, 2)
// Table didn't exist before v5
verifyNoInteractions(database)
}
@Test
fun migrateTableVersionFrom_v2_to_v3() {
onUpdate(database, 2, 3)
// Table didn't exist before v5
verifyNoInteractions(database)
}
@Test
fun migrateTableVersionFrom_v3_to_v4() {
onUpdate(database, 3, 4)
// Table didnt exist before v5
verifyNoInteractions(database)
}
@Test
fun migrateTableVersionFrom_v4_to_v5() {
onUpdate(database, 4, 5)
// Table didnt change in version 5
verifyNoInteractions(database)
}
@Test
fun migrateTableVersionFrom_v5_to_v6() {
onUpdate(database, 5, 6)
// Table didnt change in version 6
verifyNoInteractions(database)
}
@Test
fun migrateTableVersionFrom_v6_to_v7() {
onUpdate(database, 6, 7)
// Table didnt change in version 7
verifyNoInteractions(database)
}
@Test
fun migrateTableVersionFrom_v7_to_v8() {
onUpdate(database, 7, 8)
verify(database).execSQL(CREATE_TABLE_STATEMENT)
}
@Test
fun migrateTableVersionFrom_v12_to_v13() {
onUpdate(database, 12, 13)
verify(database).execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_destroyed STRING;")
}
@Test
fun migrateTableVersionFrom_v13_to_v14() {
onUpdate(database, 13, 14)
verify(database).execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_language STRING;")
}
@Test
fun migrateTableVersionFrom_v14_to_v15() {
onUpdate(database, 14, 15)
verify(database).execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_exists STRING;")
}
private fun createCursor(rows: Int): Cursor =
MatrixCursor(columns, rows).apply {
repeat(rows) {
newRow().apply {
add("placeName")
add("en")
add("placeDescription")
add("placeCategory")
add(Label.FOREST.text)
add(Label.FOREST.icon)
add("placeImage")
add("wikipediaLink")
add("wikidataLink")
add("commonsLink")
add(40.0)
add(51.4)
add("picName")
add(false)
}
}
} }
} }

View file

@ -2,6 +2,7 @@ package fr.free.nrw.commons.bookmarks.locations
import com.nhaarman.mockitokotlin2.whenever import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.nearby.Place import fr.free.nrw.commons.nearby.Place
import kotlinx.coroutines.runBlocking
import org.junit.Assert import org.junit.Assert
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@ -19,10 +20,12 @@ class BookmarkLocationControllerTest {
@Before @Before
fun setup() { fun setup() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.openMocks(this)
whenever(bookmarkDao!!.allBookmarksLocations) runBlocking {
whenever(bookmarkDao!!.getAllBookmarksLocationsPlace())
.thenReturn(mockBookmarkList) .thenReturn(mockBookmarkList)
} }
}
/** /**
* Get mock bookmark list * Get mock bookmark list
@ -66,7 +69,7 @@ class BookmarkLocationControllerTest {
* Test case where all bookmark locations are fetched and media is found against it * Test case where all bookmark locations are fetched and media is found against it
*/ */
@Test @Test
fun loadBookmarkedLocations() { fun loadBookmarkedLocations() = runBlocking {
val bookmarkedLocations = val bookmarkedLocations =
bookmarkLocationsController.loadFavoritesLocations() bookmarkLocationsController.loadFavoritesLocations()
Assert.assertEquals(2, bookmarkedLocations.size.toLong()) Assert.assertEquals(2, bookmarkedLocations.size.toLong())

View file

@ -10,6 +10,7 @@ import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction import androidx.fragment.app.FragmentTransaction
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.OkHttpConnectionFactory import fr.free.nrw.commons.OkHttpConnectionFactory
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
@ -22,11 +23,14 @@ import fr.free.nrw.commons.nearby.Place
import fr.free.nrw.commons.nearby.fragments.CommonPlaceClickActions import fr.free.nrw.commons.nearby.fragments.CommonPlaceClickActions
import fr.free.nrw.commons.nearby.fragments.PlaceAdapter import fr.free.nrw.commons.nearby.fragments.PlaceAdapter
import fr.free.nrw.commons.profile.ProfileActivity import fr.free.nrw.commons.profile.ProfileActivity
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.junit.Assert import org.junit.Assert
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.spy
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
import org.powermock.reflect.Whitebox import org.powermock.reflect.Whitebox
import org.robolectric.Robolectric import org.robolectric.Robolectric
@ -129,12 +133,14 @@ class BookmarkLocationFragmentUnitTests {
*/ */
@Test @Test
fun testInitNonEmpty() { fun testInitNonEmpty() {
runBlocking {
whenever(controller.loadFavoritesLocations()).thenReturn(mockBookmarkList) whenever(controller.loadFavoritesLocations()).thenReturn(mockBookmarkList)
val method: Method = val method: Method =
BookmarkLocationsFragment::class.java.getDeclaredMethod("initList") BookmarkLocationsFragment::class.java.getDeclaredMethod("initList")
method.isAccessible = true method.isAccessible = true
method.invoke(fragment) method.invoke(fragment)
} }
}
/** /**
* test onCreateView * test onCreateView
@ -168,7 +174,11 @@ class BookmarkLocationFragmentUnitTests {
*/ */
@Test @Test
@Throws(Exception::class) @Throws(Exception::class)
fun testOnResume() { fun testOnResume() = runBlocking {
fragment.onResume() val fragmentSpy = spy(fragment)
whenever(controller.loadFavoritesLocations()).thenReturn(mockBookmarkList)
fragmentSpy.onResume()
verify(fragmentSpy).initList()
} }
} }

View file

@ -96,7 +96,7 @@ class DeleteHelperTest {
).thenReturn("Media successfully deleted: Test Media Title") ).thenReturn("Media successfully deleted: Test Media Title")
val creatorName = "Creator" val creatorName = "Creator"
whenever(media.author).thenReturn("$creatorName") whenever(media.getAuthorOrUser()).thenReturn("$creatorName")
whenever(media.filename).thenReturn("Test file.jpg") whenever(media.filename).thenReturn("Test file.jpg")
val makeDeletion = deleteHelper.makeDeletion( val makeDeletion = deleteHelper.makeDeletion(
context, context,
@ -133,7 +133,7 @@ class DeleteHelperTest {
whenever(media.displayTitle).thenReturn("Test file") whenever(media.displayTitle).thenReturn("Test file")
whenever(media.filename).thenReturn("Test file.jpg") whenever(media.filename).thenReturn("Test file.jpg")
whenever(media.author).thenReturn("Creator (page does not exist)") whenever(media.getAuthorOrUser()).thenReturn("Creator (page does not exist)")
deleteHelper.makeDeletion(context, media, "Test reason")?.blockingGet() deleteHelper.makeDeletion(context, media, "Test reason")?.blockingGet()
} }
@ -148,7 +148,7 @@ class DeleteHelperTest {
.thenReturn(Observable.just(false)) .thenReturn(Observable.just(false))
whenever(media.displayTitle).thenReturn("Test file") whenever(media.displayTitle).thenReturn("Test file")
whenever(media.filename).thenReturn("Test file.jpg") whenever(media.filename).thenReturn("Test file.jpg")
whenever(media.author).thenReturn("Creator (page does not exist)") whenever(media.getAuthorOrUser()).thenReturn("Creator (page does not exist)")
deleteHelper.makeDeletion(context, media, "Test reason")?.blockingGet() deleteHelper.makeDeletion(context, media, "Test reason")?.blockingGet()
} }
@ -163,7 +163,7 @@ class DeleteHelperTest {
.thenReturn(Observable.just(true)) .thenReturn(Observable.just(true))
whenever(media.displayTitle).thenReturn("Test file") whenever(media.displayTitle).thenReturn("Test file")
whenever(media.filename).thenReturn("Test file.jpg") whenever(media.filename).thenReturn("Test file.jpg")
whenever(media.author).thenReturn("Creator (page does not exist)") whenever(media.getAuthorOrUser()).thenReturn("Creator (page does not exist)")
deleteHelper.makeDeletion(context, media, "Test reason")?.blockingGet() deleteHelper.makeDeletion(context, media, "Test reason")?.blockingGet()
} }
@ -221,7 +221,7 @@ class DeleteHelperTest {
whenever(media.displayTitle).thenReturn("Test file") whenever(media.displayTitle).thenReturn("Test file")
whenever(media.filename).thenReturn("Test file.jpg") whenever(media.filename).thenReturn("Test file.jpg")
whenever(media.author).thenReturn(null) whenever(media.getAuthorOrUser()).thenReturn(null)
deleteHelper.makeDeletion(context, media, "Test reason")?.blockingGet() deleteHelper.makeDeletion(context, media, "Test reason")?.blockingGet()
} }

View file

@ -8,6 +8,7 @@ import fr.free.nrw.commons.location.LatLng
import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType
import fr.free.nrw.commons.nearby.contract.NearbyParentFragmentContract import fr.free.nrw.commons.nearby.contract.NearbyParentFragmentContract
import fr.free.nrw.commons.nearby.presenter.NearbyParentFragmentPresenter import fr.free.nrw.commons.nearby.presenter.NearbyParentFragmentPresenter
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertFalse import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
@ -463,7 +464,9 @@ class NearbyParentFragmentPresenterTest {
nearbyPlacesInfo.searchLatLng = latestLocation nearbyPlacesInfo.searchLatLng = latestLocation
nearbyPlacesInfo.placeList = emptyList<Place>() nearbyPlacesInfo.placeList = emptyList<Place>()
whenever(bookmarkLocationsDao.allBookmarksLocations).thenReturn(Collections.emptyList()) runBlocking {
whenever(bookmarkLocationsDao.getAllBookmarksLocations()).thenReturn(Collections.emptyList())
}
nearbyPresenter.updateMapMarkers(nearbyPlacesInfo.placeList, latestLocation, null) nearbyPresenter.updateMapMarkers(nearbyPlacesInfo.placeList, latestLocation, null)
Mockito.verify(nearbyParentFragmentView).setProgressBarVisibility(false) Mockito.verify(nearbyParentFragmentView).setProgressBarVisibility(false)
} }