Merge pull request #2 from commons-app/master
Update to latest upstream master
|
|
@ -33,8 +33,6 @@ after_success:
|
||||||
after_failure:
|
after_failure:
|
||||||
- echo '*** Connected Test Rsults ***'
|
- echo '*** Connected Test Rsults ***'
|
||||||
- w3m -dump ${TRAVIS_BUILD_DIR}/app/build/reports/androidTests/connected/*Test.html
|
- w3m -dump ${TRAVIS_BUILD_DIR}/app/build/reports/androidTests/connected/*Test.html
|
||||||
- echo '*** Lint Results ***'
|
|
||||||
- cat ${TRAVIS_BUILD_DIR}/app/build/reports/lint-results.xml
|
|
||||||
jdk:
|
jdk:
|
||||||
# - openjdk8 # not yet available
|
# - openjdk8 # not yet available
|
||||||
- oraclejdk8
|
- oraclejdk8
|
||||||
|
|
|
||||||
35
README.md
|
|
@ -4,11 +4,14 @@ The Wikimedia Commons Android app allows users to upload pictures from their And
|
||||||
|
|
||||||
Initially started by the Wikimedia Foundation, this app is now maintained by volunteers. Anyone is welcome to improve it, just choose among the [open issues](https://github.com/commons-app/apps-android-commons/issues) and send us a pull request :-)
|
Initially started by the Wikimedia Foundation, this app is now maintained by volunteers. Anyone is welcome to improve it, just choose among the [open issues](https://github.com/commons-app/apps-android-commons/issues) and send us a pull request :-)
|
||||||
|
|
||||||
|
We are currently applying for an [IEG renewal][15] to work on the app for the next 6 months. Feedback is very much welcomed.
|
||||||
|
|
||||||
<a href="https://f-droid.org/repository/browse/?fdid=fr.free.nrw.commons" target="_blank">
|
<a href="https://f-droid.org/repository/browse/?fdid=fr.free.nrw.commons" target="_blank">
|
||||||
<img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="90"/></a>
|
<img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="90"/></a>
|
||||||
<a href="https://play.google.com/store/apps/details?id=fr.free.nrw.commons" target="_blank">
|
<a href="https://play.google.com/store/apps/details?id=fr.free.nrw.commons" target="_blank">
|
||||||
<img src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png" alt="Get it on Google Play" height="90"/></a>
|
<img src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png" alt="Get it on Google Play" height="90"/></a>
|
||||||
|
|
||||||
|
|
||||||
## Develop with Android Studio or IntelliJ ##
|
## Develop with Android Studio or IntelliJ ##
|
||||||
|
|
||||||
[Download Android Studio][1] (recommended) or [IntelliJ][2].
|
[Download Android Studio][1] (recommended) or [IntelliJ][2].
|
||||||
|
|
@ -80,6 +83,32 @@ Captured files are not currently stored within the app, but are passed by conten
|
||||||
|
|
||||||
Thumbnail images are not currently cached.
|
Thumbnail images are not currently cached.
|
||||||
|
|
||||||
|
## Volunteers welcome! ##
|
||||||
|
|
||||||
|
We are always looking for volunteers, feel free to step in! It is very easy:
|
||||||
|
|
||||||
|
1. Fork the repository and clone it to your computer, then follow the build instructions above.
|
||||||
|
2. Choose an [unassigned issue](https://github.com/commons-app/apps-android-commons/issues?q=is%3Aopen+is%3Aissue+no%3Aassignee) that sounds interesting to you.
|
||||||
|
3. Read the issue's comments to make sure you understand what is the bug, or what feature is being proposed.
|
||||||
|
4. Write a "I start working on this" comment on the issue
|
||||||
|
5. Write the code :-)
|
||||||
|
6. Commit and push
|
||||||
|
7. Go to your fork's Github webpage, select the "Pull Requests" tab and click "create a pull request", as a comment, write something like "Fix for issue #12345 crash when rotating screen", then submit the pull request.
|
||||||
|
8. Within a few hours or days, a core developer will review your patch, and either merge it or suggest a few corrections.
|
||||||
|
9. If you change your mind, or if it is too difficult, no problem, just write "Sorry I don't work on this anymore" on the issue, if possible including feedback (for instance what approaches failed) and ideas.
|
||||||
|
|
||||||
|
Thanks a lot!
|
||||||
|
|
||||||
|
## Translating the app ##
|
||||||
|
|
||||||
|
Thanks to the translation work of many volunteers this app is available in a multitude of languages.
|
||||||
|
|
||||||
|
Translation of the text content of the Wikimedia Commons Android app happens on the [Commons Android App project][10] on [translatewiki.net][11]. If you want to help translate the app please create an account there (to get "translate rights" edit 20 [random keys][13] or ask in their [chat][14]).
|
||||||
|
|
||||||
|
The translations from the translatewiki project are [periodically committed directly to this project][12] by the translatewiki team and later released with the normal updates to the Play Store.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[1]: https://developer.android.com/studio/index.html
|
[1]: https://developer.android.com/studio/index.html
|
||||||
[2]: http://www.jetbrains.com/idea/download/index.html
|
[2]: http://www.jetbrains.com/idea/download/index.html
|
||||||
|
|
@ -90,3 +119,9 @@ Thumbnail images are not currently cached.
|
||||||
[7]: https://github.com/commons-app/apps-android-commons/issues
|
[7]: https://github.com/commons-app/apps-android-commons/issues
|
||||||
[8]: https://play.google.com/store/apps/details?id=fr.free.nrw.commons
|
[8]: https://play.google.com/store/apps/details?id=fr.free.nrw.commons
|
||||||
[9]: https://commons-app.github.io/
|
[9]: https://commons-app.github.io/
|
||||||
|
[10]: https://translatewiki.net/w/i.php?title=Special:Translate&group=commons-android
|
||||||
|
[11]: https://translatewiki.net
|
||||||
|
[12]: https://github.com/commons-app/apps-android-commons/commits/master?author=translatewiki
|
||||||
|
[13]: https://translatewiki.net/wiki/Special:TranslationStash?
|
||||||
|
[14]: https://translatewiki.net/wiki/Special:WebChat
|
||||||
|
[15]: https://meta.wikimedia.org/wiki/Grants:Project/Improve_%27Upload_to_Commons%27_Android_App/Renewal
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'me.tatarka.retrolambda'
|
||||||
apply plugin: 'jacoco-android'
|
apply plugin: 'jacoco-android'
|
||||||
apply from: 'quality.gradle'
|
apply from: 'quality.gradle'
|
||||||
apply plugin: 'com.getkeepsafe.dexcount'
|
apply plugin: 'com.getkeepsafe.dexcount'
|
||||||
|
|
@ -75,6 +76,11 @@ android {
|
||||||
abortOnError false
|
abortOnError false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
//FIXME: Temporary fix for https://github.com/commons-app/apps-android-commons/issues/709
|
//FIXME: Temporary fix for https://github.com/commons-app/apps-android-commons/issues/709
|
||||||
configurations.all {
|
configurations.all {
|
||||||
resolutionStrategy.force 'com.android.support:support-annotations:25.2.0'
|
resolutionStrategy.force 'com.android.support:support-annotations:25.2.0'
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ public class NearbyControllerTest {
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup() {
|
public void setup() {
|
||||||
instrumentationContext = InstrumentationRegistry.getContext();
|
instrumentationContext = InstrumentationRegistry.getTargetContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test public void testNullAttractions() {
|
@Test public void testNullAttractions() {
|
||||||
|
|
|
||||||
|
|
@ -197,8 +197,8 @@ public class CommonsApplication extends Application {
|
||||||
|
|
||||||
AccountManager accountManager = AccountManager.get(this);
|
AccountManager accountManager = AccountManager.get(this);
|
||||||
Account[] allAccounts = accountManager.getAccountsByType(AccountUtil.accountType());
|
Account[] allAccounts = accountManager.getAccountsByType(AccountUtil.accountType());
|
||||||
for (int index = 0; index < allAccounts.length; index++) {
|
for (Account allAccount : allAccounts) {
|
||||||
accountManager.removeAccount(allAccounts[index], null, null);
|
accountManager.removeAccount(allAccount, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: fix preference manager
|
//TODO: fix preference manager
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package fr.free.nrw.commons;
|
package fr.free.nrw.commons;
|
||||||
|
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
public class License {
|
public class License {
|
||||||
String key;
|
String key;
|
||||||
String template;
|
String template;
|
||||||
|
|
@ -36,7 +38,7 @@ public class License {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUrl(String language) {
|
public @Nullable String getUrl(String language) {
|
||||||
if (url == null) {
|
if (url == null) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package fr.free.nrw.commons;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
@ -12,6 +13,8 @@ import java.util.Map;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
|
|
||||||
public class Media implements Parcelable {
|
public class Media implements Parcelable {
|
||||||
|
|
||||||
public static Creator<Media> CREATOR = new Creator<Media>() {
|
public static Creator<Media> CREATOR = new Creator<Media>() {
|
||||||
|
|
@ -99,7 +102,7 @@ public class Media implements Parcelable {
|
||||||
this.dateCreated = date;
|
this.dateCreated = date;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Date getDateUploaded() {
|
public @Nullable Date getDateUploaded() {
|
||||||
return dateUploaded;
|
return dateUploaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,11 +138,11 @@ public class Media implements Parcelable {
|
||||||
this.license = license;
|
this.license = license;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getCoordinates() {
|
public @Nullable LatLng getCoordinates() {
|
||||||
return coordinates;
|
return coordinates;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCoordinates(String coordinates) {
|
public void setCoordinates(LatLng coordinates) {
|
||||||
this.coordinates = coordinates;
|
this.coordinates = coordinates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -150,11 +153,11 @@ public class Media implements Parcelable {
|
||||||
protected String description; // monolingual description on input...
|
protected String description; // monolingual description on input...
|
||||||
protected long dataLength;
|
protected long dataLength;
|
||||||
protected Date dateCreated;
|
protected Date dateCreated;
|
||||||
protected Date dateUploaded;
|
protected @Nullable Date dateUploaded;
|
||||||
protected int width;
|
protected int width;
|
||||||
protected int height;
|
protected int height;
|
||||||
protected String license;
|
protected String license;
|
||||||
private String coordinates;
|
private @Nullable LatLng coordinates;
|
||||||
protected String creator;
|
protected String creator;
|
||||||
protected ArrayList<String> categories; // as loaded at runtime?
|
protected ArrayList<String> categories; // as loaded at runtime?
|
||||||
protected Map<String, String> descriptions; // multilingual descriptions as loaded
|
protected Map<String, String> descriptions; // multilingual descriptions as loaded
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
package fr.free.nrw.commons;
|
package fr.free.nrw.commons;
|
||||||
|
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.mediawiki.api.ApiResult;
|
||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
import org.w3c.dom.Element;
|
import org.w3c.dom.Element;
|
||||||
import org.w3c.dom.Node;
|
import org.w3c.dom.Node;
|
||||||
|
|
@ -38,7 +41,7 @@ public class MediaDataExtractor {
|
||||||
private Map<String, String> descriptions;
|
private Map<String, String> descriptions;
|
||||||
private Date date;
|
private Date date;
|
||||||
private String license;
|
private String license;
|
||||||
private String coordinates;
|
private @Nullable LatLng coordinates;
|
||||||
private LicenseList licenseList;
|
private LicenseList licenseList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -112,7 +115,7 @@ public class MediaDataExtractor {
|
||||||
if (coordinateTemplateNode != null) {
|
if (coordinateTemplateNode != null) {
|
||||||
coordinates = getCoordinates(coordinateTemplateNode);
|
coordinates = getCoordinates(coordinateTemplateNode);
|
||||||
} else {
|
} else {
|
||||||
coordinates = "No coordinates found";
|
coordinates = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -236,22 +239,20 @@ public class MediaDataExtractor {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts the coordinates from the template and returns them as pretty formatted string.
|
* Extracts the coordinates from the template.
|
||||||
* Loops over the children of the coordinate template:
|
* Loops over the children of the coordinate template:
|
||||||
* {{Location|47.50111007666667|19.055700301944444}}
|
* {{Location|47.50111007666667|19.055700301944444}}
|
||||||
* and extracts the latitude and longitude as a pretty string.
|
* and extracts the latitude and longitude.
|
||||||
*
|
*
|
||||||
* @param parentNode The node of the coordinates template.
|
* @param parentNode The node of the coordinates template.
|
||||||
* @return Pretty formatted coordinates.
|
* @return Extracted coordinates.
|
||||||
* @throws IOException Parsing failed.
|
* @throws IOException Parsing failed.
|
||||||
*/
|
*/
|
||||||
private String getCoordinates(Node parentNode) throws IOException {
|
private LatLng getCoordinates(Node parentNode) throws IOException {
|
||||||
NodeList childNodes = parentNode.getChildNodes();
|
NodeList childNodes = parentNode.getChildNodes();
|
||||||
double latitudeText = Double.parseDouble(childNodes.item(1).getTextContent());
|
double latitudeText = Double.parseDouble(childNodes.item(1).getTextContent());
|
||||||
double longitudeText = Double.parseDouble(childNodes.item(2).getTextContent());
|
double longitudeText = Double.parseDouble(childNodes.item(2).getTextContent());
|
||||||
LatLng coordinates = new LatLng(latitudeText, longitudeText, 0);
|
return new LatLng(latitudeText, longitudeText, 0);
|
||||||
|
|
||||||
return coordinates.getPrettyCoordinateString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract a dictionary of multilingual texts from a subset of the parse tree.
|
// Extract a dictionary of multilingual texts from a subset of the parse tree.
|
||||||
|
|
|
||||||
|
|
@ -27,12 +27,7 @@ public class WelcomeActivity extends BaseActivity {
|
||||||
|
|
||||||
pager.setAdapter(adapter);
|
pager.setAdapter(adapter);
|
||||||
indicator.setViewPager(pager);
|
indicator.setViewPager(pager);
|
||||||
adapter.setCallback(new WelcomePagerAdapter.Callback() {
|
adapter.setCallback(this::finish);
|
||||||
@Override
|
|
||||||
public void onYesClicked() {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,9 @@ import fr.free.nrw.commons.PageTitle;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
import static android.view.KeyEvent.KEYCODE_ENTER;
|
||||||
|
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
|
||||||
|
|
||||||
|
|
||||||
public class LoginActivity extends AccountAuthenticatorActivity {
|
public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
|
|
||||||
|
|
@ -63,18 +66,8 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
twoFactorEdit.addTextChangedListener(textWatcher);
|
twoFactorEdit.addTextChangedListener(textWatcher);
|
||||||
passwordEdit.setOnEditorActionListener(newLoginInputActionListener());
|
passwordEdit.setOnEditorActionListener(newLoginInputActionListener());
|
||||||
|
|
||||||
loginButton.setOnClickListener(new View.OnClickListener() {
|
loginButton.setOnClickListener(this::performLogin);
|
||||||
@Override
|
signupButton.setOnClickListener(this::signUp);
|
||||||
public void onClick(View v) {
|
|
||||||
performLogin();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
signupButton.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
signUp(v);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class LoginTextWatcher implements TextWatcher {
|
private class LoginTextWatcher implements TextWatcher {
|
||||||
|
|
@ -98,20 +91,17 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
private TextView.OnEditorActionListener newLoginInputActionListener() {
|
private TextView.OnEditorActionListener newLoginInputActionListener() {
|
||||||
return new TextView.OnEditorActionListener() {
|
return (textView, actionId, keyEvent) -> {
|
||||||
@Override
|
if (loginButton.isEnabled()) {
|
||||||
public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
|
if (actionId == IME_ACTION_DONE) {
|
||||||
if (loginButton.isEnabled()) {
|
performLogin(textView);
|
||||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
return true;
|
||||||
performLogin();
|
} else if ((keyEvent != null) && keyEvent.getKeyCode() == KEYCODE_ENTER) {
|
||||||
return true;
|
performLogin(textView);
|
||||||
} else if ((keyEvent != null) && keyEvent.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
|
return true;
|
||||||
performLogin();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,7 +132,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void performLogin() {
|
private void performLogin(View view) {
|
||||||
Timber.d("Login to start!");
|
Timber.d("Login to start!");
|
||||||
LoginTask task = getLoginTask();
|
LoginTask task = getLoginTask();
|
||||||
task.execute();
|
task.execute();
|
||||||
|
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
package fr.free.nrw.commons.category;
|
|
||||||
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.BaseAdapter;
|
|
||||||
import android.widget.CheckedTextView;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.R;
|
|
||||||
|
|
||||||
public class CategoriesAdapter extends BaseAdapter {
|
|
||||||
|
|
||||||
private LayoutInflater mInflater;
|
|
||||||
|
|
||||||
private ArrayList<CategorizationFragment.CategoryItem> items;
|
|
||||||
|
|
||||||
public CategoriesAdapter(Context context, ArrayList<CategorizationFragment.CategoryItem> items) {
|
|
||||||
this.items = items;
|
|
||||||
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getCount() {
|
|
||||||
return items.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getItem(int i) {
|
|
||||||
return items.get(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArrayList<CategorizationFragment.CategoryItem> getItems() {
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setItems(ArrayList<CategorizationFragment.CategoryItem> items) {
|
|
||||||
this.items = items;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getItemId(int i) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View getView(int i, View view, ViewGroup viewGroup) {
|
|
||||||
CheckedTextView checkedView;
|
|
||||||
|
|
||||||
if(view == null) {
|
|
||||||
checkedView = (CheckedTextView) mInflater.inflate(R.layout.layout_categories_item, null);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
checkedView = (CheckedTextView) view;
|
|
||||||
}
|
|
||||||
|
|
||||||
CategorizationFragment.CategoryItem item = (CategorizationFragment.CategoryItem) this.getItem(i);
|
|
||||||
checkedView.setChecked(item.selected);
|
|
||||||
checkedView.setText(item.name);
|
|
||||||
checkedView.setTag(i);
|
|
||||||
|
|
||||||
return checkedView;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package fr.free.nrw.commons.category;
|
||||||
|
|
||||||
|
import com.pedrogomez.renderers.ListAdapteeCollection;
|
||||||
|
import com.pedrogomez.renderers.RVRendererAdapter;
|
||||||
|
import com.pedrogomez.renderers.RendererBuilder;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
class CategoriesAdapterFactory {
|
||||||
|
private final CategoriesRenderer.CategoryClickedListener listener;
|
||||||
|
|
||||||
|
CategoriesAdapterFactory(CategoriesRenderer.CategoryClickedListener listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RVRendererAdapter<CategoryItem> create(List<CategoryItem> placeList) {
|
||||||
|
RendererBuilder<CategoryItem> builder = new RendererBuilder<CategoryItem>()
|
||||||
|
.bind(CategoryItem.class, new CategoriesRenderer(listener));
|
||||||
|
ListAdapteeCollection<CategoryItem> collection = new ListAdapteeCollection<>(
|
||||||
|
placeList != null ? placeList : Collections.<CategoryItem>emptyList());
|
||||||
|
return new RVRendererAdapter<>(builder, collection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
package fr.free.nrw.commons.category;
|
||||||
|
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.CheckedTextView;
|
||||||
|
|
||||||
|
import com.pedrogomez.renderers.Renderer;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
|
||||||
|
class CategoriesRenderer extends Renderer<CategoryItem> {
|
||||||
|
@BindView(R.id.tvName) CheckedTextView checkedView;
|
||||||
|
private final CategoryClickedListener listener;
|
||||||
|
|
||||||
|
CategoriesRenderer(CategoryClickedListener listener) {
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected View inflate(LayoutInflater layoutInflater, ViewGroup viewGroup) {
|
||||||
|
return layoutInflater.inflate(R.layout.layout_categories_item, viewGroup, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setUpView(View view) {
|
||||||
|
ButterKnife.bind(this, view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void hookListeners(View view) {
|
||||||
|
view.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
CategoryItem item = getContent();
|
||||||
|
item.setSelected(!item.isSelected());
|
||||||
|
checkedView.setChecked(item.isSelected());
|
||||||
|
if (listener != null) {
|
||||||
|
listener.categoryClicked(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render() {
|
||||||
|
CategoryItem item = getContent();
|
||||||
|
checkedView.setChecked(item.isSelected());
|
||||||
|
checkedView.setText(item.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CategoryClickedListener {
|
||||||
|
void categoryClicked(CategoryItem item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,36 +1,33 @@
|
||||||
package fr.free.nrw.commons.category;
|
package fr.free.nrw.commons.category;
|
||||||
|
|
||||||
import android.content.ContentProviderClient;
|
import android.content.ContentProviderClient;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.support.v7.app.AlertDialog;
|
import android.support.v7.app.AlertDialog;
|
||||||
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.CheckedTextView;
|
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.ListView;
|
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.pedrogomez.renderers.ListAdapteeCollection;
|
||||||
|
import com.pedrogomez.renderers.RVRendererAdapter;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
|
|
@ -40,90 +37,191 @@ import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.category.CategoriesRenderer.CategoryClickedListener;
|
||||||
import fr.free.nrw.commons.upload.MwVolleyApi;
|
import fr.free.nrw.commons.upload.MwVolleyApi;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
import static android.view.KeyEvent.ACTION_UP;
|
||||||
|
import static android.view.KeyEvent.KEYCODE_BACK;
|
||||||
|
import static fr.free.nrw.commons.category.CategoryContentProvider.AUTHORITY;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the category suggestion and selection screen. Category search is initiated here.
|
* Displays the category suggestion and selection screen. Category search is initiated here.
|
||||||
*/
|
*/
|
||||||
public class CategorizationFragment extends Fragment {
|
public class CategorizationFragment extends Fragment implements CategoryClickedListener {
|
||||||
public interface OnCategoriesSaveHandler {
|
public static final int SEARCH_CATS_LIMIT = 25;
|
||||||
void onCategoriesSave(ArrayList<String> categories);
|
|
||||||
}
|
|
||||||
|
|
||||||
ListView categoriesList;
|
@BindView(R.id.categoriesListBox)
|
||||||
protected EditText categoriesFilter;
|
RecyclerView categoriesList;
|
||||||
|
@BindView(R.id.categoriesSearchBox)
|
||||||
|
EditText categoriesFilter;
|
||||||
|
@BindView(R.id.categoriesSearchInProgress)
|
||||||
ProgressBar categoriesSearchInProgress;
|
ProgressBar categoriesSearchInProgress;
|
||||||
|
@BindView(R.id.categoriesNotFound)
|
||||||
TextView categoriesNotFoundView;
|
TextView categoriesNotFoundView;
|
||||||
|
@BindView(R.id.categoriesExplanation)
|
||||||
TextView categoriesSkip;
|
TextView categoriesSkip;
|
||||||
private CategoryTextWatcher textWatcher = new CategoryTextWatcher();
|
|
||||||
|
|
||||||
CategoriesAdapter categoriesAdapter;
|
|
||||||
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(2);
|
|
||||||
|
|
||||||
|
private RVRendererAdapter<CategoryItem> categoriesAdapter;
|
||||||
private OnCategoriesSaveHandler onCategoriesSaveHandler;
|
private OnCategoriesSaveHandler onCategoriesSaveHandler;
|
||||||
|
private HashMap<String, ArrayList<String>> categoriesCache;
|
||||||
protected HashMap<String, ArrayList<String>> categoriesCache;
|
|
||||||
|
|
||||||
private ArrayList<String> selectedCategories = new ArrayList<>();
|
private ArrayList<String> selectedCategories = new ArrayList<>();
|
||||||
|
private ContentProviderClient client;
|
||||||
|
private PrefixUpdater prefixUpdaterSub;
|
||||||
|
private MethodAUpdater methodAUpdaterSub;
|
||||||
|
private final CategoryTextWatcher textWatcher = new CategoryTextWatcher();
|
||||||
|
private final CategoriesAdapterFactory adapterFactory = new CategoriesAdapterFactory(this);
|
||||||
|
private final ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(2);
|
||||||
|
private final ArrayList<String> titleCatItems = new ArrayList<>();
|
||||||
|
private final CountDownLatch mergeLatch = new CountDownLatch(1);
|
||||||
// LHS guarantees ordered insertions, allowing for prioritized method A results
|
// LHS guarantees ordered insertions, allowing for prioritized method A results
|
||||||
private final Set<String> results = new LinkedHashSet<>();
|
private final Set<String> results = new LinkedHashSet<>();
|
||||||
PrefixUpdater prefixUpdaterSub;
|
|
||||||
MethodAUpdater methodAUpdaterSub;
|
|
||||||
|
|
||||||
private final ArrayList<String> titleCatItems = new ArrayList<>();
|
@SuppressWarnings("unchecked")
|
||||||
final CountDownLatch mergeLatch = new CountDownLatch(1);
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
View rootView = inflater.inflate(R.layout.fragment_categorization, container, false);
|
||||||
|
ButterKnife.bind(this, rootView);
|
||||||
|
|
||||||
private ContentProviderClient client;
|
categoriesList.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
|
||||||
protected final static int SEARCH_CATS_LIMIT = 25;
|
categoriesSkip.setOnClickListener(view -> {
|
||||||
|
getActivity().onBackPressed();
|
||||||
|
getActivity().finish();
|
||||||
|
});
|
||||||
|
|
||||||
public static class CategoryItem implements Parcelable {
|
ArrayList<CategoryItem> items;
|
||||||
public String name;
|
if (savedInstanceState == null) {
|
||||||
public boolean selected;
|
items = new ArrayList<>();
|
||||||
|
categoriesCache = new HashMap<>();
|
||||||
public static Creator<CategoryItem> CREATOR = new Creator<CategoryItem>() {
|
} else {
|
||||||
@Override
|
items = savedInstanceState.getParcelableArrayList("currentCategories");
|
||||||
public CategoryItem createFromParcel(Parcel parcel) {
|
categoriesCache = (HashMap<String, ArrayList<String>>) savedInstanceState
|
||||||
return new CategoryItem(parcel);
|
.getSerializable("categoriesCache");
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CategoryItem[] newArray(int i) {
|
|
||||||
return new CategoryItem[0];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public CategoryItem(String name, boolean selected) {
|
|
||||||
this.name = name;
|
|
||||||
this.selected = selected;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public CategoryItem(Parcel in) {
|
categoriesAdapter = adapterFactory.create(items);
|
||||||
name = in.readString();
|
categoriesList.setAdapter(categoriesAdapter);
|
||||||
selected = in.readInt() == 1;
|
categoriesFilter.addTextChangedListener(textWatcher);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
startUpdatingCategoryList();
|
||||||
public int describeContents() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
return rootView;
|
||||||
public void writeToParcel(Parcel parcel, int flags) {
|
}
|
||||||
parcel.writeString(name);
|
|
||||||
parcel.writeInt(selected ? 1 : 0);
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
menu.clear();
|
||||||
|
inflater.inflate(R.menu.fragment_categorization, menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
|
||||||
|
View rootView = getView();
|
||||||
|
if (rootView != null) {
|
||||||
|
rootView.setFocusableInTouchMode(true);
|
||||||
|
rootView.requestFocus();
|
||||||
|
rootView.setOnKeyListener((v, keyCode, event) -> {
|
||||||
|
if (event.getAction() == ACTION_UP && keyCode == KEYCODE_BACK) {
|
||||||
|
backButtonDialog();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
categoriesFilter.removeTextChangedListener(textWatcher);
|
||||||
|
super.onDestroyView();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
client.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
int itemCount = categoriesAdapter.getItemCount();
|
||||||
|
ArrayList<CategoryItem> items = new ArrayList<>(itemCount);
|
||||||
|
for (int i = 0; i < itemCount; i++) {
|
||||||
|
items.add(categoriesAdapter.getItem(i));
|
||||||
|
}
|
||||||
|
outState.putParcelableArrayList("currentCategories", items);
|
||||||
|
outState.putSerializable("categoriesCache", categoriesCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem menuItem) {
|
||||||
|
switch (menuItem.getItemId()) {
|
||||||
|
case R.id.menu_save_categories:
|
||||||
|
|
||||||
|
int numberSelected = 0;
|
||||||
|
|
||||||
|
selectedCategories = new ArrayList<>();
|
||||||
|
int count = categoriesAdapter.getItemCount();
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
CategoryItem item = categoriesAdapter.getItem(i);
|
||||||
|
if (item.isSelected()) {
|
||||||
|
selectedCategories.add(item.getName());
|
||||||
|
numberSelected++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//If no categories selected, display warning to user
|
||||||
|
if (numberSelected == 0) {
|
||||||
|
new AlertDialog.Builder(getActivity())
|
||||||
|
.setMessage("Images without categories are rarely usable. "
|
||||||
|
+ "Are you sure you want to submit without selecting "
|
||||||
|
+ "categories?")
|
||||||
|
.setTitle("No Categories Selected")
|
||||||
|
.setPositiveButton("No, go back", (dialog, id) -> {
|
||||||
|
//Exit menuItem so user can select their categories
|
||||||
|
})
|
||||||
|
.setNegativeButton("Yes, submit", (dialog, id) -> {
|
||||||
|
//Proceed to submission
|
||||||
|
onCategoriesSaveHandler.onCategoriesSave(selectedCategories);
|
||||||
|
})
|
||||||
|
.create()
|
||||||
|
.show();
|
||||||
|
} else {
|
||||||
|
//Proceed to submission
|
||||||
|
onCategoriesSaveHandler.onCategoriesSave(selectedCategories);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(menuItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
onCategoriesSaveHandler = (OnCategoriesSaveHandler) getActivity();
|
||||||
|
getActivity().setTitle(R.string.categories_activity_title);
|
||||||
|
client = getActivity().getContentResolver().acquireContentProviderClient(AUTHORITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HashMap<String, ArrayList<String>> getCategoriesCache() {
|
||||||
|
return categoriesCache;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves category suggestions from title input
|
* Retrieves category suggestions from title input
|
||||||
|
*
|
||||||
* @return a list containing title-related categories
|
* @return a list containing title-related categories
|
||||||
*/
|
*/
|
||||||
protected ArrayList<String> titleCatQuery() {
|
private ArrayList<String> titleCatQuery() {
|
||||||
|
|
||||||
TitleCategories titleCategoriesSub;
|
TitleCategories titleCategoriesSub;
|
||||||
|
|
||||||
//Retrieve the title that was saved when user tapped submit icon
|
//Retrieve the title that was saved when user tapped submit icon
|
||||||
|
|
@ -157,37 +255,42 @@ public class CategorizationFragment extends Fragment {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves recently-used categories
|
* Retrieves recently-used categories
|
||||||
|
*
|
||||||
* @return a list containing recent categories
|
* @return a list containing recent categories
|
||||||
*/
|
*/
|
||||||
protected ArrayList<String> recentCatQuery() {
|
private ArrayList<String> recentCatQuery() {
|
||||||
ArrayList<String> items = new ArrayList<>();
|
ArrayList<String> items = new ArrayList<>();
|
||||||
|
Cursor cursor = null;
|
||||||
try {
|
try {
|
||||||
Cursor cursor = client.query(
|
cursor = client.query(
|
||||||
CategoryContentProvider.BASE_URI,
|
CategoryContentProvider.BASE_URI,
|
||||||
Category.Table.ALL_FIELDS,
|
Category.Table.ALL_FIELDS,
|
||||||
null,
|
null,
|
||||||
new String[]{},
|
new String[]{},
|
||||||
Category.Table.COLUMN_LAST_USED + " DESC");
|
Category.Table.COLUMN_LAST_USED + " DESC");
|
||||||
// fixme add a limit on the original query instead of falling out of the loop?
|
// fixme add a limit on the original query instead of falling out of the loop?
|
||||||
while (cursor.moveToNext() && cursor.getPosition() < SEARCH_CATS_LIMIT) {
|
while (cursor != null && cursor.moveToNext()
|
||||||
|
&& cursor.getPosition() < SEARCH_CATS_LIMIT) {
|
||||||
Category cat = Category.fromCursor(cursor);
|
Category cat = Category.fromCursor(cursor);
|
||||||
items.add(cat.getName());
|
items.add(cat.getName());
|
||||||
}
|
}
|
||||||
cursor.close();
|
} catch (RemoteException e) {
|
||||||
}
|
|
||||||
catch (RemoteException e) {
|
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
} finally {
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merges nearby categories, categories suggested based on title, and recent categories... without duplicates.
|
* Merges nearby categories, categories suggested based on title, and recent categories...
|
||||||
|
* without duplicates.
|
||||||
|
*
|
||||||
* @return a list containing merged categories
|
* @return a list containing merged categories
|
||||||
*/
|
*/
|
||||||
protected ArrayList<String> mergeItems() {
|
ArrayList<String> mergeItems() {
|
||||||
|
|
||||||
Set<String> mergedItems = new LinkedHashSet<>();
|
Set<String> mergedItems = new LinkedHashSet<>();
|
||||||
|
|
||||||
Timber.d("Calling APIs for GPS cats, title cats and recent cats...");
|
Timber.d("Calling APIs for GPS cats, title cats and recent cats...");
|
||||||
|
|
@ -213,8 +316,9 @@ public class CategorizationFragment extends Fragment {
|
||||||
Timber.d("Adding title items: %s", titleItems);
|
Timber.d("Adding title items: %s", titleItems);
|
||||||
mergedItems.addAll(recentItems);
|
mergedItems.addAll(recentItems);
|
||||||
Timber.d("Adding recent items: %s", recentItems);
|
Timber.d("Adding recent items: %s", recentItems);
|
||||||
|
|
||||||
//Needs to be an ArrayList and not a List unless we want to modify a big portion of preexisting code
|
// Needs to be an ArrayList and not a List unless we want to modify a big portion
|
||||||
|
// of preexisting code
|
||||||
ArrayList<String> mergedItemsList = new ArrayList<>(mergedItems);
|
ArrayList<String> mergedItemsList = new ArrayList<>(mergedItems);
|
||||||
|
|
||||||
Timber.d("Merged item list: %s", mergedItemsList);
|
Timber.d("Merged item list: %s", mergedItemsList);
|
||||||
|
|
@ -223,18 +327,20 @@ public class CategorizationFragment extends Fragment {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays categories found to the user as they type in the search box
|
* Displays categories found to the user as they type in the search box
|
||||||
|
*
|
||||||
* @param categories a list of all categories found for the search string
|
* @param categories a list of all categories found for the search string
|
||||||
* @param filter the search string
|
* @param filter the search string
|
||||||
*/
|
*/
|
||||||
protected void setCatsAfterAsync(ArrayList<String> categories, String filter) {
|
private void setCatsAfterAsync(ArrayList<String> categories, String filter) {
|
||||||
|
|
||||||
if (getActivity() != null) {
|
if (getActivity() != null) {
|
||||||
ArrayList<CategoryItem> items = new ArrayList<>();
|
ArrayList<CategoryItem> items = new ArrayList<>();
|
||||||
HashSet<String> existingKeys = new HashSet<>();
|
HashSet<String> existingKeys = new HashSet<>();
|
||||||
for (CategoryItem item : categoriesAdapter.getItems()) {
|
int count = categoriesAdapter.getItemCount();
|
||||||
if (item.selected) {
|
for (int i = 0; i < count; i++) {
|
||||||
|
CategoryItem item = categoriesAdapter.getItem(i);
|
||||||
|
if (item.isSelected()) {
|
||||||
items.add(item);
|
items.add(item);
|
||||||
existingKeys.add(item.name);
|
existingKeys.add(item.getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (String category : categories) {
|
for (String category : categories) {
|
||||||
|
|
@ -243,8 +349,8 @@ public class CategorizationFragment extends Fragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
categoriesAdapter.setItems(items);
|
categoriesAdapter.setCollection(new ListAdapteeCollection<>(items));
|
||||||
categoriesAdapter.notifyDataSetInvalidated();
|
categoriesAdapter.notifyDataSetChanged();
|
||||||
categoriesSearchInProgress.setVisibility(View.GONE);
|
categoriesSearchInProgress.setVisibility(View.GONE);
|
||||||
|
|
||||||
if (categories.isEmpty()) {
|
if (categories.isEmpty()) {
|
||||||
|
|
@ -258,8 +364,7 @@ public class CategorizationFragment extends Fragment {
|
||||||
} else {
|
} else {
|
||||||
categoriesList.smoothScrollToPosition(existingKeys.size());
|
categoriesList.smoothScrollToPosition(existingKeys.size());
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
Timber.e("Error: Fragment is null");
|
Timber.e("Error: Fragment is null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -272,7 +377,6 @@ public class CategorizationFragment extends Fragment {
|
||||||
* above Prefix results.
|
* above Prefix results.
|
||||||
*/
|
*/
|
||||||
private void requestSearchResults() {
|
private void requestSearchResults() {
|
||||||
|
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
final CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
|
||||||
prefixUpdaterSub = new PrefixUpdater(this) {
|
prefixUpdaterSub = new PrefixUpdater(this) {
|
||||||
|
|
@ -282,8 +386,7 @@ public class CategorizationFragment extends Fragment {
|
||||||
try {
|
try {
|
||||||
result = super.doInBackground();
|
result = super.doInBackground();
|
||||||
latch.await();
|
latch.await();
|
||||||
}
|
} catch (InterruptedException e) {
|
||||||
catch (InterruptedException e) {
|
|
||||||
Timber.w(e);
|
Timber.w(e);
|
||||||
//Thread.currentThread().interrupt();
|
//Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
|
|
@ -325,7 +428,6 @@ public class CategorizationFragment extends Fragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startUpdatingCategoryList() {
|
private void startUpdatingCategoryList() {
|
||||||
|
|
||||||
if (prefixUpdaterSub != null) {
|
if (prefixUpdaterSub != null) {
|
||||||
prefixUpdaterSub.cancel(true);
|
prefixUpdaterSub.cancel(true);
|
||||||
}
|
}
|
||||||
|
|
@ -339,238 +441,34 @@ public class CategorizationFragment extends Fragment {
|
||||||
|
|
||||||
public int getCurrentSelectedCount() {
|
public int getCurrentSelectedCount() {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for(CategoryItem item: categoriesAdapter.getItems()) {
|
int numberOfItems = categoriesAdapter.getItemCount();
|
||||||
if(item.selected) {
|
for (int i = 0; i < numberOfItems; i++) {
|
||||||
|
CategoryItem item = categoriesAdapter.getItem(i);
|
||||||
|
if (item.isSelected()) {
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Category lookupCategory(String name) {
|
|
||||||
Cursor cursor = null;
|
|
||||||
try {
|
|
||||||
cursor = client.query(
|
|
||||||
CategoryContentProvider.BASE_URI,
|
|
||||||
Category.Table.ALL_FIELDS,
|
|
||||||
Category.Table.COLUMN_NAME + "=?",
|
|
||||||
new String[] {name},
|
|
||||||
null);
|
|
||||||
if (cursor.moveToFirst()) {
|
|
||||||
return Category.fromCursor(cursor);
|
|
||||||
}
|
|
||||||
} catch (RemoteException e) {
|
|
||||||
// This feels lazy, but to hell with checked exceptions. :)
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
} finally {
|
|
||||||
if ( cursor != null ) {
|
|
||||||
cursor.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Newly used category...
|
|
||||||
Category cat = new Category();
|
|
||||||
cat.setName(name);
|
|
||||||
cat.setLastUsed(new Date());
|
|
||||||
cat.setTimesUsed(0);
|
|
||||||
return cat;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CategoryCountUpdater extends AsyncTask<Void, Void, Void> {
|
|
||||||
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
public CategoryCountUpdater(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Void... voids) {
|
|
||||||
Category cat = lookupCategory(name);
|
|
||||||
cat.incTimesUsed();
|
|
||||||
|
|
||||||
cat.setContentProviderClient(client);
|
|
||||||
cat.save();
|
|
||||||
|
|
||||||
return null; // Make the compiler happy.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateCategoryCount(String name) {
|
|
||||||
new CategoryCountUpdater(name).executeOnExecutor(executor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
|
||||||
View rootView = inflater.inflate(R.layout.fragment_categorization, null);
|
|
||||||
categoriesList = (ListView) rootView.findViewById(R.id.categoriesListBox);
|
|
||||||
categoriesFilter = (EditText) rootView.findViewById(R.id.categoriesSearchBox);
|
|
||||||
categoriesSearchInProgress = (ProgressBar) rootView.findViewById(R.id.categoriesSearchInProgress);
|
|
||||||
categoriesNotFoundView = (TextView) rootView.findViewById(R.id.categoriesNotFound);
|
|
||||||
categoriesSkip = (TextView) rootView.findViewById(R.id.categoriesExplanation);
|
|
||||||
|
|
||||||
categoriesSkip.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View view) {
|
|
||||||
getActivity().onBackPressed();
|
|
||||||
getActivity().finish();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ArrayList<CategoryItem> items;
|
|
||||||
if(savedInstanceState == null) {
|
|
||||||
items = new ArrayList<>();
|
|
||||||
categoriesCache = new HashMap<>();
|
|
||||||
} else {
|
|
||||||
items = savedInstanceState.getParcelableArrayList("currentCategories");
|
|
||||||
categoriesCache = (HashMap<String, ArrayList<String>>) savedInstanceState.getSerializable("categoriesCache");
|
|
||||||
}
|
|
||||||
|
|
||||||
categoriesAdapter = new CategoriesAdapter(getActivity(), items);
|
|
||||||
categoriesList.setAdapter(categoriesAdapter);
|
|
||||||
|
|
||||||
categoriesList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onItemClick(AdapterView<?> adapterView, View view, int index, long id) {
|
|
||||||
CheckedTextView checkedView = (CheckedTextView) view;
|
|
||||||
CategoryItem item = (CategoryItem) adapterView.getAdapter().getItem(index);
|
|
||||||
item.selected = !item.selected;
|
|
||||||
checkedView.setChecked(item.selected);
|
|
||||||
if (item.selected) {
|
|
||||||
updateCategoryCount(item.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
categoriesFilter.addTextChangedListener(textWatcher);
|
|
||||||
|
|
||||||
startUpdatingCategoryList();
|
|
||||||
|
|
||||||
return rootView;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
|
||||||
menu.clear();
|
|
||||||
inflater.inflate(R.menu.fragment_categorization, menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
|
|
||||||
View rootView = getView();
|
|
||||||
if (rootView != null) {
|
|
||||||
rootView.setFocusableInTouchMode(true);
|
|
||||||
rootView.requestFocus();
|
|
||||||
rootView.setOnKeyListener(new View.OnKeyListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
|
||||||
if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
|
|
||||||
backButtonDialog();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroyView() {
|
|
||||||
categoriesFilter.removeTextChangedListener(textWatcher);
|
|
||||||
super.onDestroyView();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void backButtonDialog() {
|
public void backButtonDialog() {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
new AlertDialog.Builder(getActivity())
|
||||||
|
.setMessage("Are you sure you want to go back? The image will not "
|
||||||
builder.setMessage("Are you sure you want to go back? The image will not have any categories saved.")
|
+ "have any categories saved.")
|
||||||
.setTitle("Warning");
|
.setTitle("Warning")
|
||||||
builder.setPositiveButton("No", new DialogInterface.OnClickListener() {
|
.setPositiveButton("No", (dialog, id) -> {
|
||||||
@Override
|
//No need to do anything, user remains on categorization screen
|
||||||
public void onClick(DialogInterface dialog, int id) {
|
})
|
||||||
//No need to do anything, user remains on categorization screen
|
.setNegativeButton("Yes", (dialog, id) -> getActivity().finish())
|
||||||
}
|
.create()
|
||||||
});
|
.show();
|
||||||
builder.setNegativeButton("Yes", new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int id) {
|
|
||||||
getActivity().finish();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
AlertDialog dialog = builder.create();
|
|
||||||
dialog.show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void categoryClicked(CategoryItem item) {
|
||||||
super.onDestroy();
|
if (item.isSelected()) {
|
||||||
client.release();
|
new CategoryCountUpdater(item.getName(), client).executeOnExecutor(executor);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(Bundle outState) {
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
outState.putParcelableArrayList("currentCategories", categoriesAdapter.getItems());
|
|
||||||
outState.putSerializable("categoriesCache", categoriesCache);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem menuItem) {
|
|
||||||
switch(menuItem.getItemId()) {
|
|
||||||
case R.id.menu_save_categories:
|
|
||||||
|
|
||||||
int numberSelected = 0;
|
|
||||||
|
|
||||||
for(CategoryItem item: categoriesAdapter.getItems()) {
|
|
||||||
if(item.selected) {
|
|
||||||
selectedCategories.add(item.name);
|
|
||||||
numberSelected++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//If no categories selected, display warning to user
|
|
||||||
if (numberSelected == 0) {
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
|
||||||
|
|
||||||
builder.setMessage("Images without categories are rarely usable. Are you sure you want to submit without selecting categories?")
|
|
||||||
.setTitle("No Categories Selected");
|
|
||||||
builder.setPositiveButton("No, go back", new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int id) {
|
|
||||||
//Exit menuItem so user can select their categories
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
builder.setNegativeButton("Yes, submit", new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int id) {
|
|
||||||
//Proceed to submission
|
|
||||||
onCategoriesSaveHandler.onCategoriesSave(selectedCategories);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
AlertDialog dialog = builder.create();
|
|
||||||
dialog.show();
|
|
||||||
} else {
|
|
||||||
//Proceed to submission
|
|
||||||
onCategoriesSaveHandler.onCategoriesSave(selectedCategories);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(menuItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityCreated(Bundle savedInstanceState) {
|
|
||||||
super.onActivityCreated(savedInstanceState);
|
|
||||||
setHasOptionsMenu(true);
|
|
||||||
onCategoriesSaveHandler = (OnCategoriesSaveHandler) getActivity();
|
|
||||||
getActivity().setTitle(R.string.categories_activity_title);
|
|
||||||
client = getActivity().getContentResolver().acquireContentProviderClient(CategoryContentProvider.AUTHORITY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class CategoryTextWatcher implements TextWatcher {
|
private class CategoryTextWatcher implements TextWatcher {
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ public class Category {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Date getLastUsed() {
|
private Date getLastUsed() {
|
||||||
// warning: Date objects are mutable.
|
// warning: Date objects are mutable.
|
||||||
return (Date)lastUsed.clone();
|
return (Date)lastUsed.clone();
|
||||||
}
|
}
|
||||||
|
|
@ -36,11 +36,11 @@ public class Category {
|
||||||
this.lastUsed = (Date)lastUsed.clone();
|
this.lastUsed = (Date)lastUsed.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void touch() {
|
private void touch() {
|
||||||
lastUsed = new Date();
|
lastUsed = new Date();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getTimesUsed() {
|
private int getTimesUsed() {
|
||||||
return timesUsed;
|
return timesUsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,7 +70,7 @@ public class Category {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ContentValues toContentValues() {
|
private ContentValues toContentValues() {
|
||||||
ContentValues cv = new ContentValues();
|
ContentValues cv = new ContentValues();
|
||||||
cv.put(Table.COLUMN_NAME, getName());
|
cv.put(Table.COLUMN_NAME, getName());
|
||||||
cv.put(Table.COLUMN_LAST_USED, getLastUsed().getTime());
|
cv.put(Table.COLUMN_LAST_USED, getLastUsed().getTime());
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import android.database.Cursor;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.database.sqlite.SQLiteQueryBuilder;
|
import android.database.sqlite.SQLiteQueryBuilder;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
|
|
@ -41,8 +42,10 @@ public class CategoryContentProvider extends ContentProvider {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
@Override
|
@Override
|
||||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
public Cursor query(@NonNull Uri uri, String[] projection, String selection,
|
||||||
|
String[] selectionArgs, String sortOrder) {
|
||||||
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
|
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
|
||||||
queryBuilder.setTables(Category.Table.TABLE_NAME);
|
queryBuilder.setTables(Category.Table.TABLE_NAME);
|
||||||
|
|
||||||
|
|
@ -53,7 +56,8 @@ public class CategoryContentProvider extends ContentProvider {
|
||||||
|
|
||||||
switch(uriType) {
|
switch(uriType) {
|
||||||
case CATEGORIES:
|
case CATEGORIES:
|
||||||
cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
|
cursor = queryBuilder.query(db, projection, selection, selectionArgs,
|
||||||
|
null, null, sortOrder);
|
||||||
break;
|
break;
|
||||||
case CATEGORIES_ID:
|
case CATEGORIES_ID:
|
||||||
cursor = queryBuilder.query(db,
|
cursor = queryBuilder.query(db,
|
||||||
|
|
@ -75,15 +79,16 @@ public class CategoryContentProvider extends ContentProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getType(Uri uri) {
|
public String getType(@NonNull Uri uri) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
@Override
|
@Override
|
||||||
public Uri insert(Uri uri, ContentValues contentValues) {
|
public Uri insert(@NonNull Uri uri, ContentValues contentValues) {
|
||||||
int uriType = uriMatcher.match(uri);
|
int uriType = uriMatcher.match(uri);
|
||||||
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
|
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
|
||||||
long id = 0;
|
long id;
|
||||||
switch (uriType) {
|
switch (uriType) {
|
||||||
case CATEGORIES:
|
case CATEGORIES:
|
||||||
id = sqlDB.insert(Category.Table.TABLE_NAME, null, contentValues);
|
id = sqlDB.insert(Category.Table.TABLE_NAME, null, contentValues);
|
||||||
|
|
@ -96,12 +101,13 @@ public class CategoryContentProvider extends ContentProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int delete(Uri uri, String s, String[] strings) {
|
public int delete(@NonNull Uri uri, String s, String[] strings) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
@Override
|
@Override
|
||||||
public int bulkInsert(Uri uri, ContentValues[] values) {
|
public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {
|
||||||
Timber.d("Hello, bulk insert! (CategoryContentProvider)");
|
Timber.d("Hello, bulk insert! (CategoryContentProvider)");
|
||||||
int uriType = uriMatcher.match(uri);
|
int uriType = uriMatcher.match(uri);
|
||||||
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
|
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
|
||||||
|
|
@ -122,8 +128,10 @@ public class CategoryContentProvider extends ContentProvider {
|
||||||
return values.length;
|
return values.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
@Override
|
@Override
|
||||||
public int update(Uri uri, ContentValues contentValues, String selection, String[] selectionArgs) {
|
public int update(@NonNull Uri uri, ContentValues contentValues, String selection,
|
||||||
|
String[] selectionArgs) {
|
||||||
/*
|
/*
|
||||||
SQL Injection warnings: First, note that we're not exposing this to the outside world (exported="false")
|
SQL Injection warnings: First, note that we're not exposing this to the outside world (exported="false")
|
||||||
Even then, we should make sure to sanitize all user input appropriately. Input that passes through ContentValues
|
Even then, we should make sure to sanitize all user input appropriately. Input that passes through ContentValues
|
||||||
|
|
@ -133,7 +141,7 @@ public class CategoryContentProvider extends ContentProvider {
|
||||||
*/
|
*/
|
||||||
int uriType = uriMatcher.match(uri);
|
int uriType = uriMatcher.match(uri);
|
||||||
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
|
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
|
||||||
int rowsUpdated = 0;
|
int rowsUpdated;
|
||||||
switch (uriType) {
|
switch (uriType) {
|
||||||
case CATEGORIES_ID:
|
case CATEGORIES_ID:
|
||||||
int id = Integer.valueOf(uri.getLastPathSegment());
|
int id = Integer.valueOf(uri.getLastPathSegment());
|
||||||
|
|
@ -144,7 +152,8 @@ public class CategoryContentProvider extends ContentProvider {
|
||||||
Category.Table.COLUMN_ID + " = ?",
|
Category.Table.COLUMN_ID + " = ?",
|
||||||
new String[] { String.valueOf(id) } );
|
new String[] { String.valueOf(id) } );
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("Parameter `selection` should be empty when updating an ID");
|
throw new IllegalArgumentException(
|
||||||
|
"Parameter `selection` should be empty when updating an ID");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
package fr.free.nrw.commons.category;
|
||||||
|
|
||||||
|
import android.content.ContentProviderClient;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.RemoteException;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
class CategoryCountUpdater extends AsyncTask<Void, Void, Void> {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final ContentProviderClient client;
|
||||||
|
|
||||||
|
CategoryCountUpdater(String name, ContentProviderClient client) {
|
||||||
|
this.name = name;
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... voids) {
|
||||||
|
Category cat = lookupCategory(name);
|
||||||
|
cat.incTimesUsed();
|
||||||
|
|
||||||
|
cat.setContentProviderClient(client);
|
||||||
|
cat.save();
|
||||||
|
|
||||||
|
return null; // Make the compiler happy.
|
||||||
|
}
|
||||||
|
|
||||||
|
private Category lookupCategory(String name) {
|
||||||
|
Cursor cursor = null;
|
||||||
|
try {
|
||||||
|
cursor = client.query(
|
||||||
|
CategoryContentProvider.BASE_URI,
|
||||||
|
Category.Table.ALL_FIELDS,
|
||||||
|
Category.Table.COLUMN_NAME + "=?",
|
||||||
|
new String[]{name},
|
||||||
|
null);
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
return Category.fromCursor(cursor);
|
||||||
|
}
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
// This feels lazy, but to hell with checked exceptions. :)
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} finally {
|
||||||
|
if (cursor != null) {
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Newly used category...
|
||||||
|
Category cat = new Category();
|
||||||
|
cat.setName(name);
|
||||||
|
cat.setLastUsed(new Date());
|
||||||
|
cat.setTimesUsed(0);
|
||||||
|
return cat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
package fr.free.nrw.commons.category;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
class CategoryItem implements Parcelable {
|
||||||
|
private final String name;
|
||||||
|
private boolean selected;
|
||||||
|
|
||||||
|
public static Creator<CategoryItem> CREATOR = new Creator<CategoryItem>() {
|
||||||
|
@Override
|
||||||
|
public CategoryItem createFromParcel(Parcel parcel) {
|
||||||
|
return new CategoryItem(parcel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CategoryItem[] newArray(int i) {
|
||||||
|
return new CategoryItem[0];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
CategoryItem(String name, boolean selected) {
|
||||||
|
this.name = name;
|
||||||
|
this.selected = selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CategoryItem(Parcel in) {
|
||||||
|
name = in.readString();
|
||||||
|
selected = in.readInt() == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSelected() {
|
||||||
|
return selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelected(boolean selected) {
|
||||||
|
this.selected = selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel parcel, int flags) {
|
||||||
|
parcel.writeString(name);
|
||||||
|
parcel.writeInt(selected ? 1 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,6 +13,8 @@ import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
import static fr.free.nrw.commons.category.CategorizationFragment.SEARCH_CATS_LIMIT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends asynchronous queries to the Commons MediaWiki API to retrieve categories that are close to
|
* Sends asynchronous queries to the Commons MediaWiki API to retrieve categories that are close to
|
||||||
* the keyword typed in by the user. The 'srsearch' action-specific parameter is used for this
|
* the keyword typed in by the user. The 'srsearch' action-specific parameter is used for this
|
||||||
|
|
@ -20,8 +22,8 @@ import timber.log.Timber;
|
||||||
*/
|
*/
|
||||||
class MethodAUpdater extends AsyncTask<Void, Void, List<String>> {
|
class MethodAUpdater extends AsyncTask<Void, Void, List<String>> {
|
||||||
|
|
||||||
|
private final CategorizationFragment catFragment;
|
||||||
private String filter;
|
private String filter;
|
||||||
private CategorizationFragment catFragment;
|
|
||||||
|
|
||||||
MethodAUpdater(CategorizationFragment catFragment) {
|
MethodAUpdater(CategorizationFragment catFragment) {
|
||||||
this.catFragment = catFragment;
|
this.catFragment = catFragment;
|
||||||
|
|
@ -84,7 +86,7 @@ class MethodAUpdater extends AsyncTask<Void, Void, List<String>> {
|
||||||
|
|
||||||
//URL https://commons.wikimedia.org/w/api.php?action=query&format=xml&list=search&srwhat=text&srenablerewrites=1&srnamespace=14&srlimit=10&srsearch=
|
//URL https://commons.wikimedia.org/w/api.php?action=query&format=xml&list=search&srwhat=text&srenablerewrites=1&srnamespace=14&srlimit=10&srsearch=
|
||||||
try {
|
try {
|
||||||
categories = api.searchCategories(CategorizationFragment.SEARCH_CATS_LIMIT, filter);
|
categories = api.searchCategories(SEARCH_CATS_LIMIT, filter);
|
||||||
Timber.d("Method A URL filter %s", categories);
|
Timber.d("Method A URL filter %s", categories);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Timber.e(e, "IO Exception: ");
|
Timber.e(e, "IO Exception: ");
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package fr.free.nrw.commons.category;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public interface OnCategoriesSaveHandler {
|
||||||
|
void onCategoriesSave(ArrayList<String> categories);
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ import android.view.View;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
@ -14,18 +15,20 @@ import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
import static fr.free.nrw.commons.category.CategorizationFragment.SEARCH_CATS_LIMIT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends asynchronous queries to the Commons MediaWiki API to retrieve categories that share the
|
* Sends asynchronous queries to the Commons MediaWiki API to retrieve categories that share the
|
||||||
* same prefix as the keyword typed in by the user. The 'acprefix' action-specific parameter is used
|
* same prefix as the keyword typed in by the user. The 'acprefix' action-specific parameter is used
|
||||||
* for this purpose. This class should be subclassed in CategorizationFragment.java to aggregate
|
* for this purpose. This class should be subclassed in CategorizationFragment.java to aggregate
|
||||||
* the results.
|
* the results.
|
||||||
*/
|
*/
|
||||||
public class PrefixUpdater extends AsyncTask<Void, Void, List<String>> {
|
class PrefixUpdater extends AsyncTask<Void, Void, List<String>> {
|
||||||
|
|
||||||
|
private final CategorizationFragment catFragment;
|
||||||
private String filter;
|
private String filter;
|
||||||
private CategorizationFragment catFragment;
|
|
||||||
|
|
||||||
public PrefixUpdater(CategorizationFragment catFragment) {
|
PrefixUpdater(CategorizationFragment catFragment) {
|
||||||
this.catFragment = catFragment;
|
this.catFragment = catFragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,8 +93,9 @@ public class PrefixUpdater extends AsyncTask<Void, Void, List<String>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
//if user types in something that is in cache, return cached category
|
//if user types in something that is in cache, return cached category
|
||||||
if (catFragment.categoriesCache.containsKey(filter)) {
|
HashMap<String, ArrayList<String>> categoriesCache = catFragment.getCategoriesCache();
|
||||||
ArrayList<String> cachedItems = new ArrayList<>(catFragment.categoriesCache.get(filter));
|
if (categoriesCache.containsKey(filter)) {
|
||||||
|
ArrayList<String> cachedItems = new ArrayList<>(categoriesCache.get(filter));
|
||||||
Timber.d("Found cache items, waiting for filter");
|
Timber.d("Found cache items, waiting for filter");
|
||||||
return new ArrayList<>(filterIrrelevantResults(cachedItems));
|
return new ArrayList<>(filterIrrelevantResults(cachedItems));
|
||||||
}
|
}
|
||||||
|
|
@ -101,7 +105,7 @@ public class PrefixUpdater extends AsyncTask<Void, Void, List<String>> {
|
||||||
MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
|
MediaWikiApi api = CommonsApplication.getInstance().getMWApi();
|
||||||
List<String> categories = new ArrayList<>();
|
List<String> categories = new ArrayList<>();
|
||||||
try {
|
try {
|
||||||
categories = api.allCategories(CategorizationFragment.SEARCH_CATS_LIMIT, this.filter);
|
categories = api.allCategories(SEARCH_CATS_LIMIT, this.filter);
|
||||||
Timber.d("Prefix URL filter %s", categories);
|
Timber.d("Prefix URL filter %s", categories);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Timber.e(e, "IO Exception: ");
|
Timber.e(e, "IO Exception: ");
|
||||||
|
|
|
||||||
|
|
@ -19,17 +19,12 @@ class TitleCategories extends AsyncTask<Void, Void, List<String>> {
|
||||||
|
|
||||||
private final static int SEARCH_CATS_LIMIT = 25;
|
private final static int SEARCH_CATS_LIMIT = 25;
|
||||||
|
|
||||||
private String title;
|
private final String title;
|
||||||
|
|
||||||
TitleCategories(String title) {
|
TitleCategories(String title) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPreExecute() {
|
|
||||||
super.onPreExecute();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<String> doInBackground(Void... voids) {
|
protected List<String> doInBackground(Void... voids) {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,8 @@ public class BackgroundPoolExceptionHandler implements ExceptionHandler {
|
||||||
public void onException(@NonNull final Throwable t) {
|
public void onException(@NonNull final Throwable t) {
|
||||||
//Crash for debug build
|
//Crash for debug build
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
Thread thread = new Thread(new Runnable() {
|
Thread thread = new Thread(() -> {
|
||||||
@Override
|
throw new RuntimeException(t);
|
||||||
public void run() {
|
|
||||||
throw new RuntimeException(t);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
thread.start();
|
thread.start();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,17 +11,13 @@ class ThreadFactoryMaker {
|
||||||
private int count = 0;
|
private int count = 0;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Thread newThread(final Runnable runnable) {
|
public Thread newThread(@NonNull final Runnable runnable) {
|
||||||
count++;
|
count++;
|
||||||
Runnable wrapperRunnable = new Runnable() {
|
Runnable wrapperRunnable = () -> {
|
||||||
@Override
|
Process.setThreadPriority(priority);
|
||||||
public void run() {
|
runnable.run();
|
||||||
Process.setThreadPriority(priority);
|
|
||||||
runnable.run();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
Thread t = new Thread(wrapperRunnable, String.format("%s-%s", name, count));
|
return new Thread(wrapperRunnable, String.format("%s-%s", name, count));
|
||||||
return t;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.nearby.NearbyActivity;
|
import fr.free.nrw.commons.nearby.NearbyActivity;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
|
||||||
import static android.app.Activity.RESULT_OK;
|
import static android.app.Activity.RESULT_OK;
|
||||||
|
|
||||||
public class ContributionsListFragment extends Fragment {
|
public class ContributionsListFragment extends Fragment {
|
||||||
|
|
@ -110,11 +111,11 @@ public class ContributionsListFragment extends Fragment {
|
||||||
|
|
||||||
// Here, thisActivity is the current activity
|
// Here, thisActivity is the current activity
|
||||||
if (ContextCompat.checkSelfPermission(getActivity(),
|
if (ContextCompat.checkSelfPermission(getActivity(),
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE)
|
READ_EXTERNAL_STORAGE)
|
||||||
!= PackageManager.PERMISSION_GRANTED) {
|
!= PackageManager.PERMISSION_GRANTED) {
|
||||||
|
|
||||||
// Should we show an explanation?
|
// Should we show an explanation?
|
||||||
if (shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE)) {
|
if (shouldShowRequestPermissionRationale(READ_EXTERNAL_STORAGE)) {
|
||||||
|
|
||||||
// Show an explanation to the user *asynchronously* -- don't block
|
// Show an explanation to the user *asynchronously* -- don't block
|
||||||
// this thread waiting for the user's response! After the user
|
// this thread waiting for the user's response! After the user
|
||||||
|
|
@ -122,15 +123,9 @@ public class ContributionsListFragment extends Fragment {
|
||||||
|
|
||||||
new AlertDialog.Builder(getActivity())
|
new AlertDialog.Builder(getActivity())
|
||||||
.setMessage(getString(R.string.storage_permission_rationale))
|
.setMessage(getString(R.string.storage_permission_rationale))
|
||||||
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
|
.setPositiveButton("OK", (dialog, which) -> {
|
||||||
@Override
|
requestPermissions(new String[]{READ_EXTERNAL_STORAGE}, 1);
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
dialog.dismiss();
|
||||||
|
|
||||||
requestPermissions(
|
|
||||||
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
|
|
||||||
1);
|
|
||||||
dialog.dismiss();
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.setNegativeButton("Cancel", null)
|
.setNegativeButton("Cancel", null)
|
||||||
.create()
|
.create()
|
||||||
|
|
@ -140,7 +135,7 @@ public class ContributionsListFragment extends Fragment {
|
||||||
|
|
||||||
// No explanation needed, we can request the permission.
|
// No explanation needed, we can request the permission.
|
||||||
|
|
||||||
requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
|
requestPermissions(new String[]{READ_EXTERNAL_STORAGE},
|
||||||
1);
|
1);
|
||||||
|
|
||||||
// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
|
// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,10 @@ import fr.free.nrw.commons.concurrency.BackgroundPoolExceptionHandler;
|
||||||
import fr.free.nrw.commons.concurrency.ThreadPoolExecutorService;
|
import fr.free.nrw.commons.concurrency.ThreadPoolExecutorService;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class UploadCountClient {
|
class UploadCountClient {
|
||||||
private ThreadPoolExecutorService threadPoolExecutor;
|
private ThreadPoolExecutorService threadPoolExecutor;
|
||||||
|
|
||||||
public UploadCountClient() {
|
UploadCountClient() {
|
||||||
threadPoolExecutor = new ThreadPoolExecutorService.Builder("bg-pool")
|
threadPoolExecutor = new ThreadPoolExecutorService.Builder("bg-pool")
|
||||||
.setPoolSize(Runtime.getRuntime().availableProcessors())
|
.setPoolSize(Runtime.getRuntime().availableProcessors())
|
||||||
.setExceptionHandler(new BackgroundPoolExceptionHandler())
|
.setExceptionHandler(new BackgroundPoolExceptionHandler())
|
||||||
|
|
@ -27,29 +27,26 @@ public class UploadCountClient {
|
||||||
private static final String UPLOAD_COUNT_URL_TEMPLATE =
|
private static final String UPLOAD_COUNT_URL_TEMPLATE =
|
||||||
"https://tools.wmflabs.org/urbanecmbot/uploadsbyuser/uploadsbyuser.py?user=%s";
|
"https://tools.wmflabs.org/urbanecmbot/uploadsbyuser/uploadsbyuser.py?user=%s";
|
||||||
|
|
||||||
public ListenableFuture<Integer> getUploadCount(final String userName) {
|
ListenableFuture<Integer> getUploadCount(final String userName) {
|
||||||
final SettableFuture<Integer> future = SettableFuture.create();
|
final SettableFuture<Integer> future = SettableFuture.create();
|
||||||
threadPoolExecutor.schedule(new Runnable() {
|
threadPoolExecutor.schedule(() -> {
|
||||||
@Override
|
URL url;
|
||||||
public void run() {
|
try {
|
||||||
URL url;
|
url = new URL(String.format(Locale.ENGLISH, UPLOAD_COUNT_URL_TEMPLATE,
|
||||||
|
new PageTitle(userName).getText()));
|
||||||
|
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
|
||||||
try {
|
try {
|
||||||
url = new URL(String.format(Locale.ENGLISH, UPLOAD_COUNT_URL_TEMPLATE,
|
BufferedReader bufferedReader = new BufferedReader(new
|
||||||
new PageTitle(userName).getText()));
|
InputStreamReader(urlConnection.getInputStream()));
|
||||||
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
|
String uploadCount = bufferedReader.readLine();
|
||||||
try {
|
bufferedReader.close();
|
||||||
BufferedReader bufferedReader = new BufferedReader(new
|
future.set(Integer.parseInt(uploadCount));
|
||||||
InputStreamReader(urlConnection.getInputStream()));
|
} finally {
|
||||||
String uploadCount = bufferedReader.readLine();
|
urlConnection.disconnect();
|
||||||
bufferedReader.close();
|
|
||||||
future.set(Integer.parseInt(uploadCount));
|
|
||||||
} finally {
|
|
||||||
urlConnection.disconnect();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Timber.e("Error getting upload count Error", e);
|
|
||||||
future.setException(e);
|
|
||||||
}
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Timber.e("Error getting upload count Error", e);
|
||||||
|
future.setException(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return future;
|
return future;
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@ package fr.free.nrw.commons.media;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.database.DataSetObserver;
|
import android.database.DataSetObserver;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
|
@ -18,6 +20,7 @@ import java.io.IOException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import fr.free.nrw.commons.License;
|
import fr.free.nrw.commons.License;
|
||||||
import fr.free.nrw.commons.LicenseList;
|
import fr.free.nrw.commons.LicenseList;
|
||||||
|
|
@ -26,6 +29,7 @@ import fr.free.nrw.commons.MediaDataExtractor;
|
||||||
import fr.free.nrw.commons.MediaWikiImageView;
|
import fr.free.nrw.commons.MediaWikiImageView;
|
||||||
import fr.free.nrw.commons.PageTitle;
|
import fr.free.nrw.commons.PageTitle;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class MediaDetailFragment extends Fragment {
|
public class MediaDetailFragment extends Fragment {
|
||||||
|
|
@ -115,12 +119,7 @@ public class MediaDetailFragment extends Fragment {
|
||||||
licenseList = new LicenseList(getActivity());
|
licenseList = new LicenseList(getActivity());
|
||||||
|
|
||||||
// Progressively darken the image in the background when we scroll detail pane up
|
// Progressively darken the image in the background when we scroll detail pane up
|
||||||
scrollListener = new ViewTreeObserver.OnScrollChangedListener() {
|
scrollListener = this::updateTheDarkness;
|
||||||
@Override
|
|
||||||
public void onScrollChanged() {
|
|
||||||
updateTheDarkness();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
view.getViewTreeObserver().addOnScrollChangedListener(scrollListener);
|
view.getViewTreeObserver().addOnScrollChangedListener(scrollListener);
|
||||||
|
|
||||||
// Layout layoutListener to size the spacer item relative to the available space.
|
// Layout layoutListener to size the spacer item relative to the available space.
|
||||||
|
|
@ -262,6 +261,7 @@ public class MediaDetailFragment extends Fragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rebuildCatList() {
|
private void rebuildCatList() {
|
||||||
|
categoryContainer.removeAllViews();
|
||||||
// @fixme add the category items
|
// @fixme add the category items
|
||||||
for (String cat : categoryNames) {
|
for (String cat : categoryNames) {
|
||||||
View catLabel = buildCatLabel(cat);
|
View catLabel = buildCatLabel(cat);
|
||||||
|
|
@ -275,15 +275,12 @@ public class MediaDetailFragment extends Fragment {
|
||||||
|
|
||||||
textView.setText(catName);
|
textView.setText(catName);
|
||||||
if (categoriesLoaded && categoriesPresent) {
|
if (categoriesLoaded && categoriesPresent) {
|
||||||
textView.setOnClickListener(new View.OnClickListener() {
|
textView.setOnClickListener(view -> {
|
||||||
@Override
|
String selectedCategoryTitle = "Category:" + catName;
|
||||||
public void onClick(View view) {
|
Intent viewIntent = new Intent();
|
||||||
String selectedCategoryTitle = "Category:" + catName;
|
viewIntent.setAction(Intent.ACTION_VIEW);
|
||||||
Intent viewIntent = new Intent();
|
viewIntent.setData(new PageTitle(selectedCategoryTitle).getCanonicalUri());
|
||||||
viewIntent.setAction(Intent.ACTION_VIEW);
|
startActivity(viewIntent);
|
||||||
viewIntent.setData(new PageTitle(selectedCategoryTitle).getCanonicalUri());
|
|
||||||
startActivity(viewIntent);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
|
|
@ -327,7 +324,7 @@ public class MediaDetailFragment extends Fragment {
|
||||||
|
|
||||||
private String prettyUploadedDate(Media media) {
|
private String prettyUploadedDate(Media media) {
|
||||||
Date date = media.getDateUploaded();
|
Date date = media.getDateUploaded();
|
||||||
if (date.toString() == null || date.toString().isEmpty()) {
|
if (date == null || date.toString() == null || date.toString().isEmpty()) {
|
||||||
return "Uploaded date not available";
|
return "Uploaded date not available";
|
||||||
}
|
}
|
||||||
SimpleDateFormat formatter = new SimpleDateFormat("dd MMM yyyy");
|
SimpleDateFormat formatter = new SimpleDateFormat("dd MMM yyyy");
|
||||||
|
|
@ -340,6 +337,9 @@ public class MediaDetailFragment extends Fragment {
|
||||||
* @return Coordinates as text.
|
* @return Coordinates as text.
|
||||||
*/
|
*/
|
||||||
private String prettyCoordinates(Media media) {
|
private String prettyCoordinates(Media media) {
|
||||||
return media.getCoordinates();
|
if (media.getCoordinates() == null) {
|
||||||
|
return getString(R.string.media_detail_coordinates_empty);
|
||||||
|
}
|
||||||
|
return media.getCoordinates().getPrettyCoordinateString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,12 +72,7 @@ public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPa
|
||||||
public Fragment getItem(int i) {
|
public Fragment getItem(int i) {
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
// See bug https://code.google.com/p/android/issues/detail?id=27526
|
// See bug https://code.google.com/p/android/issues/detail?id=27526
|
||||||
pager.postDelayed(new Runnable() {
|
pager.postDelayed(() -> getActivity().supportInvalidateOptionsMenu(), 5);
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
getActivity().supportInvalidateOptionsMenu();
|
|
||||||
}
|
|
||||||
}, 5);
|
|
||||||
}
|
}
|
||||||
return MediaDetailFragment.forMedia(i, editable);
|
return MediaDetailFragment.forMedia(i, editable);
|
||||||
}
|
}
|
||||||
|
|
@ -100,14 +95,11 @@ public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPa
|
||||||
final int pageNumber = savedInstanceState.getInt("current-page");
|
final int pageNumber = savedInstanceState.getInt("current-page");
|
||||||
// Adapter doesn't seem to be loading immediately.
|
// Adapter doesn't seem to be loading immediately.
|
||||||
// Dear God, please forgive us for our sins
|
// Dear God, please forgive us for our sins
|
||||||
view.postDelayed(new Runnable() {
|
view.postDelayed(() -> {
|
||||||
@Override
|
pager.setAdapter(adapter);
|
||||||
public void run() {
|
pager.setCurrentItem(pageNumber, false);
|
||||||
pager.setAdapter(adapter);
|
getActivity().supportInvalidateOptionsMenu();
|
||||||
pager.setCurrentItem(pageNumber, false);
|
adapter.notifyDataSetChanged();
|
||||||
getActivity().supportInvalidateOptionsMenu();
|
|
||||||
adapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
}, 100);
|
}, 100);
|
||||||
} else {
|
} else {
|
||||||
pager.setAdapter(adapter);
|
pager.setAdapter(adapter);
|
||||||
|
|
@ -196,13 +188,8 @@ public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPa
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !(ContextCompat.checkSelfPermission(getContext(), Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !(ContextCompat.checkSelfPermission(getContext(), Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)) {
|
||||||
Snackbar.make(getView(), R.string.storage_permission_rationale,
|
Snackbar.make(getView(), R.string.storage_permission_rationale,
|
||||||
Snackbar.LENGTH_INDEFINITE)
|
Snackbar.LENGTH_INDEFINITE)
|
||||||
.setAction(R.string.ok, new View.OnClickListener() {
|
.setAction(R.string.ok, view -> ActivityCompat.requestPermissions(getActivity(),
|
||||||
@Override
|
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1)).show();
|
||||||
public void onClick(View view) {
|
|
||||||
ActivityCompat.requestPermissions(getActivity(),
|
|
||||||
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
|
|
||||||
}
|
|
||||||
}).show();
|
|
||||||
} else {
|
} else {
|
||||||
final DownloadManager manager = (DownloadManager)getActivity().getSystemService(Context.DOWNLOAD_SERVICE);
|
final DownloadManager manager = (DownloadManager)getActivity().getSystemService(Context.DOWNLOAD_SERVICE);
|
||||||
manager.enqueue(req);
|
manager.enqueue(req);
|
||||||
|
|
|
||||||
|
|
@ -351,12 +351,7 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
||||||
@Override
|
@Override
|
||||||
@NonNull
|
@NonNull
|
||||||
public UploadResult uploadFile(String filename, InputStream file, long dataLength, String pageContents, String editSummary, final ProgressListener progressListener) throws IOException {
|
public UploadResult uploadFile(String filename, InputStream file, long dataLength, String pageContents, String editSummary, final ProgressListener progressListener) throws IOException {
|
||||||
ApiResult result = api.upload(filename, file, dataLength, pageContents, editSummary, new in.yuvi.http.fluent.ProgressListener() {
|
ApiResult result = api.upload(filename, file, dataLength, pageContents, editSummary, progressListener::onProgress);
|
||||||
@Override
|
|
||||||
public void onProgress(long transferred, long total) {
|
|
||||||
progressListener.onProgress(transferred, total);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Log.e("WTF", "Result: "+result.toString());
|
Log.e("WTF", "Result: "+result.toString());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package fr.free.nrw.commons.nearby;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.location.LocationManager;
|
import android.location.LocationManager;
|
||||||
|
|
@ -41,7 +40,8 @@ import timber.log.Timber;
|
||||||
|
|
||||||
public class NearbyActivity extends NavigationBaseActivity {
|
public class NearbyActivity extends NavigationBaseActivity {
|
||||||
|
|
||||||
@BindView(R.id.progressBar) ProgressBar progressBar;
|
@BindView(R.id.progressBar)
|
||||||
|
ProgressBar progressBar;
|
||||||
private boolean isMapViewActive = false;
|
private boolean isMapViewActive = false;
|
||||||
private static final int LOCATION_REQUEST = 1;
|
private static final int LOCATION_REQUEST = 1;
|
||||||
|
|
||||||
|
|
@ -115,15 +115,11 @@ public class NearbyActivity extends NavigationBaseActivity {
|
||||||
|
|
||||||
new AlertDialog.Builder(this)
|
new AlertDialog.Builder(this)
|
||||||
.setMessage(getString(R.string.location_permission_rationale))
|
.setMessage(getString(R.string.location_permission_rationale))
|
||||||
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
|
.setPositiveButton("OK", (dialog, which) -> {
|
||||||
@Override
|
ActivityCompat.requestPermissions(NearbyActivity.this,
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
|
||||||
|
LOCATION_REQUEST);
|
||||||
ActivityCompat.requestPermissions(NearbyActivity.this,
|
dialog.dismiss();
|
||||||
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
|
|
||||||
LOCATION_REQUEST);
|
|
||||||
dialog.dismiss();
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.setNegativeButton("Cancel", null)
|
.setNegativeButton("Cancel", null)
|
||||||
.create()
|
.create()
|
||||||
|
|
@ -175,26 +171,19 @@ public class NearbyActivity extends NavigationBaseActivity {
|
||||||
LocationManager manager = (LocationManager) getSystemService(LOCATION_SERVICE);
|
LocationManager manager = (LocationManager) getSystemService(LOCATION_SERVICE);
|
||||||
if (!manager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
|
if (!manager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
|
||||||
Timber.d("GPS is not enabled");
|
Timber.d("GPS is not enabled");
|
||||||
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
|
new AlertDialog.Builder(this)
|
||||||
alertDialogBuilder.setMessage(R.string.gps_disabled)
|
.setMessage(R.string.gps_disabled)
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
.setPositiveButton(R.string.enable_gps,
|
.setPositiveButton(R.string.enable_gps,
|
||||||
new DialogInterface.OnClickListener() {
|
(dialog, id) -> {
|
||||||
public void onClick(DialogInterface dialog, int id) {
|
Intent callGPSSettingIntent = new Intent(
|
||||||
Intent callGPSSettingIntent = new Intent(
|
android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
|
||||||
android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
|
Timber.d("Loaded settings page");
|
||||||
Timber.d("Loaded settings page");
|
startActivityForResult(callGPSSettingIntent, 1);
|
||||||
startActivityForResult(callGPSSettingIntent, 1);
|
})
|
||||||
}
|
.setNegativeButton(R.string.menu_cancel_upload, (dialog, id) -> dialog.cancel())
|
||||||
});
|
.create()
|
||||||
alertDialogBuilder.setNegativeButton(R.string.menu_cancel_upload,
|
.show();
|
||||||
new DialogInterface.OnClickListener() {
|
|
||||||
public void onClick(DialogInterface dialog, int id) {
|
|
||||||
dialog.cancel();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
AlertDialog alert = alertDialogBuilder.create();
|
|
||||||
alert.show();
|
|
||||||
} else {
|
} else {
|
||||||
Timber.d("GPS is enabled");
|
Timber.d("GPS is enabled");
|
||||||
}
|
}
|
||||||
|
|
@ -257,7 +246,7 @@ public class NearbyActivity extends NavigationBaseActivity {
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
|
|
||||||
private NearbyAsyncTask (Context context) {
|
private NearbyAsyncTask(Context context) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package fr.free.nrw.commons.nearby;
|
package fr.free.nrw.commons.nearby;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
import com.pedrogomez.renderers.ListAdapteeCollection;
|
import com.pedrogomez.renderers.ListAdapteeCollection;
|
||||||
import com.pedrogomez.renderers.RVRendererAdapter;
|
import com.pedrogomez.renderers.RVRendererAdapter;
|
||||||
import com.pedrogomez.renderers.RendererBuilder;
|
import com.pedrogomez.renderers.RendererBuilder;
|
||||||
|
|
@ -10,7 +12,7 @@ import java.util.List;
|
||||||
class NearbyAdapterFactory {
|
class NearbyAdapterFactory {
|
||||||
private PlaceRenderer.PlaceClickedListener listener;
|
private PlaceRenderer.PlaceClickedListener listener;
|
||||||
|
|
||||||
NearbyAdapterFactory(PlaceRenderer.PlaceClickedListener listener) {
|
NearbyAdapterFactory(@NonNull PlaceRenderer.PlaceClickedListener listener) {
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,10 @@ package fr.free.nrw.commons.nearby;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
import android.support.graphics.drawable.VectorDrawableCompat;
|
||||||
|
|
||||||
import com.mapbox.mapboxsdk.annotations.Icon;
|
|
||||||
import com.mapbox.mapboxsdk.annotations.IconFactory;
|
import com.mapbox.mapboxsdk.annotations.IconFactory;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -18,6 +19,7 @@ import java.util.Map;
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.location.LatLng;
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
|
import fr.free.nrw.commons.utils.UiUtils;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
import static fr.free.nrw.commons.utils.LengthUtils.computeDistanceBetween;
|
import static fr.free.nrw.commons.utils.LengthUtils.computeDistanceBetween;
|
||||||
|
|
@ -50,13 +52,10 @@ public class NearbyController {
|
||||||
distances.put(place, computeDistanceBetween(place.location, curLatLng));
|
distances.put(place, computeDistanceBetween(place.location, curLatLng));
|
||||||
}
|
}
|
||||||
Collections.sort(places,
|
Collections.sort(places,
|
||||||
new Comparator<Place>() {
|
(lhs, rhs) -> {
|
||||||
@Override
|
double lhsDistance = distances.get(lhs);
|
||||||
public int compare(Place lhs, Place rhs) {
|
double rhsDistance = distances.get(rhs);
|
||||||
double lhsDistance = distances.get(lhs);
|
return (int) (lhsDistance - rhsDistance);
|
||||||
double rhsDistance = distances.get(rhs);
|
|
||||||
return (int) (lhsDistance - rhsDistance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -98,13 +97,15 @@ public class NearbyController {
|
||||||
|
|
||||||
placeList = placeList.subList(0, Math.min(placeList.size(), MAX_RESULTS));
|
placeList = placeList.subList(0, Math.min(placeList.size(), MAX_RESULTS));
|
||||||
|
|
||||||
|
Bitmap icon = UiUtils.getBitmap(
|
||||||
|
VectorDrawableCompat.create(
|
||||||
|
context.getResources(), R.drawable.ic_custom_map_marker, context.getTheme()
|
||||||
|
));
|
||||||
|
|
||||||
for (Place place: placeList) {
|
for (Place place: placeList) {
|
||||||
String distance = formatDistanceBetween(curLatLng, place.location);
|
String distance = formatDistanceBetween(curLatLng, place.location);
|
||||||
place.setDistance(distance);
|
place.setDistance(distance);
|
||||||
|
|
||||||
Icon icon = IconFactory.getInstance(context)
|
|
||||||
.fromResource(R.drawable.custom_map_marker);
|
|
||||||
|
|
||||||
NearbyBaseMarker nearbyBaseMarker = new NearbyBaseMarker();
|
NearbyBaseMarker nearbyBaseMarker = new NearbyBaseMarker();
|
||||||
nearbyBaseMarker.title(place.name);
|
nearbyBaseMarker.title(place.name);
|
||||||
nearbyBaseMarker.position(
|
nearbyBaseMarker.position(
|
||||||
|
|
@ -112,7 +113,8 @@ public class NearbyController {
|
||||||
place.location.getLatitude(),
|
place.location.getLatitude(),
|
||||||
place.location.getLongitude()));
|
place.location.getLongitude()));
|
||||||
nearbyBaseMarker.place(place);
|
nearbyBaseMarker.place(place);
|
||||||
nearbyBaseMarker.icon(icon);
|
nearbyBaseMarker.icon(IconFactory.getInstance(context)
|
||||||
|
.fromBitmap(icon));
|
||||||
|
|
||||||
baseMarkerOptions.add(nearbyBaseMarker);
|
baseMarkerOptions.add(nearbyBaseMarker);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,15 +63,10 @@ public class NearbyInfoDialog extends OverlayDialog {
|
||||||
|
|
||||||
overflowButton.setVisibility(showMenu() ? View.VISIBLE : View.GONE);
|
overflowButton.setVisibility(showMenu() ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
overflowButton.setOnClickListener(new View.OnClickListener() {
|
overflowButton.setOnClickListener(this::popupMenuListener);
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
popupMenuListener();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void popupMenuListener() {
|
private void popupMenuListener(View v) {
|
||||||
PopupMenu popupMenu = new PopupMenu(getActivity(), overflowButton);
|
PopupMenu popupMenu = new PopupMenu(getActivity(), overflowButton);
|
||||||
popupMenu.inflate(R.menu.nearby_info_dialog_options);
|
popupMenu.inflate(R.menu.nearby_info_dialog_options);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,12 +48,7 @@ public class NearbyListFragment extends Fragment {
|
||||||
View view = inflater.inflate(R.layout.fragment_nearby, container, false);
|
View view = inflater.inflate(R.layout.fragment_nearby, container, false);
|
||||||
recyclerView = (RecyclerView) view.findViewById(R.id.listView);
|
recyclerView = (RecyclerView) view.findViewById(R.id.listView);
|
||||||
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
adapterFactory = new NearbyAdapterFactory(new PlaceRenderer.PlaceClickedListener() {
|
adapterFactory = new NearbyAdapterFactory(place -> NearbyInfoDialog.showYourself(getActivity(), place));
|
||||||
@Override
|
|
||||||
public void placeClicked(Place place) {
|
|
||||||
NearbyInfoDialog.showYourself(getActivity(), place);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -88,25 +88,19 @@ public class NearbyMapFragment extends android.support.v4.app.Fragment {
|
||||||
// create map
|
// create map
|
||||||
mapView = new MapView(getActivity(), options);
|
mapView = new MapView(getActivity(), options);
|
||||||
mapView.onCreate(savedInstanceState);
|
mapView.onCreate(savedInstanceState);
|
||||||
mapView.getMapAsync(new OnMapReadyCallback() {
|
mapView.getMapAsync(mapboxMap -> {
|
||||||
@Override
|
mapboxMap.addMarkers(baseMarkerOptions);
|
||||||
public void onMapReady(MapboxMap mapboxMap) {
|
|
||||||
mapboxMap.addMarkers(baseMarkerOptions);
|
|
||||||
|
|
||||||
mapboxMap.setOnMarkerClickListener(new MapboxMap.OnMarkerClickListener() {
|
mapboxMap.setOnMarkerClickListener(marker -> {
|
||||||
@Override
|
if (marker instanceof NearbyMarker) {
|
||||||
public boolean onMarkerClick(@NonNull Marker marker) {
|
NearbyMarker nearbyMarker = (NearbyMarker) marker;
|
||||||
if (marker instanceof NearbyMarker) {
|
Place place = nearbyMarker.getNearbyBaseMarker().getPlace();
|
||||||
NearbyMarker nearbyMarker = (NearbyMarker) marker;
|
NearbyInfoDialog.showYourself(getActivity(), place);
|
||||||
Place place = nearbyMarker.getNearbyBaseMarker().getPlace();
|
}
|
||||||
NearbyInfoDialog.showYourself(getActivity(), place);
|
return false;
|
||||||
}
|
});
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
addCurrentLocationMarker(mapboxMap);
|
addCurrentLocationMarker(mapboxMap);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
if (PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean("theme",true)) {
|
if (PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean("theme",true)) {
|
||||||
mapView.setStyleUrl(getResources().getString(R.string.map_theme_dark));
|
mapView.setStyleUrl(getResources().getString(R.string.map_theme_dark));
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package fr.free.nrw.commons.nearby;
|
package fr.free.nrw.commons.nearby;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
@ -19,7 +20,7 @@ class PlaceRenderer extends Renderer<Place> {
|
||||||
@BindView(R.id.icon) ImageView icon;
|
@BindView(R.id.icon) ImageView icon;
|
||||||
private final PlaceClickedListener listener;
|
private final PlaceClickedListener listener;
|
||||||
|
|
||||||
PlaceRenderer(PlaceClickedListener listener) {
|
PlaceRenderer(@NonNull PlaceClickedListener listener) {
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,14 +36,7 @@ class PlaceRenderer extends Renderer<Place> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void hookListeners(View view) {
|
protected void hookListeners(View view) {
|
||||||
view.setOnClickListener(new View.OnClickListener() {
|
view.setOnClickListener(v -> listener.placeClicked(getContent()));
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
if (listener != null) {
|
|
||||||
listener.placeClicked(getContent());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -28,21 +28,15 @@ public class SettingsFragment extends PreferenceFragment {
|
||||||
licensePreference.setSummary(getString(Utils.licenseNameFor(licensePreference.getValue())));
|
licensePreference.setSummary(getString(Utils.licenseNameFor(licensePreference.getValue())));
|
||||||
|
|
||||||
// Keep summary updated when changing value
|
// Keep summary updated when changing value
|
||||||
licensePreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
licensePreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
@Override
|
preference.setSummary(getString(Utils.licenseNameFor((String) newValue)));
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
return true;
|
||||||
preference.setSummary(getString(Utils.licenseNameFor((String) newValue)));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
CheckBoxPreference themePreference = (CheckBoxPreference) findPreference("theme");
|
CheckBoxPreference themePreference = (CheckBoxPreference) findPreference("theme");
|
||||||
themePreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
themePreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
@Override
|
getActivity().recreate();
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
return true;
|
||||||
getActivity().recreate();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final EditTextPreference uploadLimit = (EditTextPreference) findPreference("uploads");
|
final EditTextPreference uploadLimit = (EditTextPreference) findPreference("uploads");
|
||||||
|
|
@ -51,36 +45,27 @@ public class SettingsFragment extends PreferenceFragment {
|
||||||
int uploads = sharedPref.getInt(Prefs.UPLOADS_SHOWING, 100);
|
int uploads = sharedPref.getInt(Prefs.UPLOADS_SHOWING, 100);
|
||||||
uploadLimit.setText(uploads + "");
|
uploadLimit.setText(uploads + "");
|
||||||
uploadLimit.setSummary(uploads + "");
|
uploadLimit.setSummary(uploads + "");
|
||||||
uploadLimit.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
uploadLimit.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||||
|
int value = Integer.parseInt(newValue.toString());
|
||||||
@Override
|
final SharedPreferences.Editor editor = sharedPref.edit();
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
if (value > 500) {
|
||||||
int value = Integer.parseInt(newValue.toString());
|
new AlertDialog.Builder(getActivity())
|
||||||
final SharedPreferences.Editor editor = sharedPref.edit();
|
.setTitle(R.string.maximum_limit)
|
||||||
if (value > 500) {
|
.setMessage(R.string.maximum_limit_alert)
|
||||||
new AlertDialog.Builder(getActivity())
|
.setPositiveButton(android.R.string.yes, (dialog, which) -> {})
|
||||||
.setTitle(R.string.maximum_limit)
|
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||||
.setMessage(R.string.maximum_limit_alert)
|
.show();
|
||||||
.setPositiveButton(android.R.string.yes,
|
editor.putInt(Prefs.UPLOADS_SHOWING, 500);
|
||||||
new DialogInterface.OnClickListener() {
|
editor.putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED,true);
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
uploadLimit.setSummary(500 + "");
|
||||||
}
|
uploadLimit.setText(500 + "");
|
||||||
})
|
} else {
|
||||||
.setIcon(android.R.drawable.ic_dialog_alert)
|
editor.putInt(Prefs.UPLOADS_SHOWING, Integer.parseInt(newValue.toString()));
|
||||||
.show();
|
editor.putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED,true);
|
||||||
editor.putInt(Prefs.UPLOADS_SHOWING, 500);
|
uploadLimit.setSummary(newValue.toString());
|
||||||
editor.putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED,true);
|
|
||||||
uploadLimit.setSummary(500 + "");
|
|
||||||
uploadLimit.setText(500 + "");
|
|
||||||
} else {
|
|
||||||
editor.putInt(Prefs.UPLOADS_SHOWING, Integer.parseInt(newValue.toString()));
|
|
||||||
editor.putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED,true);
|
|
||||||
uploadLimit.setSummary(newValue.toString());
|
|
||||||
}
|
|
||||||
editor.apply();
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
editor.apply();
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,12 +55,7 @@ public class NavigationBaseActivity extends BaseActivity
|
||||||
public void initBackButton() {
|
public void initBackButton() {
|
||||||
int backStackEntryCount = getSupportFragmentManager().getBackStackEntryCount();
|
int backStackEntryCount = getSupportFragmentManager().getBackStackEntryCount();
|
||||||
toggle.setDrawerIndicatorEnabled(backStackEntryCount == 0);
|
toggle.setDrawerIndicatorEnabled(backStackEntryCount == 0);
|
||||||
toggle.setToolbarNavigationClickListener(new View.OnClickListener() {
|
toggle.setToolbarNavigationClickListener(v -> onBackPressed());
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
onBackPressed();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initBack() {
|
public void initBack() {
|
||||||
|
|
@ -118,25 +113,17 @@ public class NavigationBaseActivity extends BaseActivity
|
||||||
new AlertDialog.Builder(this)
|
new AlertDialog.Builder(this)
|
||||||
.setMessage(R.string.logout_verification)
|
.setMessage(R.string.logout_verification)
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
|
.setPositiveButton(R.string.yes, (dialog, which) -> {
|
||||||
@Override
|
((CommonsApplication) getApplicationContext())
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
.clearApplicationData(NavigationBaseActivity.this);
|
||||||
((CommonsApplication) getApplicationContext())
|
Intent nearbyIntent = new Intent(
|
||||||
.clearApplicationData(NavigationBaseActivity.this);
|
NavigationBaseActivity.this, LoginActivity.class);
|
||||||
Intent nearbyIntent = new Intent(
|
nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||||
NavigationBaseActivity.this, LoginActivity.class);
|
nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
startActivity(nearbyIntent);
|
||||||
nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
finish();
|
||||||
startActivity(nearbyIntent);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
dialog.cancel();
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
.setNegativeButton(R.string.no, (dialog, which) -> dialog.cancel())
|
||||||
.show();
|
.show();
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -72,21 +72,13 @@ public class ExistingFileAsync extends AsyncTask<Void, Void, Boolean> {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||||
builder.setMessage(R.string.file_exists)
|
builder.setMessage(R.string.file_exists)
|
||||||
.setTitle(R.string.warning);
|
.setTitle(R.string.warning);
|
||||||
builder.setPositiveButton(R.string.no, new DialogInterface.OnClickListener() {
|
builder.setPositiveButton(R.string.no, (dialog, id) -> {
|
||||||
@Override
|
//Go back to ContributionsActivity
|
||||||
public void onClick(DialogInterface dialog, int id) {
|
Intent intent = new Intent(context, ContributionsActivity.class);
|
||||||
//Go back to ContributionsActivity
|
context.startActivity(intent);
|
||||||
Intent intent = new Intent(context, ContributionsActivity.class);
|
callback.onResult(Result.DUPLICATE_CANCELLED);
|
||||||
context.startActivity(intent);
|
|
||||||
callback.onResult(Result.DUPLICATE_CANCELLED);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
builder.setNegativeButton(R.string.yes, new DialogInterface.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int id) {
|
|
||||||
callback.onResult(Result.DUPLICATE_PROCEED);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
builder.setNegativeButton(R.string.yes, (dialog, id) -> callback.onResult(Result.DUPLICATE_PROCEED));
|
||||||
|
|
||||||
AlertDialog dialog = builder.create();
|
AlertDialog dialog = builder.create();
|
||||||
dialog.show();
|
dialog.show();
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import fr.free.nrw.commons.Media;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.auth.AuthenticatedActivity;
|
import fr.free.nrw.commons.auth.AuthenticatedActivity;
|
||||||
import fr.free.nrw.commons.category.CategorizationFragment;
|
import fr.free.nrw.commons.category.CategorizationFragment;
|
||||||
|
import fr.free.nrw.commons.category.OnCategoriesSaveHandler;
|
||||||
import fr.free.nrw.commons.contributions.Contribution;
|
import fr.free.nrw.commons.contributions.Contribution;
|
||||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||||
import fr.free.nrw.commons.modifications.CategoryModifier;
|
import fr.free.nrw.commons.modifications.CategoryModifier;
|
||||||
|
|
@ -43,7 +44,7 @@ public class MultipleShareActivity
|
||||||
AdapterView.OnItemClickListener,
|
AdapterView.OnItemClickListener,
|
||||||
FragmentManager.OnBackStackChangedListener,
|
FragmentManager.OnBackStackChangedListener,
|
||||||
MultipleUploadListFragment.OnMultipleUploadInitiatedHandler,
|
MultipleUploadListFragment.OnMultipleUploadInitiatedHandler,
|
||||||
CategorizationFragment.OnCategoriesSaveHandler {
|
OnCategoriesSaveHandler {
|
||||||
private CommonsApplication app;
|
private CommonsApplication app;
|
||||||
private ArrayList<Contribution> photosList = null;
|
private ArrayList<Contribution> photosList = null;
|
||||||
|
|
||||||
|
|
@ -125,19 +126,16 @@ public class MultipleShareActivity
|
||||||
Contribution up = photosList.get(i);
|
Contribution up = photosList.get(i);
|
||||||
final int uploadCount = i + 1; // Goddamn Java
|
final int uploadCount = i + 1; // Goddamn Java
|
||||||
|
|
||||||
uploadController.startUpload(up, new UploadController.ContributionUploadProgress() {
|
uploadController.startUpload(up, contribution -> {
|
||||||
@Override
|
dialog.setProgress(uploadCount);
|
||||||
public void onUploadStarted(Contribution contribution) {
|
if (uploadCount == photosList.size()) {
|
||||||
dialog.setProgress(uploadCount);
|
dialog.dismiss();
|
||||||
if(uploadCount == photosList.size()) {
|
Toast startingToast = Toast.makeText(
|
||||||
dialog.dismiss();
|
CommonsApplication.getInstance(),
|
||||||
Toast startingToast = Toast.makeText(
|
R.string.uploading_started,
|
||||||
CommonsApplication.getInstance(),
|
Toast.LENGTH_LONG
|
||||||
R.string.uploading_started,
|
);
|
||||||
Toast.LENGTH_LONG
|
startingToast.show();
|
||||||
);
|
|
||||||
startingToast.show();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ public class MwVolleyApi {
|
||||||
Timber.d("URL: %s", apiUrl);
|
Timber.d("URL: %s", apiUrl);
|
||||||
|
|
||||||
JsonRequest<QueryResponse> request = new QueryRequest(apiUrl,
|
JsonRequest<QueryResponse> request = new QueryRequest(apiUrl,
|
||||||
new LogResponseListener<QueryResponse>(), new LogResponseErrorListener());
|
new LogResponseListener<>(), new LogResponseErrorListener());
|
||||||
getQueue().add(request);
|
getQueue().add(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.Utils;
|
import fr.free.nrw.commons.Utils;
|
||||||
import fr.free.nrw.commons.auth.AuthenticatedActivity;
|
import fr.free.nrw.commons.auth.AuthenticatedActivity;
|
||||||
import fr.free.nrw.commons.category.CategorizationFragment;
|
import fr.free.nrw.commons.category.CategorizationFragment;
|
||||||
|
import fr.free.nrw.commons.category.OnCategoriesSaveHandler;
|
||||||
import fr.free.nrw.commons.contributions.Contribution;
|
import fr.free.nrw.commons.contributions.Contribution;
|
||||||
import fr.free.nrw.commons.modifications.CategoryModifier;
|
import fr.free.nrw.commons.modifications.CategoryModifier;
|
||||||
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
||||||
|
|
@ -44,6 +45,9 @@ import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
|
||||||
import fr.free.nrw.commons.mwapi.EventLog;
|
import fr.free.nrw.commons.mwapi.EventLog;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
import static fr.free.nrw.commons.upload.ExistingFileAsync.Result.DUPLICATE_PROCEED;
|
||||||
|
import static fr.free.nrw.commons.upload.ExistingFileAsync.Result.NO_DUPLICATE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity for the title/desc screen after image is selected. Also starts processing image
|
* Activity for the title/desc screen after image is selected. Also starts processing image
|
||||||
* GPS coordinates or user location (if enabled in Settings) for category suggestions.
|
* GPS coordinates or user location (if enabled in Settings) for category suggestions.
|
||||||
|
|
@ -51,7 +55,7 @@ import timber.log.Timber;
|
||||||
public class ShareActivity
|
public class ShareActivity
|
||||||
extends AuthenticatedActivity
|
extends AuthenticatedActivity
|
||||||
implements SingleUploadFragment.OnUploadActionInitiated,
|
implements SingleUploadFragment.OnUploadActionInitiated,
|
||||||
CategorizationFragment.OnCategoriesSaveHandler {
|
OnCategoriesSaveHandler {
|
||||||
|
|
||||||
private static final int REQUEST_PERM_ON_CREATE_STORAGE = 1;
|
private static final int REQUEST_PERM_ON_CREATE_STORAGE = 1;
|
||||||
private static final int REQUEST_PERM_ON_CREATE_LOCATION = 2;
|
private static final int REQUEST_PERM_ON_CREATE_LOCATION = 2;
|
||||||
|
|
@ -133,12 +137,9 @@ public class ShareActivity
|
||||||
Timber.d("Cache the categories found");
|
Timber.d("Cache the categories found");
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadController.startUpload(title, mediaUri, description, mimeType, source, decimalCoords, new UploadController.ContributionUploadProgress() {
|
uploadController.startUpload(title, mediaUri, description, mimeType, source, decimalCoords, c -> {
|
||||||
@Override
|
ShareActivity.this.contribution = c;
|
||||||
public void onUploadStarted(Contribution contribution) {
|
showPostUpload();
|
||||||
ShareActivity.this.contribution = contribution;
|
|
||||||
showPostUpload();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -379,14 +380,10 @@ public class ShareActivity
|
||||||
Timber.d("File SHA1 is: %s", fileSHA1);
|
Timber.d("File SHA1 is: %s", fileSHA1);
|
||||||
|
|
||||||
ExistingFileAsync fileAsyncTask =
|
ExistingFileAsync fileAsyncTask =
|
||||||
new ExistingFileAsync(fileSHA1, this, new ExistingFileAsync.Callback() {
|
new ExistingFileAsync(fileSHA1, this, result -> {
|
||||||
@Override
|
Timber.d("%s duplicate check: %s", mediaUri.toString(), result);
|
||||||
public void onResult(ExistingFileAsync.Result result) {
|
duplicateCheckPassed = (result == DUPLICATE_PROCEED
|
||||||
Timber.d("%s duplicate check: %s", mediaUri.toString(), result);
|
|| result == NO_DUPLICATE);
|
||||||
duplicateCheckPassed =
|
|
||||||
result == ExistingFileAsync.Result.DUPLICATE_PROCEED
|
|
||||||
|| result == ExistingFileAsync.Result.NO_DUPLICATE;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
fileAsyncTask.execute();
|
fileAsyncTask.execute();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|
@ -401,17 +398,12 @@ public class ShareActivity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Snackbar requestPermissionUsingSnackBar(
|
private Snackbar requestPermissionUsingSnackBar(String rationale,
|
||||||
String rationale, final String[] perms, final int code) {
|
final String[] perms,
|
||||||
|
final int code) {
|
||||||
Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content), rationale,
|
Snackbar snackbar = Snackbar.make(findViewById(android.R.id.content), rationale,
|
||||||
Snackbar.LENGTH_INDEFINITE)
|
Snackbar.LENGTH_INDEFINITE).setAction(R.string.ok,
|
||||||
.setAction(R.string.ok, new View.OnClickListener() {
|
view -> ActivityCompat.requestPermissions(ShareActivity.this, perms, code));
|
||||||
@Override
|
|
||||||
public void onClick(View view) {
|
|
||||||
ActivityCompat.requestPermissions(ShareActivity.this,
|
|
||||||
perms, code);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
snackbar.show();
|
snackbar.show();
|
||||||
return snackbar;
|
return snackbar;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,11 @@ import java.util.concurrent.Executor;
|
||||||
|
|
||||||
public class ExecutorUtils {
|
public class ExecutorUtils {
|
||||||
|
|
||||||
private static final Executor uiExecutor = new Executor() {
|
private static final Executor uiExecutor = command -> {
|
||||||
@Override
|
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||||
public void execute(Runnable command) {
|
command.run();
|
||||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
} else {
|
||||||
command.run();
|
new Handler(Looper.getMainLooper()).post(command);
|
||||||
} else {
|
|
||||||
new Handler(Looper.getMainLooper()).post(command);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,8 @@ public class FileUtils {
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
if (file.isDirectory()) {
|
if (file.isDirectory()) {
|
||||||
String[] children = file.list();
|
String[] children = file.list();
|
||||||
for (int i = 0; i < children.length; i++) {
|
for (String child : children) {
|
||||||
deletedAll = deleteFile(new File(file, children[i])) && deletedAll;
|
deletedAll = deleteFile(new File(file, child)) && deletedAll;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
deletedAll = file.delete();
|
deletedAll = file.delete();
|
||||||
|
|
|
||||||
22
app/src/main/java/fr/free/nrw/commons/utils/UiUtils.java
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
package fr.free.nrw.commons.utils;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.support.graphics.drawable.VectorDrawableCompat;
|
||||||
|
|
||||||
|
public class UiUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws a vectorial image onto a bitmap.
|
||||||
|
* @param vectorDrawable vectorial image
|
||||||
|
* @return bitmap representation of the vectorial image
|
||||||
|
*/
|
||||||
|
public static Bitmap getBitmap(VectorDrawableCompat vectorDrawable) {
|
||||||
|
Bitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(),
|
||||||
|
vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
|
||||||
|
Canvas canvas = new Canvas(bitmap);
|
||||||
|
vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
|
||||||
|
vectorDrawable.draw(canvas);
|
||||||
|
return bitmap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 251 B |
|
Before Width: | Height: | Size: 149 B |
|
Before Width: | Height: | Size: 139 B |
|
Before Width: | Height: | Size: 214 B |
|
Before Width: | Height: | Size: 487 B |
|
Before Width: | Height: | Size: 116 B |
|
Before Width: | Height: | Size: 317 B |
|
Before Width: | Height: | Size: 256 B |
|
Before Width: | Height: | Size: 364 B |
|
Before Width: | Height: | Size: 261 B |
|
Before Width: | Height: | Size: 247 B |
|
Before Width: | Height: | Size: 251 B |
|
Before Width: | Height: | Size: 453 B |
|
Before Width: | Height: | Size: 274 B |
|
Before Width: | Height: | Size: 255 B |
|
Before Width: | Height: | Size: 207 B |
|
Before Width: | Height: | Size: 352 B |
|
Before Width: | Height: | Size: 446 B |
|
Before Width: | Height: | Size: 590 B |
|
Before Width: | Height: | Size: 993 B |
|
Before Width: | Height: | Size: 171 B |
|
Before Width: | Height: | Size: 127 B |
|
Before Width: | Height: | Size: 114 B |
|
Before Width: | Height: | Size: 177 B |
|
Before Width: | Height: | Size: 323 B |
|
Before Width: | Height: | Size: 86 B |
|
Before Width: | Height: | Size: 231 B |
|
Before Width: | Height: | Size: 183 B |
|
Before Width: | Height: | Size: 240 B |
|
Before Width: | Height: | Size: 185 B |
|
Before Width: | Height: | Size: 168 B |
|
Before Width: | Height: | Size: 208 B |
|
Before Width: | Height: | Size: 322 B |
|
Before Width: | Height: | Size: 196 B |
|
Before Width: | Height: | Size: 246 B |
|
Before Width: | Height: | Size: 187 B |
|
Before Width: | Height: | Size: 144 B |
|
Before Width: | Height: | Size: 239 B |
|
Before Width: | Height: | Size: 640 B |
|
Before Width: | Height: | Size: 95 B |
|
Before Width: | Height: | Size: 373 B |
|
Before Width: | Height: | Size: 295 B |
|
Before Width: | Height: | Size: 446 B |
|
Before Width: | Height: | Size: 304 B |
|
Before Width: | Height: | Size: 273 B |
|
Before Width: | Height: | Size: 344 B |
|
Before Width: | Height: | Size: 557 B |
|
Before Width: | Height: | Size: 339 B |
|
Before Width: | Height: | Size: 354 B |
|
Before Width: | Height: | Size: 246 B |
|
Before Width: | Height: | Size: 175 B |
|
Before Width: | Height: | Size: 303 B |
|
Before Width: | Height: | Size: 940 B |
|
Before Width: | Height: | Size: 94 B |
|
Before Width: | Height: | Size: 528 B |