Merge branch 'main' into fix-empty-username

This commit is contained in:
Nicolas Raoul 2025-02-24 23:07:22 +09:00 committed by GitHub
commit b2407e7c4b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 708 additions and 950 deletions

View file

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

View file

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

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

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

View file

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

View file

@ -4,6 +4,7 @@ import android.app.Activity
import android.content.ContentProviderClient
import android.content.ContentResolver
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.view.inputmethod.InputMethodManager
import androidx.collection.LruCache
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.auth.SessionManager
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.customselector.database.NotForUploadStatusDao
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.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import java.util.Objects
import javax.inject.Named
import javax.inject.Singleton
@ -49,6 +52,11 @@ import javax.inject.Singleton
@Module
@Suppress("unused")
open class CommonsApplicationModule(private val applicationContext: Context) {
init {
appContext = applicationContext
}
@Provides
fun providesImageFileLoader(context: Context): ImageFileLoader =
ImageFileLoader(context)
@ -110,11 +118,6 @@ open class CommonsApplicationModule(private val applicationContext: Context) {
fun provideBookmarkContentProviderClient(context: Context): ContentProviderClient? =
context.contentResolver.acquireContentProviderClient(BuildConfig.BOOKMARK_AUTHORITY)
@Provides
@Named("bookmarksLocation")
fun provideBookmarkLocationContentProviderClient(context: Context): ContentProviderClient? =
context.contentResolver.acquireContentProviderClient(BuildConfig.BOOKMARK_LOCATIONS_AUTHORITY)
@Provides
@Named("bookmarksItem")
fun provideBookmarkItemContentProviderClient(context: Context): ContentProviderClient? =
@ -196,7 +199,10 @@ open class CommonsApplicationModule(private val applicationContext: Context) {
applicationContext,
AppDatabase::class.java,
"commons_room.db"
).addMigrations(MIGRATION_1_2).fallbackToDestructiveMigration().build()
).addMigrations(
MIGRATION_1_2,
MIGRATION_19_TO_20
).fallbackToDestructiveMigration().build()
@Provides
fun providesContributionsDao(appDatabase: AppDatabase): ContributionDao =
@ -206,6 +212,10 @@ open class CommonsApplicationModule(private val applicationContext: Context) {
fun providesPlaceDao(appDatabase: AppDatabase): PlaceDao =
appDatabase.PlaceDao()
@Provides
fun providesBookmarkLocationsDao(appDatabase: AppDatabase): BookmarkLocationsDao =
appDatabase.bookmarkLocationsDao()
@Provides
fun providesDepictDao(appDatabase: AppDatabase): DepictsDao =
appDatabase.DepictsDao()
@ -239,6 +249,9 @@ open class CommonsApplicationModule(private val applicationContext: Context) {
const val IO_THREAD: String = "io_thread"
const val MAIN_THREAD: String = "main_thread"
lateinit var appContext: Context
private set
val MIGRATION_1_2: Migration = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
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

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

View file

@ -68,7 +68,21 @@ class BottomSheetAdapter(
item.imageResourceId == R.drawable.ic_round_star_border_24px
) {
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
}
}

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

View file

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

View file

@ -85,6 +85,7 @@ import fr.free.nrw.commons.nearby.MarkerPlaceGroup
import fr.free.nrw.commons.nearby.NearbyController
import fr.free.nrw.commons.nearby.NearbyFilterSearchRecyclerViewAdapter
import fr.free.nrw.commons.nearby.NearbyFilterState
import fr.free.nrw.commons.nearby.NearbyUtil
import fr.free.nrw.commons.nearby.Place
import fr.free.nrw.commons.nearby.PlacesRepository
import fr.free.nrw.commons.nearby.WikidataFeedback
@ -664,21 +665,23 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
private fun initRvNearbyList() {
binding!!.bottomSheetNearby.rvNearbyList.layoutManager = LinearLayoutManager(context)
adapter = PlaceAdapter(
bookmarkLocationDao!!,
{ place: Place ->
bookmarkLocationsDao = bookmarkLocationDao,
scope = scope,
onPlaceClicked = { place: Place ->
moveCameraToPosition(
GeoPoint(place.location.latitude, place.location.longitude)
GeoPoint(
place.location.latitude,
place.location.longitude
)
)
Unit
},
{ place: Place?, isBookmarked: Boolean? ->
presenter!!.toggleBookmarkedStatus(place)
Unit
onBookmarkClicked = { place: Place?, _: Boolean? ->
presenter!!.toggleBookmarkedStatus(place, scope)
},
commonPlaceClickActions!!,
inAppCameraLocationPermissionLauncher,
galleryPickLauncherForResult,
cameraPickLauncherForResult
commonPlaceClickActions = commonPlaceClickActions,
inAppCameraLocationPermissionLauncher = inAppCameraLocationPermissionLauncher,
galleryPickLauncherForResult = galleryPickLauncherForResult,
cameraPickLauncherForResult = cameraPickLauncherForResult
)
binding!!.bottomSheetNearby.rvNearbyList.adapter = adapter
}
@ -2303,34 +2306,34 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
// TODO: Decide button text for fitting in the screen
(dataList as ArrayList<BottomSheetItem>).add(
BottomSheetItem(
fr.free.nrw.commons.R.drawable.ic_round_star_border_24px,
R.drawable.ic_round_star_border_24px,
""
)
)
(dataList as ArrayList<BottomSheetItem>).add(
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)
)
)
if (place.hasWikidataLink()) {
(dataList as ArrayList<BottomSheetItem>).add(
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)
)
)
}
(dataList as ArrayList<BottomSheetItem>).add(
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)
)
)
if (place.hasWikipediaLink()) {
(dataList as ArrayList<BottomSheetItem>).add(
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)
)
)
@ -2338,7 +2341,7 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
if (selectedPlace!!.hasCommonsLink()) {
(dataList as ArrayList<BottomSheetItem>).add(
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)
)
)
@ -2566,12 +2569,16 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
}
private fun updateBookmarkButtonImage(place: Place) {
val bookmarkIcon = if (bookmarkLocationDao!!.findBookmarkLocation(place)) {
fr.free.nrw.commons.R.drawable.ic_round_star_filled_24px
} else {
fr.free.nrw.commons.R.drawable.ic_round_star_border_24px
NearbyUtil.getBookmarkLocationExists(
bookmarkLocationDao,
place.getName(),
scope,
bottomSheetAdapter!!
)
}
bottomSheetAdapter!!.updateBookmarkIcon(bookmarkIcon)
private fun toggleBookmarkButtonImage() {
bottomSheetAdapter?.toggleBookmarkIcon()
}
override fun onAttach(context: Context) {
@ -2749,26 +2756,31 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
override fun onBottomSheetItemClick(view: View?, position: Int) {
val item = dataList?.get(position) ?: return // Null check for dataList
when (item.imageResourceId) {
fr.free.nrw.commons.R.drawable.ic_round_star_border_24px,
fr.free.nrw.commons.R.drawable.ic_round_star_filled_24px -> {
presenter?.toggleBookmarkedStatus(selectedPlace)
R.drawable.ic_round_star_border_24px -> {
presenter?.toggleBookmarkedStatus(selectedPlace, scope)
toggleBookmarkButtonImage()
}
R.drawable.ic_round_star_filled_24px -> {
presenter?.toggleBookmarkedStatus(selectedPlace, scope)
toggleBookmarkButtonImage()
selectedPlace?.let { updateBookmarkButtonImage(it) }
}
fr.free.nrw.commons.R.drawable.ic_directions_black_24dp -> {
R.drawable.ic_directions_black_24dp -> {
selectedPlace?.let {
Utils.handleGeoCoordinates(this.context, it.getLocation())
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 {
Utils.handleWebUrl(this.context, it)
}
}
fr.free.nrw.commons.R.drawable.ic_feedback_black_24dp -> {
R.drawable.ic_feedback_black_24dp -> {
selectedPlace?.let {
val intent = Intent(this.context, WikidataFeedback::class.java).apply {
putExtra("lat", it.location.latitude)
@ -2780,13 +2792,13 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
}
}
fr.free.nrw.commons.R.drawable.ic_wikipedia_logo_24dp -> {
R.drawable.ic_wikipedia_logo_24dp -> {
selectedPlace?.siteLinks?.wikipediaLink?.let {
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 {
Utils.handleWebUrl(this.context, it)
}
@ -2800,13 +2812,13 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(),
override fun onBottomSheetItemLongClick(view: View?, position: Int) {
val item = dataList!![position]
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)
fr.free.nrw.commons.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)
fr.free.nrw.commons.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)
fr.free.nrw.commons.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_round_star_border_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)
R.drawable.ic_directions_black_24dp -> getString(fr.free.nrw.commons.R.string.nearby_directions)
R.drawable.ic_wikidata_logo_24dp -> getString(fr.free.nrw.commons.R.string.nearby_wikidata)
R.drawable.ic_feedback_black_24dp -> getString(fr.free.nrw.commons.R.string.nearby_wikitalk)
R.drawable.ic_wikipedia_logo_24dp -> getString(fr.free.nrw.commons.R.string.nearby_wikipedia)
R.drawable.ic_commons_icon_vector -> getString(fr.free.nrw.commons.R.string.nearby_commons)
else -> "Long click"
}
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 androidx.activity.result.ActivityResultLauncher
import androidx.lifecycle.LifecycleCoroutineScope
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao
import fr.free.nrw.commons.nearby.Place
import fr.free.nrw.commons.nearby.placeAdapterDelegate
@ -9,6 +10,7 @@ import fr.free.nrw.commons.upload.categories.BaseDelegateAdapter
class PlaceAdapter(
bookmarkLocationsDao: BookmarkLocationsDao,
scope: LifecycleCoroutineScope? = null,
onPlaceClicked: ((Place) -> Unit)? = null,
onBookmarkClicked: (Place, Boolean) -> Unit,
commonPlaceClickActions: CommonPlaceClickActions,
@ -18,6 +20,7 @@ class PlaceAdapter(
) : BaseDelegateAdapter<Place>(
placeAdapterDelegate(
bookmarkLocationsDao,
scope,
onPlaceClicked,
commonPlaceClickActions.onCameraClicked(),
commonPlaceClickActions.onCameraLongPressed(),

View file

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

View file

@ -3,6 +3,7 @@
* 1917 Ekim Devrimi
* Envlh
* Gambollar
* GolyatGeri
* Gorizon
* Gırd
* Marmase
@ -199,7 +200,7 @@
<string name="navigation_item_review">Çım berze cı</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_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="upload_problem_exist">Muhtemel problemê nê resımi</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="navigation_item_login">Cı kewe</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_commons">Commons</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_mobile">Raya mobiliya biyo bar</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="wallpaper_set_successfully">Wallpaper eyar biyo!</string>
<string name="quiz">Quiz</string>

View file

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

View file

@ -876,6 +876,7 @@
<string name="account">Учётная запись</string>
<string name="vanish_account">Удалить учётную запись</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_copied_to_clipboard">Подпись скопирована в буфер обмена</string>
<string name="congratulations_all_pictures_in_this_album_have_been_either_uploaded_or_marked_as_not_for_upload">Поздравляем, все фотографии в этом альбоме либо загружены, либо помечены как не предназначенные для загрузки.</string>

View file

@ -7,6 +7,8 @@ import android.database.MatrixCursor
import android.database.sqlite.SQLiteDatabase
import android.net.Uri
import android.os.RemoteException
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import com.nhaarman.mockitokotlin2.any
import com.nhaarman.mockitokotlin2.anyOrNull
import com.nhaarman.mockitokotlin2.argumentCaptor
@ -18,36 +20,20 @@ import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.TestCommonsApplication
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsContentProvider.BASE_URI
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.db.AppDatabase
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 kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verifyNoInteractions
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@ -55,28 +41,11 @@ import org.robolectric.annotation.Config
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [21], application = TestCommonsApplication::class)
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 exampleLabel: Label
private lateinit var exampleUri: Uri
@ -89,10 +58,18 @@ class BookMarkLocationDaoTest {
exampleUri = Uri.parse("wikimedia/uri")
exampleLocation = LatLng(40.0, 51.4, 1f)
builder = Sitelinks.Builder()
builder.setWikipediaLink("wikipediaLink")
builder.setWikidataLink("wikidataLink")
builder.setCommonsLink("commonsLink")
database = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
AppDatabase::class.java
).allowMainThreadQueries().build()
bookmarkLocationsDao = database.bookmarkLocationsDao()
builder = Sitelinks.Builder().apply {
setWikipediaLink("wikipediaLink")
setWikidataLink("wikidataLink")
setCommonsLink("commonsLink")
}
examplePlaceBookmark =
Place(
@ -106,236 +83,75 @@ class BookMarkLocationDaoTest {
"picName",
false,
)
testObject = BookmarkLocationsDao { client }
}
@After
fun tearDown() {
database.close()
}
@Test
fun createTable() {
onCreate(database)
verify(database).execSQL(CREATE_TABLE_STATEMENT)
fun testForAddAndGetAllBookmarkLocations() = runBlocking {
bookmarkLocationsDao.addBookmarkLocation(examplePlaceBookmark.toBookmarksLocations())
val bookmarks = bookmarkLocationsDao.getAllBookmarksLocations()
assertEquals(1, bookmarks.size)
val retrievedBookmark = bookmarks.first()
assertEquals(examplePlaceBookmark.name, retrievedBookmark.locationName)
assertEquals(examplePlaceBookmark.language, retrievedBookmark.locationLanguage)
}
@Test
fun deleteTable() {
onDelete(database)
inOrder(database) {
verify(database).execSQL(DROP_TABLE_STATEMENT)
verify(database).execSQL(CREATE_TABLE_STATEMENT)
}
fun testFindBookmarkByNameForTrue() = runBlocking {
bookmarkLocationsDao.addBookmarkLocation(examplePlaceBookmark.toBookmarksLocations())
val exists = bookmarkLocationsDao.findBookmarkLocation(examplePlaceBookmark.name)
assertTrue(exists)
}
@Test
fun createFromCursor() {
createCursor(1).let { cursor ->
cursor.moveToFirst()
testObject.fromCursor(cursor).let {
assertEquals("en", it.language)
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)
}
}
fun testFindBookmarkByNameForFalse() = runBlocking {
bookmarkLocationsDao.addBookmarkLocation(examplePlaceBookmark.toBookmarksLocations())
val exists = bookmarkLocationsDao.findBookmarkLocation("xyz")
assertFalse(exists)
}
@Test
fun getAllLocationBookmarks() {
whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(createCursor(14))
fun testDeleteBookmark() = runBlocking {
val bookmarkLocation = examplePlaceBookmark.toBookmarksLocations()
bookmarkLocationsDao.addBookmarkLocation(bookmarkLocation)
var result = testObject.allBookmarksLocations
bookmarkLocationsDao.deleteBookmarkLocation(bookmarkLocation)
assertEquals(14, result.size)
}
@Test(expected = RuntimeException::class)
fun getAllLocationBookmarksTranslatesExceptions() {
whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenThrow(RemoteException(""))
testObject.allBookmarksLocations
val bookmarks = bookmarkLocationsDao.getAllBookmarksLocations()
assertTrue(bookmarks.isEmpty())
}
@Test
fun getAllLocationBookmarksReturnsEmptyList_emptyCursor() {
whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(createCursor(0))
assertTrue(testObject.allBookmarksLocations.isEmpty())
fun testUpdateBookmarkForTrue() = runBlocking {
val exists = bookmarkLocationsDao.updateBookmarkLocation(examplePlaceBookmark)
assertTrue(exists)
}
@Test
fun getAllLocationBookmarksReturnsEmptyList_nullCursor() {
whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(null)
assertTrue(testObject.allBookmarksLocations.isEmpty())
fun testUpdateBookmarkForFalse() = runBlocking {
val newBookmark = examplePlaceBookmark.toBookmarksLocations()
bookmarkLocationsDao.addBookmarkLocation(newBookmark)
val exists = bookmarkLocationsDao.updateBookmarkLocation(examplePlaceBookmark)
assertFalse(exists)
}
@Test
fun cursorsAreClosedAfterGetAllLocationBookmarksQuery() {
val mockCursor: Cursor = mock()
whenever(client.query(any(), any(), anyOrNull(), any(), anyOrNull())).thenReturn(mockCursor)
whenever(mockCursor.moveToFirst()).thenReturn(false)
fun testGetAllBookmarksLocationsPlace() = runBlocking {
val bookmarkLocation = examplePlaceBookmark.toBookmarksLocations()
bookmarkLocationsDao.addBookmarkLocation(bookmarkLocation)
testObject.allBookmarksLocations
verify(mockCursor).close()
}
@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)
}
}
val bookmarks = bookmarkLocationsDao.getAllBookmarksLocationsPlace()
assertEquals(1, bookmarks.size)
assertEquals(examplePlaceBookmark.name, bookmarks.first().name)
}
}

View file

@ -2,6 +2,7 @@ package fr.free.nrw.commons.bookmarks.locations
import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.nearby.Place
import kotlinx.coroutines.runBlocking
import org.junit.Assert
import org.junit.Before
import org.junit.Test
@ -19,10 +20,12 @@ class BookmarkLocationControllerTest {
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
whenever(bookmarkDao!!.allBookmarksLocations)
MockitoAnnotations.openMocks(this)
runBlocking {
whenever(bookmarkDao!!.getAllBookmarksLocationsPlace())
.thenReturn(mockBookmarkList)
}
}
/**
* 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
fun loadBookmarkedLocations() {
fun loadBookmarkedLocations() = runBlocking {
val bookmarkedLocations =
bookmarkLocationsController.loadFavoritesLocations()
Assert.assertEquals(2, bookmarkedLocations.size.toLong())

View file

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

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