Merge remote-tracking branch 'refs/remotes/commons-app/master'
4
.gitignore
vendored
|
|
@ -23,3 +23,7 @@ local.properties
|
||||||
# OS files
|
# OS files
|
||||||
*.DS_Store
|
*.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
app/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
app/gradlew
|
||||||
|
app/gradlew.bat
|
||||||
|
app/gradle/wrapper/gradle-wrapper.properties
|
||||||
|
|
|
||||||
19
.travis.yml
|
|
@ -1,4 +1,12 @@
|
||||||
language: android
|
language: android
|
||||||
|
addons:
|
||||||
|
apt:
|
||||||
|
packages:
|
||||||
|
- w3m
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
- ANDROID_TARGET=android-22
|
||||||
|
- ANDROID_ABI=armeabi-v7a
|
||||||
android:
|
android:
|
||||||
components:
|
components:
|
||||||
- platform-tools
|
- platform-tools
|
||||||
|
|
@ -6,8 +14,17 @@ android:
|
||||||
- build-tools-25.0.1
|
- build-tools-25.0.1
|
||||||
- extra-google-m2repository
|
- extra-google-m2repository
|
||||||
- extra-android-m2repository
|
- extra-android-m2repository
|
||||||
|
- ${ANDROID_TARGET}
|
||||||
- android-25
|
- android-25
|
||||||
- sys-img-x86-android-18
|
- sys-img-${ANDROID_ABI}-${ANDROID_TARGET}
|
||||||
|
before_script:
|
||||||
|
- echo no | android create avd --force -n test -t $ANDROID_TARGET --abi $ANDROID_ABI
|
||||||
|
- emulator -avd test -no-audio -no-window &
|
||||||
|
- android-wait-for-emulator
|
||||||
|
script:
|
||||||
|
- ./gradlew test connectedAndroidTest -stacktrace
|
||||||
|
after_failure:
|
||||||
|
- w3m -dump ${TRAVIS_BUILD_DIR}/app/build/reports/androidTests/connected/*Test.html
|
||||||
jdk:
|
jdk:
|
||||||
# - openjdk8 # not yet available
|
# - openjdk8 # not yet available
|
||||||
- oraclejdk8
|
- oraclejdk8
|
||||||
|
|
|
||||||
20
CHANGELOG.md
|
|
@ -1,9 +1,25 @@
|
||||||
# Wikimedia Commons for Android
|
# Wikimedia Commons for Android
|
||||||
|
|
||||||
## v2.2.2 beta
|
## v2.4 beta
|
||||||
|
- Fixed memory issue with loading contributions on main screen
|
||||||
|
- Deleted images don't show up on contributions list
|
||||||
|
- Added Fresco library for image loading and LeakCanary for memory profiling
|
||||||
|
- Added navigation drawer and overhauled action bar
|
||||||
|
- Added logout functionality
|
||||||
|
- Fixed various issues with map of Nearby places
|
||||||
|
|
||||||
|
## v2.3 beta
|
||||||
|
- Add map of Nearby places
|
||||||
|
- Add overlay dialog when a Nearby place is tapped
|
||||||
|
- Set default number of uploads to display in Main activity as 100, and add option in Settings to change it
|
||||||
|
- Detect when 2FA is used for login and display message
|
||||||
|
- Display date uploaded and image coordinates in image details page
|
||||||
|
- Display message when GPS is turned off, and when no Nearby items are found
|
||||||
|
|
||||||
|
## v2.2.2
|
||||||
- Hotfix for Nearby localization issue
|
- Hotfix for Nearby localization issue
|
||||||
|
|
||||||
## v2.2.1 beta
|
## v2.2.1
|
||||||
- Hotfix for Settings crash
|
- Hotfix for Settings crash
|
||||||
|
|
||||||
## v2.2 beta (will not be released to Production due to bugs with Settings)
|
## v2.2 beta (will not be released to Production due to bugs with Settings)
|
||||||
|
|
|
||||||
6
CREDITS
|
|
@ -19,3 +19,9 @@ their contribution to the product.
|
||||||
* Veyndan Stuart
|
* Veyndan Stuart
|
||||||
* Vivek Maskara
|
* Vivek Maskara
|
||||||
* Neslihan Turan
|
* Neslihan Turan
|
||||||
|
|
||||||
|
3rd party open source libraries used:
|
||||||
|
* Butterknife
|
||||||
|
* GSON
|
||||||
|
* Timber
|
||||||
|
* MapBox
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ dependencies {
|
||||||
compile 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar'
|
compile 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar'
|
||||||
compile 'in.yuvi:http.fluent:1.3'
|
compile 'in.yuvi:http.fluent:1.3'
|
||||||
compile 'com.android.volley:volley:1.0.0'
|
compile 'com.android.volley:volley:1.0.0'
|
||||||
compile 'com.nostra13.universalimageloader:universal-image-loader:1.8.4'
|
|
||||||
compile 'ch.acra:acra:4.7.0'
|
compile 'ch.acra:acra:4.7.0'
|
||||||
compile 'org.mediawiki:api:1.3'
|
compile 'org.mediawiki:api:1.3'
|
||||||
compile 'commons-codec:commons-codec:1.10'
|
compile 'commons-codec:commons-codec:1.10'
|
||||||
|
|
@ -16,8 +15,21 @@ dependencies {
|
||||||
compile 'com.google.code.gson:gson:2.7'
|
compile 'com.google.code.gson:gson:2.7'
|
||||||
compile "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION"
|
compile "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION"
|
||||||
annotationProcessor "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION"
|
annotationProcessor "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION"
|
||||||
|
compile 'com.jakewharton.timber:timber:4.5.1'
|
||||||
|
compile ('com.mapbox.mapboxsdk:mapbox-android-sdk:5.0.2@aar'){
|
||||||
|
transitive=true
|
||||||
|
}
|
||||||
|
compile 'com.facebook.fresco:fresco:1.3.0'
|
||||||
|
compile 'com.facebook.stetho:stetho:1.5.0'
|
||||||
|
compile "com.google.guava:guava:${GUAVA_VERSION}"
|
||||||
|
|
||||||
testCompile 'junit:junit:4.12'
|
testCompile 'junit:junit:4.12'
|
||||||
|
androidTestCompile "com.android.support:support-annotations:${project.supportLibVersion}"
|
||||||
|
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
|
||||||
|
|
||||||
|
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
|
||||||
|
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
|
||||||
|
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
@ -28,10 +40,12 @@ android {
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId 'fr.free.nrw.commons'
|
applicationId 'fr.free.nrw.commons'
|
||||||
versionCode 69
|
versionCode 71
|
||||||
versionName '2.2.2'
|
versionName '2.4'
|
||||||
minSdkVersion project.minSdkVersion
|
minSdkVersion project.minSdkVersion
|
||||||
targetSdkVersion project.targetSdkVersion
|
targetSdkVersion project.targetSdkVersion
|
||||||
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
|
vectorDrawables.useSupportLibrary = true
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|
@ -46,4 +60,4 @@ android {
|
||||||
disable 'ExtraTranslation'
|
disable 'ExtraTranslation'
|
||||||
abortOnError false
|
abortOnError false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ task checkstyle(type: Checkstyle) {
|
||||||
|
|
||||||
task pmd(type: Pmd) {
|
task pmd(type: Pmd) {
|
||||||
ignoreFailures = true
|
ignoreFailures = true
|
||||||
ruleSetFiles = files("${project.rootDir}/ruleset.xml")
|
ruleSetFiles = files("${project.rootDir}/script/style/ruleset.xml")
|
||||||
ruleSets = []
|
ruleSets = []
|
||||||
|
|
||||||
source 'src'
|
source 'src'
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package fr.free.nrw.commons;
|
||||||
|
|
||||||
|
import static android.support.test.espresso.Espresso.onView;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
|
||||||
|
import static android.support.test.espresso.matcher.ViewMatchers.withText;
|
||||||
|
|
||||||
|
import android.support.test.espresso.assertion.ViewAssertions;
|
||||||
|
import android.support.test.filters.LargeTest;
|
||||||
|
import android.support.test.rule.ActivityTestRule;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.nearby.NearbyActivity;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
@LargeTest
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class NearbyActivityTest {
|
||||||
|
@Rule
|
||||||
|
public final ActivityTestRule<NearbyActivity> nearby =
|
||||||
|
new ActivityTestRule<>(NearbyActivity.class);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testActivityLaunch() {
|
||||||
|
onView(withText("Nearby Places")).check(ViewAssertions.matches(isDisplayed()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
package fr.free.nrw.commons;
|
||||||
|
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
import android.support.test.espresso.Espresso;
|
||||||
|
import android.support.test.espresso.action.ViewActions;
|
||||||
|
import android.support.test.espresso.assertion.ViewAssertions;
|
||||||
|
import android.support.test.espresso.matcher.ViewMatchers;
|
||||||
|
import android.support.test.filters.LargeTest;
|
||||||
|
import android.support.test.rule.ActivityTestRule;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import org.hamcrest.Matcher;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.settings.SettingsActivity;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.allOf;
|
||||||
|
import static org.hamcrest.Matchers.anything;
|
||||||
|
|
||||||
|
@LargeTest
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class SettingsActivityTest {
|
||||||
|
private SharedPreferences prefs;
|
||||||
|
private Map<String,?> prefValues;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ActivityTestRule<SettingsActivity> activityRule =
|
||||||
|
new ActivityTestRule<SettingsActivity>(SettingsActivity.class,
|
||||||
|
false /* Initial touch mode */, true /* launch activity */) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void afterActivityLaunched() {
|
||||||
|
// save preferences
|
||||||
|
prefs = PreferenceManager.getDefaultSharedPreferences(this.getActivity());
|
||||||
|
prefValues = prefs.getAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void afterActivityFinished() {
|
||||||
|
// restore preferences
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
for (Map.Entry<String,?> entry: prefValues.entrySet()) {
|
||||||
|
String key = entry.getKey();
|
||||||
|
Object val = entry.getValue();
|
||||||
|
if (val instanceof String) {
|
||||||
|
editor.putString(key, (String)val);
|
||||||
|
} else if (val instanceof Boolean) {
|
||||||
|
editor.putBoolean(key, (Boolean)val);
|
||||||
|
} else if (val instanceof Integer) {
|
||||||
|
editor.putInt(key, (Integer)val);
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("type not implemented: " + entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
editor.apply();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void oneLicenseIsChecked() {
|
||||||
|
// click "License" (the first item)
|
||||||
|
Espresso.onData(anything())
|
||||||
|
.inAdapterView(findPreferenceList())
|
||||||
|
.atPosition(0)
|
||||||
|
.perform(ViewActions.click());
|
||||||
|
|
||||||
|
// test the selected item
|
||||||
|
Espresso.onView(ViewMatchers.isChecked())
|
||||||
|
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void afterClickingCcby4ItWillStay() {
|
||||||
|
// click "License" (the first item)
|
||||||
|
Espresso.onData(anything())
|
||||||
|
.inAdapterView(findPreferenceList())
|
||||||
|
.atPosition(0)
|
||||||
|
.perform(ViewActions.click());
|
||||||
|
|
||||||
|
// click "Attribution 4.0"
|
||||||
|
Espresso.onView(
|
||||||
|
ViewMatchers.withText(R.string.license_name_cc_by_four)
|
||||||
|
).perform(ViewActions.click());
|
||||||
|
|
||||||
|
// click "License" (the first item)
|
||||||
|
Espresso.onData(anything())
|
||||||
|
.inAdapterView(findPreferenceList())
|
||||||
|
.atPosition(0)
|
||||||
|
.perform(ViewActions.click());
|
||||||
|
|
||||||
|
// test the value remains "Attribution 4.0"
|
||||||
|
Espresso.onView(ViewMatchers.isChecked())
|
||||||
|
.check(ViewAssertions.matches(
|
||||||
|
ViewMatchers.withText(R.string.license_name_cc_by_four)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Matcher<View> findPreferenceList() {
|
||||||
|
return allOf(
|
||||||
|
ViewMatchers.isDescendantOfA(ViewMatchers.withId(R.id.settingsFragment)),
|
||||||
|
ViewMatchers.withResourceName("list"),
|
||||||
|
ViewMatchers.hasFocus()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
package="fr.free.nrw.commons">
|
package="fr.free.nrw.commons">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
@ -31,6 +30,10 @@
|
||||||
<activity
|
<activity
|
||||||
android:name=".auth.LoginActivity"
|
android:name=".auth.LoginActivity"
|
||||||
>
|
>
|
||||||
|
<intent-filter>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".WelcomeActivity"
|
android:name=".WelcomeActivity"
|
||||||
|
|
@ -66,10 +69,6 @@
|
||||||
android:icon="@drawable/ic_launcher"
|
android:icon="@drawable/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
>
|
>
|
||||||
<intent-filter>
|
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".settings.SettingsActivity"
|
android:name=".settings.SettingsActivity"
|
||||||
|
|
|
||||||
55
app/src/main/assets/queries/nearby_query.rq
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
SELECT
|
||||||
|
(SAMPLE(?location) as ?location)
|
||||||
|
?item
|
||||||
|
(SAMPLE(COALESCE(?item_label_preferred_language, ?item_label_any_language)) as ?label)
|
||||||
|
(SAMPLE(?classId) as ?class)
|
||||||
|
(SAMPLE(COALESCE(?class_label_preferred_language, ?class_label_any_language, "?")) as ?class_label)
|
||||||
|
(SAMPLE(COALESCE(?icon0, ?icon1)) as ?icon)
|
||||||
|
(SAMPLE(COALESCE(?emoji0, ?emoji1)) as ?emoji)
|
||||||
|
?wikipediaArticle
|
||||||
|
?commonsArticle
|
||||||
|
WHERE {
|
||||||
|
# Around given location...
|
||||||
|
SERVICE wikibase:around {
|
||||||
|
?item wdt:P625 ?location.
|
||||||
|
bd:serviceParam wikibase:center "Point(${LONG} ${LAT})"^^geo:wktLiteral.
|
||||||
|
bd:serviceParam wikibase:radius "${RAD}" . # Radius in kilometers.
|
||||||
|
}
|
||||||
|
|
||||||
|
# ... and without an image.
|
||||||
|
MINUS {?item wdt:P18 []}
|
||||||
|
|
||||||
|
# Get the label in the preferred language of the user, or any other language if no label is available in that language.
|
||||||
|
OPTIONAL {?item rdfs:label ?item_label_preferred_language. FILTER (lang(?item_label_preferred_language) = "${LANG}")}
|
||||||
|
OPTIONAL {?item rdfs:label ?item_label_any_language}
|
||||||
|
|
||||||
|
# Get the class label in the preferred language of the user, or any other language if no label is available in that language.
|
||||||
|
OPTIONAL {
|
||||||
|
?item p:P31/ps:P31 ?classId.
|
||||||
|
OPTIONAL {?classId rdfs:label ?class_label_preferred_language. FILTER (lang(?class_label_preferred_language) = "${LANG}")}
|
||||||
|
OPTIONAL {?classId rdfs:label ?class_label_any_language}
|
||||||
|
|
||||||
|
# Get icon
|
||||||
|
OPTIONAL { ?classId wdt:P2910 ?icon0. }
|
||||||
|
OPTIONAL { ?classId wdt:P279*/wdt:P2910 ?icon1. }
|
||||||
|
# Get emoji
|
||||||
|
OPTIONAL { ?classId wdt:P487 ?emoji0. }
|
||||||
|
OPTIONAL { ?classId wdt:P279*/wdt:P487 ?emoji1. }
|
||||||
|
OPTIONAL {
|
||||||
|
?sitelink schema:about ?item .
|
||||||
|
?sitelink schema:inLanguage "en"
|
||||||
|
}
|
||||||
|
OPTIONAL {
|
||||||
|
?wikipediaArticle schema:about ?item ;
|
||||||
|
schema:isPartOf <https://en.wikipedia.org/> .
|
||||||
|
SERVICE wikibase:label { bd:serviceParam wikibase:language "en" }
|
||||||
|
}
|
||||||
|
|
||||||
|
OPTIONAL {
|
||||||
|
?commonsArticle schema:about ?item ;
|
||||||
|
schema:isPartOf <https://commons.wikimedia.org/> .
|
||||||
|
SERVICE wikibase:label { bd:serviceParam wikibase:language "en" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GROUP BY ?item ?wikipediaArticle ?commonsArticle
|
||||||
|
|
@ -1,24 +1,16 @@
|
||||||
package fr.free.nrw.commons;
|
package fr.free.nrw.commons;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Html;
|
|
||||||
import android.text.method.LinkMovementMethod;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import fr.free.nrw.commons.theme.BaseActivity;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
|
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
||||||
|
|
||||||
public class AboutActivity extends BaseActivity {
|
public class AboutActivity extends NavigationBaseActivity {
|
||||||
@BindView(R.id.about_version) TextView versionText;
|
@BindView(R.id.about_version) TextView versionText;
|
||||||
@BindView(R.id.about_license) TextView licenseText;
|
|
||||||
@BindView(R.id.about_improve) TextView improveText;
|
|
||||||
@BindView(R.id.about_privacy_policy) TextView privacyPolicyText;
|
|
||||||
@BindView(R.id.about_uploads_to) TextView uploadsToText;
|
|
||||||
@BindView(R.id.about_credits) TextView creditsText;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
|
@ -27,24 +19,12 @@ public class AboutActivity extends BaseActivity {
|
||||||
|
|
||||||
ButterKnife.bind(this);
|
ButterKnife.bind(this);
|
||||||
|
|
||||||
uploadsToText.setText(CommonsApplication.EVENTLOG_WIKI);
|
|
||||||
versionText.setText(BuildConfig.VERSION_NAME);
|
versionText.setText(BuildConfig.VERSION_NAME);
|
||||||
|
initDrawer();
|
||||||
// We can't use formatted strings directly because it breaks with
|
|
||||||
// our localization tools. Grab an HTML string and turn it into
|
|
||||||
// a formatted string.
|
|
||||||
fixFormatting(licenseText, R.string.about_license);
|
|
||||||
fixFormatting(improveText, R.string.about_improve);
|
|
||||||
fixFormatting(privacyPolicyText, R.string.about_privacy_policy);
|
|
||||||
fixFormatting(creditsText, R.string.about_credits);
|
|
||||||
|
|
||||||
licenseText.setMovementMethod(LinkMovementMethod.getInstance());
|
|
||||||
improveText.setMovementMethod(LinkMovementMethod.getInstance());
|
|
||||||
privacyPolicyText.setMovementMethod(LinkMovementMethod.getInstance());
|
|
||||||
creditsText.setMovementMethod(LinkMovementMethod.getInstance());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fixFormatting(TextView textView, int resource) {
|
public static void startYourself(Context context) {
|
||||||
textView.setText(Html.fromHtml(getResources().getString(resource)));
|
Intent settingsIntent = new Intent(context, AboutActivity.class);
|
||||||
|
context.startActivity(settingsIntent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,20 +5,26 @@ import android.accounts.AccountManager;
|
||||||
import android.accounts.AuthenticatorException;
|
import android.accounts.AuthenticatorException;
|
||||||
import android.accounts.OperationCanceledException;
|
import android.accounts.OperationCanceledException;
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
import android.support.v4.util.LruCache;
|
import android.support.v4.util.LruCache;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.android.volley.RequestQueue;
|
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||||
import com.android.volley.toolbox.BasicNetwork;
|
import com.facebook.stetho.Stetho;
|
||||||
import com.android.volley.toolbox.DiskBasedCache;
|
|
||||||
import com.android.volley.toolbox.HurlStack;
|
import fr.free.nrw.commons.caching.CacheController;
|
||||||
import com.nostra13.universalimageloader.cache.disc.impl.TotalSizeLimitedDiscCache;
|
import fr.free.nrw.commons.category.Category;
|
||||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
import fr.free.nrw.commons.contributions.Contribution;
|
||||||
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
|
import fr.free.nrw.commons.data.DBOpenHelper;
|
||||||
import com.nostra13.universalimageloader.utils.StorageUtils;
|
import fr.free.nrw.commons.modifications.ModifierSequence;
|
||||||
|
import fr.free.nrw.commons.auth.AccountUtil;
|
||||||
|
import fr.free.nrw.commons.nearby.NearbyPlaces;
|
||||||
|
|
||||||
|
import com.squareup.leakcanary.LeakCanary;
|
||||||
|
|
||||||
import org.acra.ACRA;
|
import org.acra.ACRA;
|
||||||
import org.acra.ReportingInteractionMode;
|
import org.acra.ReportingInteractionMode;
|
||||||
|
|
@ -33,12 +39,12 @@ import org.apache.http.impl.client.DefaultHttpClient;
|
||||||
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
|
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
|
||||||
import org.apache.http.params.BasicHttpParams;
|
import org.apache.http.params.BasicHttpParams;
|
||||||
import org.apache.http.params.CoreProtocolPNames;
|
import org.apache.http.params.CoreProtocolPNames;
|
||||||
import org.mediawiki.api.MWApi;
|
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import fr.free.nrw.commons.auth.WikiAccountAuthenticator;
|
import fr.free.nrw.commons.utils.FileUtils;
|
||||||
import fr.free.nrw.commons.caching.CacheController;
|
import timber.log.Timber;
|
||||||
|
|
||||||
// TODO: Use ProGuard to rip out reporting when publishing
|
// TODO: Use ProGuard to rip out reporting when publishing
|
||||||
@ReportsCrashes(
|
@ReportsCrashes(
|
||||||
|
|
@ -51,7 +57,6 @@ import fr.free.nrw.commons.caching.CacheController;
|
||||||
)
|
)
|
||||||
public class CommonsApplication extends Application {
|
public class CommonsApplication extends Application {
|
||||||
|
|
||||||
private MWApi api;
|
|
||||||
private Account currentAccount = null; // Unlike a savings account...
|
private Account currentAccount = null; // Unlike a savings account...
|
||||||
public static final String API_URL = "https://commons.wikimedia.org/w/api.php";
|
public static final String API_URL = "https://commons.wikimedia.org/w/api.php";
|
||||||
public static final String IMAGE_URL_BASE = "https://upload.wikimedia.org/wikipedia/commons";
|
public static final String IMAGE_URL_BASE = "https://upload.wikimedia.org/wikipedia/commons";
|
||||||
|
|
@ -70,11 +75,37 @@ public class CommonsApplication extends Application {
|
||||||
public static final String FEEDBACK_EMAIL = "commons-app-android@googlegroups.com";
|
public static final String FEEDBACK_EMAIL = "commons-app-android@googlegroups.com";
|
||||||
public static final String FEEDBACK_EMAIL_SUBJECT = "Commons Android App (%s) Feedback";
|
public static final String FEEDBACK_EMAIL_SUBJECT = "Commons Android App (%s) Feedback";
|
||||||
|
|
||||||
public RequestQueue volleyQueue;
|
private static CommonsApplication instance = null;
|
||||||
|
private AbstractHttpClient httpClient = null;
|
||||||
|
private MWApi api = null;
|
||||||
|
LruCache<String, String> thumbnailUrlCache = new LruCache<>(1024);
|
||||||
|
private CacheController cacheData = null;
|
||||||
|
private DBOpenHelper dbOpenHelper = null;
|
||||||
|
private NearbyPlaces nearbyPlaces = null;
|
||||||
|
|
||||||
public CacheController cacheData;
|
/**
|
||||||
|
* This should not be called by ANY application code (other than the magic Android glue)
|
||||||
|
* Use CommonsApplication.getInstance() instead to get the singleton.
|
||||||
|
*/
|
||||||
|
public CommonsApplication() {
|
||||||
|
CommonsApplication.instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
public static AbstractHttpClient createHttpClient() {
|
public static CommonsApplication getInstance() {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new CommonsApplication();
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractHttpClient getHttpClient() {
|
||||||
|
if (httpClient == null) {
|
||||||
|
httpClient = newHttpClient();
|
||||||
|
}
|
||||||
|
return httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AbstractHttpClient newHttpClient() {
|
||||||
BasicHttpParams params = new BasicHttpParams();
|
BasicHttpParams params = new BasicHttpParams();
|
||||||
SchemeRegistry schemeRegistry = new SchemeRegistry();
|
SchemeRegistry schemeRegistry = new SchemeRegistry();
|
||||||
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
|
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
|
||||||
|
|
@ -85,87 +116,78 @@ public class CommonsApplication extends Application {
|
||||||
return new DefaultHttpClient(cm, params);
|
return new DefaultHttpClient(cm, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static MWApi createMWApi() {
|
public MWApi getMWApi() {
|
||||||
return new MWApi(API_URL, createHttpClient());
|
if (api == null) {
|
||||||
|
api = newMWApi();
|
||||||
|
}
|
||||||
|
return api;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MWApi newMWApi() {
|
||||||
|
return new MWApi(API_URL, getHttpClient());
|
||||||
|
}
|
||||||
|
|
||||||
|
public CacheController getCacheData() {
|
||||||
|
if (cacheData == null) {
|
||||||
|
cacheData = new CacheController();
|
||||||
|
}
|
||||||
|
return cacheData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LruCache<String, String> getThumbnailUrlCache() {
|
||||||
|
return thumbnailUrlCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized DBOpenHelper getDBOpenHelper() {
|
||||||
|
if (dbOpenHelper == null) {
|
||||||
|
dbOpenHelper = new DBOpenHelper(this);
|
||||||
|
}
|
||||||
|
return dbOpenHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized NearbyPlaces getNearbyPlaces() {
|
||||||
|
if (nearbyPlaces == null) {
|
||||||
|
nearbyPlaces = new NearbyPlaces();
|
||||||
|
}
|
||||||
|
return nearbyPlaces;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
if (LeakCanary.isInAnalyzerProcess(this)) {
|
||||||
|
// This process is dedicated to LeakCanary for heap analysis.
|
||||||
|
// You should not init your app in this process.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LeakCanary.install(this);
|
||||||
|
|
||||||
|
Timber.plant(new Timber.DebugTree());
|
||||||
|
|
||||||
|
Stetho.initializeWithDefaults(this);
|
||||||
|
|
||||||
if (!BuildConfig.DEBUG) {
|
if (!BuildConfig.DEBUG) {
|
||||||
ACRA.init(this);
|
ACRA.init(this);
|
||||||
}
|
}
|
||||||
// Fire progress callbacks for every 3% of uploaded content
|
// Fire progress callbacks for every 3% of uploaded content
|
||||||
System.setProperty("in.yuvi.http.fluent.PROGRESS_TRIGGER_THRESHOLD", "3.0");
|
System.setProperty("in.yuvi.http.fluent.PROGRESS_TRIGGER_THRESHOLD", "3.0");
|
||||||
api = createMWApi();
|
|
||||||
|
|
||||||
ImageLoaderConfiguration imageLoaderConfiguration = new ImageLoaderConfiguration.Builder(getApplicationContext())
|
Fresco.initialize(this);
|
||||||
.discCache(new TotalSizeLimitedDiscCache(StorageUtils.getCacheDirectory(this), 128 * 1024 * 1024))
|
|
||||||
.build();
|
|
||||||
ImageLoader.getInstance().init(imageLoaderConfiguration);
|
|
||||||
|
|
||||||
// Initialize EventLogging
|
// Initialize EventLogging
|
||||||
EventLog.setApp(this);
|
EventLog.setApp(this);
|
||||||
|
|
||||||
// based off https://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
|
|
||||||
// Cache for 1/8th of available VM memory
|
|
||||||
long maxMem = Runtime.getRuntime().maxMemory();
|
|
||||||
if (maxMem < 48L * 1024L * 1024L) {
|
|
||||||
// Cache only one bitmap if VM memory is too small (such as Nexus One);
|
|
||||||
Log.d("Commons", "Skipping bitmap cache; max mem is: " + maxMem);
|
|
||||||
imageCache = new LruCache<>(1);
|
|
||||||
} else {
|
|
||||||
int cacheSize = (int) (maxMem / (1024 * 8));
|
|
||||||
Log.d("Commons", "Bitmap cache size " + cacheSize + " from max mem " + maxMem);
|
|
||||||
imageCache = new LruCache<String, Bitmap>(cacheSize) {
|
|
||||||
@Override
|
|
||||||
protected int sizeOf(String key, Bitmap bitmap) {
|
|
||||||
int bitmapSize;
|
|
||||||
bitmapSize = bitmap.getByteCount();
|
|
||||||
|
|
||||||
// The cache size will be measured in kilobytes rather than number of items.
|
|
||||||
return bitmapSize / 1024;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
//For caching area -> categories
|
//For caching area -> categories
|
||||||
cacheData = new CacheController();
|
cacheData = new CacheController();
|
||||||
|
|
||||||
DiskBasedCache cache = new DiskBasedCache(getCacheDir(), 16 * 1024 * 1024);
|
|
||||||
volleyQueue = new RequestQueue(cache, new BasicNetwork(new HurlStack()));
|
|
||||||
volleyQueue.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private com.android.volley.toolbox.ImageLoader imageLoader;
|
/**
|
||||||
private LruCache<String, Bitmap> imageCache;
|
* @return Account|null
|
||||||
|
*/
|
||||||
public com.android.volley.toolbox.ImageLoader getImageLoader() {
|
|
||||||
if(imageLoader == null) {
|
|
||||||
imageLoader = new com.android.volley.toolbox.ImageLoader(volleyQueue, new com.android.volley.toolbox.ImageLoader.ImageCache() {
|
|
||||||
@Override
|
|
||||||
public Bitmap getBitmap(String key) {
|
|
||||||
return imageCache.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void putBitmap(String key, Bitmap bitmap) {
|
|
||||||
imageCache.put(key, bitmap);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
imageLoader.setBatchedResponseDelay(0);
|
|
||||||
}
|
|
||||||
return imageLoader;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MWApi getApi() {
|
|
||||||
return api;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Account getCurrentAccount() {
|
public Account getCurrentAccount() {
|
||||||
if(currentAccount == null) {
|
if(currentAccount == null) {
|
||||||
AccountManager accountManager = AccountManager.get(this);
|
AccountManager accountManager = AccountManager.get(this);
|
||||||
Account[] allAccounts = accountManager.getAccountsByType(WikiAccountAuthenticator.COMMONS_ACCOUNT_TYPE);
|
Account[] allAccounts = accountManager.getAccountsByType(AccountUtil.accountType());
|
||||||
if(allAccounts.length != 0) {
|
if(allAccounts.length != 0) {
|
||||||
currentAccount = allAccounts[0];
|
currentAccount = allAccounts[0];
|
||||||
}
|
}
|
||||||
|
|
@ -181,21 +203,12 @@ public class CommonsApplication extends Application {
|
||||||
return false; // This should never happen
|
return false; // This should never happen
|
||||||
}
|
}
|
||||||
|
|
||||||
accountManager.invalidateAuthToken(WikiAccountAuthenticator.COMMONS_ACCOUNT_TYPE, api.getAuthCookie());
|
accountManager.invalidateAuthToken(AccountUtil.accountType(), getMWApi().getAuthCookie());
|
||||||
try {
|
try {
|
||||||
String authCookie = accountManager.blockingGetAuthToken(curAccount, "", false);
|
String authCookie = accountManager.blockingGetAuthToken(curAccount, "", false);
|
||||||
api.setAuthCookie(authCookie);
|
getMWApi().setAuthCookie(authCookie);
|
||||||
return true;
|
return true;
|
||||||
} catch (OperationCanceledException e) {
|
} catch (OperationCanceledException | NullPointerException | IOException | AuthenticatorException e) {
|
||||||
e.printStackTrace();
|
|
||||||
return false;
|
|
||||||
} catch (AuthenticatorException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return false;
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return false;
|
|
||||||
} catch (NullPointerException e) {
|
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -206,4 +219,46 @@ public class CommonsApplication extends Application {
|
||||||
return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA) ||
|
return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA) ||
|
||||||
pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT);
|
pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void clearApplicationData(Context context) {
|
||||||
|
File cacheDirectory = context.getCacheDir();
|
||||||
|
File applicationDirectory = new File(cacheDirectory.getParent());
|
||||||
|
if (applicationDirectory.exists()) {
|
||||||
|
String[] fileNames = applicationDirectory.list();
|
||||||
|
for (String fileName : fileNames) {
|
||||||
|
if (!fileName.equals("lib")) {
|
||||||
|
FileUtils.deleteFile(new File(applicationDirectory, fileName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AccountManager accountManager = AccountManager.get(this);
|
||||||
|
Account[] allAccounts = accountManager.getAccountsByType(AccountUtil.accountType());
|
||||||
|
for (int index = 0; index < allAccounts.length; index++) {
|
||||||
|
accountManager.removeAccount(allAccounts[index], null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: fix preference manager
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(getInstance()).edit().clear().commit();
|
||||||
|
SharedPreferences preferences = context
|
||||||
|
.getSharedPreferences("fr.free.nrw.commons", MODE_PRIVATE);
|
||||||
|
preferences.edit().clear().commit();
|
||||||
|
context.getSharedPreferences("prefs", Context.MODE_PRIVATE).edit().clear().commit();
|
||||||
|
preferences.edit().putBoolean("firstrun", false).apply();
|
||||||
|
updateAllDatabases();
|
||||||
|
currentAccount = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes all tables and re-creates them.
|
||||||
|
*/
|
||||||
|
public void updateAllDatabases() {
|
||||||
|
DBOpenHelper dbOpenHelper = CommonsApplication.getInstance().getDBOpenHelper();
|
||||||
|
dbOpenHelper.getReadableDatabase().close();
|
||||||
|
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
|
||||||
|
|
||||||
|
ModifierSequence.Table.onDelete(db);
|
||||||
|
Category.Table.onDelete(db);
|
||||||
|
Contribution.Table.onDelete(db);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,19 +4,19 @@ import android.content.SharedPreferences;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.apache.http.HttpResponse;
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.impl.client.AbstractHttpClient;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
import fr.free.nrw.commons.settings.Prefs;
|
import fr.free.nrw.commons.settings.Prefs;
|
||||||
import in.yuvi.http.fluent.Http;
|
import in.yuvi.http.fluent.Http;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class EventLog {
|
public class EventLog {
|
||||||
|
|
||||||
|
|
@ -30,19 +30,19 @@ public class EventLog {
|
||||||
boolean allSuccess = true;
|
boolean allSuccess = true;
|
||||||
// Not using the default URL connection, since that seems to have different behavior than the rest of the code
|
// Not using the default URL connection, since that seems to have different behavior than the rest of the code
|
||||||
for(LogBuilder logBuilder: logBuilders) {
|
for(LogBuilder logBuilder: logBuilders) {
|
||||||
HttpURLConnection conn;
|
|
||||||
try {
|
try {
|
||||||
URL url = logBuilder.toUrl();
|
URL url = logBuilder.toUrl();
|
||||||
HttpResponse response = Http.get(url.toString()).use(CommonsApplication.createHttpClient()).asResponse();
|
AbstractHttpClient httpClient = CommonsApplication.getInstance().getHttpClient();
|
||||||
|
HttpResponse response = Http.get(url.toString()).use(httpClient).asResponse();
|
||||||
|
|
||||||
if(response.getStatusLine().getStatusCode() != 204) {
|
if(response.getStatusLine().getStatusCode() != 204) {
|
||||||
allSuccess = false;
|
allSuccess = false;
|
||||||
}
|
}
|
||||||
Log.d("Commons", "EventLog hit " + url.toString());
|
Timber.d("EventLog hit %s", url);
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// Probably just ignore for now. Can be much more robust with a service, etc later on.
|
// Probably just ignore for now. Can be much more robust with a service, etc later on.
|
||||||
Log.d("Commons", "IO Error, EventLog hit skipped");
|
Timber.d("IO Error, EventLog hit skipped");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,9 +94,7 @@ public class EventLog {
|
||||||
data.put("appversion", "Android/" + BuildConfig.VERSION_NAME);
|
data.put("appversion", "Android/" + BuildConfig.VERSION_NAME);
|
||||||
fullData.put("event", data);
|
fullData.put("event", data);
|
||||||
return new URL(CommonsApplication.EVENTLOG_URL + "?" + Utils.urlEncode(fullData.toString()) + ";");
|
return new URL(CommonsApplication.EVENTLOG_URL + "?" + Utils.urlEncode(fullData.toString()) + ";");
|
||||||
} catch (MalformedURLException e) {
|
} catch (MalformedURLException | JSONException e) {
|
||||||
throw new RuntimeException(e);
|
|
||||||
} catch (JSONException e) {
|
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,9 +69,8 @@ public class LicenseList {
|
||||||
int nameId = stringIdByName(stringId);
|
int nameId = stringIdByName(stringId);
|
||||||
//Log.d("Commons", "LicenseList.nameForTemplate: nameId: " + nameId);
|
//Log.d("Commons", "LicenseList.nameForTemplate: nameId: " + nameId);
|
||||||
if(nameId != 0) {
|
if(nameId != 0) {
|
||||||
String name = res.getString(nameId);
|
|
||||||
//Log.d("Commons", "LicenseList.nameForTemplate: name: " + name);
|
//Log.d("Commons", "LicenseList.nameForTemplate: name: " + name);
|
||||||
return name;
|
return res.getString(nameId);
|
||||||
}
|
}
|
||||||
return template;
|
return template;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
94
app/src/main/java/fr/free/nrw/commons/MWApi.java
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
package fr.free.nrw.commons;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.apache.http.impl.client.AbstractHttpClient;
|
||||||
|
import org.mediawiki.api.ApiResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Addshore
|
||||||
|
*/
|
||||||
|
public class MWApi extends org.mediawiki.api.MWApi {
|
||||||
|
|
||||||
|
/** We don't actually use this but need to pass it in requests */
|
||||||
|
private static String LOGIN_RETURN_TO_URL = "https://commons.wikimedia.org";
|
||||||
|
|
||||||
|
public MWApi(String apiURL, AbstractHttpClient client) {
|
||||||
|
super(apiURL, client);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param username String
|
||||||
|
* @param password String
|
||||||
|
* @return String as returned by this.getErrorCodeToReturn()
|
||||||
|
* @throws IOException On api request IO issue
|
||||||
|
*/
|
||||||
|
public String login(String username, String password) throws IOException {
|
||||||
|
String token = this.getLoginToken();
|
||||||
|
ApiResult loginApiResult = this.action("clientlogin")
|
||||||
|
.param("rememberMe", "1")
|
||||||
|
.param("username", username)
|
||||||
|
.param("password", password)
|
||||||
|
.param("logintoken", token)
|
||||||
|
.param("loginreturnurl", LOGIN_RETURN_TO_URL)
|
||||||
|
.post();
|
||||||
|
return this.getErrorCodeToReturn( loginApiResult );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param username String
|
||||||
|
* @param password String
|
||||||
|
* @param twoFactorCode String
|
||||||
|
* @return String as returned by this.getErrorCodeToReturn()
|
||||||
|
* @throws IOException On api request IO issue
|
||||||
|
*/
|
||||||
|
public String login(String username, String password, String twoFactorCode) throws IOException {
|
||||||
|
String token = this.getLoginToken();//TODO cache this instead of calling again when 2FAing
|
||||||
|
ApiResult loginApiResult = this.action("clientlogin")
|
||||||
|
.param("rememberMe", "1")
|
||||||
|
.param("username", username)
|
||||||
|
.param("password", password)
|
||||||
|
.param("logintoken", token)
|
||||||
|
.param("logincontinue", "1")
|
||||||
|
.param("OATHToken", twoFactorCode)
|
||||||
|
.post();
|
||||||
|
|
||||||
|
return this.getErrorCodeToReturn( loginApiResult );
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getLoginToken() throws IOException {
|
||||||
|
ApiResult tokenResult = this.action("query")
|
||||||
|
.param("action", "query")
|
||||||
|
.param("meta", "tokens")
|
||||||
|
.param("type", "login")
|
||||||
|
.post();
|
||||||
|
return tokenResult.getString("/api/query/tokens/@logintoken");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param loginApiResult ApiResult Any clientlogin api result
|
||||||
|
* @return String On success: "PASS"
|
||||||
|
* continue: "2FA" (More information required for 2FA)
|
||||||
|
* failure: A failure message code (defined by mediawiki)
|
||||||
|
* misc: genericerror-UI, genericerror-REDIRECT, genericerror-RESTART
|
||||||
|
*/
|
||||||
|
private String getErrorCodeToReturn( ApiResult loginApiResult ) {
|
||||||
|
String status = loginApiResult.getString("/api/clientlogin/@status");
|
||||||
|
if (status.equals("PASS")) {
|
||||||
|
this.isLoggedIn = true;
|
||||||
|
return status;
|
||||||
|
} else if (status.equals("FAIL")) {
|
||||||
|
return loginApiResult.getString("/api/clientlogin/@messagecode");
|
||||||
|
} else if (
|
||||||
|
status.equals("UI")
|
||||||
|
&& loginApiResult.getString("/api/clientlogin/requests/_v/@id").equals("TOTPAuthenticationRequest")
|
||||||
|
&& loginApiResult.getString("/api/clientlogin/requests/_v/@provider").equals("Two-factor authentication (OATH).")
|
||||||
|
) {
|
||||||
|
return "2FA";
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI, REDIRECT, RESTART
|
||||||
|
return "genericerror-" + status;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -116,10 +116,6 @@ public class Media implements Parcelable {
|
||||||
this.creator = creator;
|
this.creator = creator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getThumbnailUrl(int width) {
|
|
||||||
return Utils.makeThumbUrl(getImageUrl(), getFilename(), width);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getWidth() {
|
public int getWidth() {
|
||||||
return width;
|
return width;
|
||||||
}
|
}
|
||||||
|
|
@ -144,6 +140,14 @@ public class Media implements Parcelable {
|
||||||
this.license = license;
|
this.license = license;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getCoordinates() {
|
||||||
|
return coordinates;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCoordinates(String coordinates) {
|
||||||
|
this.coordinates = coordinates;
|
||||||
|
}
|
||||||
|
|
||||||
// Primary metadata fields
|
// Primary metadata fields
|
||||||
protected Uri localUri;
|
protected Uri localUri;
|
||||||
protected String imageUrl;
|
protected String imageUrl;
|
||||||
|
|
@ -155,6 +159,7 @@ public class Media implements Parcelable {
|
||||||
protected int width;
|
protected int width;
|
||||||
protected int height;
|
protected int height;
|
||||||
protected String license;
|
protected String license;
|
||||||
|
private String 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
|
||||||
|
|
@ -164,7 +169,7 @@ public class Media implements Parcelable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCategories(List<String> categories) {
|
public void setCategories(List<String> categories) {
|
||||||
this.categories.removeAll(this.categories);
|
this.categories.clear();
|
||||||
this.categories.addAll(categories);
|
this.categories.addAll(categories);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
package fr.free.nrw.commons;
|
package fr.free.nrw.commons;
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.mediawiki.api.ApiResult;
|
import org.mediawiki.api.ApiResult;
|
||||||
import org.mediawiki.api.MWApi;
|
|
||||||
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;
|
||||||
|
|
@ -23,6 +20,9 @@ import javax.xml.parsers.DocumentBuilder;
|
||||||
import javax.xml.parsers.DocumentBuilderFactory;
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
import javax.xml.parsers.ParserConfigurationException;
|
import javax.xml.parsers.ParserConfigurationException;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch additional media data from the network that we don't store locally.
|
* Fetch additional media data from the network that we don't store locally.
|
||||||
*
|
*
|
||||||
|
|
@ -31,14 +31,13 @@ import javax.xml.parsers.ParserConfigurationException;
|
||||||
*/
|
*/
|
||||||
public class MediaDataExtractor {
|
public class MediaDataExtractor {
|
||||||
private boolean fetched;
|
private boolean fetched;
|
||||||
private boolean processed;
|
|
||||||
|
|
||||||
private String filename;
|
private String filename;
|
||||||
private ArrayList<String> categories;
|
private ArrayList<String> categories;
|
||||||
private Map<String, String> descriptions;
|
private Map<String, String> descriptions;
|
||||||
private String author;
|
|
||||||
private Date date;
|
private Date date;
|
||||||
private String license;
|
private String license;
|
||||||
|
private String coordinates;
|
||||||
private LicenseList licenseList;
|
private LicenseList licenseList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -49,7 +48,6 @@ public class MediaDataExtractor {
|
||||||
categories = new ArrayList<>();
|
categories = new ArrayList<>();
|
||||||
descriptions = new HashMap<>();
|
descriptions = new HashMap<>();
|
||||||
fetched = false;
|
fetched = false;
|
||||||
processed = false;
|
|
||||||
this.licenseList = licenseList;
|
this.licenseList = licenseList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,7 +62,7 @@ public class MediaDataExtractor {
|
||||||
throw new IllegalStateException("Tried to call MediaDataExtractor.fetch() again.");
|
throw new IllegalStateException("Tried to call MediaDataExtractor.fetch() again.");
|
||||||
}
|
}
|
||||||
|
|
||||||
MWApi api = CommonsApplication.createMWApi();
|
MWApi api = CommonsApplication.getInstance().getMWApi();
|
||||||
ApiResult result = api.action("query")
|
ApiResult result = api.action("query")
|
||||||
.param("prop", "revisions")
|
.param("prop", "revisions")
|
||||||
.param("titles", filename)
|
.param("titles", filename)
|
||||||
|
|
@ -111,9 +109,7 @@ public class MediaDataExtractor {
|
||||||
doc = docBuilder.parse(new ByteArrayInputStream(source.getBytes("UTF-8")));
|
doc = docBuilder.parse(new ByteArrayInputStream(source.getBytes("UTF-8")));
|
||||||
} catch (ParserConfigurationException e) {
|
} catch (ParserConfigurationException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
} catch (IllegalStateException e) {
|
} catch (IllegalStateException | SAXException e) {
|
||||||
throw new IOException(e);
|
|
||||||
} catch (SAXException e) {
|
|
||||||
throw new IOException(e);
|
throw new IOException(e);
|
||||||
}
|
}
|
||||||
Node templateNode = findTemplate(doc.getDocumentElement(), "information");
|
Node templateNode = findTemplate(doc.getDocumentElement(), "information");
|
||||||
|
|
@ -122,7 +118,14 @@ public class MediaDataExtractor {
|
||||||
descriptions = getMultilingualText(descriptionNode);
|
descriptions = getMultilingualText(descriptionNode);
|
||||||
|
|
||||||
Node authorNode = findTemplateParameter(templateNode, "author");
|
Node authorNode = findTemplateParameter(templateNode, "author");
|
||||||
author = getFlatText(authorNode);
|
}
|
||||||
|
|
||||||
|
Node coordinateTemplateNode = findTemplate(doc.getDocumentElement(), "location");
|
||||||
|
|
||||||
|
if (coordinateTemplateNode != null) {
|
||||||
|
coordinates = getCoordinates(coordinateTemplateNode);
|
||||||
|
} else {
|
||||||
|
coordinates = "No coordinates found";
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -131,20 +134,20 @@ public class MediaDataExtractor {
|
||||||
* look for 'self' template and check its first parameter
|
* look for 'self' template and check its first parameter
|
||||||
* if none, look for any of the known templates
|
* if none, look for any of the known templates
|
||||||
*/
|
*/
|
||||||
Log.d("Commons", "MediaDataExtractor searching for license");
|
Timber.d("MediaDataExtractor searching for license");
|
||||||
Node selfLicenseNode = findTemplate(doc.getDocumentElement(), "self");
|
Node selfLicenseNode = findTemplate(doc.getDocumentElement(), "self");
|
||||||
if (selfLicenseNode != null) {
|
if (selfLicenseNode != null) {
|
||||||
Node firstNode = findTemplateParameter(selfLicenseNode, 1);
|
Node firstNode = findTemplateParameter(selfLicenseNode, 1);
|
||||||
String licenseTemplate = getFlatText(firstNode);
|
String licenseTemplate = getFlatText(firstNode);
|
||||||
License license = licenseList.licenseForTemplate(licenseTemplate);
|
License license = licenseList.licenseForTemplate(licenseTemplate);
|
||||||
if (license == null) {
|
if (license == null) {
|
||||||
Log.d("Commons", "MediaDataExtractor found no matching license for self parameter: " + licenseTemplate + "; faking it");
|
Timber.d("MediaDataExtractor found no matching license for self parameter: %s; faking it", licenseTemplate);
|
||||||
this.license = licenseTemplate; // hack hack! For non-selectable licenses that are still in the system.
|
this.license = licenseTemplate; // hack hack! For non-selectable licenses that are still in the system.
|
||||||
} else {
|
} else {
|
||||||
// fixme: record the self-ness in here too... sigh
|
// fixme: record the self-ness in here too... sigh
|
||||||
// all this needs better server-side metadata
|
// all this needs better server-side metadata
|
||||||
this.license = license.getKey();
|
this.license = license.getKey();
|
||||||
Log.d("Commons", "MediaDataExtractor found self-license " + this.license);
|
Timber.d("MediaDataExtractor found self-license %s", this.license);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (License license : licenseList.values()) {
|
for (License license : licenseList.values()) {
|
||||||
|
|
@ -153,7 +156,7 @@ public class MediaDataExtractor {
|
||||||
if (template != null) {
|
if (template != null) {
|
||||||
// Found!
|
// Found!
|
||||||
this.license = license.getKey();
|
this.license = license.getKey();
|
||||||
Log.d("Commons", "MediaDataExtractor found non-self license " + this.license);
|
Timber.d("MediaDataExtractor found non-self license %s", this.license);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -245,6 +248,25 @@ public class MediaDataExtractor {
|
||||||
return parentNode.getTextContent();
|
return parentNode.getTextContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the coordinates from the template and returns them as pretty formatted string.
|
||||||
|
* Loops over the children of the coordinate template:
|
||||||
|
* {{Location|47.50111007666667|19.055700301944444}}
|
||||||
|
* and extracts the latitude and longitude as a pretty string.
|
||||||
|
*
|
||||||
|
* @param parentNode The node of the coordinates template.
|
||||||
|
* @return Pretty formatted coordinates.
|
||||||
|
* @throws IOException Parsing failed.
|
||||||
|
*/
|
||||||
|
private String getCoordinates(Node parentNode) throws IOException {
|
||||||
|
NodeList childNodes = parentNode.getChildNodes();
|
||||||
|
double latitudeText = Double.parseDouble(childNodes.item(1).getTextContent());
|
||||||
|
double longitudeText = Double.parseDouble(childNodes.item(2).getTextContent());
|
||||||
|
LatLng coordinates = new LatLng(latitudeText, longitudeText);
|
||||||
|
|
||||||
|
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.
|
||||||
// Texts are wrapped in things like {{en|foo} or {{en|1=foo bar}}.
|
// Texts are wrapped in things like {{en|foo} or {{en|1=foo bar}}.
|
||||||
// Text outside those wrappers is stuffed into a 'default' faux language key if present.
|
// Text outside those wrappers is stuffed into a 'default' faux language key if present.
|
||||||
|
|
@ -290,6 +312,7 @@ public class MediaDataExtractor {
|
||||||
|
|
||||||
media.setCategories(categories);
|
media.setCategories(categories);
|
||||||
media.setDescriptions(descriptions);
|
media.setDescriptions(descriptions);
|
||||||
|
media.setCoordinates(coordinates);
|
||||||
if (license != null) {
|
if (license != null) {
|
||||||
media.setLicense(license);
|
media.setLicense(license);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
package fr.free.nrw.commons;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.mediawiki.api.ApiResult;
|
||||||
|
|
||||||
|
class MediaThumbnailFetchTask extends AsyncTask<String, String, String> {
|
||||||
|
private static final String THUMB_SIZE = "640";
|
||||||
|
protected final Media media;
|
||||||
|
|
||||||
|
public MediaThumbnailFetchTask(@NonNull Media media) {
|
||||||
|
this.media = media;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String doInBackground(String... params) {
|
||||||
|
try {
|
||||||
|
MWApi api = CommonsApplication.getInstance().getMWApi();
|
||||||
|
ApiResult result =api.action("query")
|
||||||
|
.param("format", "xml")
|
||||||
|
.param("prop", "imageinfo")
|
||||||
|
.param("iiprop", "url")
|
||||||
|
.param("iiurlwidth", THUMB_SIZE)
|
||||||
|
.param("titles", params[0])
|
||||||
|
.get();
|
||||||
|
return result.getString("/api/query/pages/page/imageinfo/ii/@thumburl");
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Do something better!
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,47 +1,15 @@
|
||||||
/**
|
|
||||||
* Copyright (C) 2013 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package fr.free.nrw.commons;
|
package fr.free.nrw.commons;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.TypedArray;
|
import android.support.annotation.NonNull;
|
||||||
import android.graphics.drawable.BitmapDrawable;
|
import android.support.annotation.Nullable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.View;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
|
|
||||||
import com.android.volley.VolleyError;
|
import com.facebook.drawee.view.SimpleDraweeView;
|
||||||
import com.android.volley.toolbox.ImageLoader;
|
|
||||||
import com.android.volley.toolbox.ImageLoader.ImageContainer;
|
|
||||||
import com.android.volley.toolbox.ImageLoader.ImageListener;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.contributions.Contribution;
|
public class MediaWikiImageView extends SimpleDraweeView {
|
||||||
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
|
private ThumbnailFetchTask currentThumbnailTask;
|
||||||
|
|
||||||
public class MediaWikiImageView extends ImageView {
|
|
||||||
|
|
||||||
private Media mMedia;
|
|
||||||
|
|
||||||
private ImageLoader mImageLoader;
|
|
||||||
|
|
||||||
private ImageContainer mImageContainer;
|
|
||||||
|
|
||||||
private View loadingView;
|
|
||||||
|
|
||||||
private boolean isThumbnail;
|
|
||||||
|
|
||||||
public MediaWikiImageView(Context context) {
|
public MediaWikiImageView(Context context) {
|
||||||
this(context, null);
|
this(context, null);
|
||||||
|
|
@ -49,179 +17,58 @@ public class MediaWikiImageView extends ImageView {
|
||||||
|
|
||||||
public MediaWikiImageView(Context context, AttributeSet attrs) {
|
public MediaWikiImageView(Context context, AttributeSet attrs) {
|
||||||
this(context, attrs, 0);
|
this(context, attrs, 0);
|
||||||
TypedArray actualAttrs = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MediaWikiImageView, 0, 0);
|
|
||||||
isThumbnail = actualAttrs.getBoolean(0, false);
|
|
||||||
actualAttrs.recycle();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public MediaWikiImageView(Context context, AttributeSet attrs, int defStyle) {
|
public MediaWikiImageView(Context context, AttributeSet attrs, int defStyle) {
|
||||||
super(context, attrs, defStyle);
|
super(context, attrs, defStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMedia(Media media, ImageLoader imageLoader) {
|
public void setMedia(Media media) {
|
||||||
this.mMedia = media;
|
if (currentThumbnailTask != null) {
|
||||||
mImageLoader = imageLoader;
|
currentThumbnailTask.cancel(true);
|
||||||
loadImageIfNecessary(false);
|
}
|
||||||
}
|
if(media == null) {
|
||||||
|
|
||||||
public void setLoadingView(View loadingView) {
|
|
||||||
this.loadingView = loadingView;
|
|
||||||
}
|
|
||||||
|
|
||||||
public View getLoadingView() {
|
|
||||||
return loadingView;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadImageIfNecessary(final boolean isInLayoutPass) {
|
|
||||||
loadImageIfNecessary(isInLayoutPass, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadImageIfNecessary(final boolean isInLayoutPass, final boolean tryOriginal) {
|
|
||||||
int width = getWidth();
|
|
||||||
int height = getHeight();
|
|
||||||
|
|
||||||
// if the view's bounds aren't known yet, hold off on loading the image.
|
|
||||||
if (width == 0 && height == 0) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mMedia == null) {
|
if (CommonsApplication.getInstance().getThumbnailUrlCache().get(media.getFilename()) != null) {
|
||||||
return;
|
setImageUrl(CommonsApplication.getInstance().getThumbnailUrlCache().get(media.getFilename()));
|
||||||
}
|
|
||||||
|
|
||||||
// Do not count for density when loading thumbnails.
|
|
||||||
// FIXME: Use another 'algorithm' that doesn't punish low res devices
|
|
||||||
if(isThumbnail) {
|
|
||||||
float dpFactor = Math.max(getResources().getDisplayMetrics().density, 1.0f);
|
|
||||||
width = (int) (width / dpFactor);
|
|
||||||
height = (int) (height / dpFactor);
|
|
||||||
}
|
|
||||||
|
|
||||||
final String mUrl;
|
|
||||||
if(tryOriginal) {
|
|
||||||
mUrl = mMedia.getImageUrl();
|
|
||||||
} else {
|
} else {
|
||||||
// Round it to the nearest 320
|
setImageUrl(null);
|
||||||
// Possible a similar size image has already been generated.
|
currentThumbnailTask = new ThumbnailFetchTask(media);
|
||||||
// Reduces Server cache fragmentation, also increases chance of cache hit
|
currentThumbnailTask.execute(media.getFilename());
|
||||||
// If width is less than 320, we round up to 320
|
|
||||||
int bucketedWidth = width <= 320 ? 320 : Math.round((float)width / 320.0f) * 320;
|
|
||||||
if(mMedia.getWidth() != 0 && mMedia.getWidth() < bucketedWidth) {
|
|
||||||
// If we know that the width of the image is lesser than the required width
|
|
||||||
// We don't even try to load the thumbnai, go directly to the source
|
|
||||||
loadImageIfNecessary(isInLayoutPass, true);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
mUrl = mMedia.getThumbnailUrl(bucketedWidth);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the URL to be loaded in this view is empty, cancel any old requests and clear the
|
|
||||||
// currently loaded image.
|
|
||||||
if (TextUtils.isEmpty(mUrl)) {
|
|
||||||
if (mImageContainer != null) {
|
|
||||||
mImageContainer.cancelRequest();
|
|
||||||
mImageContainer = null;
|
|
||||||
}
|
|
||||||
setImageBitmap(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't repeat work. Prevents onLayout cascades
|
|
||||||
// We ignore it if the image request was for either the current URL of for the full URL
|
|
||||||
// Since the full URL is always the second, and
|
|
||||||
if (mImageContainer != null && mImageContainer.getRequestUrl() != null) {
|
|
||||||
if (mImageContainer.getRequestUrl().equals(mMedia.getImageUrl()) || mImageContainer.getRequestUrl().equals(mUrl)) {
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
// if there is a pre-existing request, cancel it if it's fetching a different URL.
|
|
||||||
mImageContainer.cancelRequest();
|
|
||||||
BitmapDrawable actualDrawable = (BitmapDrawable)getDrawable();
|
|
||||||
if(actualDrawable != null && actualDrawable.getBitmap() != null) {
|
|
||||||
setImageBitmap(null);
|
|
||||||
if(loadingView != null) {
|
|
||||||
loadingView.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The pre-existing content of this view didn't match the current URL. Load the new image
|
|
||||||
// from the network.
|
|
||||||
ImageContainer newContainer = mImageLoader.get(mUrl,
|
|
||||||
new ImageListener() {
|
|
||||||
@Override
|
|
||||||
public void onErrorResponse(final VolleyError error) {
|
|
||||||
if(!tryOriginal) {
|
|
||||||
post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
loadImageIfNecessary(false, true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResponse(final ImageContainer response, boolean isImmediate) {
|
|
||||||
// If this was an immediate response that was delivered inside of a layout
|
|
||||||
// pass do not set the image immediately as it will trigger a requestLayout
|
|
||||||
// inside of a layout. Instead, defer setting the image by posting back to
|
|
||||||
// the main thread.
|
|
||||||
if (isImmediate && isInLayoutPass) {
|
|
||||||
post(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
onResponse(response, false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.getBitmap() != null) {
|
|
||||||
setImageBitmap(response.getBitmap());
|
|
||||||
if(tryOriginal && mMedia instanceof Contribution && (response.getBitmap().getWidth() > mMedia.getWidth() || response.getBitmap().getHeight() > mMedia.getHeight())) {
|
|
||||||
// If there is no width information for this image, save it. This speeds up image loading massively for smaller images
|
|
||||||
mMedia.setHeight(response.getBitmap().getHeight());
|
|
||||||
mMedia.setWidth(response.getBitmap().getWidth());
|
|
||||||
((Contribution)mMedia).setContentProviderClient(MediaWikiImageView.this.getContext().getContentResolver().acquireContentProviderClient(ContributionsContentProvider.AUTHORITY));
|
|
||||||
((Contribution)mMedia).save();
|
|
||||||
}
|
|
||||||
if(loadingView != null) {
|
|
||||||
loadingView.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// I'm not really sure where this would hit but not onError
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// update the ImageContainer to be the new bitmap container.
|
|
||||||
mImageContainer = newContainer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
|
||||||
super.onLayout(changed, left, top, right, bottom);
|
|
||||||
loadImageIfNecessary(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDetachedFromWindow() {
|
protected void onDetachedFromWindow() {
|
||||||
if (mImageContainer != null) {
|
if (currentThumbnailTask != null) {
|
||||||
// If the view was bound to an image request, cancel it and clear
|
currentThumbnailTask.cancel(true);
|
||||||
// out the image from the view.
|
|
||||||
mImageContainer.cancelRequest();
|
|
||||||
setImageBitmap(null);
|
|
||||||
// also clear out the container so we can reload the image if necessary.
|
|
||||||
mImageContainer = null;
|
|
||||||
}
|
}
|
||||||
super.onDetachedFromWindow();
|
super.onDetachedFromWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void setImageUrl(@Nullable String url) {
|
||||||
protected void drawableStateChanged() {
|
setImageURI(url);
|
||||||
super.drawableStateChanged();
|
}
|
||||||
invalidate();
|
|
||||||
|
private class ThumbnailFetchTask extends MediaThumbnailFetchTask {
|
||||||
|
ThumbnailFetchTask(@NonNull Media media) {
|
||||||
|
super(media);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(String result) {
|
||||||
|
if (isCancelled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (TextUtils.isEmpty(result) && media.getLocalUri() != null) {
|
||||||
|
result = media.getLocalUri().toString();
|
||||||
|
} else {
|
||||||
|
// only cache meaningful thumbnails received from network.
|
||||||
|
CommonsApplication.getInstance().getThumbnailUrlCache().put(media.getFilename(), result);
|
||||||
|
}
|
||||||
|
setImageUrl(result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,11 @@
|
||||||
package fr.free.nrw.commons;
|
package fr.free.nrw.commons;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.preference.PreferenceManager;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
|
||||||
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
|
|
||||||
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.settings.Prefs;
|
import fr.free.nrw.commons.settings.Prefs;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
@ -44,8 +41,6 @@ import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
public class Utils {
|
public class Utils {
|
||||||
|
|
||||||
private static final String TAG = Utils.class.getName();
|
|
||||||
|
|
||||||
// Get SHA1 of file from input stream
|
// Get SHA1 of file from input stream
|
||||||
public static String getSHA1(InputStream is) {
|
public static String getSHA1(InputStream is) {
|
||||||
|
|
||||||
|
|
@ -53,7 +48,7 @@ public class Utils {
|
||||||
try {
|
try {
|
||||||
digest = MessageDigest.getInstance("SHA1");
|
digest = MessageDigest.getInstance("SHA1");
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
Log.e(TAG, "Exception while getting Digest", e);
|
Timber.e(e, "Exception while getting Digest");
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,17 +63,17 @@ public class Utils {
|
||||||
String output = bigInt.toString(16);
|
String output = bigInt.toString(16);
|
||||||
// Fill to 40 chars
|
// Fill to 40 chars
|
||||||
output = String.format("%40s", output).replace(' ', '0');
|
output = String.format("%40s", output).replace(' ', '0');
|
||||||
Log.i(TAG, "File SHA1: " + output);
|
Timber.i("File SHA1: %s", output);
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, "IO Exception", e);
|
Timber.e(e, "IO Exception");
|
||||||
return "";
|
return "";
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
is.close();
|
is.close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, "Exception on closing MD5 input stream", e);
|
Timber.e(e, "Exception on closing MD5 input stream");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -124,10 +119,7 @@ public class Utils {
|
||||||
Transformer transformer = null;
|
Transformer transformer = null;
|
||||||
try {
|
try {
|
||||||
transformer = TransformerFactory.newInstance().newTransformer();
|
transformer = TransformerFactory.newInstance().newTransformer();
|
||||||
} catch (TransformerConfigurationException e) {
|
} catch (TransformerConfigurationException | TransformerFactoryConfigurationError e) {
|
||||||
// TODO Auto-generated catch block
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (TransformerFactoryConfigurationError e) {
|
|
||||||
// TODO Auto-generated catch block
|
// TODO Auto-generated catch block
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
@ -145,26 +137,6 @@ public class Utils {
|
||||||
return outputStream.toString();
|
return outputStream.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DisplayImageOptions.Builder defaultImageOptionsBuilder;
|
|
||||||
|
|
||||||
public static DisplayImageOptions.Builder getGenericDisplayOptions() {
|
|
||||||
if (defaultImageOptionsBuilder == null) {
|
|
||||||
defaultImageOptionsBuilder = new DisplayImageOptions.Builder().cacheInMemory()
|
|
||||||
.imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2);
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
|
||||||
// List views flicker badly during data updates on Android 2.3; we
|
|
||||||
// haven't quite figured out why but cells seem to be rearranged oddly.
|
|
||||||
// Disable the fade-in on 2.3 to reduce the effect.
|
|
||||||
defaultImageOptionsBuilder = defaultImageOptionsBuilder
|
|
||||||
.displayer(new FadeInBitmapDisplayer(300));
|
|
||||||
}
|
|
||||||
defaultImageOptionsBuilder = defaultImageOptionsBuilder
|
|
||||||
.cacheInMemory()
|
|
||||||
.resetViewBeforeLoading();
|
|
||||||
}
|
|
||||||
return defaultImageOptionsBuilder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final URLCodec urlCodec = new URLCodec();
|
private static final URLCodec urlCodec = new URLCodec();
|
||||||
|
|
||||||
public static String urlEncode(String url) {
|
public static String urlEncode(String url) {
|
||||||
|
|
@ -184,76 +156,64 @@ public class Utils {
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String makeThumbUrl(String imageUrl, String filename, int width) {
|
|
||||||
// Ugly Hack!
|
|
||||||
// Update: OH DEAR GOD WHAT A HORRIBLE HACK I AM SO SORRY
|
|
||||||
if (imageUrl.endsWith("webm")) {
|
|
||||||
return imageUrl.replaceFirst("test/", "test/thumb/").replace("commons/", "commons/thumb/") + "/" + width + "px--" + filename.replaceAll("File:", "").replaceAll(" ", "_") + ".jpg";
|
|
||||||
} else {
|
|
||||||
String thumbUrl = imageUrl.replaceFirst("test/", "test/thumb/").replace("commons/", "commons/thumb/") + "/" + width + "px-" + filename.replaceAll("File:", "").replaceAll(" ", "_");
|
|
||||||
if (thumbUrl.endsWith("jpg") || thumbUrl.endsWith("png") || thumbUrl.endsWith("jpeg")) {
|
|
||||||
return thumbUrl;
|
|
||||||
} else {
|
|
||||||
return thumbUrl + ".png";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String capitalize(String string) {
|
public static String capitalize(String string) {
|
||||||
return string.substring(0, 1).toUpperCase(Locale.getDefault()) + string.substring(1);
|
return string.substring(0, 1).toUpperCase(Locale.getDefault()) + string.substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String licenseTemplateFor(String license) {
|
public static String licenseTemplateFor(String license) {
|
||||||
if (license.equals(Prefs.Licenses.CC_BY_3)) {
|
switch (license) {
|
||||||
return "{{self|cc-by-3.0}}";
|
case Prefs.Licenses.CC_BY_3:
|
||||||
} else if (license.equals(Prefs.Licenses.CC_BY_4)) {
|
return "{{self|cc-by-3.0}}";
|
||||||
return "{{self|cc-by-4.0}}";
|
case Prefs.Licenses.CC_BY_4:
|
||||||
} else if (license.equals(Prefs.Licenses.CC_BY_SA_3)) {
|
return "{{self|cc-by-4.0}}";
|
||||||
return "{{self|cc-by-sa-3.0}}";
|
case Prefs.Licenses.CC_BY_SA_3:
|
||||||
} else if (license.equals(Prefs.Licenses.CC_BY_SA_4)) {
|
return "{{self|cc-by-sa-3.0}}";
|
||||||
return "{{self|cc-by-sa-4.0}}";
|
case Prefs.Licenses.CC_BY_SA_4:
|
||||||
} else if (license.equals(Prefs.Licenses.CC0)) {
|
return "{{self|cc-by-sa-4.0}}";
|
||||||
return "{{self|cc-zero}}";
|
case Prefs.Licenses.CC0:
|
||||||
} else if (license.equals(Prefs.Licenses.CC_BY)) {
|
return "{{self|cc-zero}}";
|
||||||
return "{{self|cc-by-3.0}}";
|
case Prefs.Licenses.CC_BY:
|
||||||
} else if (license.equals(Prefs.Licenses.CC_BY_SA)) {
|
return "{{self|cc-by-3.0}}";
|
||||||
return "{{self|cc-by-sa-3.0}}";
|
case Prefs.Licenses.CC_BY_SA:
|
||||||
|
return "{{self|cc-by-sa-3.0}}";
|
||||||
}
|
}
|
||||||
throw new RuntimeException("Unrecognized license value");
|
throw new RuntimeException("Unrecognized license value: " + license);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int licenseNameFor(String license) {
|
public static int licenseNameFor(String license) {
|
||||||
if (license.equals(Prefs.Licenses.CC_BY_3)) {
|
switch (license) {
|
||||||
return R.string.license_name_cc_by;
|
case Prefs.Licenses.CC_BY_3:
|
||||||
} else if (license.equals(Prefs.Licenses.CC_BY_4)) {
|
return R.string.license_name_cc_by;
|
||||||
return R.string.license_name_cc_by_four;
|
case Prefs.Licenses.CC_BY_4:
|
||||||
} else if (license.equals(Prefs.Licenses.CC_BY_SA_3)) {
|
return R.string.license_name_cc_by_four;
|
||||||
return R.string.license_name_cc_by_sa;
|
case Prefs.Licenses.CC_BY_SA_3:
|
||||||
} else if (license.equals(Prefs.Licenses.CC_BY_SA_4)) {
|
return R.string.license_name_cc_by_sa;
|
||||||
return R.string.license_name_cc_by_sa_four;
|
case Prefs.Licenses.CC_BY_SA_4:
|
||||||
} else if (license.equals(Prefs.Licenses.CC0)) {
|
return R.string.license_name_cc_by_sa_four;
|
||||||
return R.string.license_name_cc0;
|
case Prefs.Licenses.CC0:
|
||||||
} else if (license.equals(Prefs.Licenses.CC_BY)) { // for backward compatibility to v2.1
|
return R.string.license_name_cc0;
|
||||||
return R.string.license_name_cc_by_3_0;
|
case Prefs.Licenses.CC_BY: // for backward compatibility to v2.1
|
||||||
} else if (license.equals(Prefs.Licenses.CC_BY_SA)) { // for backward compatibility to v2.1
|
return R.string.license_name_cc_by_3_0;
|
||||||
return R.string.license_name_cc_by_sa_3_0;
|
case Prefs.Licenses.CC_BY_SA: // for backward compatibility to v2.1
|
||||||
|
return R.string.license_name_cc_by_sa_3_0;
|
||||||
}
|
}
|
||||||
throw new RuntimeException("Unrecognized license value");
|
throw new RuntimeException("Unrecognized license value: " + license);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String licenseUrlFor(String license) {
|
public static String licenseUrlFor(String license) {
|
||||||
if (license.equals(Prefs.Licenses.CC_BY_3)) {
|
switch (license) {
|
||||||
return "https://creativecommons.org/licenses/by/3.0/";
|
case Prefs.Licenses.CC_BY_3:
|
||||||
} else if (license.equals(Prefs.Licenses.CC_BY_4)) {
|
return "https://creativecommons.org/licenses/by/3.0/";
|
||||||
return "https://creativecommons.org/licenses/by/4.0/";
|
case Prefs.Licenses.CC_BY_4:
|
||||||
} else if (license.equals(Prefs.Licenses.CC_BY_SA_3)) {
|
return "https://creativecommons.org/licenses/by/4.0/";
|
||||||
return "https://creativecommons.org/licenses/by-sa/3.0/";
|
case Prefs.Licenses.CC_BY_SA_3:
|
||||||
} else if (license.equals(Prefs.Licenses.CC_BY_SA_4)) {
|
return "https://creativecommons.org/licenses/by-sa/3.0/";
|
||||||
return "https://creativecommons.org/licenses/by-sa/4.0/";
|
case Prefs.Licenses.CC_BY_SA_4:
|
||||||
} else if (license.equals(Prefs.Licenses.CC0)) {
|
return "https://creativecommons.org/licenses/by-sa/4.0/";
|
||||||
return "https://creativecommons.org/publicdomain/zero/1.0/";
|
case Prefs.Licenses.CC0:
|
||||||
|
return "https://creativecommons.org/publicdomain/zero/1.0/";
|
||||||
}
|
}
|
||||||
throw new RuntimeException("Unrecognized license value");
|
throw new RuntimeException("Unrecognized license value: " + license);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Uri uriForWikiPage(String name) {
|
public static Uri uriForWikiPage(String name) {
|
||||||
|
|
@ -308,4 +268,12 @@ public class Utils {
|
||||||
public static boolean isNullOrWhiteSpace(String value) {
|
public static boolean isNullOrWhiteSpace(String value) {
|
||||||
return value == null || value.trim().isEmpty();
|
return value == null || value.trim().isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isDarkTheme(Context context) {
|
||||||
|
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("theme",true)) {
|
||||||
|
return true;
|
||||||
|
}else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import butterknife.ButterKnife;
|
||||||
import fr.free.nrw.commons.theme.BaseActivity;
|
import fr.free.nrw.commons.theme.BaseActivity;
|
||||||
|
|
||||||
public class WelcomeActivity extends BaseActivity {
|
public class WelcomeActivity extends BaseActivity {
|
||||||
private WelcomePagerAdapter adapter;
|
|
||||||
|
|
||||||
@BindView(R.id.welcomePager) ViewPager pager;
|
@BindView(R.id.welcomePager) ViewPager pager;
|
||||||
@BindView(R.id.welcomePagerIndicator) CirclePageIndicator indicator;
|
@BindView(R.id.welcomePagerIndicator) CirclePageIndicator indicator;
|
||||||
|
|
@ -20,14 +19,16 @@ public class WelcomeActivity extends BaseActivity {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_welcome);
|
setContentView(R.layout.activity_welcome);
|
||||||
|
|
||||||
getSupportActionBar().hide();
|
if (getSupportActionBar() != null) {
|
||||||
|
getSupportActionBar().hide();
|
||||||
|
}
|
||||||
ButterKnife.bind(this);
|
ButterKnife.bind(this);
|
||||||
|
|
||||||
setUpAdapter();
|
setUpAdapter();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setUpAdapter() {
|
private void setUpAdapter() {
|
||||||
adapter = new WelcomePagerAdapter(this);
|
WelcomePagerAdapter adapter = new WelcomePagerAdapter(this);
|
||||||
pager.setAdapter(adapter);
|
pager.setAdapter(adapter);
|
||||||
indicator.setViewPager(pager);
|
indicator.setViewPager(pager);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
61
app/src/main/java/fr/free/nrw/commons/auth/AccountUtil.java
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
package fr.free.nrw.commons.auth;
|
||||||
|
|
||||||
|
import android.accounts.Account;
|
||||||
|
import android.accounts.AccountAuthenticatorResponse;
|
||||||
|
import android.accounts.AccountManager;
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
|
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
|
||||||
|
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
public class AccountUtil {
|
||||||
|
|
||||||
|
public static void createAccount(@Nullable AccountAuthenticatorResponse response,
|
||||||
|
String username, String password) {
|
||||||
|
|
||||||
|
Account account = new Account(username, accountType());
|
||||||
|
boolean created = accountManager().addAccountExplicitly(account, password, null);
|
||||||
|
|
||||||
|
Timber.d("account creation " + (created ? "successful" : "failure"));
|
||||||
|
|
||||||
|
if (created) {
|
||||||
|
if (response != null) {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putString(AccountManager.KEY_ACCOUNT_NAME, username);
|
||||||
|
bundle.putString(AccountManager.KEY_ACCOUNT_TYPE, accountType());
|
||||||
|
|
||||||
|
|
||||||
|
response.onResult(bundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (response != null) {
|
||||||
|
response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "");
|
||||||
|
}
|
||||||
|
Timber.d("account creation failure");
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: If the user turns it off, it shouldn't be auto turned back on
|
||||||
|
ContentResolver.setSyncAutomatically(account, ContributionsContentProvider.AUTHORITY, true); // Enable sync by default!
|
||||||
|
ContentResolver.setSyncAutomatically(account, ModificationsContentProvider.AUTHORITY, true); // Enable sync by default!
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static String accountType() {
|
||||||
|
return "fr.free.nrw.commons";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AccountManager accountManager() {
|
||||||
|
return AccountManager.get(app());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static CommonsApplication app() {
|
||||||
|
return CommonsApplication.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -10,18 +10,18 @@ import android.os.Bundle;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import fr.free.nrw.commons.theme.BaseActivity;
|
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
|
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
||||||
|
|
||||||
public abstract class AuthenticatedActivity extends BaseActivity {
|
public abstract class AuthenticatedActivity extends NavigationBaseActivity {
|
||||||
|
|
||||||
String accountType;
|
String accountType;
|
||||||
CommonsApplication app;
|
CommonsApplication app;
|
||||||
|
|
||||||
private String authCookie;
|
private String authCookie;
|
||||||
|
|
||||||
public AuthenticatedActivity(String accountType) {
|
public AuthenticatedActivity() {
|
||||||
this.accountType = accountType;
|
this.accountType = AccountUtil.accountType();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class GetAuthCookieTask extends AsyncTask<Void, String, String> {
|
private class GetAuthCookieTask extends AsyncTask<Void, String, String> {
|
||||||
|
|
@ -131,7 +131,7 @@ public abstract class AuthenticatedActivity extends BaseActivity {
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
app = (CommonsApplication)this.getApplicationContext();
|
app = CommonsApplication.getInstance();
|
||||||
if(savedInstanceState != null) {
|
if(savedInstanceState != null) {
|
||||||
authCookie = savedInstanceState.getString("authCookie");
|
authCookie = savedInstanceState.getString("authCookie");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,171 +1,78 @@
|
||||||
package fr.free.nrw.commons.auth;
|
package fr.free.nrw.commons.auth;
|
||||||
|
|
||||||
import android.accounts.Account;
|
|
||||||
import android.accounts.AccountAuthenticatorActivity;
|
import android.accounts.AccountAuthenticatorActivity;
|
||||||
import android.accounts.AccountAuthenticatorResponse;
|
|
||||||
import android.accounts.AccountManager;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.content.ContentResolver;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.NavUtils;
|
import android.support.v4.app.NavUtils;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
import fr.free.nrw.commons.*;
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
|
||||||
import fr.free.nrw.commons.EventLog;
|
|
||||||
import fr.free.nrw.commons.R;
|
|
||||||
import fr.free.nrw.commons.Utils;
|
|
||||||
import fr.free.nrw.commons.WelcomeActivity;
|
|
||||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
|
import timber.log.Timber;
|
||||||
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
|
||||||
|
|
||||||
|
|
||||||
public class LoginActivity extends AccountAuthenticatorActivity {
|
public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
|
|
||||||
public static final String PARAM_USERNAME = "fr.free.nrw.commons.login.username";
|
public static final String PARAM_USERNAME = "fr.free.nrw.commons.login.username";
|
||||||
|
|
||||||
private CommonsApplication app;
|
|
||||||
|
|
||||||
private SharedPreferences prefs = null;
|
private SharedPreferences prefs = null;
|
||||||
|
|
||||||
Button loginButton;
|
private Button loginButton;
|
||||||
Button signupButton;
|
private EditText usernameEdit;
|
||||||
EditText usernameEdit;
|
|
||||||
EditText passwordEdit;
|
EditText passwordEdit;
|
||||||
ProgressDialog dialog;
|
EditText twoFactorEdit;
|
||||||
|
ProgressDialog progressDialog;
|
||||||
|
|
||||||
private class LoginTask extends AsyncTask<String, String, String> {
|
private CommonsApplication app;
|
||||||
|
|
||||||
Activity context;
|
|
||||||
String username;
|
|
||||||
String password;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(String result) {
|
|
||||||
super.onPostExecute(result);
|
|
||||||
Log.d("Commons", "Login done!");
|
|
||||||
|
|
||||||
EventLog.schema(CommonsApplication.EVENT_LOGIN_ATTEMPT)
|
|
||||||
.param("username", username)
|
|
||||||
.param("result", result)
|
|
||||||
.log();
|
|
||||||
|
|
||||||
if (result.equals("Success")) {
|
|
||||||
if (dialog != null && dialog.isShowing()) {
|
|
||||||
dialog.dismiss();
|
|
||||||
}
|
|
||||||
Toast successToast = Toast.makeText(context, R.string.login_success, Toast.LENGTH_SHORT);
|
|
||||||
successToast.show();
|
|
||||||
Account account = new Account(username, WikiAccountAuthenticator.COMMONS_ACCOUNT_TYPE);
|
|
||||||
boolean accountCreated = AccountManager.get(context).addAccountExplicitly(account, password, null);
|
|
||||||
|
|
||||||
Bundle extras = context.getIntent().getExtras();
|
|
||||||
|
|
||||||
if (extras != null) {
|
|
||||||
Log.d("LoginActivity", "Bundle of extras: " + extras.toString());
|
|
||||||
if (accountCreated) { // Pass the new account back to the account manager
|
|
||||||
AccountAuthenticatorResponse response = extras.getParcelable(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE);
|
|
||||||
Bundle authResult = new Bundle();
|
|
||||||
authResult.putString(AccountManager.KEY_ACCOUNT_NAME, username);
|
|
||||||
authResult.putString(AccountManager.KEY_ACCOUNT_TYPE, WikiAccountAuthenticator.COMMONS_ACCOUNT_TYPE);
|
|
||||||
|
|
||||||
if (response != null) {
|
|
||||||
response.onResult(authResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// FIXME: If the user turns it off, it shouldn't be auto turned back on
|
|
||||||
ContentResolver.setSyncAutomatically(account, ContributionsContentProvider.AUTHORITY, true); // Enable sync by default!
|
|
||||||
ContentResolver.setSyncAutomatically(account, ModificationsContentProvider.AUTHORITY, true); // Enable sync by default!
|
|
||||||
|
|
||||||
Intent intent = new Intent(context, ContributionsActivity.class);
|
|
||||||
startActivity(intent);
|
|
||||||
finish();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
int response;
|
|
||||||
if(result.equals("NetworkFailure")) {
|
|
||||||
response = R.string.login_failed_network;
|
|
||||||
} else if(result.equals("NotExists") || result.equals("Illegal") || result.equals("NotExists")) {
|
|
||||||
response = R.string.login_failed_username;
|
|
||||||
passwordEdit.setText("");
|
|
||||||
} else if(result.equals("EmptyPass") || result.equals("WrongPass") || result.equals("WrongPluginPass")) {
|
|
||||||
response = R.string.login_failed_password;
|
|
||||||
passwordEdit.setText("");
|
|
||||||
} else if(result.equals("Throttled")) {
|
|
||||||
response = R.string.login_failed_throttled;
|
|
||||||
} else if(result.equals("Blocked")) {
|
|
||||||
response = R.string.login_failed_blocked;
|
|
||||||
} else {
|
|
||||||
// Should never really happen
|
|
||||||
Log.d("Commons", "Login failed with reason: " + result);
|
|
||||||
response = R.string.login_failed_generic;
|
|
||||||
}
|
|
||||||
Toast.makeText(getApplicationContext(), response, Toast.LENGTH_LONG).show();
|
|
||||||
dialog.cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPreExecute() {
|
|
||||||
super.onPreExecute();
|
|
||||||
dialog = new ProgressDialog(context);
|
|
||||||
dialog.setIndeterminate(true);
|
|
||||||
dialog.setTitle(getString(R.string.logging_in_title));
|
|
||||||
dialog.setMessage(getString(R.string.logging_in_message));
|
|
||||||
dialog.setCanceledOnTouchOutside(false);
|
|
||||||
dialog.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
LoginTask(Activity context) {
|
|
||||||
this.context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String doInBackground(String... params) {
|
|
||||||
username = params[0];
|
|
||||||
password = params[1];
|
|
||||||
try {
|
|
||||||
return app.getApi().login(username, password);
|
|
||||||
} catch (IOException e) {
|
|
||||||
// Do something better!
|
|
||||||
return "NetworkFailure";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
app = (CommonsApplication) this.getApplicationContext();
|
|
||||||
|
app = CommonsApplication.getInstance();
|
||||||
|
|
||||||
setContentView(R.layout.activity_login);
|
setContentView(R.layout.activity_login);
|
||||||
|
final LoginActivity that = this;
|
||||||
|
|
||||||
loginButton = (Button) findViewById(R.id.loginButton);
|
loginButton = (Button) findViewById(R.id.loginButton);
|
||||||
signupButton = (Button) findViewById(R.id.signupButton);
|
Button signupButton = (Button) findViewById(R.id.signupButton);
|
||||||
usernameEdit = (EditText) findViewById(R.id.loginUsername);
|
usernameEdit = (EditText) findViewById(R.id.loginUsername);
|
||||||
passwordEdit = (EditText) findViewById(R.id.loginPassword);
|
passwordEdit = (EditText) findViewById(R.id.loginPassword);
|
||||||
final LoginActivity that = this;
|
twoFactorEdit = (EditText) findViewById(R.id.loginTwoFactor);
|
||||||
|
|
||||||
prefs = getSharedPreferences("fr.free.nrw.commons", MODE_PRIVATE);
|
prefs = getSharedPreferences("fr.free.nrw.commons", MODE_PRIVATE);
|
||||||
|
|
||||||
TextWatcher loginEnabler = new TextWatcher() {
|
TextWatcher loginEnabler = newLoginTextWatcher();
|
||||||
|
usernameEdit.addTextChangedListener(loginEnabler);
|
||||||
|
passwordEdit.addTextChangedListener(loginEnabler);
|
||||||
|
twoFactorEdit.addTextChangedListener(loginEnabler);
|
||||||
|
passwordEdit.setOnEditorActionListener( newLoginInputActionListener() );
|
||||||
|
|
||||||
|
loginButton.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
that.performLogin();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
signupButton.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) { that.signUp(v); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextWatcher newLoginTextWatcher() {
|
||||||
|
return new TextWatcher() {
|
||||||
@Override
|
@Override
|
||||||
public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) { }
|
public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) { }
|
||||||
|
|
||||||
|
|
@ -174,17 +81,21 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterTextChanged(Editable editable) {
|
public void afterTextChanged(Editable editable) {
|
||||||
if(usernameEdit.getText().length() != 0 && passwordEdit.getText().length() != 0) {
|
if(
|
||||||
|
usernameEdit.getText().length() != 0 &&
|
||||||
|
passwordEdit.getText().length() != 0 &&
|
||||||
|
( BuildConfig.DEBUG || twoFactorEdit.getText().length() != 0 || twoFactorEdit.getVisibility() != View.VISIBLE )
|
||||||
|
) {
|
||||||
loginButton.setEnabled(true);
|
loginButton.setEnabled(true);
|
||||||
} else {
|
} else {
|
||||||
loginButton.setEnabled(false);
|
loginButton.setEnabled(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
usernameEdit.addTextChangedListener(loginEnabler);
|
private TextView.OnEditorActionListener newLoginInputActionListener() {
|
||||||
passwordEdit.addTextChangedListener(loginEnabler);
|
return new TextView.OnEditorActionListener() {
|
||||||
passwordEdit.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
|
public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
|
||||||
if (loginButton.isEnabled()) {
|
if (loginButton.isEnabled()) {
|
||||||
|
|
@ -198,36 +109,31 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
loginButton.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
that.performLogin();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
if (prefs.getBoolean("firstrun", true)) {
|
if (prefs.getBoolean("firstrun", true)) {
|
||||||
// Do first run stuff here then set 'firstrun' as false
|
this.startWelcomeIntent();
|
||||||
Intent welcomeIntent = new Intent(this, WelcomeActivity.class);
|
|
||||||
startActivity(welcomeIntent);
|
|
||||||
prefs.edit().putBoolean("firstrun", false).apply();
|
prefs.edit().putBoolean("firstrun", false).apply();
|
||||||
}
|
}
|
||||||
|
if (app.getCurrentAccount() != null) {
|
||||||
|
startMainActivity();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void startWelcomeIntent() {
|
||||||
|
Intent welcomeIntent = new Intent(this, WelcomeActivity.class);
|
||||||
|
startActivity(welcomeIntent);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
try {
|
try {
|
||||||
// To prevent leaked window when finish() is called, see http://stackoverflow.com/questions/32065854/activity-has-leaked-window-at-alertdialog-show-method
|
// To prevent leaked window when finish() is called, see http://stackoverflow.com/questions/32065854/activity-has-leaked-window-at-alertdialog-show-method
|
||||||
if (dialog != null && dialog.isShowing()) {
|
if (progressDialog != null && progressDialog.isShowing()) {
|
||||||
dialog.dismiss();
|
progressDialog.dismiss();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|
@ -236,15 +142,27 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void performLogin() {
|
private void performLogin() {
|
||||||
String username = usernameEdit.getText().toString();
|
Timber.d("Login to start!");
|
||||||
// Because Mediawiki is upercase-first-char-then-case-sensitive :)
|
LoginTask task = getLoginTask();
|
||||||
String canonicalUsername = Utils.capitalize(username.substring(0,1)) + username.substring(1);
|
task.execute();
|
||||||
|
}
|
||||||
|
|
||||||
String password = passwordEdit.getText().toString();
|
private LoginTask getLoginTask() {
|
||||||
|
return new LoginTask(
|
||||||
|
this,
|
||||||
|
canonicializeUsername( usernameEdit.getText().toString() ),
|
||||||
|
passwordEdit.getText().toString(),
|
||||||
|
twoFactorEdit.getText().toString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Log.d("Commons", "Login to start!");
|
/**
|
||||||
LoginTask task = new LoginTask(this);
|
* Because Mediawiki is upercase-first-char-then-case-sensitive :)
|
||||||
task.execute(canonicalUsername, password);
|
* @param username String
|
||||||
|
* @return String canonicial username
|
||||||
|
*/
|
||||||
|
private String canonicializeUsername( String username ) {
|
||||||
|
return Utils.capitalize(username.substring(0,1)) + username.substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -257,9 +175,48 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Called when Sign Up button is clicked
|
/**
|
||||||
|
* Called when Sign Up button is clicked.
|
||||||
|
* @param view View
|
||||||
|
*/
|
||||||
public void signUp(View view) {
|
public void signUp(View view) {
|
||||||
Intent intent = new Intent(this, SignupActivity.class);
|
Intent intent = new Intent(this, SignupActivity.class);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void askUserForTwoFactorAuth() {
|
||||||
|
if(BuildConfig.DEBUG) {
|
||||||
|
twoFactorEdit.setVisibility(View.VISIBLE);
|
||||||
|
showUserToastAndCancelDialog( R.string.login_failed_2fa_needed );
|
||||||
|
}else{
|
||||||
|
showUserToastAndCancelDialog( R.string.login_failed_2fa_not_supported );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showUserToastAndCancelDialog( int resId ) {
|
||||||
|
showUserToast( resId );
|
||||||
|
progressDialog.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showUserToast( int resId ) {
|
||||||
|
Toast.makeText(this, resId, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showSuccessToastAndDismissDialog() {
|
||||||
|
Toast successToast = Toast.makeText(this, R.string.login_success, Toast.LENGTH_SHORT);
|
||||||
|
successToast.show();
|
||||||
|
progressDialog.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void emptySensitiveEditFields() {
|
||||||
|
passwordEdit.setText("");
|
||||||
|
twoFactorEdit.setText("");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startMainActivity() {
|
||||||
|
Intent intent = new Intent(this, ContributionsActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
125
app/src/main/java/fr/free/nrw/commons/auth/LoginTask.java
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
package fr.free.nrw.commons.auth;
|
||||||
|
|
||||||
|
import android.accounts.AccountAuthenticatorResponse;
|
||||||
|
import android.accounts.AccountManager;
|
||||||
|
import android.app.ProgressDialog;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
|
import fr.free.nrw.commons.EventLog;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
class LoginTask extends AsyncTask<String, String, String> {
|
||||||
|
|
||||||
|
private LoginActivity loginActivity;
|
||||||
|
private String username;
|
||||||
|
private String password;
|
||||||
|
private String twoFactorCode = "";
|
||||||
|
private CommonsApplication app;
|
||||||
|
|
||||||
|
public LoginTask(LoginActivity loginActivity, String username, String password, String twoFactorCode) {
|
||||||
|
this.loginActivity = loginActivity;
|
||||||
|
this.username = username;
|
||||||
|
this.password = password;
|
||||||
|
this.twoFactorCode = twoFactorCode;
|
||||||
|
app = CommonsApplication.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPreExecute() {
|
||||||
|
super.onPreExecute();
|
||||||
|
loginActivity.progressDialog = new ProgressDialog(loginActivity);
|
||||||
|
loginActivity.progressDialog.setIndeterminate(true);
|
||||||
|
loginActivity.progressDialog.setTitle(loginActivity.getString(R.string.logging_in_title));
|
||||||
|
loginActivity.progressDialog.setMessage(loginActivity.getString(R.string.logging_in_message));
|
||||||
|
loginActivity.progressDialog.setCanceledOnTouchOutside(false);
|
||||||
|
loginActivity.progressDialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String doInBackground(String... params) {
|
||||||
|
try {
|
||||||
|
if (twoFactorCode.isEmpty()) {
|
||||||
|
return app.getMWApi().login(username, password);
|
||||||
|
} else {
|
||||||
|
return app.getMWApi().login(username, password, twoFactorCode);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Do something better!
|
||||||
|
return "NetworkFailure";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(String result) {
|
||||||
|
super.onPostExecute(result);
|
||||||
|
Timber.d("Login done!");
|
||||||
|
|
||||||
|
EventLog.schema(CommonsApplication.EVENT_LOGIN_ATTEMPT)
|
||||||
|
.param("username", username)
|
||||||
|
.param("result", result)
|
||||||
|
.log();
|
||||||
|
|
||||||
|
if (result.equals("PASS")) {
|
||||||
|
handlePassResult();
|
||||||
|
} else {
|
||||||
|
handleOtherResults( result );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handlePassResult() {
|
||||||
|
loginActivity.showSuccessToastAndDismissDialog();
|
||||||
|
|
||||||
|
AccountAuthenticatorResponse response = null;
|
||||||
|
|
||||||
|
Bundle extras = loginActivity.getIntent().getExtras();
|
||||||
|
if (extras != null) {
|
||||||
|
Timber.d("Bundle of extras: %s", extras);
|
||||||
|
response = extras.getParcelable(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE);
|
||||||
|
if (response != null) {
|
||||||
|
Bundle authResult = new Bundle();
|
||||||
|
authResult.putString(AccountManager.KEY_ACCOUNT_NAME, username);
|
||||||
|
authResult.putString(AccountManager.KEY_ACCOUNT_TYPE, AccountUtil.accountType());
|
||||||
|
response.onResult(authResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AccountUtil.createAccount( response, username, password );
|
||||||
|
loginActivity.startMainActivity();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Match known failure message codes and provide messages
|
||||||
|
* @param result String
|
||||||
|
*/
|
||||||
|
private void handleOtherResults( String result ) {
|
||||||
|
if (result.equals("NetworkFailure")) {
|
||||||
|
// Matches NetworkFailure which is created by the doInBackground method
|
||||||
|
loginActivity.showUserToastAndCancelDialog( R.string.login_failed_network );
|
||||||
|
} else if (result.toLowerCase().contains("nosuchuser".toLowerCase()) || result.toLowerCase().contains("noname".toLowerCase())) {
|
||||||
|
// Matches nosuchuser, nosuchusershort, noname
|
||||||
|
loginActivity.showUserToastAndCancelDialog( R.string.login_failed_username );
|
||||||
|
loginActivity.emptySensitiveEditFields();
|
||||||
|
} else if (result.toLowerCase().contains("wrongpassword".toLowerCase())) {
|
||||||
|
// Matches wrongpassword, wrongpasswordempty
|
||||||
|
loginActivity.showUserToastAndCancelDialog( R.string.login_failed_password );
|
||||||
|
loginActivity.emptySensitiveEditFields();
|
||||||
|
} else if (result.toLowerCase().contains("throttle".toLowerCase())) {
|
||||||
|
// Matches unknown throttle error codes
|
||||||
|
loginActivity.showUserToastAndCancelDialog( R.string.login_failed_throttled );
|
||||||
|
} else if (result.toLowerCase().contains("userblocked".toLowerCase())) {
|
||||||
|
// Matches login-userblocked
|
||||||
|
loginActivity.showUserToastAndCancelDialog( R.string.login_failed_blocked );
|
||||||
|
} else if (result.equals("2FA")) {
|
||||||
|
loginActivity.askUserForTwoFactorAuth();
|
||||||
|
} else {
|
||||||
|
// Occurs with unhandled login failure codes
|
||||||
|
Timber.d("Login failed with reason: %s", result);
|
||||||
|
loginActivity.showUserToastAndCancelDialog( R.string.login_failed_generic );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,13 +2,14 @@ package fr.free.nrw.commons.auth;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
|
||||||
import android.webkit.WebSettings;
|
import android.webkit.WebSettings;
|
||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
import android.webkit.WebViewClient;
|
import android.webkit.WebViewClient;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.theme.BaseActivity;
|
import fr.free.nrw.commons.theme.BaseActivity;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class SignupActivity extends BaseActivity {
|
public class SignupActivity extends BaseActivity {
|
||||||
|
|
||||||
|
|
@ -17,9 +18,7 @@ public class SignupActivity extends BaseActivity {
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
Log.d("SignupActivity", "Signup Activity started");
|
Timber.d("Signup Activity started");
|
||||||
|
|
||||||
getSupportActionBar().hide();
|
|
||||||
|
|
||||||
webView = new WebView(this);
|
webView = new WebView(this);
|
||||||
setContentView(webView);
|
setContentView(webView);
|
||||||
|
|
@ -37,17 +36,21 @@ public class SignupActivity extends BaseActivity {
|
||||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||||
if (url.equals("https://commons.m.wikimedia.org/w/index.php?title=Main_Page&welcome=yes")) {
|
if (url.equals("https://commons.m.wikimedia.org/w/index.php?title=Main_Page&welcome=yes")) {
|
||||||
//Signup success, so clear cookies, notify user, and load LoginActivity again
|
//Signup success, so clear cookies, notify user, and load LoginActivity again
|
||||||
Log.d("SignupActivity", "Overriding URL" + url);
|
Timber.d("Overriding URL %s", url);
|
||||||
|
|
||||||
Toast toast = Toast.makeText(getApplicationContext(), "Account created!", Toast.LENGTH_LONG);
|
Toast toast = Toast.makeText(
|
||||||
|
CommonsApplication.getInstance(),
|
||||||
|
"Account created!",
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
);
|
||||||
toast.show();
|
toast.show();
|
||||||
|
|
||||||
Intent intent = new Intent(getApplicationContext(), LoginActivity.class);
|
Intent intent = new Intent(CommonsApplication.getInstance(), LoginActivity.class);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
//If user clicks any other links in the webview
|
//If user clicks any other links in the webview
|
||||||
Log.d("SignupActivity", "Not overriding URL, URL is: " + url);
|
Timber.d("Not overriding URL, URL is: %s", url);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,44 +9,76 @@ import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import org.mediawiki.api.MWApi;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
|
import fr.free.nrw.commons.MWApi;
|
||||||
|
|
||||||
public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
|
public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
|
||||||
|
|
||||||
public static final String COMMONS_ACCOUNT_TYPE = "fr.free.nrw.commons";
|
|
||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
public WikiAccountAuthenticator(Context context) {
|
public WikiAccountAuthenticator(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Bundle unsupportedOperation() {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION);
|
||||||
|
|
||||||
|
// HACK: the docs indicate that this is a required key bit it's not displayed to the user.
|
||||||
|
bundle.putString(AccountManager.KEY_ERROR_MESSAGE, "");
|
||||||
|
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean supportedAccountType(@Nullable String type) {
|
||||||
|
return AccountUtil.accountType().equals(type);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
|
public Bundle addAccount(@NonNull AccountAuthenticatorResponse response,
|
||||||
final Intent intent = new Intent(context, LoginActivity.class);
|
@NonNull String accountType, @Nullable String authTokenType,
|
||||||
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
|
@Nullable String[] requiredFeatures, @Nullable Bundle options)
|
||||||
final Bundle bundle = new Bundle();
|
throws NetworkErrorException {
|
||||||
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
|
|
||||||
|
if (!supportedAccountType(accountType)) {
|
||||||
|
return unsupportedOperation();
|
||||||
|
}
|
||||||
|
|
||||||
|
return addAccount(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Bundle addAccount(AccountAuthenticatorResponse response) {
|
||||||
|
Intent Intent = new Intent(context, LoginActivity.class);
|
||||||
|
Intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
|
||||||
|
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putParcelable(AccountManager.KEY_INTENT, Intent);
|
||||||
|
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException {
|
public Bundle confirmCredentials(@NonNull AccountAuthenticatorResponse response,
|
||||||
return null;
|
@NonNull Account account, @Nullable Bundle options)
|
||||||
|
throws NetworkErrorException {
|
||||||
|
return unsupportedOperation();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
|
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
|
||||||
return null;
|
return unsupportedOperation();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getAuthCookie(String username, String password) throws IOException {
|
private String getAuthCookie(String username, String password) throws IOException {
|
||||||
MWApi api = CommonsApplication.createMWApi();
|
MWApi api = CommonsApplication.getInstance().getMWApi();
|
||||||
|
//TODO add 2fa support here
|
||||||
String result = api.login(username, password);
|
String result = api.login(username, password);
|
||||||
if(result.equals("Success")) {
|
if(result.equals("PASS")) {
|
||||||
return api.getAuthCookie();
|
return api.getAuthCookie();
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -70,7 +102,7 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
|
||||||
if (authCookie != null) {
|
if (authCookie != null) {
|
||||||
final Bundle result = new Bundle();
|
final Bundle result = new Bundle();
|
||||||
result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
|
result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
|
||||||
result.putString(AccountManager.KEY_ACCOUNT_TYPE, COMMONS_ACCOUNT_TYPE);
|
result.putString(AccountManager.KEY_ACCOUNT_TYPE, AccountUtil.accountType());
|
||||||
result.putString(AccountManager.KEY_AUTHTOKEN, authCookie);
|
result.putString(AccountManager.KEY_AUTHTOKEN, authCookie);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -87,21 +119,31 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public String getAuthTokenLabel(String authTokenType) {
|
public String getAuthTokenLabel(@NonNull String authTokenType) {
|
||||||
|
//Note: the wikipedia app actually returns a string here....
|
||||||
|
//return supportedAccountType(authTokenType) ? context.getString(R.string.wikimedia) : null;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException {
|
public Bundle hasFeatures(@NonNull AccountAuthenticatorResponse response,
|
||||||
final Bundle result = new Bundle();
|
@NonNull Account account, @NonNull String[] features)
|
||||||
result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
|
throws NetworkErrorException {
|
||||||
return result;
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
|
||||||
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
|
public Bundle updateCredentials(@NonNull AccountAuthenticatorResponse response,
|
||||||
return null;
|
@NonNull Account account, @Nullable String authTokenType,
|
||||||
|
@Nullable Bundle options)
|
||||||
|
throws NetworkErrorException {
|
||||||
|
return unsupportedOperation();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
package fr.free.nrw.commons.caching;
|
package fr.free.nrw.commons.caching;
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.github.varunpant.quadtree.Point;
|
import com.github.varunpant.quadtree.Point;
|
||||||
import com.github.varunpant.quadtree.QuadTree;
|
import com.github.varunpant.quadtree.QuadTree;
|
||||||
|
|
||||||
|
|
@ -10,15 +8,14 @@ import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import fr.free.nrw.commons.upload.MwVolleyApi;
|
import fr.free.nrw.commons.upload.MwVolleyApi;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class CacheController {
|
public class CacheController {
|
||||||
|
|
||||||
private double x, y;
|
private double x, y;
|
||||||
private QuadTree<List<String>> quadTree;
|
private QuadTree<List<String>> quadTree;
|
||||||
private Point<List<String>>[] pointsFound;
|
|
||||||
private double xMinus, xPlus, yMinus, yPlus;
|
private double xMinus, xPlus, yMinus, yPlus;
|
||||||
|
|
||||||
private static final String TAG = CacheController.class.getName();
|
|
||||||
private static final int EARTH_RADIUS = 6378137;
|
private static final int EARTH_RADIUS = 6378137;
|
||||||
|
|
||||||
public CacheController() {
|
public CacheController() {
|
||||||
|
|
@ -28,40 +25,41 @@ public class CacheController {
|
||||||
public void setQtPoint(double decLongitude, double decLatitude) {
|
public void setQtPoint(double decLongitude, double decLatitude) {
|
||||||
x = decLongitude;
|
x = decLongitude;
|
||||||
y = decLatitude;
|
y = decLatitude;
|
||||||
Log.d(TAG, "New QuadTree created");
|
Timber.d("New QuadTree created");
|
||||||
Log.d(TAG, "X (longitude) value: " + x + ", Y (latitude) value: " + y);
|
Timber.d("X (longitude) value: %f, Y (latitude) value: %f", x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cacheCategory() {
|
public void cacheCategory() {
|
||||||
List<String> pointCatList = new ArrayList<>();
|
List<String> pointCatList = new ArrayList<>();
|
||||||
if (MwVolleyApi.GpsCatExists.getGpsCatExists() == true) {
|
if (MwVolleyApi.GpsCatExists.getGpsCatExists()) {
|
||||||
pointCatList.addAll(MwVolleyApi.getGpsCat());
|
pointCatList.addAll(MwVolleyApi.getGpsCat());
|
||||||
Log.d(TAG, "Categories being cached: " + pointCatList);
|
Timber.d("Categories being cached: %s", pointCatList);
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "No categories found, so no categories cached");
|
Timber.d("No categories found, so no categories cached");
|
||||||
}
|
}
|
||||||
quadTree.set(x, y, pointCatList);
|
quadTree.set(x, y, pointCatList);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> findCategory() {
|
public List<String> findCategory() {
|
||||||
|
Point<List<String>>[] pointsFound;
|
||||||
//Convert decLatitude and decLongitude to a coordinate offset range
|
//Convert decLatitude and decLongitude to a coordinate offset range
|
||||||
convertCoordRange();
|
convertCoordRange();
|
||||||
pointsFound = quadTree.searchWithin(xMinus, yMinus, xPlus, yPlus);
|
pointsFound = quadTree.searchWithin(xMinus, yMinus, xPlus, yPlus);
|
||||||
List<String> displayCatList = new ArrayList<>();
|
List<String> displayCatList = new ArrayList<>();
|
||||||
Log.d(TAG, "Points found in quadtree: " + Arrays.asList(pointsFound));
|
Timber.d("Points found in quadtree: %s", Arrays.toString(pointsFound));
|
||||||
|
|
||||||
if (pointsFound.length != 0) {
|
if (pointsFound.length != 0) {
|
||||||
Log.d(TAG, "Entering for loop");
|
Timber.d("Entering for loop");
|
||||||
|
|
||||||
for (Point<List<String>> point : pointsFound) {
|
for (Point<List<String>> point : pointsFound) {
|
||||||
Log.d(TAG, "Nearby point: " + point.toString());
|
Timber.d("Nearby point: %s", point);
|
||||||
displayCatList = point.getValue();
|
displayCatList = point.getValue();
|
||||||
Log.d(TAG, "Nearby cat: " + point.getValue());
|
Timber.d("Nearby cat: %s", point.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(TAG, "Categories found in cache: " + displayCatList.toString());
|
Timber.d("Categories found in cache: %s", displayCatList);
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "No categories found in cache");
|
Timber.d("No categories found in cache");
|
||||||
}
|
}
|
||||||
return displayCatList;
|
return displayCatList;
|
||||||
}
|
}
|
||||||
|
|
@ -84,6 +82,7 @@ public class CacheController {
|
||||||
yMinus = lat - dLat * 180/Math.PI;
|
yMinus = lat - dLat * 180/Math.PI;
|
||||||
xPlus = lon + dLon * 180/Math.PI;
|
xPlus = lon + dLon * 180/Math.PI;
|
||||||
xMinus = lon - dLon * 180/Math.PI;
|
xMinus = lon - dLon * 180/Math.PI;
|
||||||
Log.d(TAG, "Search within: xMinus=" + xMinus + ", yMinus=" + yMinus + ", xPlus=" + xPlus + ", yPlus=" + yPlus);
|
Timber.d("Search within: xMinus=%s, yMinus=%s, xPlus=%s, yPlus=%s",
|
||||||
|
xMinus, yMinus, xPlus, yPlus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,11 @@ import fr.free.nrw.commons.R;
|
||||||
|
|
||||||
public class CategoriesAdapter extends BaseAdapter {
|
public class CategoriesAdapter extends BaseAdapter {
|
||||||
|
|
||||||
private Context context;
|
|
||||||
private LayoutInflater mInflater;
|
private LayoutInflater mInflater;
|
||||||
|
|
||||||
private ArrayList<CategorizationFragment.CategoryItem> items;
|
private ArrayList<CategorizationFragment.CategoryItem> items;
|
||||||
|
|
||||||
public CategoriesAdapter(Context context, ArrayList<CategorizationFragment.CategoryItem> items) {
|
public CategoriesAdapter(Context context, ArrayList<CategorizationFragment.CategoryItem> items) {
|
||||||
this.context = context;
|
|
||||||
this.items = items;
|
this.items = items;
|
||||||
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ import android.support.v7.app.AlertDialog;
|
||||||
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.util.Log;
|
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
|
|
@ -44,6 +43,7 @@ import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.upload.MwVolleyApi;
|
import fr.free.nrw.commons.upload.MwVolleyApi;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the category suggestion and selection screen. Category search is initiated here.
|
* Displays the category suggestion and selection screen. Category search is initiated here.
|
||||||
|
|
@ -79,7 +79,6 @@ public class CategorizationFragment extends Fragment {
|
||||||
private ContentProviderClient client;
|
private ContentProviderClient client;
|
||||||
|
|
||||||
protected final static int SEARCH_CATS_LIMIT = 25;
|
protected final static int SEARCH_CATS_LIMIT = 25;
|
||||||
private static final String TAG = CategorizationFragment.class.getName();
|
|
||||||
|
|
||||||
public static class CategoryItem implements Parcelable {
|
public static class CategoryItem implements Parcelable {
|
||||||
public String name;
|
public String name;
|
||||||
|
|
@ -130,28 +129,28 @@ public class CategorizationFragment extends Fragment {
|
||||||
//Retrieve the title that was saved when user tapped submit icon
|
//Retrieve the title that was saved when user tapped submit icon
|
||||||
SharedPreferences titleDesc = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
SharedPreferences titleDesc = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||||
String title = titleDesc.getString("Title", "");
|
String title = titleDesc.getString("Title", "");
|
||||||
Log.d(TAG, "Title: " + title);
|
Timber.d("Title: %s", title);
|
||||||
|
|
||||||
//Override onPostExecute to access the results of async API call
|
//Override onPostExecute to access the results of async API call
|
||||||
titleCategoriesSub = new TitleCategories(title) {
|
titleCategoriesSub = new TitleCategories(title) {
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(ArrayList<String> result) {
|
protected void onPostExecute(ArrayList<String> result) {
|
||||||
super.onPostExecute(result);
|
super.onPostExecute(result);
|
||||||
Log.d(TAG, "Results in onPostExecute: " + result);
|
Timber.d("Results in onPostExecute: %s", result);
|
||||||
titleCatItems.addAll(result);
|
titleCatItems.addAll(result);
|
||||||
Log.d(TAG, "TitleCatItems in onPostExecute: " + titleCatItems);
|
Timber.d("TitleCatItems in onPostExecute: %s", titleCatItems);
|
||||||
mergeLatch.countDown();
|
mergeLatch.countDown();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
titleCategoriesSub.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
titleCategoriesSub.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
Log.d(TAG, "TitleCatItems in titleCatQuery: " + titleCatItems);
|
Timber.d("TitleCatItems in titleCatQuery: %s", titleCatItems);
|
||||||
|
|
||||||
//Only return titleCatItems after API call has finished
|
//Only return titleCatItems after API call has finished
|
||||||
try {
|
try {
|
||||||
mergeLatch.await(5L, TimeUnit.SECONDS);
|
mergeLatch.await(5L, TimeUnit.SECONDS);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Log.e(TAG, "Interrupted exception: ", e);
|
Timber.e(e, "Interrupted exception: ");
|
||||||
}
|
}
|
||||||
return titleCatItems;
|
return titleCatItems;
|
||||||
}
|
}
|
||||||
|
|
@ -191,7 +190,7 @@ public class CategorizationFragment extends Fragment {
|
||||||
|
|
||||||
Set<String> mergedItems = new LinkedHashSet<>();
|
Set<String> mergedItems = new LinkedHashSet<>();
|
||||||
|
|
||||||
Log.d(TAG, "Calling APIs for GPS cats, title cats and recent cats...");
|
Timber.d("Calling APIs for GPS cats, title cats and recent cats...");
|
||||||
|
|
||||||
List<String> gpsItems = new ArrayList<>();
|
List<String> gpsItems = new ArrayList<>();
|
||||||
if (MwVolleyApi.GpsCatExists.getGpsCatExists()) {
|
if (MwVolleyApi.GpsCatExists.getGpsCatExists()) {
|
||||||
|
|
@ -203,22 +202,22 @@ public class CategorizationFragment extends Fragment {
|
||||||
//Await results of titleItems, which is likely to come in last
|
//Await results of titleItems, which is likely to come in last
|
||||||
try {
|
try {
|
||||||
mergeLatch.await(5L, TimeUnit.SECONDS);
|
mergeLatch.await(5L, TimeUnit.SECONDS);
|
||||||
Log.d(TAG, "Waited for merge");
|
Timber.d("Waited for merge");
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Log.e(TAG, "Interrupted Exception: ", e);
|
Timber.e(e, "Interrupted Exception: ");
|
||||||
}
|
}
|
||||||
|
|
||||||
mergedItems.addAll(gpsItems);
|
mergedItems.addAll(gpsItems);
|
||||||
Log.d(TAG, "Adding GPS items: " + gpsItems);
|
Timber.d("Adding GPS items: %s", gpsItems);
|
||||||
mergedItems.addAll(titleItems);
|
mergedItems.addAll(titleItems);
|
||||||
Log.d(TAG, "Adding title items: " + titleItems);
|
Timber.d("Adding title items: %s", titleItems);
|
||||||
mergedItems.addAll(recentItems);
|
mergedItems.addAll(recentItems);
|
||||||
Log.d(TAG, "Adding recent items: " + 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);
|
||||||
|
|
||||||
Log.d(TAG, "Merged item list: " + mergedItemsList);
|
Timber.d("Merged item list: %s", mergedItemsList);
|
||||||
return mergedItemsList;
|
return mergedItemsList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -261,7 +260,7 @@ public class CategorizationFragment extends Fragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Log.e(TAG, "Error: Fragment is null");
|
Timber.e("Error: Fragment is null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -285,7 +284,7 @@ public class CategorizationFragment extends Fragment {
|
||||||
latch.await();
|
latch.await();
|
||||||
}
|
}
|
||||||
catch (InterruptedException e) {
|
catch (InterruptedException e) {
|
||||||
Log.w(TAG, e);
|
Timber.w(e);
|
||||||
//Thread.currentThread().interrupt();
|
//Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -296,12 +295,12 @@ public class CategorizationFragment extends Fragment {
|
||||||
super.onPostExecute(result);
|
super.onPostExecute(result);
|
||||||
|
|
||||||
results.addAll(result);
|
results.addAll(result);
|
||||||
Log.d(TAG, "Prefix result: " + result);
|
Timber.d("Prefix result: %s", result);
|
||||||
|
|
||||||
String filter = categoriesFilter.getText().toString();
|
String filter = categoriesFilter.getText().toString();
|
||||||
ArrayList<String> resultsList = new ArrayList<>(results);
|
ArrayList<String> resultsList = new ArrayList<>(results);
|
||||||
categoriesCache.put(filter, resultsList);
|
categoriesCache.put(filter, resultsList);
|
||||||
Log.d(TAG, "Final results List: " + resultsList);
|
Timber.d("Final results List: %s", resultsList);
|
||||||
|
|
||||||
categoriesAdapter.notifyDataSetChanged();
|
categoriesAdapter.notifyDataSetChanged();
|
||||||
setCatsAfterAsync(resultsList, filter);
|
setCatsAfterAsync(resultsList, filter);
|
||||||
|
|
@ -315,7 +314,7 @@ public class CategorizationFragment extends Fragment {
|
||||||
super.onPostExecute(result);
|
super.onPostExecute(result);
|
||||||
|
|
||||||
results.addAll(result);
|
results.addAll(result);
|
||||||
Log.d(TAG, "Method A result: " + result);
|
Timber.d("Method A result: %s", result);
|
||||||
categoriesAdapter.notifyDataSetChanged();
|
categoriesAdapter.notifyDataSetChanged();
|
||||||
|
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
|
|
@ -358,8 +357,7 @@ public class CategorizationFragment extends Fragment {
|
||||||
new String[] {name},
|
new String[] {name},
|
||||||
null);
|
null);
|
||||||
if (cursor.moveToFirst()) {
|
if (cursor.moveToFirst()) {
|
||||||
Category cat = Category.fromCursor(cursor);
|
return Category.fromCursor(cursor);
|
||||||
return cat;
|
|
||||||
}
|
}
|
||||||
} catch (RemoteException e) {
|
} catch (RemoteException e) {
|
||||||
// This feels lazy, but to hell with checked exceptions. :)
|
// This feels lazy, but to hell with checked exceptions. :)
|
||||||
|
|
@ -500,7 +498,7 @@ public class CategorizationFragment extends Fragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void backButtonDialog() {
|
public void backButtonDialog() {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||||
|
|
||||||
builder.setMessage("Are you sure you want to go back? The image will not have any categories saved.")
|
builder.setMessage("Are you sure you want to go back? The image will not have any categories saved.")
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,11 @@ public class Category {
|
||||||
db.execSQL(CREATE_TABLE_STATEMENT);
|
db.execSQL(CREATE_TABLE_STATEMENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void onDelete(SQLiteDatabase db) {
|
||||||
|
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
|
||||||
|
onCreate(db);
|
||||||
|
}
|
||||||
|
|
||||||
public static void onUpdate(SQLiteDatabase db, int from, int to) {
|
public static void onUpdate(SQLiteDatabase db, int from, int to) {
|
||||||
if(from == to) {
|
if(from == to) {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,10 @@ 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.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.data.DBOpenHelper;
|
import fr.free.nrw.commons.data.DBOpenHelper;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class CategoryContentProvider extends ContentProvider {
|
public class CategoryContentProvider extends ContentProvider {
|
||||||
|
|
||||||
|
|
@ -36,7 +37,7 @@ public class CategoryContentProvider extends ContentProvider {
|
||||||
private DBOpenHelper dbOpenHelper;
|
private DBOpenHelper dbOpenHelper;
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreate() {
|
public boolean onCreate() {
|
||||||
dbOpenHelper = DBOpenHelper.getInstance(getContext());
|
dbOpenHelper = CommonsApplication.getInstance().getDBOpenHelper();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,14 +102,14 @@ public class CategoryContentProvider extends ContentProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int bulkInsert(Uri uri, ContentValues[] values) {
|
public int bulkInsert(Uri uri, ContentValues[] values) {
|
||||||
Log.d("Commons", "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();
|
||||||
sqlDB.beginTransaction();
|
sqlDB.beginTransaction();
|
||||||
switch (uriType) {
|
switch (uriType) {
|
||||||
case CATEGORIES:
|
case CATEGORIES:
|
||||||
for(ContentValues value: values) {
|
for(ContentValues value: values) {
|
||||||
Log.d("Commons", "Inserting! " + value.toString());
|
Timber.d("Inserting! %s", value);
|
||||||
sqlDB.insert(Category.Table.TABLE_NAME, null, value);
|
sqlDB.insert(Category.Table.TABLE_NAME, null, value);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
package fr.free.nrw.commons.category;
|
package fr.free.nrw.commons.category;
|
||||||
|
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.MWApi;
|
||||||
import org.mediawiki.api.ApiResult;
|
import org.mediawiki.api.ApiResult;
|
||||||
import org.mediawiki.api.MWApi;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -13,6 +12,7 @@ import java.util.Calendar;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
|
|
@ -22,7 +22,6 @@ import fr.free.nrw.commons.CommonsApplication;
|
||||||
public class MethodAUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
public class MethodAUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
||||||
|
|
||||||
private String filter;
|
private String filter;
|
||||||
private static final String TAG = MethodAUpdater.class.getName();
|
|
||||||
CategorizationFragment catFragment;
|
CategorizationFragment catFragment;
|
||||||
|
|
||||||
public MethodAUpdater(CategorizationFragment catFragment) {
|
public MethodAUpdater(CategorizationFragment catFragment) {
|
||||||
|
|
@ -54,11 +53,11 @@ public class MethodAUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
||||||
Calendar now = Calendar.getInstance();
|
Calendar now = Calendar.getInstance();
|
||||||
int year = now.get(Calendar.YEAR);
|
int year = now.get(Calendar.YEAR);
|
||||||
String yearInString = String.valueOf(year);
|
String yearInString = String.valueOf(year);
|
||||||
Log.d(TAG, "Year: " + yearInString);
|
Timber.d("Year: %s", yearInString);
|
||||||
|
|
||||||
int prevYear = year - 1;
|
int prevYear = year - 1;
|
||||||
String prevYearInString = String.valueOf(prevYear);
|
String prevYearInString = String.valueOf(prevYear);
|
||||||
Log.d(TAG, "Previous year: " + prevYearInString);
|
Timber.d("Previous year: %s", prevYearInString);
|
||||||
|
|
||||||
//Copy to Iterator to prevent ConcurrentModificationException when removing item
|
//Copy to Iterator to prevent ConcurrentModificationException when removing item
|
||||||
for(iterator = items.iterator(); iterator.hasNext();) {
|
for(iterator = items.iterator(); iterator.hasNext();) {
|
||||||
|
|
@ -67,12 +66,12 @@ public class MethodAUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
||||||
//Check if s contains a 4-digit word anywhere within the string (.* is wildcard)
|
//Check if s contains a 4-digit word anywhere within the string (.* is wildcard)
|
||||||
//And that s does not equal the current year or previous year
|
//And that s does not equal the current year or previous year
|
||||||
if(s.matches(".*(19|20)\\d{2}.*") && !s.contains(yearInString) && !s.contains(prevYearInString)) {
|
if(s.matches(".*(19|20)\\d{2}.*") && !s.contains(yearInString) && !s.contains(prevYearInString)) {
|
||||||
Log.d(TAG, "Filtering out year " + s);
|
Timber.d("Filtering out year %s", s);
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(TAG, "Items: " + items.toString());
|
Timber.d("Items: %s", items);
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,7 +79,7 @@ public class MethodAUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
||||||
protected ArrayList<String> doInBackground(Void... voids) {
|
protected ArrayList<String> doInBackground(Void... voids) {
|
||||||
|
|
||||||
//otherwise if user has typed something in that isn't in cache, search API for matching categories
|
//otherwise if user has typed something in that isn't in cache, search API for matching categories
|
||||||
MWApi api = CommonsApplication.createMWApi();
|
MWApi api = CommonsApplication.getInstance().getMWApi();
|
||||||
ApiResult result;
|
ApiResult result;
|
||||||
ArrayList<String> categories = new ArrayList<>();
|
ArrayList<String> categories = new ArrayList<>();
|
||||||
|
|
||||||
|
|
@ -94,9 +93,9 @@ public class MethodAUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
||||||
.param("srlimit", catFragment.SEARCH_CATS_LIMIT)
|
.param("srlimit", catFragment.SEARCH_CATS_LIMIT)
|
||||||
.param("srsearch", filter)
|
.param("srsearch", filter)
|
||||||
.get();
|
.get();
|
||||||
Log.d(TAG, "Method A URL filter" + result.toString());
|
Timber.d("Method A URL filter %s", result);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, "IO Exception: ", e);
|
Timber.e(e, "IO Exception: ");
|
||||||
//Return empty arraylist
|
//Return empty arraylist
|
||||||
return categories;
|
return categories;
|
||||||
}
|
}
|
||||||
|
|
@ -108,8 +107,7 @@ public class MethodAUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
||||||
categories.add(catString);
|
categories.add(catString);
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(TAG, "Found categories from Method A search, waiting for filter");
|
Timber.d("Found categories from Method A search, waiting for filter");
|
||||||
ArrayList<String> filteredItems = new ArrayList<>(filterYears(categories));
|
return new ArrayList<>(filterYears(categories));
|
||||||
return filteredItems;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,10 @@ package fr.free.nrw.commons.category;
|
||||||
|
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.MWApi;
|
||||||
import org.mediawiki.api.ApiResult;
|
import org.mediawiki.api.ApiResult;
|
||||||
import org.mediawiki.api.MWApi;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -14,6 +13,7 @@ import java.util.Calendar;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
|
|
@ -24,7 +24,6 @@ import fr.free.nrw.commons.CommonsApplication;
|
||||||
public class PrefixUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
public class PrefixUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
||||||
|
|
||||||
private String filter;
|
private String filter;
|
||||||
private static final String TAG = PrefixUpdater.class.getName();
|
|
||||||
private CategorizationFragment catFragment;
|
private CategorizationFragment catFragment;
|
||||||
|
|
||||||
public PrefixUpdater(CategorizationFragment catFragment) {
|
public PrefixUpdater(CategorizationFragment catFragment) {
|
||||||
|
|
@ -56,11 +55,11 @@ public class PrefixUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
||||||
Calendar now = Calendar.getInstance();
|
Calendar now = Calendar.getInstance();
|
||||||
int year = now.get(Calendar.YEAR);
|
int year = now.get(Calendar.YEAR);
|
||||||
String yearInString = String.valueOf(year);
|
String yearInString = String.valueOf(year);
|
||||||
Log.d(TAG, "Year: " + yearInString);
|
Timber.d("Year: %s", yearInString);
|
||||||
|
|
||||||
int prevYear = year - 1;
|
int prevYear = year - 1;
|
||||||
String prevYearInString = String.valueOf(prevYear);
|
String prevYearInString = String.valueOf(prevYear);
|
||||||
Log.d(TAG, "Previous year: " + prevYearInString);
|
Timber.d("Previous year: %s", prevYearInString);
|
||||||
|
|
||||||
//Copy to Iterator to prevent ConcurrentModificationException when removing item
|
//Copy to Iterator to prevent ConcurrentModificationException when removing item
|
||||||
for(iterator = items.iterator(); iterator.hasNext();) {
|
for(iterator = items.iterator(); iterator.hasNext();) {
|
||||||
|
|
@ -69,12 +68,12 @@ public class PrefixUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
||||||
//Check if s contains a 4-digit word anywhere within the string (.* is wildcard)
|
//Check if s contains a 4-digit word anywhere within the string (.* is wildcard)
|
||||||
//And that s does not equal the current year or previous year
|
//And that s does not equal the current year or previous year
|
||||||
if(s.matches(".*(19|20)\\d{2}.*") && !s.contains(yearInString) && !s.contains(prevYearInString)) {
|
if(s.matches(".*(19|20)\\d{2}.*") && !s.contains(yearInString) && !s.contains(prevYearInString)) {
|
||||||
Log.d(TAG, "Filtering out year " + s);
|
Timber.d("Filtering out year %s", s);
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(TAG, "Items: " + items.toString());
|
Timber.d("Items: %s", items);
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,22 +82,20 @@ public class PrefixUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
||||||
//If user hasn't typed anything in yet, get GPS and recent items
|
//If user hasn't typed anything in yet, get GPS and recent items
|
||||||
if(TextUtils.isEmpty(filter)) {
|
if(TextUtils.isEmpty(filter)) {
|
||||||
ArrayList<String> mergedItems = new ArrayList<>(catFragment.mergeItems());
|
ArrayList<String> mergedItems = new ArrayList<>(catFragment.mergeItems());
|
||||||
Log.d(TAG, "Merged items, waiting for filter");
|
Timber.d("Merged items, waiting for filter");
|
||||||
ArrayList<String> filteredItems = new ArrayList<>(filterYears(mergedItems));
|
return new ArrayList<>(filterYears(mergedItems));
|
||||||
return filteredItems;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//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)) {
|
if(catFragment.categoriesCache.containsKey(filter)) {
|
||||||
ArrayList<String> cachedItems = new ArrayList<>(catFragment.categoriesCache.get(filter));
|
ArrayList<String> cachedItems = new ArrayList<>(catFragment.categoriesCache.get(filter));
|
||||||
Log.d(TAG, "Found cache items, waiting for filter");
|
Timber.d("Found cache items, waiting for filter");
|
||||||
ArrayList<String> filteredItems = new ArrayList<>(filterYears(cachedItems));
|
return new ArrayList<>(filterYears(cachedItems));
|
||||||
return filteredItems;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//otherwise if user has typed something in that isn't in cache, search API for matching categories
|
//otherwise if user has typed something in that isn't in cache, search API for matching categories
|
||||||
//URL: https://commons.wikimedia.org/w/api.php?action=query&list=allcategories&acprefix=filter&aclimit=25
|
//URL: https://commons.wikimedia.org/w/api.php?action=query&list=allcategories&acprefix=filter&aclimit=25
|
||||||
MWApi api = CommonsApplication.createMWApi();
|
MWApi api = CommonsApplication.getInstance().getMWApi();
|
||||||
ApiResult result;
|
ApiResult result;
|
||||||
ArrayList<String> categories = new ArrayList<>();
|
ArrayList<String> categories = new ArrayList<>();
|
||||||
try {
|
try {
|
||||||
|
|
@ -107,9 +104,9 @@ public class PrefixUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
||||||
.param("acprefix", filter)
|
.param("acprefix", filter)
|
||||||
.param("aclimit", catFragment.SEARCH_CATS_LIMIT)
|
.param("aclimit", catFragment.SEARCH_CATS_LIMIT)
|
||||||
.get();
|
.get();
|
||||||
Log.d(TAG, "Prefix URL filter" + result.toString());
|
Timber.d("Prefix URL filter %s", result);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, "IO Exception: ", e);
|
Timber.e(e, "IO Exception: ");
|
||||||
//Return empty arraylist
|
//Return empty arraylist
|
||||||
return categories;
|
return categories;
|
||||||
}
|
}
|
||||||
|
|
@ -119,8 +116,7 @@ public class PrefixUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
||||||
categories.add(categoryNode.getDocument().getTextContent());
|
categories.add(categoryNode.getDocument().getTextContent());
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(TAG, "Found categories from Prefix search, waiting for filter");
|
Timber.d("Found categories from Prefix search, waiting for filter");
|
||||||
ArrayList<String> filteredItems = new ArrayList<>(filterYears(categories));
|
return new ArrayList<>(filterYears(categories));
|
||||||
return filteredItems;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
package fr.free.nrw.commons.category;
|
package fr.free.nrw.commons.category;
|
||||||
|
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.MWApi;
|
||||||
import org.mediawiki.api.ApiResult;
|
import org.mediawiki.api.ApiResult;
|
||||||
import org.mediawiki.api.MWApi;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends asynchronous queries to the Commons MediaWiki API to retrieve categories that are related to
|
* Sends asynchronous queries to the Commons MediaWiki API to retrieve categories that are related to
|
||||||
|
|
@ -19,7 +19,7 @@ import fr.free.nrw.commons.CommonsApplication;
|
||||||
public class TitleCategories extends AsyncTask<Void, Void, ArrayList<String>> {
|
public class TitleCategories extends AsyncTask<Void, Void, ArrayList<String>> {
|
||||||
|
|
||||||
private final static int SEARCH_CATS_LIMIT = 25;
|
private final static int SEARCH_CATS_LIMIT = 25;
|
||||||
private static final String TAG = TitleCategories.class.getName();
|
|
||||||
private String title;
|
private String title;
|
||||||
|
|
||||||
public TitleCategories(String title) {
|
public TitleCategories(String title) {
|
||||||
|
|
@ -34,7 +34,7 @@ public class TitleCategories extends AsyncTask<Void, Void, ArrayList<String>> {
|
||||||
@Override
|
@Override
|
||||||
protected ArrayList<String> doInBackground(Void... voids) {
|
protected ArrayList<String> doInBackground(Void... voids) {
|
||||||
|
|
||||||
MWApi api = CommonsApplication.createMWApi();
|
MWApi api = CommonsApplication.getInstance().getMWApi();
|
||||||
ApiResult result;
|
ApiResult result;
|
||||||
ArrayList<String> items = new ArrayList<>();
|
ArrayList<String> items = new ArrayList<>();
|
||||||
|
|
||||||
|
|
@ -48,9 +48,9 @@ public class TitleCategories extends AsyncTask<Void, Void, ArrayList<String>> {
|
||||||
.param("srlimit", SEARCH_CATS_LIMIT)
|
.param("srlimit", SEARCH_CATS_LIMIT)
|
||||||
.param("srsearch", title)
|
.param("srsearch", title)
|
||||||
.get();
|
.get();
|
||||||
Log.d(TAG, "Searching for cats for title: " + result.toString());
|
Timber.d("Searching for cats for title: %s", result);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, "IO Exception: ", e);
|
Timber.e(e, "IO Exception: ");
|
||||||
//Return empty arraylist
|
//Return empty arraylist
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
@ -62,7 +62,7 @@ public class TitleCategories extends AsyncTask<Void, Void, ArrayList<String>> {
|
||||||
items.add(catString);
|
items.add(catString);
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(TAG, "Title cat query results: " + items);
|
Timber.d("Title cat query results: %s", items);
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,6 @@ public class Contribution extends Media {
|
||||||
public static final String SOURCE_GALLERY = "gallery";
|
public static final String SOURCE_GALLERY = "gallery";
|
||||||
public static final String SOURCE_EXTERNAL = "external";
|
public static final String SOURCE_EXTERNAL = "external";
|
||||||
|
|
||||||
private static final String TAG = "Contribution";
|
|
||||||
|
|
||||||
private ContentProviderClient client;
|
private ContentProviderClient client;
|
||||||
private Uri contentUri;
|
private Uri contentUri;
|
||||||
private String source;
|
private String source;
|
||||||
|
|
@ -197,7 +195,7 @@ public class Contribution extends Media {
|
||||||
cv.put(Table.COLUMN_LOCAL_URI, getLocalUri().toString());
|
cv.put(Table.COLUMN_LOCAL_URI, getLocalUri().toString());
|
||||||
}
|
}
|
||||||
if(getImageUrl() != null) {
|
if(getImageUrl() != null) {
|
||||||
cv.put(Table.COLUMN_IMAGE_URL, getImageUrl().toString());
|
cv.put(Table.COLUMN_IMAGE_URL, getImageUrl());
|
||||||
}
|
}
|
||||||
if(getDateUploaded() != null) {
|
if(getDateUploaded() != null) {
|
||||||
cv.put(Table.COLUMN_UPLOADED, getDateUploaded().getTime());
|
cv.put(Table.COLUMN_UPLOADED, getDateUploaded().getTime());
|
||||||
|
|
@ -333,6 +331,11 @@ public class Contribution extends Media {
|
||||||
db.execSQL(CREATE_TABLE_STATEMENT);
|
db.execSQL(CREATE_TABLE_STATEMENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void onDelete(SQLiteDatabase db) {
|
||||||
|
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
|
||||||
|
onCreate(db);
|
||||||
|
}
|
||||||
|
|
||||||
public static void onUpdate(SQLiteDatabase db, int from, int to) {
|
public static void onUpdate(SQLiteDatabase db, int from, int to) {
|
||||||
if(from == to) {
|
if(from == to) {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import android.os.Bundle;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
@ -15,6 +14,7 @@ import java.util.Date;
|
||||||
|
|
||||||
import fr.free.nrw.commons.upload.ShareActivity;
|
import fr.free.nrw.commons.upload.ShareActivity;
|
||||||
import fr.free.nrw.commons.upload.UploadService;
|
import fr.free.nrw.commons.upload.UploadService;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class ContributionController {
|
public class ContributionController {
|
||||||
private Fragment fragment;
|
private Fragment fragment;
|
||||||
|
|
@ -38,13 +38,13 @@ public class ContributionController {
|
||||||
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Commons/images/" + new Date().getTime() + ".jpg";
|
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Commons/images/" + new Date().getTime() + ".jpg";
|
||||||
File _photoFile = new File(path);
|
File _photoFile = new File(path);
|
||||||
try {
|
try {
|
||||||
if(_photoFile.exists() == false) {
|
if(!_photoFile.exists()) {
|
||||||
_photoFile.getParentFile().mkdirs();
|
_photoFile.getParentFile().mkdirs();
|
||||||
_photoFile.createNewFile();
|
_photoFile.createNewFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e("Commons", "Could not create file: " + path, e);
|
Timber.e(e, "Could not create file: %s", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Uri.fromFile(_photoFile);
|
return Uri.fromFile(_photoFile);
|
||||||
|
|
@ -84,11 +84,11 @@ public class ContributionController {
|
||||||
shareIntent.putExtra(UploadService.EXTRA_SOURCE, Contribution.SOURCE_CAMERA);
|
shareIntent.putExtra(UploadService.EXTRA_SOURCE, Contribution.SOURCE_CAMERA);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Log.i("Image", "Image selected");
|
Timber.i("Image selected");
|
||||||
try {
|
try {
|
||||||
activity.startActivity(shareIntent);
|
activity.startActivity(shareIntent);
|
||||||
} catch (SecurityException e) {
|
} catch (SecurityException e) {
|
||||||
Log.e("ContributionController", "Security Exception", e);
|
Timber.e(e, "Security Exception");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,6 @@ class ContributionViewHolder {
|
||||||
final TextView seqNumView;
|
final TextView seqNumView;
|
||||||
final ProgressBar progressView;
|
final ProgressBar progressView;
|
||||||
|
|
||||||
String url;
|
|
||||||
|
|
||||||
ContributionViewHolder(View parent) {
|
ContributionViewHolder(View parent) {
|
||||||
imageView = (MediaWikiImageView) parent.findViewById(R.id.contributionImage);
|
imageView = (MediaWikiImageView) parent.findViewById(R.id.contributionImage);
|
||||||
titleView = (TextView)parent.findViewById(R.id.contributionTitle);
|
titleView = (TextView)parent.findViewById(R.id.contributionTitle);
|
||||||
|
|
|
||||||
|
|
@ -5,16 +5,17 @@ import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.ServiceConnection;
|
import android.content.ServiceConnection;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.database.DataSetObserver;
|
import android.database.DataSetObserver;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
import android.support.v4.app.FragmentManager;
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.support.v4.app.LoaderManager;
|
import android.support.v4.app.LoaderManager;
|
||||||
import android.support.v4.content.CursorLoader;
|
import android.support.v4.content.CursorLoader;
|
||||||
import android.support.v4.content.Loader;
|
import android.support.v4.content.Loader;
|
||||||
import android.support.v4.widget.CursorAdapter;
|
import android.support.v4.widget.CursorAdapter;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
@ -22,16 +23,19 @@ import android.widget.Adapter;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Objects;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import butterknife.ButterKnife;
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.HandlerService;
|
import fr.free.nrw.commons.HandlerService;
|
||||||
import fr.free.nrw.commons.Media;
|
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.auth.WikiAccountAuthenticator;
|
import fr.free.nrw.commons.hamburger.HamburgerMenuContainer;
|
||||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||||
|
import fr.free.nrw.commons.settings.Prefs;
|
||||||
import fr.free.nrw.commons.upload.UploadService;
|
import fr.free.nrw.commons.upload.UploadService;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class ContributionsActivity
|
public class ContributionsActivity
|
||||||
extends AuthenticatedActivity
|
extends AuthenticatedActivity
|
||||||
|
|
@ -39,7 +43,8 @@ public class ContributionsActivity
|
||||||
AdapterView.OnItemClickListener,
|
AdapterView.OnItemClickListener,
|
||||||
MediaDetailPagerFragment.MediaDetailProvider,
|
MediaDetailPagerFragment.MediaDetailProvider,
|
||||||
FragmentManager.OnBackStackChangedListener,
|
FragmentManager.OnBackStackChangedListener,
|
||||||
ContributionsListFragment.SourceRefresher {
|
ContributionsListFragment.SourceRefresher,
|
||||||
|
HamburgerMenuContainer {
|
||||||
|
|
||||||
private Cursor allContributions;
|
private Cursor allContributions;
|
||||||
private ContributionsListFragment contributionsList;
|
private ContributionsListFragment contributionsList;
|
||||||
|
|
@ -48,6 +53,7 @@ public class ContributionsActivity
|
||||||
private boolean isUploadServiceConnected;
|
private boolean isUploadServiceConnected;
|
||||||
private ArrayList<DataSetObserver> observersWaitingForLoad = new ArrayList<>();
|
private ArrayList<DataSetObserver> observersWaitingForLoad = new ArrayList<>();
|
||||||
private String CONTRIBUTION_SELECTION = "";
|
private String CONTRIBUTION_SELECTION = "";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
This sorts in the following order:
|
This sorts in the following order:
|
||||||
Currently Uploading
|
Currently Uploading
|
||||||
|
|
@ -59,10 +65,6 @@ public class ContributionsActivity
|
||||||
*/
|
*/
|
||||||
private String CONTRIBUTION_SORT = Contribution.Table.COLUMN_STATE + " DESC, " + Contribution.Table.COLUMN_UPLOADED + " DESC , (" + Contribution.Table.COLUMN_TIMESTAMP + " * " + Contribution.Table.COLUMN_STATE + ")";
|
private String CONTRIBUTION_SORT = Contribution.Table.COLUMN_STATE + " DESC, " + Contribution.Table.COLUMN_UPLOADED + " DESC , (" + Contribution.Table.COLUMN_TIMESTAMP + " * " + Contribution.Table.COLUMN_STATE + ")";
|
||||||
|
|
||||||
public ContributionsActivity() {
|
|
||||||
super(WikiAccountAuthenticator.COMMONS_ACCOUNT_TYPE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ServiceConnection uploadServiceConnection = new ServiceConnection() {
|
private ServiceConnection uploadServiceConnection = new ServiceConnection() {
|
||||||
@Override
|
@Override
|
||||||
public void onServiceConnected(ComponentName componentName, IBinder binder) {
|
public void onServiceConnected(ComponentName componentName, IBinder binder) {
|
||||||
|
|
@ -88,6 +90,15 @@ public class ContributionsActivity
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
|
boolean isSettingsChanged =
|
||||||
|
sharedPreferences.getBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED,false);
|
||||||
|
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||||
|
editor.putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED,false);
|
||||||
|
editor.apply();
|
||||||
|
if (isSettingsChanged) {
|
||||||
|
refreshSource();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -98,7 +109,7 @@ public class ContributionsActivity
|
||||||
@Override
|
@Override
|
||||||
protected void onAuthCookieAcquired(String authCookie) {
|
protected void onAuthCookieAcquired(String authCookie) {
|
||||||
// Do a sync everytime we get here!
|
// Do a sync everytime we get here!
|
||||||
ContentResolver.requestSync(((CommonsApplication) getApplicationContext()).getCurrentAccount(), ContributionsContentProvider.AUTHORITY, new Bundle());
|
ContentResolver.requestSync(CommonsApplication.getInstance().getCurrentAccount(), ContributionsContentProvider.AUTHORITY, new Bundle());
|
||||||
Intent uploadServiceIntent = new Intent(this, UploadService.class);
|
Intent uploadServiceIntent = new Intent(this, UploadService.class);
|
||||||
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
|
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
|
||||||
startService(uploadServiceIntent);
|
startService(uploadServiceIntent);
|
||||||
|
|
@ -112,23 +123,22 @@ public class ContributionsActivity
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setTitle(R.string.title_activity_contributions);
|
|
||||||
setContentView(R.layout.activity_contributions);
|
setContentView(R.layout.activity_contributions);
|
||||||
|
ButterKnife.bind(this);
|
||||||
|
|
||||||
// Activity can call methods in the fragment by acquiring a reference to the Fragment from FragmentManager, using findFragmentById()
|
// Activity can call methods in the fragment by acquiring a
|
||||||
contributionsList = (ContributionsListFragment)getSupportFragmentManager().findFragmentById(R.id.contributionsListFragment);
|
// reference to the Fragment from FragmentManager, using findFragmentById()
|
||||||
|
contributionsList = (ContributionsListFragment)getSupportFragmentManager()
|
||||||
|
.findFragmentById(R.id.contributionsListFragment);
|
||||||
|
|
||||||
getSupportFragmentManager().addOnBackStackChangedListener(this);
|
getSupportFragmentManager().addOnBackStackChangedListener(this);
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
mediaDetails = (MediaDetailPagerFragment)getSupportFragmentManager().findFragmentById(R.id.contributionsFragmentContainer);
|
mediaDetails = (MediaDetailPagerFragment)getSupportFragmentManager()
|
||||||
// onBackStackChanged uses mediaDetails.isVisible() but this returns false now.
|
.findFragmentById(R.id.contributionsFragmentContainer);
|
||||||
// Use the saved value from before pause or orientation change.
|
|
||||||
if (mediaDetails != null && savedInstanceState.getBoolean("mediaDetailsVisible")) {
|
|
||||||
// Feels awful that we have to reset this manually!
|
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
requestAuthToken();
|
requestAuthToken();
|
||||||
|
initDrawer();
|
||||||
|
setTitle(getString(R.string.title_activity_contributions));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -158,9 +168,9 @@ public class ContributionsActivity
|
||||||
Contribution c = Contribution.fromCursor(allContributions);
|
Contribution c = Contribution.fromCursor(allContributions);
|
||||||
if(c.getState() == Contribution.STATE_FAILED) {
|
if(c.getState() == Contribution.STATE_FAILED) {
|
||||||
uploadService.queue(UploadService.ACTION_UPLOAD_FILE, c);
|
uploadService.queue(UploadService.ACTION_UPLOAD_FILE, c);
|
||||||
Log.d("Commons", "Restarting for" + c.toContentValues().toString());
|
Timber.d("Restarting for %s", c.toContentValues());
|
||||||
} else {
|
} else {
|
||||||
Log.d("Commons", "Skipping re-upload for non-failed " + c.toContentValues().toString());
|
Timber.d("Skipping re-upload for non-failed %s", c.toContentValues());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -168,11 +178,11 @@ public class ContributionsActivity
|
||||||
allContributions.moveToPosition(i);
|
allContributions.moveToPosition(i);
|
||||||
Contribution c = Contribution.fromCursor(allContributions);
|
Contribution c = Contribution.fromCursor(allContributions);
|
||||||
if(c.getState() == Contribution.STATE_FAILED) {
|
if(c.getState() == Contribution.STATE_FAILED) {
|
||||||
Log.d("Commons", "Deleting failed contrib " + c.toContentValues().toString());
|
Timber.d("Deleting failed contrib %s", c.toContentValues());
|
||||||
c.setContentProviderClient(getContentResolver().acquireContentProviderClient(ContributionsContentProvider.AUTHORITY));
|
c.setContentProviderClient(getContentResolver().acquireContentProviderClient(ContributionsContentProvider.AUTHORITY));
|
||||||
c.delete();
|
c.delete();
|
||||||
} else {
|
} else {
|
||||||
Log.d("Commons", "Skipping deletion for non-failed contrib " + c.toContentValues().toString());
|
Timber.d("Skipping deletion for non-failed contrib %s", c.toContentValues());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -206,7 +216,12 @@ public class ContributionsActivity
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
|
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
|
||||||
return new CursorLoader(this, ContributionsContentProvider.BASE_URI, Contribution.Table.ALL_FIELDS, CONTRIBUTION_SELECTION, null, CONTRIBUTION_SORT);
|
SharedPreferences sharedPref =
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
|
int uploads = sharedPref.getInt(Prefs.UPLOADS_SHOWING, 100);
|
||||||
|
return new CursorLoader(this, ContributionsContentProvider.BASE_URI,
|
||||||
|
Contribution.Table.ALL_FIELDS, CONTRIBUTION_SELECTION, null,
|
||||||
|
CONTRIBUTION_SORT + "LIMIT " + uploads);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -217,7 +232,18 @@ public class ContributionsActivity
|
||||||
((CursorAdapter)contributionsList.getAdapter()).swapCursor(cursor);
|
((CursorAdapter)contributionsList.getAdapter()).swapCursor(cursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
getSupportActionBar().setSubtitle(getResources().getQuantityString(R.plurals.contributions_subtitle, cursor.getCount(), cursor.getCount()));
|
if (cursor.getCount() == 0
|
||||||
|
&& Locale.getDefault().getISO3Language().equals(Locale.ENGLISH.getISO3Language())) {
|
||||||
|
//cursor count is zero and language is english -
|
||||||
|
// we need to set the message for 0 case explicitly.
|
||||||
|
getSupportActionBar().setSubtitle(getResources()
|
||||||
|
.getString(R.string.contributions_subtitle_zero));
|
||||||
|
} else {
|
||||||
|
getSupportActionBar().setSubtitle(getResources()
|
||||||
|
.getQuantityString(R.plurals.contributions_subtitle,
|
||||||
|
cursor.getCount(),
|
||||||
|
cursor.getCount()));
|
||||||
|
}
|
||||||
|
|
||||||
contributionsList.clearSyncMessage();
|
contributionsList.clearSyncMessage();
|
||||||
notifyAndMigrateDataSetObservers();
|
notifyAndMigrateDataSetObservers();
|
||||||
|
|
@ -289,15 +315,16 @@ public class ContributionsActivity
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackStackChanged() {
|
public void onBackStackChanged() {
|
||||||
if(mediaDetails != null && mediaDetails.isVisible()) {
|
initBackButton();
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
} else {
|
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void refreshSource() {
|
public void refreshSource() {
|
||||||
getSupportLoaderManager().restartLoader(0, null, this);
|
getSupportLoaderManager().restartLoader(0, null, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void startYourself(Context context) {
|
||||||
|
Intent settingsIntent = new Intent(context, ContributionsActivity.class);
|
||||||
|
context.startActivity(settingsIntent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,10 @@ 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.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.data.DBOpenHelper;
|
import fr.free.nrw.commons.data.DBOpenHelper;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class ContributionsContentProvider extends ContentProvider{
|
public class ContributionsContentProvider extends ContentProvider{
|
||||||
|
|
||||||
|
|
@ -35,7 +36,7 @@ public class ContributionsContentProvider extends ContentProvider{
|
||||||
private DBOpenHelper dbOpenHelper;
|
private DBOpenHelper dbOpenHelper;
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreate() {
|
public boolean onCreate() {
|
||||||
dbOpenHelper = DBOpenHelper.getInstance(getContext());
|
dbOpenHelper = CommonsApplication.getInstance().getDBOpenHelper();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,7 +103,7 @@ public class ContributionsContentProvider extends ContentProvider{
|
||||||
|
|
||||||
switch(uriType) {
|
switch(uriType) {
|
||||||
case CONTRIBUTIONS_ID:
|
case CONTRIBUTIONS_ID:
|
||||||
Log.d("Commons", "Deleting contribution id " + uri.getLastPathSegment());
|
Timber.d("Deleting contribution id %s", uri.getLastPathSegment());
|
||||||
rows = db.delete(Contribution.Table.TABLE_NAME,
|
rows = db.delete(Contribution.Table.TABLE_NAME,
|
||||||
"_id = ?",
|
"_id = ?",
|
||||||
new String[] { uri.getLastPathSegment() }
|
new String[] { uri.getLastPathSegment() }
|
||||||
|
|
@ -117,14 +118,14 @@ public class ContributionsContentProvider extends ContentProvider{
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int bulkInsert(Uri uri, ContentValues[] values) {
|
public int bulkInsert(Uri uri, ContentValues[] values) {
|
||||||
Log.d("Commons", "Hello, bulk insert! (ContributionsContentProvider)");
|
Timber.d("Hello, bulk insert! (ContributionsContentProvider)");
|
||||||
int uriType = uriMatcher.match(uri);
|
int uriType = uriMatcher.match(uri);
|
||||||
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
|
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
|
||||||
sqlDB.beginTransaction();
|
sqlDB.beginTransaction();
|
||||||
switch (uriType) {
|
switch (uriType) {
|
||||||
case CONTRIBUTIONS:
|
case CONTRIBUTIONS:
|
||||||
for(ContentValues value: values) {
|
for(ContentValues value: values) {
|
||||||
Log.d("Commons", "Inserting! " + value.toString());
|
Timber.d("Inserting! %s", value);
|
||||||
sqlDB.insert(Contribution.Table.TABLE_NAME, null, value);
|
sqlDB.insert(Contribution.Table.TABLE_NAME, null, value);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -3,27 +3,13 @@ package fr.free.nrw.commons.contributions;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.graphics.drawable.BitmapDrawable;
|
|
||||||
import android.support.v4.widget.CursorAdapter;
|
import android.support.v4.widget.CursorAdapter;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
|
||||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
|
||||||
import com.nostra13.universalimageloader.core.assist.FailReason;
|
|
||||||
import com.nostra13.universalimageloader.core.assist.SimpleImageLoadingListener;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
|
||||||
import fr.free.nrw.commons.MediaWikiImageView;
|
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.Utils;
|
|
||||||
|
|
||||||
class ContributionsListAdapter extends CursorAdapter {
|
class ContributionsListAdapter extends CursorAdapter {
|
||||||
|
|
||||||
private DisplayImageOptions contributionDisplayOptions = Utils.getGenericDisplayOptions().build();
|
|
||||||
|
|
||||||
private Activity activity;
|
private Activity activity;
|
||||||
|
|
||||||
public ContributionsListAdapter(Activity activity, Cursor c, int flags) {
|
public ContributionsListAdapter(Activity activity, Cursor c, int flags) {
|
||||||
|
|
@ -38,48 +24,12 @@ class ContributionsListAdapter extends CursorAdapter {
|
||||||
return parent;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
//FIXME: Potential cause of wrong image display bug
|
|
||||||
@Override
|
@Override
|
||||||
public void bindView(View view, Context context, Cursor cursor) {
|
public void bindView(View view, Context context, Cursor cursor) {
|
||||||
final ContributionViewHolder views = (ContributionViewHolder)view.getTag();
|
final ContributionViewHolder views = (ContributionViewHolder)view.getTag();
|
||||||
final Contribution contribution = Contribution.fromCursor(cursor);
|
final Contribution contribution = Contribution.fromCursor(cursor);
|
||||||
|
|
||||||
String actualUrl = (contribution.getLocalUri() != null && !TextUtils.isEmpty(contribution.getLocalUri().toString())) ? contribution.getLocalUri().toString() : contribution.getThumbnailUrl(640);
|
views.imageView.setMedia(contribution);
|
||||||
|
|
||||||
if(views.url == null || !views.url.equals(actualUrl)) {
|
|
||||||
if(actualUrl.startsWith("http")) {
|
|
||||||
MediaWikiImageView mwImageView = views.imageView;
|
|
||||||
mwImageView.setMedia(contribution, ((CommonsApplication) activity.getApplicationContext()).getImageLoader());
|
|
||||||
// FIXME: For transparent images
|
|
||||||
} else {
|
|
||||||
ImageLoader.getInstance().displayImage(actualUrl, views.imageView, contributionDisplayOptions, new SimpleImageLoadingListener() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
|
|
||||||
if(loadedImage.hasAlpha()) {
|
|
||||||
views.imageView.setBackgroundResource(android.R.color.white);
|
|
||||||
}
|
|
||||||
views.seqNumView.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
|
|
||||||
super.onLoadingFailed(imageUri, view, failReason);
|
|
||||||
MediaWikiImageView mwImageView = views.imageView;
|
|
||||||
mwImageView.setMedia(contribution, ((CommonsApplication) activity.getApplicationContext()).getImageLoader());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
views.url = actualUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
BitmapDrawable actualImageDrawable = (BitmapDrawable)views.imageView.getDrawable();
|
|
||||||
if(actualImageDrawable != null && actualImageDrawable.getBitmap() != null && actualImageDrawable.getBitmap().hasAlpha()) {
|
|
||||||
views.imageView.setBackgroundResource(android.R.color.white);
|
|
||||||
} else {
|
|
||||||
views.imageView.setBackgroundDrawable(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
views.titleView.setText(contribution.getDisplayTitle());
|
views.titleView.setText(contribution.getDisplayTitle());
|
||||||
|
|
||||||
views.seqNumView.setText(String.valueOf(cursor.getPosition() + 1));
|
views.seqNumView.setText(String.valueOf(cursor.getPosition() + 1));
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
|
|
@ -21,16 +20,13 @@ import android.widget.AdapterView;
|
||||||
import android.widget.GridView;
|
import android.widget.GridView;
|
||||||
import android.widget.ListAdapter;
|
import android.widget.ListAdapter;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import fr.free.nrw.commons.AboutActivity;
|
|
||||||
import fr.free.nrw.commons.BuildConfig;
|
|
||||||
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.settings.SettingsActivity;
|
|
||||||
import fr.free.nrw.commons.nearby.NearbyActivity;
|
import fr.free.nrw.commons.nearby.NearbyActivity;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
import static android.app.Activity.RESULT_OK;
|
import static android.app.Activity.RESULT_OK;
|
||||||
|
|
||||||
|
|
@ -45,7 +41,6 @@ public class ContributionsListFragment extends Fragment {
|
||||||
@BindView(R.id.emptyMessage) TextView emptyMessage;
|
@BindView(R.id.emptyMessage) TextView emptyMessage;
|
||||||
|
|
||||||
private ContributionController controller;
|
private ContributionController controller;
|
||||||
private static final String TAG = "ContributionsList";
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
|
@ -54,14 +49,14 @@ public class ContributionsListFragment extends Fragment {
|
||||||
|
|
||||||
contributionsList.setOnItemClickListener((AdapterView.OnItemClickListener)getActivity());
|
contributionsList.setOnItemClickListener((AdapterView.OnItemClickListener)getActivity());
|
||||||
if(savedInstanceState != null) {
|
if(savedInstanceState != null) {
|
||||||
Log.d(TAG, "Scrolling to " + savedInstanceState.getInt("grid-position"));
|
Timber.d("Scrolling to %d", savedInstanceState.getInt("grid-position"));
|
||||||
contributionsList.setSelection(savedInstanceState.getInt("grid-position"));
|
contributionsList.setSelection(savedInstanceState.getInt("grid-position"));
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Should this be in onResume?
|
//TODO: Should this be in onResume?
|
||||||
SharedPreferences prefs = this.getActivity().getSharedPreferences("prefs", Context.MODE_PRIVATE);
|
SharedPreferences prefs = this.getActivity().getSharedPreferences("prefs", Context.MODE_PRIVATE);
|
||||||
String lastModified = prefs.getString("lastSyncTimestamp", "");
|
String lastModified = prefs.getString("lastSyncTimestamp", "");
|
||||||
Log.d(TAG, "Last Sync Timestamp: " + lastModified);
|
Timber.d("Last Sync Timestamp: %s", lastModified);
|
||||||
|
|
||||||
if (lastModified.equals("")) {
|
if (lastModified.equals("")) {
|
||||||
waitingMessage.setVisibility(View.VISIBLE);
|
waitingMessage.setVisibility(View.VISIBLE);
|
||||||
|
|
@ -96,10 +91,12 @@ public class ContributionsListFragment extends Fragment {
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
|
||||||
if ( resultCode == RESULT_OK ) {
|
if ( resultCode == RESULT_OK ) {
|
||||||
Log.d("Contributions", "OnActivityResult() parameters: Req code: " + requestCode + " Result code: " + resultCode + " Data: " + data);
|
Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
|
||||||
|
requestCode, resultCode, data);
|
||||||
controller.handleImagePicked(requestCode, data);
|
controller.handleImagePicked(requestCode, data);
|
||||||
} else {
|
} else {
|
||||||
Log.e("Contributions", "OnActivityResult() parameters: Req code: " + requestCode + " Result code: " + resultCode + " Data: " + data);
|
Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
|
||||||
|
requestCode, resultCode, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,46 +122,6 @@ public class ContributionsListFragment extends Fragment {
|
||||||
case R.id.menu_from_camera:
|
case R.id.menu_from_camera:
|
||||||
controller.startCameraCapture();
|
controller.startCameraCapture();
|
||||||
return true;
|
return true;
|
||||||
case R.id.menu_settings:
|
|
||||||
Intent settingsIntent = new Intent(getActivity(), SettingsActivity.class);
|
|
||||||
startActivity(settingsIntent);
|
|
||||||
return true;
|
|
||||||
case R.id.menu_about:
|
|
||||||
Intent aboutIntent = new Intent(getActivity(), AboutActivity.class);
|
|
||||||
startActivity(aboutIntent);
|
|
||||||
return true;
|
|
||||||
case R.id.menu_feedback:
|
|
||||||
Intent feedbackIntent = new Intent(Intent.ACTION_SEND);
|
|
||||||
feedbackIntent.setType("message/rfc822");
|
|
||||||
feedbackIntent.putExtra(Intent.EXTRA_EMAIL, new String[] { CommonsApplication.FEEDBACK_EMAIL });
|
|
||||||
feedbackIntent.putExtra(Intent.EXTRA_SUBJECT, String.format(CommonsApplication.FEEDBACK_EMAIL_SUBJECT, BuildConfig.VERSION_NAME));
|
|
||||||
try {
|
|
||||||
startActivity(feedbackIntent);
|
|
||||||
}
|
|
||||||
catch (ActivityNotFoundException e) {
|
|
||||||
Toast.makeText(getActivity(), R.string.no_email_client, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
case R.id.menu_nearby:
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
if (ContextCompat.checkSelfPermission(this.getActivity(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
|
||||||
//See http://stackoverflow.com/questions/33169455/onrequestpermissionsresult-not-being-called-in-dialog-fragment
|
|
||||||
requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 2);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
Intent nearbyIntent = new Intent(getActivity(), NearbyActivity.class);
|
|
||||||
startActivity(nearbyIntent);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Intent nearbyIntent = new Intent(getActivity(), NearbyActivity.class);
|
|
||||||
startActivity(nearbyIntent);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
case R.id.menu_refresh:
|
|
||||||
((SourceRefresher)getActivity()).refreshSource();
|
|
||||||
return true;
|
|
||||||
default:
|
default:
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
@ -176,7 +133,7 @@ public class ContributionsListFragment extends Fragment {
|
||||||
// 1 = Storage allowed when gallery selected
|
// 1 = Storage allowed when gallery selected
|
||||||
case 1: {
|
case 1: {
|
||||||
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
Log.d("ContributionsList", "Call controller.startGalleryPick()");
|
Timber.d("Call controller.startGalleryPick()");
|
||||||
controller.startGalleryPick();
|
controller.startGalleryPick();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -184,7 +141,7 @@ public class ContributionsListFragment extends Fragment {
|
||||||
// 2 = Location allowed when 'nearby places' selected
|
// 2 = Location allowed when 'nearby places' selected
|
||||||
case 2: {
|
case 2: {
|
||||||
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
Log.d("ContributionsList", "Location permission granted");
|
Timber.d("Location permission granted");
|
||||||
Intent nearbyIntent = new Intent(getActivity(), NearbyActivity.class);
|
Intent nearbyIntent = new Intent(getActivity(), NearbyActivity.class);
|
||||||
startActivity(nearbyIntent);
|
startActivity(nearbyIntent);
|
||||||
}
|
}
|
||||||
|
|
@ -198,12 +155,9 @@ public class ContributionsListFragment extends Fragment {
|
||||||
menu.clear(); // See http://stackoverflow.com/a/8495697/17865
|
menu.clear(); // See http://stackoverflow.com/a/8495697/17865
|
||||||
inflater.inflate(R.menu.fragment_contributions_list, menu);
|
inflater.inflate(R.menu.fragment_contributions_list, menu);
|
||||||
|
|
||||||
CommonsApplication app = (CommonsApplication)getActivity().getApplicationContext();
|
if (!CommonsApplication.getInstance().deviceHasCamera()) {
|
||||||
if (!app.deviceHasCamera()) {
|
|
||||||
menu.findItem(R.id.menu_from_camera).setEnabled(false);
|
menu.findItem(R.id.menu_from_camera).setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
menu.findItem(R.id.menu_refresh).setVisible(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -11,17 +11,17 @@ import android.database.Cursor;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.mediawiki.api.ApiResult;
|
import org.mediawiki.api.ApiResult;
|
||||||
import org.mediawiki.api.MWApi;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
|
import fr.free.nrw.commons.MWApi;
|
||||||
import fr.free.nrw.commons.Utils;
|
import fr.free.nrw.commons.Utils;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
|
public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||||
private static int COMMIT_THRESHOLD = 10;
|
private static int COMMIT_THRESHOLD = 10;
|
||||||
|
|
@ -30,7 +30,10 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getLimit() {
|
private int getLimit() {
|
||||||
return 500; // FIXME: Parameterize!
|
|
||||||
|
int limit = 500;
|
||||||
|
Timber.d("Max number of uploads set to %d", limit);
|
||||||
|
return limit; // FIXME: Parameterize!
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String[] existsQuery = { Contribution.Table.COLUMN_FILENAME };
|
private static final String[] existsQuery = { Contribution.Table.COLUMN_FILENAME };
|
||||||
|
|
@ -58,7 +61,7 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||||
public void onPerformSync(Account account, Bundle bundle, String s, ContentProviderClient contentProviderClient, SyncResult syncResult) {
|
public void onPerformSync(Account account, Bundle bundle, String s, ContentProviderClient contentProviderClient, SyncResult syncResult) {
|
||||||
// This code is fraught with possibilities of race conditions, but lalalalala I can't hear you!
|
// This code is fraught with possibilities of race conditions, but lalalalala I can't hear you!
|
||||||
String user = account.name;
|
String user = account.name;
|
||||||
MWApi api = CommonsApplication.createMWApi();
|
MWApi api = CommonsApplication.getInstance().getMWApi();
|
||||||
SharedPreferences prefs = this.getContext().getSharedPreferences("prefs", Context.MODE_PRIVATE);
|
SharedPreferences prefs = this.getContext().getSharedPreferences("prefs", Context.MODE_PRIVATE);
|
||||||
String lastModified = prefs.getString("lastSyncTimestamp", "");
|
String lastModified = prefs.getString("lastSyncTimestamp", "");
|
||||||
Date curTime = new Date();
|
Date curTime = new Date();
|
||||||
|
|
@ -71,7 +74,7 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||||
MWApi.RequestBuilder builder = api.action("query")
|
MWApi.RequestBuilder builder = api.action("query")
|
||||||
.param("list", "logevents")
|
.param("list", "logevents")
|
||||||
.param("letype", "upload")
|
.param("letype", "upload")
|
||||||
.param("leprop", "title|timestamp")
|
.param("leprop", "title|timestamp|ids")
|
||||||
.param("leuser", user)
|
.param("leuser", user)
|
||||||
.param("lelimit", getLimit());
|
.param("lelimit", getLimit());
|
||||||
if(!TextUtils.isEmpty(lastModified)) {
|
if(!TextUtils.isEmpty(lastModified)) {
|
||||||
|
|
@ -85,18 +88,23 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||||
// There isn't really much we can do, eh?
|
// There isn't really much we can do, eh?
|
||||||
// FIXME: Perhaps add EventLogging?
|
// FIXME: Perhaps add EventLogging?
|
||||||
syncResult.stats.numIoExceptions += 1; // Not sure if this does anything. Shitty docs
|
syncResult.stats.numIoExceptions += 1; // Not sure if this does anything. Shitty docs
|
||||||
Log.d("Commons", "Syncing failed due to " + e.toString());
|
Timber.d("Syncing failed due to %s", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Log.d("Commons", "Last modified at " + lastModified);
|
Timber.d("Last modified at %s", lastModified);
|
||||||
|
|
||||||
ArrayList<ApiResult> uploads = result.getNodes("/api/query/logevents/item");
|
ArrayList<ApiResult> uploads = result.getNodes("/api/query/logevents/item");
|
||||||
Log.d("Commons", uploads.size() + " results!");
|
Timber.d("%d results!", uploads.size());
|
||||||
ArrayList<ContentValues> imageValues = new ArrayList<>();
|
ArrayList<ContentValues> imageValues = new ArrayList<>();
|
||||||
for(ApiResult image: uploads) {
|
for(ApiResult image: uploads) {
|
||||||
|
String pageId = image.getString("@pageid");
|
||||||
|
if (pageId.equals("0")) {
|
||||||
|
// means that this upload was deleted.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
String filename = image.getString("@title");
|
String filename = image.getString("@title");
|
||||||
if(fileExists(contentProviderClient, filename)) {
|
if(fileExists(contentProviderClient, filename)) {
|
||||||
Log.d("Commons", "Skipping " + filename);
|
Timber.d("Skipping %s", filename);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
String thumbUrl = Utils.makeThumbBaseUrl(filename);
|
String thumbUrl = Utils.makeThumbBaseUrl(filename);
|
||||||
|
|
@ -128,6 +136,6 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
prefs.edit().putString("lastSyncTimestamp", Utils.toMWDate(curTime)).apply();
|
prefs.edit().putString("lastSyncTimestamp", Utils.toMWDate(curTime)).apply();
|
||||||
Log.d("Commons", "Oh hai, everyone! Look, a kitty!");
|
Timber.d("Oh hai, everyone! Look, a kitty!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ public class ContributionsSyncService extends Service {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
synchronized (sSyncAdapterLock) {
|
synchronized (sSyncAdapterLock) {
|
||||||
if (sSyncAdapter == null) {
|
if (sSyncAdapter == null) {
|
||||||
sSyncAdapter = new ContributionsSyncAdapter(getApplicationContext(), true);
|
sSyncAdapter = new ContributionsSyncAdapter(this, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,16 +12,11 @@ public class DBOpenHelper extends SQLiteOpenHelper{
|
||||||
|
|
||||||
private static final String DATABASE_NAME = "commons.db";
|
private static final String DATABASE_NAME = "commons.db";
|
||||||
private static final int DATABASE_VERSION = 6;
|
private static final int DATABASE_VERSION = 6;
|
||||||
private static DBOpenHelper singleton = null;
|
|
||||||
|
|
||||||
public static synchronized DBOpenHelper getInstance(Context context) {
|
/**
|
||||||
if ( singleton == null ) {
|
* Do not use, please call CommonsApplication.getDBOpenHelper()
|
||||||
singleton = new DBOpenHelper(context);
|
*/
|
||||||
}
|
public DBOpenHelper(Context context) {
|
||||||
return singleton;
|
|
||||||
}
|
|
||||||
|
|
||||||
private DBOpenHelper(Context context) {
|
|
||||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package fr.free.nrw.commons.hamburger;
|
||||||
|
|
||||||
|
import android.support.v7.app.ActionBarDrawerToggle;
|
||||||
|
|
||||||
|
public interface HamburgerMenuContainer {
|
||||||
|
void setDrawerListener(ActionBarDrawerToggle listener);
|
||||||
|
void toggleDrawer();
|
||||||
|
boolean isDrawerVisible();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,164 @@
|
||||||
|
package fr.free.nrw.commons.hamburger;
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v4.widget.DrawerLayout;
|
||||||
|
import android.support.v7.app.ActionBarDrawerToggle;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.OnClick;
|
||||||
|
import fr.free.nrw.commons.AboutActivity;
|
||||||
|
import fr.free.nrw.commons.BuildConfig;
|
||||||
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.auth.LoginActivity;
|
||||||
|
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||||
|
import fr.free.nrw.commons.nearby.NearbyActivity;
|
||||||
|
import fr.free.nrw.commons.settings.SettingsActivity;
|
||||||
|
|
||||||
|
|
||||||
|
public class NavigationBaseFragment extends Fragment {
|
||||||
|
@BindView(R.id.pictureOfTheDay)
|
||||||
|
ImageView pictureOfTheDay;
|
||||||
|
|
||||||
|
@BindView(R.id.upload_item)
|
||||||
|
LinearLayout uploadItem;
|
||||||
|
|
||||||
|
@BindView(R.id.nearby_item)
|
||||||
|
LinearLayout nearbyItem;
|
||||||
|
|
||||||
|
@BindView(R.id.about_item)
|
||||||
|
LinearLayout aboutItem;
|
||||||
|
|
||||||
|
@BindView(R.id.settings_item)
|
||||||
|
LinearLayout settingsItem;
|
||||||
|
|
||||||
|
@BindView(R.id.feedback_item)
|
||||||
|
LinearLayout feedbackItem;
|
||||||
|
|
||||||
|
@BindView(R.id.logout_item)
|
||||||
|
LinearLayout logoutItem;
|
||||||
|
|
||||||
|
private DrawerLayout drawerLayout;
|
||||||
|
private RelativeLayout drawerPane;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater,
|
||||||
|
ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
View hamburgerView = inflater.inflate(R.layout.navigation_drawer_menu, container, false);
|
||||||
|
ButterKnife.bind(this, hamburgerView);
|
||||||
|
showPictureOfTheDay();
|
||||||
|
setupHamburgerMenu();
|
||||||
|
return hamburgerView;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showPictureOfTheDay() {
|
||||||
|
pictureOfTheDay.setImageDrawable(getResources().getDrawable(R.drawable.commons_logo_large));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupHamburgerMenu() {
|
||||||
|
ActionBarDrawerToggle drawerToggle = new ActionBarDrawerToggle(getActivity(),
|
||||||
|
drawerLayout, R.string.ok, R.string.cancel);
|
||||||
|
if (getActivity() instanceof HamburgerMenuContainer) {
|
||||||
|
((HamburgerMenuContainer) getActivity()).setDrawerListener(drawerToggle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.upload_item)
|
||||||
|
protected void onUploadItemClicked() {
|
||||||
|
closeDrawer();
|
||||||
|
ContributionsActivity.startYourself(getActivity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.settings_item)
|
||||||
|
protected void onSettingsItemClicked() {
|
||||||
|
closeDrawer();
|
||||||
|
SettingsActivity.startYourself(getActivity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.about_item)
|
||||||
|
protected void onAboutItemClicked() {
|
||||||
|
closeDrawer();
|
||||||
|
AboutActivity.startYourself(getActivity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.nearby_item)
|
||||||
|
protected void onNearbyItemClicked() {
|
||||||
|
closeDrawer();
|
||||||
|
NearbyActivity.startYourself(getActivity());
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.feedback_item)
|
||||||
|
protected void onFeedbackItemClicked() {
|
||||||
|
closeDrawer();
|
||||||
|
Intent feedbackIntent = new Intent(Intent.ACTION_SEND);
|
||||||
|
feedbackIntent.setType("message/rfc822");
|
||||||
|
feedbackIntent.putExtra(Intent.EXTRA_EMAIL,
|
||||||
|
new String[]{CommonsApplication.FEEDBACK_EMAIL});
|
||||||
|
feedbackIntent.putExtra(Intent.EXTRA_SUBJECT,
|
||||||
|
String.format(CommonsApplication.FEEDBACK_EMAIL_SUBJECT,
|
||||||
|
BuildConfig.VERSION_NAME));
|
||||||
|
try {
|
||||||
|
startActivity(feedbackIntent);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
Toast.makeText(getActivity(), R.string.no_email_client, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.logout_item)
|
||||||
|
protected void onLogoutItemClicked() {
|
||||||
|
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity());
|
||||||
|
alertDialogBuilder.setMessage(R.string.logout_verification)
|
||||||
|
.setCancelable(false)
|
||||||
|
.setPositiveButton(R.string.yes,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
((CommonsApplication)getActivity().getApplicationContext())
|
||||||
|
.clearApplicationData(getContext());
|
||||||
|
Intent nearbyIntent = new Intent
|
||||||
|
(getActivity(), LoginActivity.class);
|
||||||
|
nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||||
|
nearbyIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
startActivity(nearbyIntent);
|
||||||
|
getActivity().finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
alertDialogBuilder.setNegativeButton(R.string.no,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
dialog.cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
AlertDialog alert = alertDialogBuilder.create();
|
||||||
|
alert.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeDrawer() {
|
||||||
|
if (drawerLayout != null) {
|
||||||
|
drawerLayout.closeDrawer(drawerPane);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDrawerLayout(DrawerLayout drawerLayout, RelativeLayout drawerPane) {
|
||||||
|
this.drawerLayout = drawerLayout;
|
||||||
|
this.drawerPane = drawerPane;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,8 @@ public class LatLng {
|
||||||
public final double longitude;
|
public final double longitude;
|
||||||
|
|
||||||
/** Accepts latitude and longitude.
|
/** Accepts latitude and longitude.
|
||||||
|
* North and South values are cut off at 90°
|
||||||
|
*
|
||||||
* @param latitude double value
|
* @param latitude double value
|
||||||
* @param longitude double value
|
* @param longitude double value
|
||||||
*/
|
*/
|
||||||
|
|
@ -42,4 +44,53 @@ public class LatLng {
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "lat/lng: (" + this.latitude + "," + this.longitude + ")";
|
return "lat/lng: (" + this.latitude + "," + this.longitude + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rounds the float to 4 digits and returns absolute value.
|
||||||
|
*
|
||||||
|
* @param coordinate A coordinate value as string.
|
||||||
|
* @return String of the rounded number.
|
||||||
|
*/
|
||||||
|
private String formatCoordinate(double coordinate) {
|
||||||
|
double roundedNumber = Math.round(coordinate * 10000d) / 10000d;
|
||||||
|
double absoluteNumber = Math.abs(roundedNumber);
|
||||||
|
return String.valueOf(absoluteNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns "N" or "S" depending on the latitude.
|
||||||
|
*
|
||||||
|
* @return "N" or "S".
|
||||||
|
*/
|
||||||
|
private String getNorthSouth() {
|
||||||
|
if (this.latitude < 0) {
|
||||||
|
return "S";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "N";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns "E" or "W" depending on the longitude.
|
||||||
|
*
|
||||||
|
* @return "E" or "W".
|
||||||
|
*/
|
||||||
|
private String getEastWest() {
|
||||||
|
if (this.longitude >= 0 && this.longitude < 180) {
|
||||||
|
return "E";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "W";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a nicely formatted coordinate string. Used e.g. in
|
||||||
|
* the detail view.
|
||||||
|
*
|
||||||
|
* @return The formatted string.
|
||||||
|
*/
|
||||||
|
public String getPrettyCoordinateString() {
|
||||||
|
return formatCoordinate(this.latitude) + " " + this.getNorthSouth() + ", "
|
||||||
|
+ formatCoordinate(this.longitude) + " " + this.getEastWest();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,11 @@ import android.location.Location;
|
||||||
import android.location.LocationListener;
|
import android.location.LocationListener;
|
||||||
import android.location.LocationManager;
|
import android.location.LocationManager;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class LocationServiceManager implements LocationListener {
|
public class LocationServiceManager implements LocationListener {
|
||||||
public static final String TAG = "LocationServiceManager";
|
|
||||||
private String provider;
|
private String provider;
|
||||||
private LocationManager locationManager;
|
private LocationManager locationManager;
|
||||||
private LatLng latestLocation;
|
private LatLng latestLocation;
|
||||||
|
|
@ -31,14 +32,14 @@ public class LocationServiceManager implements LocationListener {
|
||||||
Location location = locationManager.getLastKnownLocation(provider);
|
Location location = locationManager.getLastKnownLocation(provider);
|
||||||
//Location works, just need to 'send' GPS coords
|
//Location works, just need to 'send' GPS coords
|
||||||
// via emulator extended controls if testing on emulator
|
// via emulator extended controls if testing on emulator
|
||||||
Log.d(TAG, "Checking for location...");
|
Timber.d("Checking for location...");
|
||||||
if (location != null) {
|
if (location != null) {
|
||||||
this.onLocationChanged(location);
|
this.onLocationChanged(location);
|
||||||
}
|
}
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
Log.e(TAG, "Illegal argument exception", e);
|
Timber.e(e, "Illegal argument exception");
|
||||||
} catch (SecurityException e) {
|
} catch (SecurityException e) {
|
||||||
Log.e(TAG, "Security exception", e);
|
Timber.e(e, "Security exception");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -48,7 +49,7 @@ public class LocationServiceManager implements LocationListener {
|
||||||
try {
|
try {
|
||||||
locationManager.removeUpdates(this);
|
locationManager.removeUpdates(this);
|
||||||
} catch (SecurityException e) {
|
} catch (SecurityException e) {
|
||||||
Log.e(TAG, "Security exception", e);
|
Timber.e(e, "Security exception");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,24 +57,23 @@ public class LocationServiceManager implements LocationListener {
|
||||||
public void onLocationChanged(Location location) {
|
public void onLocationChanged(Location location) {
|
||||||
double currentLatitude = location.getLatitude();
|
double currentLatitude = location.getLatitude();
|
||||||
double currentLongitude = location.getLongitude();
|
double currentLongitude = location.getLongitude();
|
||||||
Log.d(TAG, "Latitude: " + String.valueOf(currentLatitude)
|
Timber.d("Latitude: %f Longitude: %f", currentLatitude, currentLongitude);
|
||||||
+ " Longitude: " + String.valueOf(currentLongitude));
|
|
||||||
|
|
||||||
latestLocation = new LatLng(currentLatitude, currentLongitude);
|
latestLocation = new LatLng(currentLatitude, currentLongitude);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStatusChanged(String provider, int status, Bundle extras) {
|
public void onStatusChanged(String provider, int status, Bundle extras) {
|
||||||
Log.d(TAG, provider + "'s status changed to " + status);
|
Timber.d("%s's status changed to %d", provider, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onProviderEnabled(String provider) {
|
public void onProviderEnabled(String provider) {
|
||||||
Log.d(TAG, "Provider " + provider + " enabled");
|
Timber.d("Provider %s enabled", provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onProviderDisabled(String provider) {
|
public void onProviderDisabled(String provider) {
|
||||||
Log.d(TAG, "Provider " + provider + " disabled");
|
Timber.d("Provider %s disabled", provider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,31 +2,23 @@ package fr.free.nrw.commons.media;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.database.DataSetObserver;
|
import android.database.DataSetObserver;
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.util.Log;
|
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewTreeObserver;
|
import android.view.ViewTreeObserver;
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.ProgressBar;
|
|
||||||
import android.widget.ScrollView;
|
import android.widget.ScrollView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.android.volley.toolbox.ImageLoader;
|
|
||||||
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
|
||||||
import com.nostra13.universalimageloader.core.assist.FailReason;
|
|
||||||
import com.nostra13.universalimageloader.core.assist.ImageLoadingListener;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
|
||||||
import fr.free.nrw.commons.License;
|
import fr.free.nrw.commons.License;
|
||||||
import fr.free.nrw.commons.LicenseList;
|
import fr.free.nrw.commons.LicenseList;
|
||||||
import fr.free.nrw.commons.Media;
|
import fr.free.nrw.commons.Media;
|
||||||
|
|
@ -34,18 +26,14 @@ import fr.free.nrw.commons.MediaDataExtractor;
|
||||||
import fr.free.nrw.commons.MediaWikiImageView;
|
import fr.free.nrw.commons.MediaWikiImageView;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.Utils;
|
import fr.free.nrw.commons.Utils;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class MediaDetailFragment extends Fragment {
|
public class MediaDetailFragment extends Fragment {
|
||||||
|
|
||||||
private boolean editable;
|
private boolean editable;
|
||||||
private DisplayImageOptions displayOptions;
|
|
||||||
private MediaDetailPagerFragment.MediaDetailProvider detailProvider;
|
private MediaDetailPagerFragment.MediaDetailProvider detailProvider;
|
||||||
private int index;
|
private int index;
|
||||||
|
|
||||||
public static MediaDetailFragment forMedia(int index) {
|
|
||||||
return forMedia(index, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MediaDetailFragment forMedia(int index, boolean editable) {
|
public static MediaDetailFragment forMedia(int index, boolean editable) {
|
||||||
MediaDetailFragment mf = new MediaDetailFragment();
|
MediaDetailFragment mf = new MediaDetailFragment();
|
||||||
|
|
||||||
|
|
@ -60,16 +48,15 @@ public class MediaDetailFragment extends Fragment {
|
||||||
return mf;
|
return mf;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImageView image;
|
private MediaWikiImageView image;
|
||||||
//private EditText title;
|
|
||||||
private ProgressBar loadingProgress;
|
|
||||||
private ImageView loadingFailed;
|
|
||||||
private MediaDetailSpacer spacer;
|
private MediaDetailSpacer spacer;
|
||||||
private int initialListTop = 0;
|
private int initialListTop = 0;
|
||||||
|
|
||||||
private TextView title;
|
private TextView title;
|
||||||
private TextView desc;
|
private TextView desc;
|
||||||
private TextView license;
|
private TextView license;
|
||||||
|
private TextView coordinates;
|
||||||
|
private TextView uploadedDate;
|
||||||
private LinearLayout categoryContainer;
|
private LinearLayout categoryContainer;
|
||||||
private ScrollView scrollView;
|
private ScrollView scrollView;
|
||||||
private ArrayList<String> categoryNames;
|
private ArrayList<String> categoryNames;
|
||||||
|
|
@ -113,9 +100,7 @@ public class MediaDetailFragment extends Fragment {
|
||||||
|
|
||||||
final View view = inflater.inflate(R.layout.fragment_media_detail, container, false);
|
final View view = inflater.inflate(R.layout.fragment_media_detail, container, false);
|
||||||
|
|
||||||
image = (ImageView) view.findViewById(R.id.mediaDetailImage);
|
image = (MediaWikiImageView) view.findViewById(R.id.mediaDetailImage);
|
||||||
loadingProgress = (ProgressBar) view.findViewById(R.id.mediaDetailImageLoading);
|
|
||||||
loadingFailed = (ImageView) view.findViewById(R.id.mediaDetailImageFailed);
|
|
||||||
scrollView = (ScrollView) view.findViewById(R.id.mediaDetailScrollView);
|
scrollView = (ScrollView) view.findViewById(R.id.mediaDetailScrollView);
|
||||||
|
|
||||||
// Detail consists of a list view with main pane in header view, plus category list.
|
// Detail consists of a list view with main pane in header view, plus category list.
|
||||||
|
|
@ -123,29 +108,12 @@ public class MediaDetailFragment extends Fragment {
|
||||||
title = (TextView) view.findViewById(R.id.mediaDetailTitle);
|
title = (TextView) view.findViewById(R.id.mediaDetailTitle);
|
||||||
desc = (TextView) view.findViewById(R.id.mediaDetailDesc);
|
desc = (TextView) view.findViewById(R.id.mediaDetailDesc);
|
||||||
license = (TextView) view.findViewById(R.id.mediaDetailLicense);
|
license = (TextView) view.findViewById(R.id.mediaDetailLicense);
|
||||||
|
coordinates = (TextView) view.findViewById(R.id.mediaDetailCoordinates);
|
||||||
|
uploadedDate = (TextView) view.findViewById(R.id.mediaDetailuploadeddate);
|
||||||
categoryContainer = (LinearLayout) view.findViewById(R.id.mediaDetailCategoryContainer);
|
categoryContainer = (LinearLayout) view.findViewById(R.id.mediaDetailCategoryContainer);
|
||||||
|
|
||||||
licenseList = new LicenseList(getActivity());
|
licenseList = new LicenseList(getActivity());
|
||||||
|
|
||||||
Media media = detailProvider.getMediaAtPosition(index);
|
|
||||||
if (media == null) {
|
|
||||||
// Ask the detail provider to ping us when we're ready
|
|
||||||
Log.d("Commons", "MediaDetailFragment not yet ready to display details; registering observer");
|
|
||||||
dataObserver = new DataSetObserver() {
|
|
||||||
@Override
|
|
||||||
public void onChanged() {
|
|
||||||
Log.d("Commons", "MediaDetailFragment ready to display delayed details!");
|
|
||||||
detailProvider.unregisterDataSetObserver(dataObserver);
|
|
||||||
dataObserver = null;
|
|
||||||
displayMediaDetails(detailProvider.getMediaAtPosition(index));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
detailProvider.registerDataSetObserver(dataObserver);
|
|
||||||
} else {
|
|
||||||
Log.d("Commons", "MediaDetailFragment ready to display details");
|
|
||||||
displayMediaDetails(media);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 = new ViewTreeObserver.OnScrollChangedListener() {
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -182,97 +150,75 @@ public class MediaDetailFragment extends Fragment {
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void displayMediaDetails(final Media media) {
|
@Override public void onResume() {
|
||||||
//Always load image from Internet to allow viewing the desc, license, and cats
|
super.onResume();
|
||||||
String actualUrl = media.getThumbnailUrl(640);
|
Media media = detailProvider.getMediaAtPosition(index);
|
||||||
if(actualUrl.startsWith("http")) {
|
if (media == null) {
|
||||||
Log.d("Volley", "Actual URL starts with http and is: " + actualUrl);
|
// Ask the detail provider to ping us when we're ready
|
||||||
|
Timber.d("MediaDetailFragment not yet ready to display details; registering observer");
|
||||||
ImageLoader loader = ((CommonsApplication)getActivity().getApplicationContext()).getImageLoader();
|
dataObserver = new DataSetObserver() {
|
||||||
MediaWikiImageView mwImage = (MediaWikiImageView)image;
|
|
||||||
mwImage.setLoadingView(loadingProgress); //FIXME: Set this as an attribute
|
|
||||||
mwImage.setMedia(media, loader);
|
|
||||||
|
|
||||||
// FIXME: For transparent images
|
|
||||||
// FIXME: keep the spinner going while we load data
|
|
||||||
// FIXME: cache this data
|
|
||||||
// Load image metadata: desc, license, categories
|
|
||||||
detailFetchTask = new AsyncTask<Void, Void, Boolean>() {
|
|
||||||
private MediaDataExtractor extractor;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPreExecute() {
|
public void onChanged() {
|
||||||
extractor = new MediaDataExtractor(media.getFilename(), licenseList);
|
if (!isAdded()) {
|
||||||
}
|
return;
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Boolean doInBackground(Void... voids) {
|
|
||||||
try {
|
|
||||||
extractor.fetch();
|
|
||||||
return Boolean.TRUE;
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return Boolean.FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Boolean success) {
|
|
||||||
detailFetchTask = null;
|
|
||||||
|
|
||||||
if (success.booleanValue()) {
|
|
||||||
extractor.fill(media);
|
|
||||||
|
|
||||||
// Set text of desc, license, and categories
|
|
||||||
desc.setText(prettyDescription(media));
|
|
||||||
license.setText(prettyLicense(media));
|
|
||||||
|
|
||||||
categoryNames.removeAll(categoryNames);
|
|
||||||
categoryNames.addAll(media.getCategories());
|
|
||||||
|
|
||||||
categoriesLoaded = true;
|
|
||||||
categoriesPresent = (categoryNames.size() > 0);
|
|
||||||
if (!categoriesPresent) {
|
|
||||||
// Stick in a filler element.
|
|
||||||
categoryNames.add(getString(R.string.detail_panel_cats_none));
|
|
||||||
}
|
|
||||||
rebuildCatList();
|
|
||||||
} else {
|
|
||||||
Log.d("Commons", "Failed to load photo details.");
|
|
||||||
}
|
}
|
||||||
|
Timber.d("MediaDetailFragment ready to display delayed details!");
|
||||||
|
detailProvider.unregisterDataSetObserver(dataObserver);
|
||||||
|
dataObserver = null;
|
||||||
|
displayMediaDetails(detailProvider.getMediaAtPosition(index));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
detailFetchTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
detailProvider.registerDataSetObserver(dataObserver);
|
||||||
} else {
|
} else {
|
||||||
//This should not usually happen, image along with associated details should always be loaded from Internet, but keeping this for now for backup.
|
Timber.d("MediaDetailFragment ready to display details");
|
||||||
//Even if image is loaded from device storage, it will display, albeit with empty desc and cat.
|
displayMediaDetails(media);
|
||||||
Log.d("Volley", "Actual URL does not start with http and is: " + actualUrl);
|
}
|
||||||
com.nostra13.universalimageloader.core.ImageLoader.getInstance().displayImage(actualUrl, image, displayOptions, new ImageLoadingListener() {
|
}
|
||||||
@Override
|
|
||||||
public void onLoadingStarted(String s, View view) {
|
private void displayMediaDetails(final Media media) {
|
||||||
loadingProgress.setVisibility(View.VISIBLE);
|
//Always load image from Internet to allow viewing the desc, license, and cats
|
||||||
|
image.setMedia(media);
|
||||||
|
|
||||||
|
// FIXME: For transparent images
|
||||||
|
// FIXME: keep the spinner going while we load data
|
||||||
|
// FIXME: cache this data
|
||||||
|
// Load image metadata: desc, license, categories
|
||||||
|
detailFetchTask = new AsyncTask<Void, Void, Boolean>() {
|
||||||
|
private MediaDataExtractor extractor;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPreExecute() {
|
||||||
|
extractor = new MediaDataExtractor(media.getFilename(), licenseList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Boolean doInBackground(Void... voids) {
|
||||||
|
try {
|
||||||
|
extractor.fetch();
|
||||||
|
return Boolean.TRUE;
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return Boolean.FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Boolean success) {
|
||||||
|
detailFetchTask = null;
|
||||||
|
if (!isAdded()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
if (success) {
|
||||||
public void onLoadingFailed(String s, View view, FailReason failReason) {
|
extractor.fill(media);
|
||||||
loadingProgress.setVisibility(View.GONE);
|
|
||||||
loadingFailed.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLoadingComplete(String s, View view, Bitmap bitmap) {
|
|
||||||
loadingProgress.setVisibility(View.GONE);
|
|
||||||
loadingFailed.setVisibility(View.GONE);
|
|
||||||
image.setVisibility(View.VISIBLE);
|
|
||||||
if(bitmap.hasAlpha()) {
|
|
||||||
image.setBackgroundResource(android.R.color.white);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set text of desc, license, and categories
|
// Set text of desc, license, and categories
|
||||||
desc.setText(prettyDescription(media));
|
desc.setText(prettyDescription(media));
|
||||||
license.setText(prettyLicense(media));
|
license.setText(prettyLicense(media));
|
||||||
|
coordinates.setText(prettyCoordinates(media));
|
||||||
|
uploadedDate.setText(prettyUploadedDate(media));
|
||||||
|
|
||||||
categoryNames.removeAll(categoryNames);
|
categoryNames.clear();
|
||||||
categoryNames.addAll(media.getCategories());
|
categoryNames.addAll(media.getCategories());
|
||||||
|
|
||||||
categoriesLoaded = true;
|
categoriesLoaded = true;
|
||||||
|
|
@ -282,27 +228,18 @@ public class MediaDetailFragment extends Fragment {
|
||||||
categoryNames.add(getString(R.string.detail_panel_cats_none));
|
categoryNames.add(getString(R.string.detail_panel_cats_none));
|
||||||
}
|
}
|
||||||
rebuildCatList();
|
rebuildCatList();
|
||||||
|
} else {
|
||||||
|
Timber.d("Failed to load photo details.");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@Override
|
};
|
||||||
public void onLoadingCancelled(String s, View view) {
|
detailFetchTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
Log.e("Volley", "Image loading cancelled. But why?");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
title.setText(media.getDisplayTitle());
|
title.setText(media.getDisplayTitle());
|
||||||
desc.setText(""); // fill in from network...
|
desc.setText(""); // fill in from network...
|
||||||
license.setText(""); // fill in from network...
|
license.setText(""); // fill in from network...
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityCreated(Bundle savedInstanceState) {
|
|
||||||
super.onActivityCreated(savedInstanceState);
|
|
||||||
|
|
||||||
displayOptions = Utils.getGenericDisplayOptions().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
if (detailFetchTask != null) {
|
if (detailFetchTask != null) {
|
||||||
|
|
@ -377,7 +314,7 @@ public class MediaDetailFragment extends Fragment {
|
||||||
|
|
||||||
private String prettyLicense(Media media) {
|
private String prettyLicense(Media media) {
|
||||||
String licenseKey = media.getLicense();
|
String licenseKey = media.getLicense();
|
||||||
Log.d("Commons", "Media license is: " + licenseKey);
|
Timber.d("Media license is: %s", licenseKey);
|
||||||
if (licenseKey == null || licenseKey.equals("")) {
|
if (licenseKey == null || licenseKey.equals("")) {
|
||||||
return getString(R.string.detail_license_empty);
|
return getString(R.string.detail_license_empty);
|
||||||
}
|
}
|
||||||
|
|
@ -388,4 +325,22 @@ public class MediaDetailFragment extends Fragment {
|
||||||
return licenseObj.getName();
|
return licenseObj.getName();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String prettyUploadedDate(Media media) {
|
||||||
|
Date date = media.getDateUploaded();
|
||||||
|
if (date.toString() == null || date.toString().isEmpty()) {
|
||||||
|
return "Uploaded date not available";
|
||||||
|
}
|
||||||
|
SimpleDateFormat formatter = new SimpleDateFormat("dd MMM yyyy");
|
||||||
|
return formatter.format(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the coordinates nicely formatted.
|
||||||
|
*
|
||||||
|
* @return Coordinates as text.
|
||||||
|
*/
|
||||||
|
private String prettyCoordinates(Media media) {
|
||||||
|
return media.getCoordinates();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,7 @@ public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPa
|
||||||
if(savedInstanceState != null) {
|
if(savedInstanceState != null) {
|
||||||
editable = savedInstanceState.getBoolean("editable");
|
editable = savedInstanceState.getBoolean("editable");
|
||||||
}
|
}
|
||||||
app = (CommonsApplication)getActivity().getApplicationContext();
|
app = CommonsApplication.getInstance();
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,10 @@ 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.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.data.DBOpenHelper;
|
import fr.free.nrw.commons.data.DBOpenHelper;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class ModificationsContentProvider extends ContentProvider{
|
public class ModificationsContentProvider extends ContentProvider{
|
||||||
|
|
||||||
|
|
@ -35,7 +36,7 @@ public class ModificationsContentProvider extends ContentProvider{
|
||||||
private DBOpenHelper dbOpenHelper;
|
private DBOpenHelper dbOpenHelper;
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreate() {
|
public boolean onCreate() {
|
||||||
dbOpenHelper = DBOpenHelper.getInstance(getContext());
|
dbOpenHelper = CommonsApplication.getInstance().getDBOpenHelper();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,14 +102,14 @@ public class ModificationsContentProvider extends ContentProvider{
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int bulkInsert(Uri uri, ContentValues[] values) {
|
public int bulkInsert(Uri uri, ContentValues[] values) {
|
||||||
Log.d("Commons", "Hello, bulk insert! (ModificationsContentProvider)");
|
Timber.d("Hello, bulk insert! (ModificationsContentProvider)");
|
||||||
int uriType = uriMatcher.match(uri);
|
int uriType = uriMatcher.match(uri);
|
||||||
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
|
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
|
||||||
sqlDB.beginTransaction();
|
sqlDB.beginTransaction();
|
||||||
switch (uriType) {
|
switch (uriType) {
|
||||||
case MODIFICATIONS:
|
case MODIFICATIONS:
|
||||||
for(ContentValues value: values) {
|
for(ContentValues value: values) {
|
||||||
Log.d("Commons", "Inserting! " + value.toString());
|
Timber.d("Inserting! %s", value);
|
||||||
sqlDB.insert(ModifierSequence.Table.TABLE_NAME, null, value);
|
sqlDB.insert(ModifierSequence.Table.TABLE_NAME, null, value);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,9 @@ import android.content.SyncResult;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.MWApi;
|
||||||
import org.mediawiki.api.ApiResult;
|
import org.mediawiki.api.ApiResult;
|
||||||
import org.mediawiki.api.MWApi;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
|
@ -22,6 +21,7 @@ import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.Utils;
|
import fr.free.nrw.commons.Utils;
|
||||||
import fr.free.nrw.commons.contributions.Contribution;
|
import fr.free.nrw.commons.contributions.Contribution;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
|
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
|
public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||||
|
|
||||||
|
|
@ -42,7 +42,7 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||||
|
|
||||||
// Exit early if nothing to do
|
// Exit early if nothing to do
|
||||||
if(allModifications == null || allModifications.getCount() == 0) {
|
if(allModifications == null || allModifications.getCount() == 0) {
|
||||||
Log.d("Commons", "No modifications to perform");
|
Timber.d("No modifications to perform");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,16 +52,16 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||||
} catch (OperationCanceledException | AuthenticatorException e) {
|
} catch (OperationCanceledException | AuthenticatorException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.d("Commons", "Could not authenticate :(");
|
Timber.d("Could not authenticate :(");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(Utils.isNullOrWhiteSpace(authCookie)) {
|
if(Utils.isNullOrWhiteSpace(authCookie)) {
|
||||||
Log.d("Commons", "Could not authenticate :(");
|
Timber.d("Could not authenticate :(");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MWApi api = CommonsApplication.createMWApi();
|
MWApi api = CommonsApplication.getInstance().getMWApi();
|
||||||
api.setAuthCookie(authCookie);
|
api.setAuthCookie(authCookie);
|
||||||
String editToken;
|
String editToken;
|
||||||
|
|
||||||
|
|
@ -69,13 +69,13 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||||
try {
|
try {
|
||||||
editToken = api.getEditToken();
|
editToken = api.getEditToken();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.d("Commons", "Can not retreive edit token!");
|
Timber.d("Can not retreive edit token!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
allModifications.moveToFirst();
|
allModifications.moveToFirst();
|
||||||
|
|
||||||
Log.d("Commons", "Found " + allModifications.getCount() + " modifications to execute");
|
Timber.d("Found %d modifications to execute", allModifications.getCount());
|
||||||
|
|
||||||
ContentProviderClient contributionsClient = null;
|
ContentProviderClient contributionsClient = null;
|
||||||
try {
|
try {
|
||||||
|
|
@ -104,11 +104,11 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||||
.param("titles", contrib.getFilename())
|
.param("titles", contrib.getFilename())
|
||||||
.get();
|
.get();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.d("Commons", "Network fuckup on modifications sync!");
|
Timber.d("Network fuckup on modifications sync!");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d("Commons", "Page content is " + Utils.getStringFromDOM(requestResult.getDocument()));
|
Timber.d("Page content is %s", Utils.getStringFromDOM(requestResult.getDocument()));
|
||||||
String pageContent = requestResult.getString("/api/query/pages/page/revisions/rev");
|
String pageContent = requestResult.getString("/api/query/pages/page/revisions/rev");
|
||||||
String processedPageContent = sequence.executeModifications(contrib.getFilename(), pageContent);
|
String processedPageContent = sequence.executeModifications(contrib.getFilename(), pageContent);
|
||||||
|
|
||||||
|
|
@ -120,16 +120,16 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||||
.param("summary", sequence.getEditSummary())
|
.param("summary", sequence.getEditSummary())
|
||||||
.post();
|
.post();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.d("Commons", "Network fuckup on modifications sync!");
|
Timber.d("Network fuckup on modifications sync!");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d("Commons", "Response is" + Utils.getStringFromDOM(responseResult.getDocument()));
|
Timber.d("Response is %s", Utils.getStringFromDOM(responseResult.getDocument()));
|
||||||
|
|
||||||
String result = responseResult.getString("/api/edit/@result");
|
String result = responseResult.getString("/api/edit/@result");
|
||||||
if(!result.equals("Success")) {
|
if(!result.equals("Success")) {
|
||||||
// FIXME: Log this somewhere else
|
// FIXME: Log this somewhere else
|
||||||
Log.d("Commons", "Non success result!" + result);
|
Timber.d("Non success result! %s", result);
|
||||||
} else {
|
} else {
|
||||||
sequence.delete();
|
sequence.delete();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ public class ModificationsSyncService extends Service {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
synchronized (sSyncAdapterLock) {
|
synchronized (sSyncAdapterLock) {
|
||||||
if (sSyncAdapter == null) {
|
if (sSyncAdapter == null) {
|
||||||
sSyncAdapter = new ModificationsSyncAdapter(getApplicationContext(), true);
|
sSyncAdapter = new ModificationsSyncAdapter(this, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -142,5 +142,10 @@ public class ModifierSequence {
|
||||||
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
|
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
|
||||||
onCreate(db);
|
onCreate(db);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void onDelete(SQLiteDatabase db) {
|
||||||
|
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
|
||||||
|
onCreate(db);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,65 @@
|
||||||
package fr.free.nrw.commons.nearby;
|
package fr.free.nrw.commons.nearby;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.location.LocationManager;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
import android.support.v4.app.FragmentTransaction;
|
import android.support.v4.app.FragmentTransaction;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
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.widget.ProgressBar;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
import fr.free.nrw.commons.location.LocationServiceManager;
|
import fr.free.nrw.commons.location.LocationServiceManager;
|
||||||
import fr.free.nrw.commons.theme.BaseActivity;
|
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
||||||
|
import fr.free.nrw.commons.utils.UriSerializer;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class NearbyActivity extends BaseActivity {
|
|
||||||
|
public class NearbyActivity extends NavigationBaseActivity {
|
||||||
|
|
||||||
|
@BindView(R.id.progressBar)
|
||||||
|
ProgressBar progressBar;
|
||||||
|
private boolean isMapViewActive = false;
|
||||||
|
|
||||||
private LocationServiceManager locationManager;
|
private LocationServiceManager locationManager;
|
||||||
|
private LatLng curLatLang;
|
||||||
private static final String TAG = NearbyActivity.class.getName();
|
private Bundle bundle;
|
||||||
|
private NearbyAsyncTask nearbyAsyncTask;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_nearby);
|
setContentView(R.layout.activity_nearby);
|
||||||
|
ButterKnife.bind(this);
|
||||||
if (getSupportActionBar() != null) {
|
if (getSupportActionBar() != null) {
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bundle = new Bundle();
|
||||||
locationManager = new LocationServiceManager(this);
|
locationManager = new LocationServiceManager(this);
|
||||||
locationManager.registerLocationManager();
|
locationManager.registerLocationManager();
|
||||||
|
curLatLang = locationManager.getLatestLocation();
|
||||||
// Begin the transaction
|
nearbyAsyncTask = new NearbyAsyncTask(this);
|
||||||
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
|
nearbyAsyncTask.execute();
|
||||||
NearbyListFragment fragment = new NearbyListFragment();
|
initDrawer();
|
||||||
ft.add(R.id.container, fragment);
|
|
||||||
ft.commit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -47,19 +76,87 @@ public class NearbyActivity extends BaseActivity {
|
||||||
case R.id.action_refresh:
|
case R.id.action_refresh:
|
||||||
refreshView();
|
refreshView();
|
||||||
return true;
|
return true;
|
||||||
|
case R.id.action_map:
|
||||||
|
showMapView();
|
||||||
|
if (isMapViewActive) {
|
||||||
|
item.setIcon(R.drawable.ic_list_white_24dp);
|
||||||
|
} else {
|
||||||
|
item.setIcon(R.drawable.ic_map_white_24dp);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
default:
|
default:
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void checkGps() {
|
||||||
|
LocationManager manager = (LocationManager) getSystemService(LOCATION_SERVICE);
|
||||||
|
if (!manager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
|
||||||
|
Timber.d("GPS is not enabled");
|
||||||
|
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
|
||||||
|
alertDialogBuilder.setMessage(R.string.gps_disabled)
|
||||||
|
.setCancelable(false)
|
||||||
|
.setPositiveButton(R.string.enable_gps,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
Intent callGPSSettingIntent = new Intent(
|
||||||
|
android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
|
||||||
|
Timber.d("Loaded settings page");
|
||||||
|
startActivityForResult(callGPSSettingIntent, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
alertDialogBuilder.setNegativeButton(R.string.menu_cancel_upload,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
|
dialog.cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
AlertDialog alert = alertDialogBuilder.create();
|
||||||
|
alert.show();
|
||||||
|
} else {
|
||||||
|
Timber.d("GPS is enabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
if (requestCode == 1) {
|
||||||
|
Timber.d("User is back from Settings page");
|
||||||
|
refreshView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showMapView() {
|
||||||
|
if (!isMapViewActive) {
|
||||||
|
isMapViewActive = true;
|
||||||
|
if (nearbyAsyncTask.getStatus() == AsyncTask.Status.FINISHED) {
|
||||||
|
setMapFragment();
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
isMapViewActive = false;
|
||||||
|
if (nearbyAsyncTask.getStatus() == AsyncTask.Status.FINISHED) {
|
||||||
|
setListFragment();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
checkGps();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
nearbyAsyncTask.cancel(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void refreshView() {
|
protected void refreshView() {
|
||||||
getSupportFragmentManager().beginTransaction()
|
nearbyAsyncTask = new NearbyAsyncTask(this);
|
||||||
.replace(R.id.container, new NearbyListFragment()).commit();
|
nearbyAsyncTask.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
public LocationServiceManager getLocationManager() {
|
public LocationServiceManager getLocationManager() {
|
||||||
|
|
@ -71,4 +168,93 @@ public class NearbyActivity extends BaseActivity {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
locationManager.unregisterLocationManager();
|
locationManager.unregisterLocationManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class NearbyAsyncTask extends AsyncTask<Void, Integer, List<Place>> {
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
|
||||||
|
private NearbyAsyncTask (Context context) {
|
||||||
|
mContext = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPreExecute() {
|
||||||
|
super.onPreExecute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onProgressUpdate(Integer... values) {
|
||||||
|
super.onProgressUpdate(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<Place> doInBackground(Void... params) {
|
||||||
|
return NearbyController
|
||||||
|
.loadAttractionsFromLocation(curLatLang, CommonsApplication.getInstance()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(List<Place> placeList) {
|
||||||
|
super.onPostExecute(placeList);
|
||||||
|
|
||||||
|
if (isCancelled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gson gson = new GsonBuilder()
|
||||||
|
.registerTypeAdapter(Uri.class, new UriSerializer())
|
||||||
|
.create();
|
||||||
|
String gsonPlaceList = gson.toJson(placeList);
|
||||||
|
String gsonCurLatLng = gson.toJson(curLatLang);
|
||||||
|
|
||||||
|
if (placeList.size() == 0) {
|
||||||
|
int duration = Toast.LENGTH_SHORT;
|
||||||
|
Toast toast = Toast.makeText(mContext, R.string.no_nearby, duration);
|
||||||
|
toast.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
bundle.clear();
|
||||||
|
bundle.putString("PlaceList", gsonPlaceList);
|
||||||
|
bundle.putString("CurLatLng", gsonCurLatLng);
|
||||||
|
|
||||||
|
// Begin the transaction
|
||||||
|
if (isMapViewActive) {
|
||||||
|
setMapFragment();
|
||||||
|
} else {
|
||||||
|
setListFragment();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progressBar != null) {
|
||||||
|
progressBar.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls fragment for map view.
|
||||||
|
*/
|
||||||
|
public void setMapFragment() {
|
||||||
|
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||||
|
Fragment fragment = new NearbyMapFragment();
|
||||||
|
fragment.setArguments(bundle);
|
||||||
|
fragmentTransaction.replace(R.id.container, fragment);
|
||||||
|
fragmentTransaction.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls fragment for list view.
|
||||||
|
*/
|
||||||
|
public void setListFragment() {
|
||||||
|
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
|
||||||
|
Fragment fragment = new NearbyListFragment();
|
||||||
|
fragment.setArguments(bundle);
|
||||||
|
fragmentTransaction.replace(R.id.container, fragment);
|
||||||
|
fragmentTransaction.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void startYourself(Context context) {
|
||||||
|
Intent settingsIntent = new Intent(context, NearbyActivity.class);
|
||||||
|
context.startActivity(settingsIntent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,28 @@
|
||||||
package fr.free.nrw.commons.nearby;
|
package fr.free.nrw.commons.nearby;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
|
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
|
import timber.log.Timber;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class NearbyAdapter extends ArrayAdapter<Place> {
|
public class NearbyAdapter extends ArrayAdapter<Place> {
|
||||||
private List<Place> placesList;
|
|
||||||
private Context context;
|
|
||||||
|
|
||||||
public List<Place> getPlacesList() {
|
|
||||||
return placesList;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Accepts activity context and list of places.
|
/** Accepts activity context and list of places.
|
||||||
* @param context activity context
|
* @param context activity context
|
||||||
* @param places list of places
|
|
||||||
*/
|
*/
|
||||||
public NearbyAdapter(Context context, List<Place> places) {
|
public NearbyAdapter(Context context) {
|
||||||
super(context, R.layout.item_place, places);
|
super(context, R.layout.item_place);
|
||||||
this.context = context;
|
|
||||||
placesList = places;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View getView(int position, View convertView, ViewGroup parent) {
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
// Get the data item for this position
|
// Get the data item for this position
|
||||||
Place place = getItem(position);
|
Place place = getItem(position);
|
||||||
Log.v("NearbyAdapter", "" + place);
|
Timber.v(String.valueOf(place));
|
||||||
|
|
||||||
// Check if an existing view is being reused, otherwise inflate the view
|
// Check if an existing view is being reused, otherwise inflate the view
|
||||||
if (convertView == null) {
|
if (convertView == null) {
|
||||||
|
|
@ -42,13 +31,14 @@ public class NearbyAdapter extends ArrayAdapter<Place> {
|
||||||
}
|
}
|
||||||
|
|
||||||
NearbyViewHolder viewHolder = new NearbyViewHolder(convertView);
|
NearbyViewHolder viewHolder = new NearbyViewHolder(convertView);
|
||||||
viewHolder.bindModel(context, place);
|
viewHolder.bindModel(getContext(), place);
|
||||||
// Return the completed view to render on screen
|
// Return the completed view to render on screen
|
||||||
return convertView;
|
return convertView;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getItemId(int position) {
|
public long getItemId(int position) {
|
||||||
|
// TODO: use Wikidata Q-ID instead?
|
||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
package fr.free.nrw.commons.nearby;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.mapbox.mapboxsdk.annotations.BaseMarkerOptions;
|
||||||
|
import com.mapbox.mapboxsdk.annotations.Icon;
|
||||||
|
import com.mapbox.mapboxsdk.annotations.IconFactory;
|
||||||
|
import com.mapbox.mapboxsdk.geometry.LatLng;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.utils.UriDeserializer;
|
||||||
|
import fr.free.nrw.commons.utils.UriSerializer;
|
||||||
|
|
||||||
|
public class NearbyBaseMarker extends BaseMarkerOptions<NearbyMarker, NearbyBaseMarker> {
|
||||||
|
private Place place;
|
||||||
|
public NearbyBaseMarker() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public NearbyBaseMarker place(Place place) {
|
||||||
|
this.place = place;
|
||||||
|
return getThis();
|
||||||
|
}
|
||||||
|
|
||||||
|
public NearbyBaseMarker(Parcel in) {
|
||||||
|
Gson gson = new GsonBuilder()
|
||||||
|
.registerTypeAdapter(Uri.class, new UriDeserializer())
|
||||||
|
.create();
|
||||||
|
|
||||||
|
position((LatLng) in.readParcelable(LatLng.class.getClassLoader()));
|
||||||
|
snippet(in.readString());
|
||||||
|
String iconId = in.readString();
|
||||||
|
Bitmap iconBitmap = in.readParcelable(Bitmap.class.getClassLoader());
|
||||||
|
Icon icon = IconFactory.recreate(iconId, iconBitmap);
|
||||||
|
icon(icon);
|
||||||
|
title(in.readString());
|
||||||
|
String gsonString = in.readString();
|
||||||
|
place(gson.fromJson(gsonString, Place.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NearbyBaseMarker getThis() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NearbyMarker getMarker() {
|
||||||
|
return new NearbyMarker(this, place);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
Gson gson = new GsonBuilder()
|
||||||
|
.registerTypeAdapter(Uri.class, new UriSerializer())
|
||||||
|
.create();
|
||||||
|
|
||||||
|
dest.writeParcelable(position, flags);
|
||||||
|
dest.writeString(snippet);
|
||||||
|
dest.writeString(icon.getId());
|
||||||
|
dest.writeParcelable(icon.getBitmap(), flags);
|
||||||
|
dest.writeString(title);
|
||||||
|
dest.writeString(gson.toJson(place));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Place getPlace() {
|
||||||
|
return place;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Parcelable.Creator<NearbyBaseMarker> CREATOR
|
||||||
|
= new Parcelable.Creator<NearbyBaseMarker>() {
|
||||||
|
public NearbyBaseMarker createFromParcel(Parcel in) {
|
||||||
|
return new NearbyBaseMarker(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NearbyBaseMarker[] newArray(int size) {
|
||||||
|
return new NearbyBaseMarker[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
package fr.free.nrw.commons.nearby;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
|
||||||
|
import com.mapbox.mapboxsdk.annotations.Icon;
|
||||||
|
import com.mapbox.mapboxsdk.annotations.IconFactory;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
import static fr.free.nrw.commons.utils.LengthUtils.computeDistanceBetween;
|
||||||
|
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
|
||||||
|
|
||||||
|
|
||||||
|
public class NearbyController {
|
||||||
|
private static final int MAX_RESULTS = 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares Place list to make their distance information update later.
|
||||||
|
* @param curLatLng current location for user
|
||||||
|
* @param context context
|
||||||
|
* @return Place list without distance information
|
||||||
|
*/
|
||||||
|
public static List<Place> loadAttractionsFromLocation(LatLng curLatLng, Context context) {
|
||||||
|
Timber.d("Loading attractions near %s", curLatLng);
|
||||||
|
if (curLatLng == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
NearbyPlaces nearbyPlaces = CommonsApplication.getInstance().getNearbyPlaces();
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
List<Place> places = prefs.getBoolean("useWikidata", true)
|
||||||
|
? nearbyPlaces.getFromWikidataQuery(curLatLng, Locale.getDefault().getLanguage())
|
||||||
|
: nearbyPlaces.getFromWikiNeedsPictures();
|
||||||
|
if (curLatLng != null) {
|
||||||
|
Timber.d("Sorting places by distance...");
|
||||||
|
final Map<Place, Double> distances = new HashMap<>();
|
||||||
|
for (Place place: places) {
|
||||||
|
distances.put(place, computeDistanceBetween(place.location, curLatLng));
|
||||||
|
}
|
||||||
|
Collections.sort(places,
|
||||||
|
new Comparator<Place>() {
|
||||||
|
@Override
|
||||||
|
public int compare(Place lhs, Place rhs) {
|
||||||
|
double lhsDistance = distances.get(lhs);
|
||||||
|
double rhsDistance = distances.get(rhs);
|
||||||
|
return (int) (lhsDistance - rhsDistance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return places;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads attractions from location for list view, we need to return Place data type.
|
||||||
|
* @param curLatLng users current location
|
||||||
|
* @param placeList list of nearby places in Place data type
|
||||||
|
* @return Place list that holds nearby places
|
||||||
|
*/
|
||||||
|
public static List<Place> loadAttractionsFromLocationToPlaces(
|
||||||
|
LatLng curLatLng,
|
||||||
|
List<Place> placeList) {
|
||||||
|
placeList = placeList.subList(0, Math.min(placeList.size(), MAX_RESULTS));
|
||||||
|
for (Place place: placeList) {
|
||||||
|
String distance = formatDistanceBetween(curLatLng, place.location);
|
||||||
|
place.setDistance(distance);
|
||||||
|
}
|
||||||
|
return placeList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*Loads attractions from location for map view, we need to return BaseMarkerOption data type.
|
||||||
|
* @param curLatLng users current location
|
||||||
|
* @param placeList list of nearby places in Place data type
|
||||||
|
* @return BaseMarkerOprions list that holds nearby places
|
||||||
|
*/
|
||||||
|
public static List<NearbyBaseMarker> loadAttractionsFromLocationToBaseMarkerOptions(
|
||||||
|
LatLng curLatLng,
|
||||||
|
List<Place> placeList,
|
||||||
|
Context context) {
|
||||||
|
List<NearbyBaseMarker> baseMarkerOptionses = new ArrayList<>();
|
||||||
|
placeList = placeList.subList(0, Math.min(placeList.size(), MAX_RESULTS));
|
||||||
|
for (Place place: placeList) {
|
||||||
|
String distance = formatDistanceBetween(curLatLng, place.location);
|
||||||
|
place.setDistance(distance);
|
||||||
|
|
||||||
|
Icon icon = IconFactory.getInstance(context)
|
||||||
|
.fromResource(R.drawable.custom_map_marker);
|
||||||
|
|
||||||
|
NearbyBaseMarker nearbyBaseMarker = new NearbyBaseMarker();
|
||||||
|
nearbyBaseMarker.title(place.name);
|
||||||
|
nearbyBaseMarker.position(
|
||||||
|
new com.mapbox.mapboxsdk.geometry.LatLng(
|
||||||
|
place.location.latitude,
|
||||||
|
place.location.longitude));
|
||||||
|
nearbyBaseMarker.place(place);
|
||||||
|
nearbyBaseMarker.icon(icon);
|
||||||
|
|
||||||
|
baseMarkerOptionses.add(nearbyBaseMarker);
|
||||||
|
}
|
||||||
|
return baseMarkerOptionses;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,163 @@
|
||||||
|
package fr.free.nrw.commons.nearby;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.app.FragmentActivity;
|
||||||
|
import android.support.v7.widget.PopupMenu;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.OnClick;
|
||||||
|
import butterknife.Unbinder;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
|
import fr.free.nrw.commons.ui.widget.OverlayDialog;
|
||||||
|
import fr.free.nrw.commons.utils.DialogUtil;
|
||||||
|
|
||||||
|
public class NearbyInfoDialog extends OverlayDialog {
|
||||||
|
|
||||||
|
private final static String ARG_TITLE = "placeTitle";
|
||||||
|
private final static String ARG_DESC = "placeDesc";
|
||||||
|
private final static String ARG_LATITUDE = "latitude";
|
||||||
|
private final static String ARG_LONGITUDE = "longitude";
|
||||||
|
private final static String ARG_SITE_LINK = "sitelink";
|
||||||
|
|
||||||
|
@BindView(R.id.link_preview_title)
|
||||||
|
TextView placeTitle;
|
||||||
|
@BindView(R.id.link_preview_extract)
|
||||||
|
TextView placeDescription;
|
||||||
|
|
||||||
|
@BindView(R.id.link_preview_go_button)
|
||||||
|
TextView goToButton;
|
||||||
|
|
||||||
|
@BindView(R.id.link_preview_overflow_button)
|
||||||
|
ImageView overflowButton;
|
||||||
|
|
||||||
|
private Unbinder unbinder;
|
||||||
|
|
||||||
|
private LatLng location;
|
||||||
|
private Sitelinks sitelinks;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View view = inflater.inflate(R.layout.dialog_nearby_info, container, false);
|
||||||
|
unbinder = ButterKnife.bind(this, view);
|
||||||
|
initUi();
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initUi() {
|
||||||
|
Bundle bundle = getArguments();
|
||||||
|
placeTitle.setText(bundle.getString(ARG_TITLE));
|
||||||
|
placeDescription.setText(bundle.getString(ARG_DESC));
|
||||||
|
location = new LatLng(bundle.getDouble(ARG_LATITUDE), bundle.getDouble(ARG_LONGITUDE));
|
||||||
|
getArticleLink(bundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getArticleLink(Bundle bundle) {
|
||||||
|
this.sitelinks = bundle.getParcelable(ARG_SITE_LINK);
|
||||||
|
|
||||||
|
if (sitelinks.getWikipediaLink() == null) {
|
||||||
|
goToButton.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
overflowButton.setVisibility(showMenu() ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
overflowButton.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
popupMenuListener();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void popupMenuListener() {
|
||||||
|
PopupMenu popupMenu = new PopupMenu(getActivity(), overflowButton);
|
||||||
|
popupMenu.inflate(R.menu.nearby_info_dialog_options);
|
||||||
|
|
||||||
|
MenuItem commonsArticle = popupMenu.getMenu()
|
||||||
|
.findItem(R.id.nearby_info_menu_commons_article);
|
||||||
|
MenuItem wikiDataArticle = popupMenu.getMenu()
|
||||||
|
.findItem(R.id.nearby_info_menu_wikidata_article);
|
||||||
|
|
||||||
|
commonsArticle.setEnabled(sitelinks.getCommonsLink() != null);
|
||||||
|
wikiDataArticle.setEnabled(sitelinks.getWikidataLink() != null);
|
||||||
|
|
||||||
|
popupMenu.setOnMenuItemClickListener(menuListener);
|
||||||
|
popupMenu.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean showMenu() {
|
||||||
|
return sitelinks.getCommonsLink() != null
|
||||||
|
|| sitelinks.getWikidataLink() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PopupMenu.OnMenuItemClickListener menuListener = new PopupMenu
|
||||||
|
.OnMenuItemClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onMenuItemClick(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.nearby_info_menu_commons_article:
|
||||||
|
openWebView(sitelinks.getCommonsLink());
|
||||||
|
return true;
|
||||||
|
case R.id.nearby_info_menu_wikidata_article:
|
||||||
|
openWebView(sitelinks.getWikidataLink());
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static void showYourself(FragmentActivity fragmentActivity, Place place) {
|
||||||
|
NearbyInfoDialog mDialog = new NearbyInfoDialog();
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putString(ARG_TITLE, place.name);
|
||||||
|
bundle.putString(ARG_DESC, place.description);
|
||||||
|
bundle.putDouble(ARG_LATITUDE, place.location.latitude);
|
||||||
|
bundle.putDouble(ARG_LONGITUDE, place.location.longitude);
|
||||||
|
bundle.putParcelable(ARG_SITE_LINK, place.siteLinks);
|
||||||
|
mDialog.setArguments(bundle);
|
||||||
|
DialogUtil.showSafely(fragmentActivity, mDialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
unbinder.unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.link_preview_directions_button)
|
||||||
|
void onDirectionsClick() {
|
||||||
|
//Open map app at given position
|
||||||
|
Uri gmmIntentUri = Uri.parse("geo:0,0?q=" + location.latitude + "," + location.longitude);
|
||||||
|
Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri);
|
||||||
|
|
||||||
|
if (mapIntent.resolveActivity(getActivity().getPackageManager()) != null) {
|
||||||
|
startActivity(mapIntent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.link_preview_go_button)
|
||||||
|
void onReadArticleClick() {
|
||||||
|
openWebView(sitelinks.getWikipediaLink());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openWebView(Uri link) {
|
||||||
|
Intent browserIntent = new Intent(Intent.ACTION_VIEW, link);
|
||||||
|
startActivity(browserIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.emptyLayout)
|
||||||
|
void onCloseClicked() {
|
||||||
|
dismissAllowingStateLoss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,46 +1,35 @@
|
||||||
package fr.free.nrw.commons.nearby;
|
package fr.free.nrw.commons.nearby;
|
||||||
|
|
||||||
import static fr.free.nrw.commons.utils.LengthUtils.computeDistanceBetween;
|
|
||||||
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.support.v4.app.ListFragment;
|
import android.support.v4.app.ListFragment;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.ProgressBar;
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import butterknife.OnItemClick;
|
import butterknife.OnItemClick;
|
||||||
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.UriDeserializer;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
import java.util.Collections;
|
public class NearbyListFragment extends ListFragment {
|
||||||
import java.util.Comparator;
|
private List<Place> placeList;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class NearbyListFragment extends ListFragment implements TaskListener {
|
@BindView(R.id.listView) ListView listview;
|
||||||
|
|
||||||
private static final int MAX_RESULTS = 1000;
|
|
||||||
private NearbyAsyncTask nearbyAsyncTask;
|
|
||||||
|
|
||||||
@BindView(R.id.listview) ListView listview;
|
private NearbyAdapter adapter;
|
||||||
@BindView(R.id.progressBar) ProgressBar progressBar;
|
|
||||||
|
|
||||||
private boolean isTaskRunning = false;
|
|
||||||
|
|
||||||
private static final String TAG = NearbyListFragment.class.getName();
|
|
||||||
|
|
||||||
public NearbyListFragment() {
|
public NearbyListFragment() {
|
||||||
}
|
}
|
||||||
|
|
@ -55,118 +44,43 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
|
|
||||||
Log.d(TAG, "NearbyListFragment created");
|
Timber.d("NearbyListFragment created");
|
||||||
View view = inflater.inflate(R.layout.fragment_nearby, container, false);
|
View view = inflater.inflate(R.layout.fragment_nearby, container, false);
|
||||||
ButterKnife.bind(this, view);
|
ButterKnife.bind(this, view);
|
||||||
|
adapter = new NearbyAdapter(getActivity());
|
||||||
|
listview.setAdapter(adapter);
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
|
|
||||||
//Check that this is the first time view is created, to avoid double list when screen orientation changed
|
// Check that this is the first time view is created,
|
||||||
|
// to avoid double list when screen orientation changed
|
||||||
|
Bundle bundle = this.getArguments();
|
||||||
|
Gson gson = new GsonBuilder()
|
||||||
|
.registerTypeAdapter(Uri.class, new UriDeserializer())
|
||||||
|
.create();
|
||||||
|
if (bundle != null) {
|
||||||
|
String gsonPlaceList = bundle.getString("PlaceList");
|
||||||
|
String gsonLatLng = bundle.getString("CurLatLng");
|
||||||
|
Type listType = new TypeToken<List<Place>>() {}.getType();
|
||||||
|
placeList = gson.fromJson(gsonPlaceList, listType);
|
||||||
|
Type curLatLngType = new TypeToken<LatLng>() {}.getType();
|
||||||
|
LatLng curLatLng = gson.fromJson(gsonLatLng, curLatLngType);
|
||||||
|
placeList = NearbyController.loadAttractionsFromLocationToPlaces(curLatLng, placeList);
|
||||||
|
}
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
nearbyAsyncTask = new NearbyAsyncTask(this);
|
adapter.clear();
|
||||||
nearbyAsyncTask.execute();
|
Timber.d("Saved instance state is null, populating ListView");
|
||||||
progressBar.setVisibility(View.VISIBLE);
|
|
||||||
Log.d(TAG, "Saved instance state is null, populating ListView");
|
|
||||||
} else {
|
|
||||||
progressBar.setVisibility(View.GONE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we are returning here from a screen orientation and the AsyncTask is still working,
|
adapter.clear();
|
||||||
// re-create and display the progress dialog.
|
adapter.addAll(placeList);
|
||||||
if (isTaskRunning) {
|
adapter.notifyDataSetChanged();
|
||||||
progressBar.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@OnItemClick(R.id.listView)
|
||||||
public void onSaveInstanceState(Bundle outInstanceState) {
|
|
||||||
// See http://stackoverflow.com/questions/8942135/listview-added-dublicate-item-in-list-when-screen-orientation-changes
|
|
||||||
outInstanceState.putInt("value", 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTaskStarted() {
|
|
||||||
isTaskRunning = true;
|
|
||||||
progressBar.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTaskFinished(List<Place> result) {
|
|
||||||
if (progressBar != null) {
|
|
||||||
progressBar.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
isTaskRunning = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDetach() {
|
|
||||||
// All dialogs should be closed before leaving the activity in order to avoid
|
|
||||||
// the: Activity has leaked window com.android.internal.policy... exception
|
|
||||||
if (progressBar != null && progressBar.isShown()) {
|
|
||||||
progressBar.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
super.onDetach();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
|
|
||||||
// See http://stackoverflow.com/questions/18264408/incomplete-asynctask-crashes-my-app
|
|
||||||
if (nearbyAsyncTask != null && nearbyAsyncTask.getStatus() != AsyncTask.Status.FINISHED) {
|
|
||||||
nearbyAsyncTask.cancel(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class NearbyAsyncTask extends AsyncTask<Void, Integer, List<Place>> {
|
|
||||||
|
|
||||||
private final TaskListener listener;
|
|
||||||
|
|
||||||
public NearbyAsyncTask(TaskListener listener) {
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPreExecute() {
|
|
||||||
super.onPreExecute();
|
|
||||||
listener.onTaskStarted();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onProgressUpdate(Integer... values) {
|
|
||||||
super.onProgressUpdate(values);
|
|
||||||
progressBar.setProgress(values[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected List<Place> doInBackground(Void... params) {
|
|
||||||
return loadAttractionsFromLocation(
|
|
||||||
((NearbyActivity)getActivity()).getLocationManager().getLatestLocation()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(List<Place> places) {
|
|
||||||
super.onPostExecute(places);
|
|
||||||
|
|
||||||
if (isCancelled()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
progressBar.setVisibility(View.GONE);
|
|
||||||
NearbyAdapter adapter = new NearbyAdapter(getActivity(), places);
|
|
||||||
|
|
||||||
listview.setAdapter(adapter);
|
|
||||||
|
|
||||||
listener.onTaskFinished(places);
|
|
||||||
adapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnItemClick(R.id.listview)
|
|
||||||
void onItemClicked(int position) {
|
void onItemClicked(int position) {
|
||||||
Place place = (Place) listview.getItemAtPosition(position);
|
Place place = (Place) listview.getItemAtPosition(position);
|
||||||
LatLng placeLatLng = place.location;
|
LatLng placeLatLng = place.location;
|
||||||
|
|
@ -174,53 +88,8 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
|
||||||
double latitude = placeLatLng.latitude;
|
double latitude = placeLatLng.latitude;
|
||||||
double longitude = placeLatLng.longitude;
|
double longitude = placeLatLng.longitude;
|
||||||
|
|
||||||
Log.d(TAG, "Item at position "
|
Timber.d("Item at position %d has coords: Lat: %f Long: %f", position, latitude, longitude);
|
||||||
+ position + " has coords: Lat: "
|
|
||||||
+ latitude + " Long: "
|
|
||||||
+ longitude);
|
|
||||||
|
|
||||||
//Open map app at given position
|
NearbyInfoDialog.showYourself(getActivity(), place);
|
||||||
Uri gmmIntentUri = Uri.parse("geo:0,0?q=" + latitude + "," + longitude);
|
|
||||||
Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri);
|
|
||||||
|
|
||||||
if (mapIntent.resolveActivity(getActivity().getPackageManager()) != null) {
|
|
||||||
startActivity(mapIntent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Place> loadAttractionsFromLocation(LatLng curLatLng) {
|
|
||||||
Log.d(TAG, "Loading attractions near " + curLatLng);
|
|
||||||
if (curLatLng == null) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
|
||||||
List<Place> places = prefs.getBoolean("useWikidata", true)
|
|
||||||
? NearbyPlaces.getInstance().getFromWikidataQuery(
|
|
||||||
curLatLng, Locale.getDefault().getLanguage())
|
|
||||||
: NearbyPlaces.getInstance().getFromWikiNeedsPictures();
|
|
||||||
if (curLatLng != null) {
|
|
||||||
Log.d(TAG, "Sorting places by distance...");
|
|
||||||
final Map<Place, Double> distances = new HashMap<>();
|
|
||||||
for (Place place: places) {
|
|
||||||
distances.put(place, computeDistanceBetween(place.location, curLatLng));
|
|
||||||
}
|
|
||||||
Collections.sort(places,
|
|
||||||
new Comparator<Place>() {
|
|
||||||
@Override
|
|
||||||
public int compare(Place lhs, Place rhs) {
|
|
||||||
double lhsDistance = distances.get(lhs);
|
|
||||||
double rhsDistance = distances.get(rhs);
|
|
||||||
return (int) (lhsDistance - rhsDistance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
places = places.subList(0, Math.min(places.size(), MAX_RESULTS));
|
|
||||||
for (Place place: places) {
|
|
||||||
String distance = formatDistanceBetween(curLatLng, place.location);
|
|
||||||
place.setDistance(distance);
|
|
||||||
}
|
|
||||||
return places;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
package fr.free.nrw.commons.nearby;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
|
import com.mapbox.mapboxsdk.Mapbox;
|
||||||
|
import com.mapbox.mapboxsdk.annotations.Marker;
|
||||||
|
import com.mapbox.mapboxsdk.camera.CameraPosition;
|
||||||
|
import com.mapbox.mapboxsdk.constants.Style;
|
||||||
|
import com.mapbox.mapboxsdk.geometry.LatLng;
|
||||||
|
import com.mapbox.mapboxsdk.maps.MapView;
|
||||||
|
import com.mapbox.mapboxsdk.maps.MapboxMap;
|
||||||
|
import com.mapbox.mapboxsdk.maps.MapboxMapOptions;
|
||||||
|
import com.mapbox.mapboxsdk.maps.OnMapReadyCallback;
|
||||||
|
import com.mapbox.services.android.telemetry.MapboxTelemetry;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.utils.UriDeserializer;
|
||||||
|
|
||||||
|
public class NearbyMapFragment extends android.support.v4.app.Fragment {
|
||||||
|
private MapView mapView;
|
||||||
|
private List<NearbyBaseMarker> baseMarkerOptionses;
|
||||||
|
private fr.free.nrw.commons.location.LatLng curLatLng;
|
||||||
|
|
||||||
|
public NearbyMapFragment() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
Bundle bundle = this.getArguments();
|
||||||
|
Gson gson = new GsonBuilder()
|
||||||
|
.registerTypeAdapter(Uri.class, new UriDeserializer())
|
||||||
|
.create();
|
||||||
|
if (bundle != null) {
|
||||||
|
String gsonPlaceList = bundle.getString("PlaceList");
|
||||||
|
String gsonLatLng = bundle.getString("CurLatLng");
|
||||||
|
Type listType = new TypeToken<List<Place>>() {}.getType();
|
||||||
|
List<Place> placeList = gson.fromJson(gsonPlaceList, listType);
|
||||||
|
Type curLatLngType = new TypeToken<fr.free.nrw.commons.location.LatLng>() {}.getType();
|
||||||
|
curLatLng = gson.fromJson(gsonLatLng, curLatLngType);
|
||||||
|
baseMarkerOptionses = NearbyController
|
||||||
|
.loadAttractionsFromLocationToBaseMarkerOptions(curLatLng,
|
||||||
|
placeList,
|
||||||
|
getActivity());
|
||||||
|
}
|
||||||
|
Mapbox.getInstance(getActivity(),
|
||||||
|
getString(R.string.mapbox_commons_app_token));
|
||||||
|
MapboxTelemetry.getInstance().setTelemetryEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
|
||||||
|
if (curLatLng != null) {
|
||||||
|
setupMapView(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
setHasOptionsMenu(false);
|
||||||
|
|
||||||
|
return mapView;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupMapView(Bundle savedInstanceState) {
|
||||||
|
MapboxMapOptions options = new MapboxMapOptions()
|
||||||
|
.styleUrl(Style.OUTDOORS)
|
||||||
|
.camera(new CameraPosition.Builder()
|
||||||
|
.target(new LatLng(curLatLng.latitude, curLatLng.longitude))
|
||||||
|
.zoom(11)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
// create map
|
||||||
|
mapView = new MapView(getActivity(), options);
|
||||||
|
mapView.onCreate(savedInstanceState);
|
||||||
|
mapView.getMapAsync(new OnMapReadyCallback() {
|
||||||
|
@Override
|
||||||
|
public void onMapReady(MapboxMap mapboxMap) {
|
||||||
|
mapboxMap.addMarkers(baseMarkerOptionses);
|
||||||
|
|
||||||
|
mapboxMap.setOnMarkerClickListener(new MapboxMap.OnMarkerClickListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onMarkerClick(@NonNull Marker marker) {
|
||||||
|
if (marker instanceof NearbyMarker) {
|
||||||
|
NearbyMarker nearbyMarker = (NearbyMarker) marker;
|
||||||
|
Place place = nearbyMarker.getNearbyBaseMarker().getPlace();
|
||||||
|
NearbyInfoDialog.showYourself(getActivity(), place);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean("theme",true)) {
|
||||||
|
mapView.setStyleUrl(getResources().getString(R.string.map_theme_dark));
|
||||||
|
} else {
|
||||||
|
mapView.setStyleUrl(getResources().getString(R.string.map_theme_light));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
if (mapView != null) {
|
||||||
|
mapView.onStart();
|
||||||
|
}
|
||||||
|
super.onStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
if (mapView != null) {
|
||||||
|
mapView.onPause();
|
||||||
|
}
|
||||||
|
super.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
if (mapView != null) {
|
||||||
|
mapView.onResume();
|
||||||
|
}
|
||||||
|
super.onResume();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
if (mapView != null) {
|
||||||
|
mapView.onStop();
|
||||||
|
}
|
||||||
|
super.onStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
if (mapView != null) {
|
||||||
|
mapView.onDestroy();
|
||||||
|
}
|
||||||
|
super.onDestroyView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package fr.free.nrw.commons.nearby;
|
||||||
|
|
||||||
|
import com.mapbox.mapboxsdk.annotations.Marker;
|
||||||
|
|
||||||
|
public class NearbyMarker extends Marker {
|
||||||
|
private Place place;
|
||||||
|
private NearbyBaseMarker nearbyBaseMarker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a instance of {@link Marker} using the builder of Marker.
|
||||||
|
*
|
||||||
|
* @param baseMarkerOptions The builder used to construct the Marker.
|
||||||
|
*/
|
||||||
|
public NearbyMarker(NearbyBaseMarker baseMarkerOptions, Place place) {
|
||||||
|
super(baseMarkerOptions);
|
||||||
|
this.place = place;
|
||||||
|
this.nearbyBaseMarker = baseMarkerOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NearbyBaseMarker getNearbyBaseMarker() {
|
||||||
|
return nearbyBaseMarker;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Place getPlace() {
|
||||||
|
return place;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNearbyBaseMarker(NearbyBaseMarker nearbyBaseMarker) {
|
||||||
|
this.nearbyBaseMarker = nearbyBaseMarker;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,17 +2,12 @@ package fr.free.nrw.commons.nearby;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.StrictMode;
|
import android.os.StrictMode;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.Utils;
|
|
||||||
import fr.free.nrw.commons.location.LatLng;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLConnection;
|
import java.net.URLConnection;
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
@ -20,58 +15,31 @@ import java.util.Locale;
|
||||||
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.Utils;
|
||||||
|
import fr.free.nrw.commons.location.LatLng;
|
||||||
|
import fr.free.nrw.commons.utils.FileUtils;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class NearbyPlaces {
|
public class NearbyPlaces {
|
||||||
|
|
||||||
private static final String TAG = NearbyPlaces.class.getName();
|
|
||||||
private static final int MIN_RESULTS = 40;
|
private static final int MIN_RESULTS = 40;
|
||||||
private static final double INITIAL_RADIUS = 1.0;
|
private static final double INITIAL_RADIUS = 1.0; // in kilometers
|
||||||
private static final double MAX_RADIUS = 300.0;
|
private static final double MAX_RADIUS = 300.0; // in kilometers
|
||||||
private static final double RADIUS_MULTIPLIER = 1.618;
|
private static final double RADIUS_MULTIPLIER = 1.618;
|
||||||
private static final String WIKIDATA_QUERY_URL = "https://query.wikidata.org/sparql?query=${QUERY}";
|
private static final Uri WIKIDATA_QUERY_URL = Uri.parse("https://query.wikidata.org/sparql");
|
||||||
private static final String WIKIDATA_QUERY_TEMPLATE = "SELECT\n" +
|
private static final Uri WIKIDATA_QUERY_UI_URL = Uri.parse("https://query.wikidata.org/");
|
||||||
" (SAMPLE(?location) as ?location)\n" +
|
private final String wikidataQuery;
|
||||||
" ?item\n" +
|
|
||||||
" (SAMPLE(COALESCE(?item_label_preferred_language, ?item_label_any_language)) as ?label)\n" +
|
|
||||||
" (SAMPLE(?classId) as ?class)\n" +
|
|
||||||
" (SAMPLE(COALESCE(?class_label_preferred_language, ?class_label_any_language, \"?\")) as ?class_label)\n" +
|
|
||||||
" (SAMPLE(COALESCE(?icon0, ?icon1)) as ?icon)\n" +
|
|
||||||
" (SAMPLE(COALESCE(?emoji0, ?emoji1)) as ?emoji)\n" +
|
|
||||||
"WHERE {\n" +
|
|
||||||
" # Around given location...\n" +
|
|
||||||
" SERVICE wikibase:around {\n" +
|
|
||||||
" ?item wdt:P625 ?location.\n" +
|
|
||||||
" bd:serviceParam wikibase:center \"Point(${LONG} ${LAT})\"^^geo:wktLiteral. \n" +
|
|
||||||
" bd:serviceParam wikibase:radius \"${RADIUS}\" . # Radius in kilometers.\n" +
|
|
||||||
" }\n" +
|
|
||||||
" \n" +
|
|
||||||
" # ... and without an image.\n" +
|
|
||||||
" MINUS {?item wdt:P18 []}\n" +
|
|
||||||
" \n" +
|
|
||||||
" # Get the label in the preferred language of the user, or any other language if no label is available in that language.\n" +
|
|
||||||
" OPTIONAL {?item rdfs:label ?item_label_preferred_language. FILTER (lang(?item_label_preferred_language) = \"${LANG}\")}\n" +
|
|
||||||
" OPTIONAL {?item rdfs:label ?item_label_any_language}\n" +
|
|
||||||
" \n" +
|
|
||||||
" # Get the class label in the preferred language of the user, or any other language if no label is available in that language.\n" +
|
|
||||||
" OPTIONAL {\n" +
|
|
||||||
" ?item p:P31/ps:P31 ?classId.\n" +
|
|
||||||
" OPTIONAL {?classId rdfs:label ?class_label_preferred_language. FILTER (lang(?class_label_preferred_language) = \"${LANG}\")}\n" +
|
|
||||||
" OPTIONAL {?classId rdfs:label ?class_label_any_language}\n" +
|
|
||||||
"\n" +
|
|
||||||
" # Get icon\n" +
|
|
||||||
" OPTIONAL { ?classId wdt:P2910 ?icon0. }\n" +
|
|
||||||
" OPTIONAL { ?classId wdt:P279*/wdt:P2910 ?icon1. }\n" +
|
|
||||||
" # Get emoji\n" +
|
|
||||||
" OPTIONAL { ?classId wdt:P487 ?emoji0. }\n" +
|
|
||||||
" OPTIONAL { ?classId wdt:P279*/wdt:P487 ?emoji1. }\n" +
|
|
||||||
" }\n" +
|
|
||||||
"}\n" +
|
|
||||||
"GROUP BY ?item\n";
|
|
||||||
private static NearbyPlaces singleton;
|
|
||||||
private double radius = INITIAL_RADIUS;
|
private double radius = INITIAL_RADIUS;
|
||||||
private List<Place> places;
|
private List<Place> places;
|
||||||
|
|
||||||
private NearbyPlaces(){
|
public NearbyPlaces() {
|
||||||
|
try {
|
||||||
|
String query = FileUtils.readFromResource("/assets/queries/nearby_query.rq");
|
||||||
|
wikidataQuery = query;
|
||||||
|
Timber.v(wikidataQuery);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Place> getFromWikidataQuery(LatLng curLatLng, String lang) {
|
List<Place> getFromWikidataQuery(LatLng curLatLng, String lang) {
|
||||||
|
|
@ -81,7 +49,7 @@ public class NearbyPlaces {
|
||||||
// increase the radius gradually to find a satisfactory number of nearby places
|
// increase the radius gradually to find a satisfactory number of nearby places
|
||||||
while (radius < MAX_RADIUS) {
|
while (radius < MAX_RADIUS) {
|
||||||
places = getFromWikidataQuery(curLatLng, lang, radius);
|
places = getFromWikidataQuery(curLatLng, lang, radius);
|
||||||
Log.d(TAG, places.size() + " results at radius: " + radius);
|
Timber.d("%d results at radius: %f", places.size(), radius);
|
||||||
if (places.size() >= MIN_RESULTS) {
|
if (places.size() >= MIN_RESULTS) {
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -89,33 +57,41 @@ public class NearbyPlaces {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.d(TAG, "" + e.toString());
|
Timber.d(e.toString());
|
||||||
// errors tend to be caused by too many results (and time out)
|
// errors tend to be caused by too many results (and time out)
|
||||||
// try a small radius next time
|
// try a small radius next time
|
||||||
Log.d(TAG, "back to initial radius: " + radius);
|
Timber.d("back to initial radius: %f", radius);
|
||||||
radius = INITIAL_RADIUS;
|
radius = INITIAL_RADIUS;
|
||||||
}
|
}
|
||||||
return places;
|
return places;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Place> getFromWikidataQuery(LatLng cur, String lang, double radius)
|
private List<Place> getFromWikidataQuery(LatLng cur,
|
||||||
|
String lang,
|
||||||
|
double radius)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
List<Place> places = new ArrayList<>();
|
List<Place> places = new ArrayList<>();
|
||||||
String query = WIKIDATA_QUERY_TEMPLATE.replace("${RADIUS}", "" + radius)
|
|
||||||
.replace("${LAT}", "" + String.format(Locale.ROOT, "%.3f", cur.latitude))
|
String query = wikidataQuery
|
||||||
.replace("${LONG}", "" + String.format(Locale.ROOT, "%.3f", cur.longitude))
|
.replace("${RAD}", String.format(Locale.ROOT, "%.2f", radius))
|
||||||
.replace("${LANG}", "" + lang);
|
.replace("${LAT}", String.format(Locale.ROOT, "%.4f", cur.latitude))
|
||||||
query = URLEncoder.encode(query, "utf-8").replace("+", "%20");
|
.replace("${LONG}", String.format(Locale.ROOT, "%.4f", cur.longitude))
|
||||||
String url = WIKIDATA_QUERY_URL.replace("${QUERY}", query);
|
.replace("${LANG}", lang);
|
||||||
Log.d(TAG, url);
|
|
||||||
|
Timber.v("# Wikidata query: \n" + query);
|
||||||
|
|
||||||
|
// format as a URL
|
||||||
|
Timber.d(WIKIDATA_QUERY_UI_URL.buildUpon().fragment(query).build().toString());
|
||||||
|
String url = WIKIDATA_QUERY_URL.buildUpon()
|
||||||
|
.appendQueryParameter("query", query).build().toString();
|
||||||
URLConnection conn = new URL(url).openConnection();
|
URLConnection conn = new URL(url).openConnection();
|
||||||
conn.setRequestProperty("Accept", "text/tab-separated-values");
|
conn.setRequestProperty("Accept", "text/tab-separated-values");
|
||||||
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||||
|
|
||||||
String line;
|
String line;
|
||||||
Log.d(TAG, "Reading from query result...");
|
Timber.d("Reading from query result...");
|
||||||
while ((line = in.readLine()) != null) {
|
while ((line = in.readLine()) != null) {
|
||||||
Log.v(TAG, line);
|
Timber.v(line);
|
||||||
line = line + "\n"; // to pad columns and make fields a fixed size
|
line = line + "\n"; // to pad columns and make fields a fixed size
|
||||||
if (!line.startsWith("\"Point")) {
|
if (!line.startsWith("\"Point")) {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -125,6 +101,9 @@ public class NearbyPlaces {
|
||||||
String point = fields[0];
|
String point = fields[0];
|
||||||
String name = Utils.stripLocalizedString(fields[2]);
|
String name = Utils.stripLocalizedString(fields[2]);
|
||||||
String type = Utils.stripLocalizedString(fields[4]);
|
String type = Utils.stripLocalizedString(fields[4]);
|
||||||
|
String wikipediaSitelink = Utils.stripLocalizedString(fields[7]);
|
||||||
|
String commonsSitelink = Utils.stripLocalizedString(fields[8]);
|
||||||
|
String wikiDataLink = Utils.stripLocalizedString(fields[1]);
|
||||||
String icon = fields[5];
|
String icon = fields[5];
|
||||||
|
|
||||||
double latitude = 0;
|
double latitude = 0;
|
||||||
|
|
@ -146,7 +125,12 @@ public class NearbyPlaces {
|
||||||
type, // list
|
type, // list
|
||||||
type, // details
|
type, // details
|
||||||
Uri.parse(icon),
|
Uri.parse(icon),
|
||||||
new LatLng(latitude, longitude)
|
new LatLng(latitude, longitude),
|
||||||
|
new Sitelinks.Builder()
|
||||||
|
.setWikipediaLink(wikipediaSitelink)
|
||||||
|
.setCommonsLink(commonsSitelink)
|
||||||
|
.setWikidataLink(wikiDataLink)
|
||||||
|
.build()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
in.close();
|
in.close();
|
||||||
|
|
@ -170,7 +154,7 @@ public class NearbyPlaces {
|
||||||
|
|
||||||
boolean firstLine = true;
|
boolean firstLine = true;
|
||||||
String line;
|
String line;
|
||||||
Log.d(TAG, "Reading from CSV file...");
|
Timber.d("Reading from CSV file...");
|
||||||
|
|
||||||
while ((line = in.readLine()) != null) {
|
while ((line = in.readLine()) != null) {
|
||||||
|
|
||||||
|
|
@ -203,28 +187,16 @@ public class NearbyPlaces {
|
||||||
type, // list
|
type, // list
|
||||||
type, // details
|
type, // details
|
||||||
null,
|
null,
|
||||||
new LatLng(latitude, longitude)
|
new LatLng(latitude, longitude),
|
||||||
|
new Sitelinks.Builder().build()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
in.close();
|
in.close();
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.d(TAG, e.toString());
|
Timber.d(e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return places;
|
return places;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the singleton instance of this class.
|
|
||||||
* The instance is created upon the first invocation of this method, and then reused.
|
|
||||||
*
|
|
||||||
* @return The singleton instance
|
|
||||||
*/
|
|
||||||
public static synchronized NearbyPlaces getInstance() {
|
|
||||||
if (singleton == null) {
|
|
||||||
singleton = new NearbyPlaces();
|
|
||||||
}
|
|
||||||
return singleton;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,9 +25,12 @@ public class NearbyViewHolder implements ViewHolder<Place> {
|
||||||
public void bindModel(Context context, Place place) {
|
public void bindModel(Context context, Place place) {
|
||||||
// Populate the data into the template view using the data object
|
// Populate the data into the template view using the data object
|
||||||
tvName.setText(place.name);
|
tvName.setText(place.name);
|
||||||
tvDesc.setText(place.description);
|
String description = place.description;
|
||||||
|
if ( description == null || description.isEmpty() || description.equals("?")) {
|
||||||
|
description = "No Description Found";
|
||||||
|
}
|
||||||
|
tvDesc.setText(description);
|
||||||
distance.setText(place.distance);
|
distance.setText(place.distance);
|
||||||
|
|
||||||
icon.setImageResource(ResourceUtils.getDescriptionIcon(place.description));
|
icon.setImageResource(ResourceUtils.getDescriptionIcon(place.description));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,14 +16,17 @@ public class Place {
|
||||||
public Bitmap image;
|
public Bitmap image;
|
||||||
public Bitmap secondaryImage;
|
public Bitmap secondaryImage;
|
||||||
public String distance;
|
public String distance;
|
||||||
|
public Sitelinks siteLinks;
|
||||||
|
|
||||||
|
|
||||||
public Place(String name, String description, String longDescription,
|
public Place(String name, String description, String longDescription,
|
||||||
Uri secondaryImageUrl, LatLng location) {
|
Uri secondaryImageUrl, LatLng location, Sitelinks siteLinks) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.longDescription = longDescription;
|
this.longDescription = longDescription;
|
||||||
this.secondaryImageUrl = secondaryImageUrl;
|
this.secondaryImageUrl = secondaryImageUrl;
|
||||||
this.location = location;
|
this.location = location;
|
||||||
|
this.siteLinks = siteLinks;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDistance(String distance) {
|
public void setDistance(String distance) {
|
||||||
|
|
|
||||||
106
app/src/main/java/fr/free/nrw/commons/nearby/Sitelinks.java
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
package fr.free.nrw.commons.nearby;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.Utils;
|
||||||
|
|
||||||
|
public class Sitelinks implements Parcelable {
|
||||||
|
private final String wikipediaLink;
|
||||||
|
private final String commonsLink;
|
||||||
|
private final String wikidataLink;
|
||||||
|
|
||||||
|
|
||||||
|
protected Sitelinks(Parcel in) {
|
||||||
|
wikipediaLink = in.readString();
|
||||||
|
commonsLink = in.readString();
|
||||||
|
wikidataLink = in.readString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeString(wikipediaLink);
|
||||||
|
dest.writeString(commonsLink);
|
||||||
|
dest.writeString(wikidataLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Creator<Sitelinks> CREATOR = new Creator<Sitelinks>() {
|
||||||
|
@Override
|
||||||
|
public Sitelinks createFromParcel(Parcel in) {
|
||||||
|
return new Sitelinks(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Sitelinks[] newArray(int size) {
|
||||||
|
return new Sitelinks[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Uri getWikipediaLink() {
|
||||||
|
return sanitiseString(wikipediaLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Uri getCommonsLink() {
|
||||||
|
return sanitiseString(commonsLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Uri getWikidataLink() {
|
||||||
|
return sanitiseString(wikidataLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Uri sanitiseString(String stringUrl) {
|
||||||
|
stringUrl = stringUrl
|
||||||
|
.replaceAll("<", "")
|
||||||
|
.replaceAll(">", "")
|
||||||
|
.replaceAll("[\n\r]", "");
|
||||||
|
if (!Utils.isNullOrWhiteSpace(stringUrl) && stringUrl != null) {
|
||||||
|
return Uri.parse(stringUrl);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Sitelinks(Sitelinks.Builder builder) {
|
||||||
|
this.wikidataLink = builder.wikidataLink;
|
||||||
|
this.wikipediaLink = builder.wikipediaLink;
|
||||||
|
this.commonsLink = builder.commonsLink;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
private String wikidataLink;
|
||||||
|
private String commonsLink;
|
||||||
|
private String wikipediaLink;
|
||||||
|
|
||||||
|
public Builder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Sitelinks.Builder setWikipediaLink(String link) {
|
||||||
|
this.wikipediaLink = link;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Sitelinks.Builder setWikidataLink(String link) {
|
||||||
|
this.wikidataLink = link;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Sitelinks.Builder setCommonsLink(@Nullable String link) {
|
||||||
|
this.commonsLink = link;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Sitelinks build() {
|
||||||
|
return new Sitelinks(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
package fr.free.nrw.commons.nearby;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
// As per https://androidresearch.wordpress.com/2013/05/10/dealing-with-asynctask-and-screen-orientation/
|
|
||||||
public interface TaskListener {
|
|
||||||
void onTaskStarted();
|
|
||||||
|
|
||||||
void onTaskFinished(List<Place> result);
|
|
||||||
}
|
|
||||||
|
|
@ -5,6 +5,8 @@ public class Prefs {
|
||||||
|
|
||||||
public static String TRACKING_ENABLED = "eventLogging";
|
public static String TRACKING_ENABLED = "eventLogging";
|
||||||
public static final String DEFAULT_LICENSE = "defaultLicense";
|
public static final String DEFAULT_LICENSE = "defaultLicense";
|
||||||
|
public static final String UPLOADS_SHOWING = "uploadsshowing";
|
||||||
|
public static final String IS_CONTRIBUTION_COUNT_CHANGED = "ccontributionCountChanged";
|
||||||
|
|
||||||
public static class Licenses {
|
public static class Licenses {
|
||||||
public static final String CC_BY_SA_3 = "CC BY-SA 3.0";
|
public static final String CC_BY_SA_3 = "CC BY-SA 3.0";
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,19 @@
|
||||||
package fr.free.nrw.commons.settings;
|
package fr.free.nrw.commons.settings;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceActivity;
|
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.v7.app.AppCompatDelegate;
|
import android.support.v7.app.AppCompatDelegate;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
|
||||||
|
import butterknife.ButterKnife;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.theme.NavigationBaseActivity;
|
||||||
|
|
||||||
|
public class SettingsActivity extends NavigationBaseActivity {
|
||||||
|
private SettingsFragment settingsFragment;
|
||||||
|
|
||||||
public class SettingsActivity extends PreferenceActivity {
|
|
||||||
private AppCompatDelegate settingsDelegate;
|
private AppCompatDelegate settingsDelegate;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -19,11 +25,13 @@ public class SettingsActivity extends PreferenceActivity {
|
||||||
setTheme(R.style.LightAppTheme);
|
setTheme(R.style.LightAppTheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display the fragment as the main content.
|
settingsFragment = (SettingsFragment) getFragmentManager().findFragmentById(R.id.settingsFragment);
|
||||||
getFragmentManager().beginTransaction()
|
|
||||||
.replace(android.R.id.content, new SettingsFragment()).commit();
|
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_settings);
|
||||||
|
|
||||||
|
ButterKnife.bind(this);
|
||||||
|
initDrawer();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get an action bar
|
// Get an action bar
|
||||||
|
|
@ -34,5 +42,25 @@ public class SettingsActivity extends PreferenceActivity {
|
||||||
settingsDelegate = AppCompatDelegate.create(this, null);
|
settingsDelegate = AppCompatDelegate.create(this, null);
|
||||||
}
|
}
|
||||||
settingsDelegate.onPostCreate(savedInstanceState);
|
settingsDelegate.onPostCreate(savedInstanceState);
|
||||||
|
|
||||||
|
//Get an up button
|
||||||
|
//settingsDelegate.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Handle action-bar clicks
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case android.R.id.home:
|
||||||
|
finish();
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void startYourself(Context context) {
|
||||||
|
Intent settingsIntent = new Intent(context, SettingsActivity.class);
|
||||||
|
context.startActivity(settingsIntent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,11 +1,17 @@
|
||||||
package fr.free.nrw.commons.settings;
|
package fr.free.nrw.commons.settings;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.CheckBoxPreference;
|
import android.preference.CheckBoxPreference;
|
||||||
|
import android.preference.EditTextPreference;
|
||||||
import android.preference.ListPreference;
|
import android.preference.ListPreference;
|
||||||
import android.preference.Preference;
|
import android.preference.Preference;
|
||||||
import android.preference.PreferenceFragment;
|
import android.preference.PreferenceFragment;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.Utils;
|
import fr.free.nrw.commons.Utils;
|
||||||
|
|
||||||
|
|
@ -19,23 +25,9 @@ public class SettingsFragment extends PreferenceFragment {
|
||||||
|
|
||||||
// Update spinner to show selected value as summary
|
// Update spinner to show selected value as summary
|
||||||
ListPreference licensePreference = (ListPreference) findPreference(Prefs.DEFAULT_LICENSE);
|
ListPreference licensePreference = (ListPreference) findPreference(Prefs.DEFAULT_LICENSE);
|
||||||
// WARNING: ORDERING NEEDS TO MATCH FOR THE LICENSE NAMES AND DISPLAY VALUES
|
|
||||||
licensePreference.setEntries(new String[]{
|
|
||||||
getString(R.string.license_name_cc0),
|
|
||||||
getString(R.string.license_name_cc_by_3_0),
|
|
||||||
getString(R.string.license_name_cc_by_4_0),
|
|
||||||
getString(R.string.license_name_cc_by_sa_3_0),
|
|
||||||
getString(R.string.license_name_cc_by_sa_4_0)
|
|
||||||
});
|
|
||||||
licensePreference.setEntryValues(new String[]{
|
|
||||||
Prefs.Licenses.CC0,
|
|
||||||
Prefs.Licenses.CC_BY_3,
|
|
||||||
Prefs.Licenses.CC_BY_4,
|
|
||||||
Prefs.Licenses.CC_BY_SA_3,
|
|
||||||
Prefs.Licenses.CC_BY_SA_4
|
|
||||||
});
|
|
||||||
|
|
||||||
licensePreference.setSummary(getString(Utils.licenseNameFor(licensePreference.getValue())));
|
licensePreference.setSummary(getString(Utils.licenseNameFor(licensePreference.getValue())));
|
||||||
|
|
||||||
|
// Keep summary updated when changing value
|
||||||
licensePreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
licensePreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||||
|
|
@ -52,5 +44,45 @@ public class SettingsFragment extends PreferenceFragment {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final EditTextPreference uploadLimit = (EditTextPreference) findPreference("uploads");
|
||||||
|
final SharedPreferences sharedPref = PreferenceManager
|
||||||
|
.getDefaultSharedPreferences(CommonsApplication.getInstance());
|
||||||
|
int uploads = sharedPref.getInt(Prefs.UPLOADS_SHOWING, 100);
|
||||||
|
uploadLimit.setText(uploads + "");
|
||||||
|
uploadLimit.setSummary(uploads + "");
|
||||||
|
uploadLimit.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||||
|
int value = Integer.parseInt(newValue.toString());
|
||||||
|
final SharedPreferences.Editor editor = sharedPref.edit();
|
||||||
|
if (value > 500) {
|
||||||
|
new AlertDialog.Builder(getActivity())
|
||||||
|
.setTitle(R.string.maximum_limit)
|
||||||
|
.setMessage(R.string.maximum_limit_alert)
|
||||||
|
.setPositiveButton(android.R.string.yes,
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setIcon(android.R.drawable.ic_dialog_alert)
|
||||||
|
.show();
|
||||||
|
editor.putInt(Prefs.UPLOADS_SHOWING, 500);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,18 @@ import android.preference.PreferenceManager;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.Utils;
|
||||||
|
|
||||||
public class BaseActivity extends AppCompatActivity {
|
public class BaseActivity extends AppCompatActivity {
|
||||||
boolean currentTheme;
|
boolean currentTheme;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("theme",true)) {
|
if(Utils.isDarkTheme(this)){
|
||||||
currentTheme = true;
|
currentTheme = true;
|
||||||
setTheme(R.style.DarkAppTheme);
|
setTheme(R.style.DarkAppTheme);
|
||||||
}else {
|
} else {
|
||||||
currentTheme = false;
|
currentTheme = false;
|
||||||
setTheme(R.style.LightAppTheme); // default
|
setTheme(R.style.LightAppTheme); // default
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
package fr.free.nrw.commons.theme;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.widget.DrawerLayout;
|
||||||
|
import android.support.v7.app.ActionBarDrawerToggle;
|
||||||
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import fr.free.nrw.commons.R;
|
||||||
|
import fr.free.nrw.commons.hamburger.HamburgerMenuContainer;
|
||||||
|
import fr.free.nrw.commons.hamburger.NavigationBaseFragment;
|
||||||
|
import fr.free.nrw.commons.utils.FragmentUtils;
|
||||||
|
|
||||||
|
import static android.support.v4.view.GravityCompat.START;
|
||||||
|
|
||||||
|
public class NavigationBaseActivity extends BaseActivity implements HamburgerMenuContainer {
|
||||||
|
@BindView(R.id.toolbar)
|
||||||
|
Toolbar toolbar;
|
||||||
|
|
||||||
|
@BindView(R.id.drawer_layout)
|
||||||
|
DrawerLayout drawerLayout;
|
||||||
|
|
||||||
|
@BindView(R.id.drawer_pane)
|
||||||
|
RelativeLayout drawerPane;
|
||||||
|
|
||||||
|
private ActionBarDrawerToggle toggle;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initDrawer() {
|
||||||
|
initSubviews();
|
||||||
|
NavigationBaseFragment baseFragment = new NavigationBaseFragment();
|
||||||
|
baseFragment.setDrawerLayout(drawerLayout, drawerPane);
|
||||||
|
FragmentUtils.addAndCommitFragmentWithImmediateExecution(getSupportFragmentManager(),
|
||||||
|
R.id.drawer_fragment,
|
||||||
|
baseFragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initSubviews() {
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
toggle = new ActionBarDrawerToggle(this,
|
||||||
|
drawerLayout,
|
||||||
|
toolbar,
|
||||||
|
R.string.navigation_drawer_open,
|
||||||
|
R.string.navigation_drawer_close);
|
||||||
|
drawerLayout.setDrawerListener(toggle);
|
||||||
|
toggle.setDrawerIndicatorEnabled(true);
|
||||||
|
toggle.syncState();
|
||||||
|
setDrawerPaneWidth();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initBackButton() {
|
||||||
|
int backStackEntryCount = getSupportFragmentManager().getBackStackEntryCount();
|
||||||
|
toggle.setDrawerIndicatorEnabled(backStackEntryCount == 0);
|
||||||
|
toggle.setToolbarNavigationClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
onBackPressed();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initBack() {
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setDrawerPaneWidth() {
|
||||||
|
ViewGroup.LayoutParams params = drawerPane.getLayoutParams();
|
||||||
|
// set width to lowerBound of 80% of the screen size
|
||||||
|
params.width = (getResources().getDisplayMetrics().widthPixels * 70) / 100;
|
||||||
|
drawerPane.setLayoutParams(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDrawerListener(ActionBarDrawerToggle listener) {
|
||||||
|
drawerLayout.setDrawerListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void toggleDrawer() {
|
||||||
|
if (drawerLayout.isDrawerVisible(START)) {
|
||||||
|
drawerLayout.closeDrawer(START);
|
||||||
|
} else {
|
||||||
|
drawerLayout.openDrawer(START);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDrawerVisible() {
|
||||||
|
return drawerLayout.isDrawerVisible(START);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package fr.free.nrw.commons.ui.widget;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.v7.widget.AppCompatTextView;
|
||||||
|
import android.text.Html;
|
||||||
|
import android.text.method.LinkMovementMethod;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link AppCompatTextView} which formats the text to HTML displayable text and makes any
|
||||||
|
* links clickable.
|
||||||
|
*/
|
||||||
|
public class HtmlTextView extends AppCompatTextView {
|
||||||
|
|
||||||
|
public HtmlTextView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
|
||||||
|
setMovementMethod(LinkMovementMethod.getInstance());
|
||||||
|
setText(Html.fromHtml(getText().toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package fr.free.nrw.commons.ui.widget;
|
||||||
|
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.drawable.ColorDrawable;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.v4.app.DialogFragment;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.Window;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
|
||||||
|
public abstract class OverlayDialog extends DialogFragment {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setStyle(STYLE_NO_FRAME, android.R.style.Theme_Holo_Light);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||||
|
setDialogLayoutToFullScreen();
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setDialogLayoutToFullScreen() {
|
||||||
|
Window window = getDialog().getWindow();
|
||||||
|
WindowManager.LayoutParams wlp = window.getAttributes();
|
||||||
|
window.requestFeature(Window.FEATURE_NO_TITLE);
|
||||||
|
wlp.gravity = Gravity.BOTTOM;
|
||||||
|
wlp.width = WindowManager.LayoutParams.MATCH_PARENT;
|
||||||
|
wlp.height = WindowManager.LayoutParams.MATCH_PARENT;
|
||||||
|
window.setAttributes(wlp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
|
Dialog dialog = super.onCreateDialog(savedInstanceState);
|
||||||
|
Window window = dialog.getWindow();
|
||||||
|
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,19 +4,18 @@ import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.util.Log;
|
import android.support.v7.app.AlertDialog;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.MWApi;
|
||||||
import org.mediawiki.api.ApiResult;
|
import org.mediawiki.api.ApiResult;
|
||||||
import org.mediawiki.api.MWApi;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import android.support.v7.app.AlertDialog;
|
|
||||||
|
|
||||||
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.contributions.ContributionsActivity;
|
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends asynchronous queries to the Commons MediaWiki API to check that file doesn't already exist
|
* Sends asynchronous queries to the Commons MediaWiki API to check that file doesn't already exist
|
||||||
|
|
@ -24,8 +23,6 @@ import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||||
*/
|
*/
|
||||||
public class ExistingFileAsync extends AsyncTask<Void, Void, Boolean> {
|
public class ExistingFileAsync extends AsyncTask<Void, Void, Boolean> {
|
||||||
|
|
||||||
private static final String TAG = ExistingFileAsync.class.getName();
|
|
||||||
|
|
||||||
private String fileSHA1;
|
private String fileSHA1;
|
||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
|
|
@ -41,7 +38,7 @@ public class ExistingFileAsync extends AsyncTask<Void, Void, Boolean> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Boolean doInBackground(Void... voids) {
|
protected Boolean doInBackground(Void... voids) {
|
||||||
MWApi api = CommonsApplication.createMWApi();
|
MWApi api = CommonsApplication.getInstance().getMWApi();
|
||||||
ApiResult result;
|
ApiResult result;
|
||||||
|
|
||||||
// https://commons.wikimedia.org/w/api.php?action=query&list=allimages&format=xml&aisha1=801957214aba50cb63bb6eb1b0effa50188900ba
|
// https://commons.wikimedia.org/w/api.php?action=query&list=allimages&format=xml&aisha1=801957214aba50cb63bb6eb1b0effa50188900ba
|
||||||
|
|
@ -51,14 +48,14 @@ public class ExistingFileAsync extends AsyncTask<Void, Void, Boolean> {
|
||||||
.param("list", "allimages")
|
.param("list", "allimages")
|
||||||
.param("aisha1", fileSHA1)
|
.param("aisha1", fileSHA1)
|
||||||
.get();
|
.get();
|
||||||
Log.d(TAG, "Searching Commons API for existing file: " + result.toString());
|
Timber.d("Searching Commons API for existing file: %s", result);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, "IO Exception: ", e);
|
Timber.e(e, "IO Exception: ");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ArrayList<ApiResult> resultNodes = result.getNodes("/api/query/allimages/img");
|
ArrayList<ApiResult> resultNodes = result.getNodes("/api/query/allimages/img");
|
||||||
Log.d(TAG, "Result nodes: " + resultNodes);
|
Timber.d("Result nodes: %s", resultNodes);
|
||||||
|
|
||||||
boolean fileExists;
|
boolean fileExists;
|
||||||
if (!resultNodes.isEmpty()) {
|
if (!resultNodes.isEmpty()) {
|
||||||
|
|
@ -67,7 +64,7 @@ public class ExistingFileAsync extends AsyncTask<Void, Void, Boolean> {
|
||||||
fileExists = false;
|
fileExists = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(TAG, "File already exists in Commons:" + fileExists);
|
Timber.d("File already exists in Commons: %s", fileExists);
|
||||||
return fileExists;
|
return fileExists;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package fr.free.nrw.commons.upload;
|
package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.content.ContentUris;
|
import android.content.ContentUris;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
|
@ -20,6 +21,8 @@ public class FileUtils {
|
||||||
* @param uri The Uri to query.
|
* @param uri The Uri to query.
|
||||||
* @author paulburke
|
* @author paulburke
|
||||||
*/
|
*/
|
||||||
|
// Can be safely suppressed, checks for isKitKat before running isDocumentUri
|
||||||
|
@SuppressLint("NewApi")
|
||||||
public static String getPath(final Context context, final Uri uri) {
|
public static String getPath(final Context context, final Uri uri) {
|
||||||
|
|
||||||
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
|
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,11 @@ import android.media.ExifInterface;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts geolocation to be passed to API for category suggestions. If a picture with geolocation
|
* Extracts geolocation to be passed to API for category suggestions. If a picture with geolocation
|
||||||
* is uploaded, extract latitude and longitude from EXIF data of image. If a picture without
|
* is uploaded, extract latitude and longitude from EXIF data of image. If a picture without
|
||||||
|
|
@ -21,8 +22,6 @@ import java.io.IOException;
|
||||||
*/
|
*/
|
||||||
public class GPSExtractor {
|
public class GPSExtractor {
|
||||||
|
|
||||||
private static final String TAG = GPSExtractor.class.getName();
|
|
||||||
|
|
||||||
private String filePath;
|
private String filePath;
|
||||||
private double decLatitude, decLongitude;
|
private double decLatitude, decLongitude;
|
||||||
private Double currentLatitude = null;
|
private Double currentLatitude = null;
|
||||||
|
|
@ -31,8 +30,7 @@ public class GPSExtractor {
|
||||||
public boolean imageCoordsExists;
|
public boolean imageCoordsExists;
|
||||||
private MyLocationListener myLocationListener;
|
private MyLocationListener myLocationListener;
|
||||||
private LocationManager locationManager;
|
private LocationManager locationManager;
|
||||||
private String provider;
|
|
||||||
private Criteria criteria;
|
|
||||||
|
|
||||||
public GPSExtractor(String filePath, Context context){
|
public GPSExtractor(String filePath, Context context){
|
||||||
this.filePath = filePath;
|
this.filePath = filePath;
|
||||||
|
|
@ -46,7 +44,7 @@ public class GPSExtractor {
|
||||||
private boolean gpsPreferenceEnabled() {
|
private boolean gpsPreferenceEnabled() {
|
||||||
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
|
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
boolean gpsPref = sharedPref.getBoolean("allowGps", false);
|
boolean gpsPref = sharedPref.getBoolean("allowGps", false);
|
||||||
Log.d(TAG, "Gps pref set to: " + gpsPref);
|
Timber.d("Gps pref set to: %b", gpsPref);
|
||||||
return gpsPref;
|
return gpsPref;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,8 +53,8 @@ public class GPSExtractor {
|
||||||
*/
|
*/
|
||||||
protected void registerLocationManager() {
|
protected void registerLocationManager() {
|
||||||
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
||||||
criteria = new Criteria();
|
Criteria criteria = new Criteria();
|
||||||
provider = locationManager.getBestProvider(criteria, true);
|
String provider = locationManager.getBestProvider(criteria, true);
|
||||||
myLocationListener = new MyLocationListener();
|
myLocationListener = new MyLocationListener();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -66,9 +64,9 @@ public class GPSExtractor {
|
||||||
myLocationListener.onLocationChanged(location);
|
myLocationListener.onLocationChanged(location);
|
||||||
}
|
}
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
Log.e(TAG, "Illegal argument exception", e);
|
Timber.e(e, "Illegal argument exception");
|
||||||
} catch (SecurityException e) {
|
} catch (SecurityException e) {
|
||||||
Log.e(TAG, "Security exception", e);
|
Timber.e(e, "Security exception");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,7 +74,7 @@ public class GPSExtractor {
|
||||||
try {
|
try {
|
||||||
locationManager.removeUpdates(myLocationListener);
|
locationManager.removeUpdates(myLocationListener);
|
||||||
} catch (SecurityException e) {
|
} catch (SecurityException e) {
|
||||||
Log.e(TAG, "Security exception", e);
|
Timber.e(e, "Security exception");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -98,10 +96,10 @@ public class GPSExtractor {
|
||||||
try {
|
try {
|
||||||
exif = new ExifInterface(filePath);
|
exif = new ExifInterface(filePath);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.w(TAG, e);
|
Timber.w(e);
|
||||||
return null;
|
return null;
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
Log.w(TAG, e);
|
Timber.w(e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -110,14 +108,15 @@ public class GPSExtractor {
|
||||||
registerLocationManager();
|
registerLocationManager();
|
||||||
|
|
||||||
imageCoordsExists = false;
|
imageCoordsExists = false;
|
||||||
Log.d(TAG, "EXIF data has no location info");
|
Timber.d("EXIF data has no location info");
|
||||||
|
|
||||||
//Check what user's preference is for automatic location detection
|
//Check what user's preference is for automatic location detection
|
||||||
boolean gpsPrefEnabled = gpsPreferenceEnabled();
|
boolean gpsPrefEnabled = gpsPreferenceEnabled();
|
||||||
|
|
||||||
//Check that currentLatitude and currentLongitude have been explicitly set by MyLocationListener and do not default to (0.0,0.0)
|
//Check that currentLatitude and currentLongitude have been explicitly set by MyLocationListener and do not default to (0.0,0.0)
|
||||||
if (gpsPrefEnabled && currentLatitude != null && currentLongitude != null) {
|
if (gpsPrefEnabled && currentLatitude != null && currentLongitude != null) {
|
||||||
Log.d(TAG, "Current location values: Lat = " + currentLatitude + " Long = " + currentLongitude);
|
Timber.d("Current location values: Lat = %f Long = %f",
|
||||||
|
currentLatitude, currentLongitude);
|
||||||
return String.valueOf(currentLatitude) + "|" + String.valueOf(currentLongitude);
|
return String.valueOf(currentLatitude) + "|" + String.valueOf(currentLongitude);
|
||||||
} else {
|
} else {
|
||||||
// No coords found
|
// No coords found
|
||||||
|
|
@ -128,7 +127,7 @@ public class GPSExtractor {
|
||||||
} else {
|
} else {
|
||||||
//If image has EXIF data, extract image coords
|
//If image has EXIF data, extract image coords
|
||||||
imageCoordsExists = true;
|
imageCoordsExists = true;
|
||||||
Log.d(TAG, "EXIF data has location info");
|
Timber.d("EXIF data has location info");
|
||||||
|
|
||||||
latitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE);
|
latitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE);
|
||||||
latitude_ref = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF);
|
latitude_ref = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF);
|
||||||
|
|
@ -136,8 +135,8 @@ public class GPSExtractor {
|
||||||
longitude_ref = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF);
|
longitude_ref = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF);
|
||||||
|
|
||||||
if (latitude!=null && latitude_ref!=null && longitude!=null && longitude_ref!=null) {
|
if (latitude!=null && latitude_ref!=null && longitude!=null && longitude_ref!=null) {
|
||||||
Log.d(TAG, "Latitude: " + latitude + " " + latitude_ref);
|
Timber.d("Latitude: %s %s", latitude, latitude_ref);
|
||||||
Log.d(TAG, "Longitude: " + longitude + " " + longitude_ref);
|
Timber.d("Longitude: %s %s", longitude, longitude_ref);
|
||||||
|
|
||||||
decimalCoords = getDecimalCoords(latitude, latitude_ref, longitude, longitude_ref);
|
decimalCoords = getDecimalCoords(latitude, latitude_ref, longitude, longitude_ref);
|
||||||
return decimalCoords;
|
return decimalCoords;
|
||||||
|
|
@ -160,17 +159,17 @@ public class GPSExtractor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStatusChanged(String provider, int status, Bundle extras) {
|
public void onStatusChanged(String provider, int status, Bundle extras) {
|
||||||
Log.d(TAG, provider + "'s status changed to " + status);
|
Timber.d("%s's status changed to %d", provider, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onProviderEnabled(String provider) {
|
public void onProviderEnabled(String provider) {
|
||||||
Log.d(TAG, "Provider " + provider + " enabled");
|
Timber.d("Provider %s enabled", provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onProviderDisabled(String provider) {
|
public void onProviderDisabled(String provider) {
|
||||||
Log.d(TAG, "Provider " + provider + " disabled");
|
Timber.d("Provider %s disabled", provider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -201,7 +200,7 @@ public class GPSExtractor {
|
||||||
}
|
}
|
||||||
|
|
||||||
String decimalCoords = String.valueOf(decLatitude) + "|" + String.valueOf(decLongitude);
|
String decimalCoords = String.valueOf(decLatitude) + "|" + String.valueOf(decLongitude);
|
||||||
Log.d(TAG, "Latitude and Longitude are " + decimalCoords);
|
Timber.d("Latitude and Longitude are %s", decimalCoords);
|
||||||
return decimalCoords;
|
return decimalCoords;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,14 @@ import android.os.Bundle;
|
||||||
import android.support.v4.app.ActivityCompat;
|
import android.support.v4.app.ActivityCompat;
|
||||||
import android.support.v4.app.FragmentManager;
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
|
|
@ -28,7 +29,6 @@ import fr.free.nrw.commons.EventLog;
|
||||||
import fr.free.nrw.commons.Media;
|
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.auth.WikiAccountAuthenticator;
|
|
||||||
import fr.free.nrw.commons.category.CategorizationFragment;
|
import fr.free.nrw.commons.category.CategorizationFragment;
|
||||||
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;
|
||||||
|
|
@ -36,6 +36,7 @@ import fr.free.nrw.commons.modifications.CategoryModifier;
|
||||||
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
||||||
import fr.free.nrw.commons.modifications.ModifierSequence;
|
import fr.free.nrw.commons.modifications.ModifierSequence;
|
||||||
import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
|
import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class MultipleShareActivity
|
public class MultipleShareActivity
|
||||||
extends AuthenticatedActivity
|
extends AuthenticatedActivity
|
||||||
|
|
@ -53,10 +54,6 @@ public class MultipleShareActivity
|
||||||
|
|
||||||
private UploadController uploadController;
|
private UploadController uploadController;
|
||||||
|
|
||||||
public MultipleShareActivity() {
|
|
||||||
super(WikiAccountAuthenticator.COMMONS_ACCOUNT_TYPE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Media getMediaAtPosition(int i) {
|
public Media getMediaAtPosition(int i) {
|
||||||
return photosList.get(i);
|
return photosList.get(i);
|
||||||
|
|
@ -116,7 +113,7 @@ public class MultipleShareActivity
|
||||||
|
|
||||||
private void multipleUploadBegins() {
|
private void multipleUploadBegins() {
|
||||||
|
|
||||||
Log.d("MultipleShareActivity", "Multiple upload begins");
|
Timber.d("Multiple upload begins");
|
||||||
|
|
||||||
final ProgressDialog dialog = new ProgressDialog(MultipleShareActivity.this);
|
final ProgressDialog dialog = new ProgressDialog(MultipleShareActivity.this);
|
||||||
dialog.setIndeterminate(false);
|
dialog.setIndeterminate(false);
|
||||||
|
|
@ -135,7 +132,11 @@ public class MultipleShareActivity
|
||||||
dialog.setProgress(uploadCount);
|
dialog.setProgress(uploadCount);
|
||||||
if(uploadCount == photosList.size()) {
|
if(uploadCount == photosList.size()) {
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
Toast startingToast = Toast.makeText(getApplicationContext(), R.string.uploading_started, Toast.LENGTH_LONG);
|
Toast startingToast = Toast.makeText(
|
||||||
|
CommonsApplication.getInstance(),
|
||||||
|
R.string.uploading_started,
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
);
|
||||||
startingToast.show();
|
startingToast.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -205,7 +206,9 @@ public class MultipleShareActivity
|
||||||
uploadController = new UploadController(this);
|
uploadController = new UploadController(this);
|
||||||
|
|
||||||
setContentView(R.layout.activity_multiple_uploads);
|
setContentView(R.layout.activity_multiple_uploads);
|
||||||
app = (CommonsApplication)this.getApplicationContext();
|
app = CommonsApplication.getInstance();
|
||||||
|
ButterKnife.bind(this);
|
||||||
|
initDrawer();
|
||||||
|
|
||||||
if(savedInstanceState != null) {
|
if(savedInstanceState != null) {
|
||||||
photosList = savedInstanceState.getParcelableArrayList("uploadsList");
|
photosList = savedInstanceState.getParcelableArrayList("uploadsList");
|
||||||
|
|
@ -242,7 +245,7 @@ public class MultipleShareActivity
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onAuthCookieAcquired(String authCookie) {
|
protected void onAuthCookieAcquired(String authCookie) {
|
||||||
app.getApi().setAuthCookie(authCookie);
|
app.getMWApi().setAuthCookie(authCookie);
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
|
|
||||||
if(intent.getAction().equals(Intent.ACTION_SEND_MULTIPLE)) {
|
if(intent.getAction().equals(Intent.ACTION_SEND_MULTIPLE)) {
|
||||||
|
|
|
||||||
|
|
@ -21,19 +21,15 @@ import android.widget.BaseAdapter;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.GridView;
|
import android.widget.GridView;
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
import com.facebook.drawee.view.SimpleDraweeView;
|
||||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.Utils;
|
|
||||||
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;
|
||||||
|
|
||||||
|
|
||||||
public class MultipleUploadListFragment extends Fragment {
|
public class MultipleUploadListFragment extends Fragment {
|
||||||
|
|
||||||
public interface OnMultipleUploadInitiatedHandler {
|
public interface OnMultipleUploadInitiatedHandler {
|
||||||
|
|
@ -48,17 +44,13 @@ public class MultipleUploadListFragment extends Fragment {
|
||||||
private MediaDetailPagerFragment.MediaDetailProvider detailProvider;
|
private MediaDetailPagerFragment.MediaDetailProvider detailProvider;
|
||||||
private OnMultipleUploadInitiatedHandler multipleUploadInitiatedHandler;
|
private OnMultipleUploadInitiatedHandler multipleUploadInitiatedHandler;
|
||||||
|
|
||||||
private DisplayImageOptions uploadDisplayOptions;
|
|
||||||
|
|
||||||
private boolean imageOnlyMode;
|
private boolean imageOnlyMode;
|
||||||
|
|
||||||
private static class UploadHolderView {
|
private static class UploadHolderView {
|
||||||
Uri imageUri;
|
private Uri imageUri;
|
||||||
|
private SimpleDraweeView image;
|
||||||
ImageView image;
|
private TextView title;
|
||||||
TextView title;
|
private RelativeLayout overlay;
|
||||||
|
|
||||||
RelativeLayout overlay;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PhotoDisplayAdapter extends BaseAdapter {
|
private class PhotoDisplayAdapter extends BaseAdapter {
|
||||||
|
|
@ -85,7 +77,7 @@ public class MultipleUploadListFragment extends Fragment {
|
||||||
if(view == null) {
|
if(view == null) {
|
||||||
view = getLayoutInflater(null).inflate(R.layout.layout_upload_item, null);
|
view = getLayoutInflater(null).inflate(R.layout.layout_upload_item, null);
|
||||||
holder = new UploadHolderView();
|
holder = new UploadHolderView();
|
||||||
holder.image = (ImageView) view.findViewById(R.id.uploadImage);
|
holder.image = (SimpleDraweeView) view.findViewById(R.id.uploadImage);
|
||||||
holder.title = (TextView) view.findViewById(R.id.uploadTitle);
|
holder.title = (TextView) view.findViewById(R.id.uploadTitle);
|
||||||
holder.overlay = (RelativeLayout) view.findViewById(R.id.uploadOverlay);
|
holder.overlay = (RelativeLayout) view.findViewById(R.id.uploadOverlay);
|
||||||
|
|
||||||
|
|
@ -100,7 +92,7 @@ public class MultipleUploadListFragment extends Fragment {
|
||||||
Contribution up = (Contribution)this.getItem(i);
|
Contribution up = (Contribution)this.getItem(i);
|
||||||
|
|
||||||
if(holder.imageUri == null || !holder.imageUri.equals(up.getLocalUri())) {
|
if(holder.imageUri == null || !holder.imageUri.equals(up.getLocalUri())) {
|
||||||
ImageLoader.getInstance().displayImage(up.getLocalUri().toString(), holder.image, uploadDisplayOptions);
|
holder.image.setImageURI(up.getLocalUri().toString());
|
||||||
holder.imageUri = up.getLocalUri();
|
holder.imageUri = up.getLocalUri();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -221,7 +213,6 @@ public class MultipleUploadListFragment extends Fragment {
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
uploadDisplayOptions = Utils.getGenericDisplayOptions().build();
|
|
||||||
detailProvider = (MediaDetailPagerFragment.MediaDetailProvider)getActivity();
|
detailProvider = (MediaDetailPagerFragment.MediaDetailProvider)getActivity();
|
||||||
multipleUploadInitiatedHandler = (OnMultipleUploadInitiatedHandler) getActivity();
|
multipleUploadInitiatedHandler = (OnMultipleUploadInitiatedHandler) getActivity();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package fr.free.nrw.commons.upload;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.android.volley.Cache;
|
import com.android.volley.Cache;
|
||||||
import com.android.volley.NetworkResponse;
|
import com.android.volley.NetworkResponse;
|
||||||
|
|
@ -22,6 +21,8 @@ import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uses the Volley library to implement asynchronous calls to the Commons MediaWiki API to match
|
* Uses the Volley library to implement asynchronous calls to the Commons MediaWiki API to match
|
||||||
* GPS coordinates with nearby Commons categories. Parses the results using GSON to obtain a list
|
* GPS coordinates with nearby Commons categories. Parses the results using GSON to obtain a list
|
||||||
|
|
@ -32,13 +33,11 @@ public class MwVolleyApi {
|
||||||
private static RequestQueue REQUEST_QUEUE;
|
private static RequestQueue REQUEST_QUEUE;
|
||||||
private static final Gson GSON = new GsonBuilder().create();
|
private static final Gson GSON = new GsonBuilder().create();
|
||||||
private Context context;
|
private Context context;
|
||||||
private String coordsLog;
|
|
||||||
|
|
||||||
protected static Set<String> categorySet;
|
protected static Set<String> categorySet;
|
||||||
private static List<String> categoryList;
|
private static List<String> categoryList;
|
||||||
|
|
||||||
private static final String MWURL = "https://commons.wikimedia.org/";
|
private static final String MWURL = "https://commons.wikimedia.org/";
|
||||||
private static final String TAG = MwVolleyApi.class.getName();
|
|
||||||
|
|
||||||
public MwVolleyApi(Context context) {
|
public MwVolleyApi(Context context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
|
@ -52,13 +51,12 @@ public class MwVolleyApi {
|
||||||
public static void setGpsCat(List<String> cachedList) {
|
public static void setGpsCat(List<String> cachedList) {
|
||||||
categoryList = new ArrayList<>();
|
categoryList = new ArrayList<>();
|
||||||
categoryList.addAll(cachedList);
|
categoryList.addAll(cachedList);
|
||||||
Log.d(TAG, "Setting GPS cats from cache: " + categoryList.toString());
|
Timber.d("Setting GPS cats from cache: %s", categoryList);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void request(String coords) {
|
public void request(String coords) {
|
||||||
coordsLog = coords;
|
|
||||||
String apiUrl = buildUrl(coords);
|
String apiUrl = buildUrl(coords);
|
||||||
Log.d(TAG, "URL: " + 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<QueryResponse>(), new LogResponseErrorListener());
|
||||||
|
|
@ -101,7 +99,7 @@ public class MwVolleyApi {
|
||||||
|
|
||||||
private static RequestQueue getQueue(Context context) {
|
private static RequestQueue getQueue(Context context) {
|
||||||
if (REQUEST_QUEUE == null) {
|
if (REQUEST_QUEUE == null) {
|
||||||
REQUEST_QUEUE = Volley.newRequestQueue(context.getApplicationContext());
|
REQUEST_QUEUE = Volley.newRequestQueue(context);
|
||||||
}
|
}
|
||||||
return REQUEST_QUEUE;
|
return REQUEST_QUEUE;
|
||||||
}
|
}
|
||||||
|
|
@ -110,7 +108,7 @@ public class MwVolleyApi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(T response) {
|
public void onResponse(T response) {
|
||||||
Log.d(TAG, response.toString());
|
Timber.d(response.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,12 +116,11 @@ public class MwVolleyApi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onErrorResponse(VolleyError error) {
|
public void onErrorResponse(VolleyError error) {
|
||||||
Log.e(TAG, error.toString());
|
Timber.e(error.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class QueryRequest extends JsonRequest<QueryResponse> {
|
private static class QueryRequest extends JsonRequest<QueryResponse> {
|
||||||
private static final String TAG = QueryRequest.class.getName();
|
|
||||||
|
|
||||||
public QueryRequest(String url,
|
public QueryRequest(String url,
|
||||||
Response.Listener<QueryResponse> listener,
|
Response.Listener<QueryResponse> listener,
|
||||||
|
|
@ -169,11 +166,11 @@ public class MwVolleyApi {
|
||||||
private String printSet() {
|
private String printSet() {
|
||||||
if (categorySet == null || categorySet.isEmpty()) {
|
if (categorySet == null || categorySet.isEmpty()) {
|
||||||
GpsCatExists.setGpsCatExists(false);
|
GpsCatExists.setGpsCatExists(false);
|
||||||
Log.d(TAG, "gpsCatExists=" + GpsCatExists.getGpsCatExists());
|
Timber.d("gpsCatExists=%b", GpsCatExists.getGpsCatExists());
|
||||||
return "No collection of categories";
|
return "No collection of categories";
|
||||||
} else {
|
} else {
|
||||||
GpsCatExists.setGpsCatExists(true);
|
GpsCatExists.setGpsCatExists(true);
|
||||||
Log.d(TAG, "gpsCatExists=" + GpsCatExists.getGpsCatExists());
|
Timber.d("gpsCatExists=%b", GpsCatExists.getGpsCatExists());
|
||||||
return "CATEGORIES FOUND" + categorySet.toString();
|
return "CATEGORIES FOUND" + categorySet.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,34 +10,32 @@ import android.os.Bundle;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.support.design.widget.Snackbar;
|
import android.support.design.widget.Snackbar;
|
||||||
import android.support.v4.app.ActivityCompat;
|
import android.support.v4.app.ActivityCompat;
|
||||||
import android.support.v4.app.NavUtils;
|
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
import com.facebook.drawee.view.SimpleDraweeView;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import butterknife.ButterKnife;
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.EventLog;
|
import fr.free.nrw.commons.EventLog;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.Utils;
|
import fr.free.nrw.commons.Utils;
|
||||||
import fr.free.nrw.commons.auth.AuthenticatedActivity;
|
import fr.free.nrw.commons.auth.AuthenticatedActivity;
|
||||||
import fr.free.nrw.commons.auth.WikiAccountAuthenticator;
|
|
||||||
import fr.free.nrw.commons.category.CategorizationFragment;
|
import fr.free.nrw.commons.category.CategorizationFragment;
|
||||||
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;
|
||||||
import fr.free.nrw.commons.modifications.ModifierSequence;
|
import fr.free.nrw.commons.modifications.ModifierSequence;
|
||||||
import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
|
import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
|
|
@ -48,9 +46,6 @@ public class ShareActivity
|
||||||
implements SingleUploadFragment.OnUploadActionInitiated,
|
implements SingleUploadFragment.OnUploadActionInitiated,
|
||||||
CategorizationFragment.OnCategoriesSaveHandler {
|
CategorizationFragment.OnCategoriesSaveHandler {
|
||||||
|
|
||||||
private static final String TAG = ShareActivity.class.getName();
|
|
||||||
|
|
||||||
private SingleUploadFragment shareView;
|
|
||||||
private CategorizationFragment categorizationFragment;
|
private CategorizationFragment categorizationFragment;
|
||||||
|
|
||||||
private CommonsApplication app;
|
private CommonsApplication app;
|
||||||
|
|
@ -61,14 +56,14 @@ public class ShareActivity
|
||||||
|
|
||||||
private Uri mediaUri;
|
private Uri mediaUri;
|
||||||
private Contribution contribution;
|
private Contribution contribution;
|
||||||
private ImageView backgroundImageView;
|
private SimpleDraweeView backgroundImageView;
|
||||||
|
|
||||||
private UploadController uploadController;
|
private UploadController uploadController;
|
||||||
|
|
||||||
private CommonsApplication cacheObj;
|
private CommonsApplication cacheObj;
|
||||||
private boolean cacheFound;
|
private boolean cacheFound;
|
||||||
|
|
||||||
private GPSExtractor imageObj;
|
private GPSExtractor imageObj;
|
||||||
private String filePath;
|
|
||||||
private String decimalCoords;
|
private String decimalCoords;
|
||||||
|
|
||||||
private boolean useNewPermissions = false;
|
private boolean useNewPermissions = false;
|
||||||
|
|
@ -79,10 +74,6 @@ public class ShareActivity
|
||||||
private String description;
|
private String description;
|
||||||
private Snackbar snackbar;
|
private Snackbar snackbar;
|
||||||
|
|
||||||
public ShareActivity() {
|
|
||||||
super(WikiAccountAuthenticator.COMMONS_ACCOUNT_TYPE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when user taps the submit button
|
* Called when user taps the submit button
|
||||||
*/
|
*/
|
||||||
|
|
@ -112,13 +103,17 @@ public class ShareActivity
|
||||||
getFileMetadata(false);
|
getFileMetadata(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
Toast startingToast = Toast.makeText(getApplicationContext(), R.string.uploading_started, Toast.LENGTH_LONG);
|
Toast startingToast = Toast.makeText(
|
||||||
|
CommonsApplication.getInstance(),
|
||||||
|
R.string.uploading_started,
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
);
|
||||||
startingToast.show();
|
startingToast.show();
|
||||||
|
|
||||||
if (cacheFound == false) {
|
if (!cacheFound) {
|
||||||
//Has to be called after apiCall.request()
|
//Has to be called after apiCall.request()
|
||||||
app.cacheData.cacheCategory();
|
app.getCacheData().cacheCategory();
|
||||||
Log.d(TAG, "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, new UploadController.ContributionUploadProgress() {
|
||||||
|
|
@ -195,9 +190,9 @@ public class ShareActivity
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onAuthCookieAcquired(String authCookie) {
|
protected void onAuthCookieAcquired(String authCookie) {
|
||||||
app.getApi().setAuthCookie(authCookie);
|
app.getMWApi().setAuthCookie(authCookie);
|
||||||
|
|
||||||
shareView = (SingleUploadFragment) getSupportFragmentManager().findFragmentByTag("shareView");
|
SingleUploadFragment shareView = (SingleUploadFragment) getSupportFragmentManager().findFragmentByTag("shareView");
|
||||||
categorizationFragment = (CategorizationFragment) getSupportFragmentManager().findFragmentByTag("categorization");
|
categorizationFragment = (CategorizationFragment) getSupportFragmentManager().findFragmentByTag("categorization");
|
||||||
if(shareView == null && categorizationFragment == null) {
|
if(shareView == null && categorizationFragment == null) {
|
||||||
shareView = new SingleUploadFragment();
|
shareView = new SingleUploadFragment();
|
||||||
|
|
@ -221,9 +216,10 @@ public class ShareActivity
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
uploadController = new UploadController(this);
|
uploadController = new UploadController(this);
|
||||||
setContentView(R.layout.activity_share);
|
setContentView(R.layout.activity_share);
|
||||||
|
ButterKnife.bind(this);
|
||||||
app = (CommonsApplication)this.getApplicationContext();
|
initBack();
|
||||||
backgroundImageView = (ImageView)findViewById(R.id.backgroundImage);
|
app = CommonsApplication.getInstance();
|
||||||
|
backgroundImageView = (SimpleDraweeView)findViewById(R.id.backgroundImage);
|
||||||
|
|
||||||
//Receive intent from ContributionController.java when user selects picture to upload
|
//Receive intent from ContributionController.java when user selects picture to upload
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
|
|
@ -240,20 +236,20 @@ public class ShareActivity
|
||||||
|
|
||||||
if (mediaUri != null) {
|
if (mediaUri != null) {
|
||||||
mediaUriString = mediaUri.toString();
|
mediaUriString = mediaUri.toString();
|
||||||
ImageLoader.getInstance().displayImage(mediaUriString, backgroundImageView);
|
backgroundImageView.setImageURI(mediaUriString);
|
||||||
|
|
||||||
//Test SHA1 of image to see if it matches SHA1 of a file on Commons
|
//Test SHA1 of image to see if it matches SHA1 of a file on Commons
|
||||||
try {
|
try {
|
||||||
InputStream inputStream = getContentResolver().openInputStream(mediaUri);
|
InputStream inputStream = getContentResolver().openInputStream(mediaUri);
|
||||||
Log.d(TAG, "Input stream created from " + mediaUriString);
|
Timber.d("Input stream created from %s", mediaUriString);
|
||||||
String fileSHA1 = Utils.getSHA1(inputStream);
|
String fileSHA1 = Utils.getSHA1(inputStream);
|
||||||
Log.d(TAG, "File SHA1 is: " + fileSHA1);
|
Timber.d("File SHA1 is: %s", fileSHA1);
|
||||||
|
|
||||||
ExistingFileAsync fileAsyncTask = new ExistingFileAsync(fileSHA1, this);
|
ExistingFileAsync fileAsyncTask = new ExistingFileAsync(fileSHA1, this);
|
||||||
fileAsyncTask.execute();
|
fileAsyncTask.execute();
|
||||||
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.d(TAG, "IO Exception: ", e);
|
Timber.d(e, "IO Exception: ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -263,8 +259,8 @@ public class ShareActivity
|
||||||
|
|
||||||
requestAuthToken();
|
requestAuthToken();
|
||||||
|
|
||||||
Log.d(TAG, "Uri: " + mediaUriString);
|
Timber.d("Uri: %s", mediaUriString);
|
||||||
Log.d(TAG, "Ext storage dir: " + Environment.getExternalStorageDirectory());
|
Timber.d("Ext storage dir: %s", Environment.getExternalStorageDirectory());
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
useNewPermissions = true;
|
useNewPermissions = true;
|
||||||
|
|
@ -377,9 +373,9 @@ public class ShareActivity
|
||||||
* @param gpsEnabled
|
* @param gpsEnabled
|
||||||
*/
|
*/
|
||||||
public void getFileMetadata(boolean gpsEnabled) {
|
public void getFileMetadata(boolean gpsEnabled) {
|
||||||
filePath = FileUtils.getPath(this, mediaUri);
|
String filePath = FileUtils.getPath(this, mediaUri);
|
||||||
Log.d(TAG, "Filepath: " + filePath);
|
Timber.d("Filepath: %s", filePath);
|
||||||
Log.d(TAG, "Calling GPSExtractor");
|
Timber.d("Calling GPSExtractor");
|
||||||
if(imageObj == null) {
|
if(imageObj == null) {
|
||||||
imageObj = new GPSExtractor(filePath, this);
|
imageObj = new GPSExtractor(filePath, this);
|
||||||
}
|
}
|
||||||
|
|
@ -397,28 +393,28 @@ public class ShareActivity
|
||||||
*/
|
*/
|
||||||
public void useImageCoords() {
|
public void useImageCoords() {
|
||||||
if(decimalCoords != null) {
|
if(decimalCoords != null) {
|
||||||
Log.d(TAG, "Decimal coords of image: " + decimalCoords);
|
Timber.d("Decimal coords of image: %s", decimalCoords);
|
||||||
|
|
||||||
// Only set cache for this point if image has coords
|
// Only set cache for this point if image has coords
|
||||||
if (imageObj.imageCoordsExists) {
|
if (imageObj.imageCoordsExists) {
|
||||||
double decLongitude = imageObj.getDecLongitude();
|
double decLongitude = imageObj.getDecLongitude();
|
||||||
double decLatitude = imageObj.getDecLatitude();
|
double decLatitude = imageObj.getDecLatitude();
|
||||||
app.cacheData.setQtPoint(decLongitude, decLatitude);
|
app.getCacheData().setQtPoint(decLongitude, decLatitude);
|
||||||
}
|
}
|
||||||
|
|
||||||
MwVolleyApi apiCall = new MwVolleyApi(this);
|
MwVolleyApi apiCall = new MwVolleyApi(this);
|
||||||
|
|
||||||
List<String> displayCatList = app.cacheData.findCategory();
|
List<String> displayCatList = app.getCacheData().findCategory();
|
||||||
boolean catListEmpty = displayCatList.isEmpty();
|
boolean catListEmpty = displayCatList.isEmpty();
|
||||||
|
|
||||||
// If no categories found in cache, call MediaWiki API to match image coords with nearby Commons categories
|
// If no categories found in cache, call MediaWiki API to match image coords with nearby Commons categories
|
||||||
if (catListEmpty) {
|
if (catListEmpty) {
|
||||||
cacheFound = false;
|
cacheFound = false;
|
||||||
apiCall.request(decimalCoords);
|
apiCall.request(decimalCoords);
|
||||||
Log.d(TAG, "displayCatList size 0, calling MWAPI" + displayCatList.toString());
|
Timber.d("displayCatList size 0, calling MWAPI %s", displayCatList);
|
||||||
} else {
|
} else {
|
||||||
cacheFound = true;
|
cacheFound = true;
|
||||||
Log.d(TAG, "Cache found, setting categoryList in MwVolleyApi to " + displayCatList.toString());
|
Timber.d("Cache found, setting categoryList in MwVolleyApi to %s", displayCatList);
|
||||||
MwVolleyApi.setGpsCat(displayCatList);
|
MwVolleyApi.setGpsCat(displayCatList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -429,10 +425,10 @@ public class ShareActivity
|
||||||
super.onPause();
|
super.onPause();
|
||||||
try {
|
try {
|
||||||
imageObj.unregisterLocationManager();
|
imageObj.unregisterLocationManager();
|
||||||
Log.d(TAG, "Unregistered locationManager");
|
Timber.d("Unregistered locationManager");
|
||||||
}
|
}
|
||||||
catch (NullPointerException e) {
|
catch (NullPointerException e) {
|
||||||
Log.d(TAG, "locationManager does not exist, not unregistered");
|
Timber.d("locationManager does not exist, not unregistered");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -446,7 +442,11 @@ public class ShareActivity
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case android.R.id.home:
|
case android.R.id.home:
|
||||||
NavUtils.navigateUpFromSameTask(this);
|
if(categorizationFragment!=null && categorizationFragment.isVisible()) {
|
||||||
|
categorizationFragment.backButtonDialog();
|
||||||
|
} else {
|
||||||
|
onBackPressed();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import android.preference.PreferenceManager;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
|
|
@ -37,6 +36,7 @@ import butterknife.OnTouch;
|
||||||
import fr.free.nrw.commons.settings.Prefs;
|
import fr.free.nrw.commons.settings.Prefs;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.Utils;
|
import fr.free.nrw.commons.Utils;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class SingleUploadFragment extends Fragment {
|
public class SingleUploadFragment extends Fragment {
|
||||||
private SharedPreferences prefs;
|
private SharedPreferences prefs;
|
||||||
|
|
@ -54,8 +54,6 @@ public class SingleUploadFragment extends Fragment {
|
||||||
|
|
||||||
private OnUploadActionInitiated uploadActionInitiatedHandler;
|
private OnUploadActionInitiated uploadActionInitiatedHandler;
|
||||||
|
|
||||||
private static final String TAG = SingleUploadFragment.class.getName();
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
inflater.inflate(R.menu.activity_share, menu);
|
inflater.inflate(R.menu.activity_share, menu);
|
||||||
|
|
@ -103,7 +101,7 @@ public class SingleUploadFragment extends Fragment {
|
||||||
prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||||
license = prefs.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3);
|
license = prefs.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3);
|
||||||
|
|
||||||
Log.d("Single Upload fragment", license);
|
Timber.d(license);
|
||||||
|
|
||||||
ArrayAdapter<String> adapter;
|
ArrayAdapter<String> adapter;
|
||||||
if (PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean("theme",true)) {
|
if (PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean("theme",true)) {
|
||||||
|
|
@ -117,7 +115,14 @@ public class SingleUploadFragment extends Fragment {
|
||||||
licenseSpinner.setAdapter(adapter);
|
licenseSpinner.setAdapter(adapter);
|
||||||
|
|
||||||
int position = licenseItems.indexOf(getString(Utils.licenseNameFor(license)));
|
int position = licenseItems.indexOf(getString(Utils.licenseNameFor(license)));
|
||||||
Log.d("Single Upload fragment", "Position:"+position+" "+getString(Utils.licenseNameFor(license)));
|
|
||||||
|
// Check position is valid
|
||||||
|
if (position < 0) {
|
||||||
|
Timber.d("Invalid position: %d. Using default license", position);
|
||||||
|
position = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
Timber.d("Position: %d %s", position, getString(Utils.licenseNameFor(license)));
|
||||||
licenseSpinner.setSelection(position);
|
licenseSpinner.setSelection(position);
|
||||||
|
|
||||||
TextWatcher uploadEnabler = new TextWatcher() {
|
TextWatcher uploadEnabler = new TextWatcher() {
|
||||||
|
|
@ -190,7 +195,7 @@ public class SingleUploadFragment extends Fragment {
|
||||||
SharedPreferences titleDesc = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
SharedPreferences titleDesc = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||||
String title = titleDesc.getString("Title", "");
|
String title = titleDesc.getString("Title", "");
|
||||||
String desc = titleDesc.getString("Desc", "");
|
String desc = titleDesc.getString("Desc", "");
|
||||||
Log.d(TAG, "Title: " + title + ", Desc: " + desc);
|
Timber.d("Title: %s, Desc: %s", title, desc);
|
||||||
|
|
||||||
titleEdit.setText(title);
|
titleEdit.setText(title);
|
||||||
descEdit.setText(desc);
|
descEdit.setText(desc);
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import android.os.IBinder;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
@ -23,6 +22,7 @@ import fr.free.nrw.commons.HandlerService;
|
||||||
import fr.free.nrw.commons.settings.Prefs;
|
import fr.free.nrw.commons.settings.Prefs;
|
||||||
import fr.free.nrw.commons.Utils;
|
import fr.free.nrw.commons.Utils;
|
||||||
import fr.free.nrw.commons.contributions.Contribution;
|
import fr.free.nrw.commons.contributions.Contribution;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class UploadController {
|
public class UploadController {
|
||||||
private UploadService uploadService;
|
private UploadService uploadService;
|
||||||
|
|
@ -36,7 +36,7 @@ public class UploadController {
|
||||||
|
|
||||||
public UploadController(Activity activity) {
|
public UploadController(Activity activity) {
|
||||||
this.activity = activity;
|
this.activity = activity;
|
||||||
app = (CommonsApplication)activity.getApplicationContext();
|
app = CommonsApplication.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isUploadServiceConnected;
|
private boolean isUploadServiceConnected;
|
||||||
|
|
@ -55,7 +55,7 @@ public class UploadController {
|
||||||
};
|
};
|
||||||
|
|
||||||
public void prepareService() {
|
public void prepareService() {
|
||||||
Intent uploadServiceIntent = new Intent(activity.getApplicationContext(), UploadService.class);
|
Intent uploadServiceIntent = new Intent(activity, UploadService.class);
|
||||||
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
|
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
|
||||||
activity.startService(uploadServiceIntent);
|
activity.startService(uploadServiceIntent);
|
||||||
activity.bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE);
|
activity.bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE);
|
||||||
|
|
@ -115,11 +115,11 @@ public class UploadController {
|
||||||
contribution.setDataLength(length);
|
contribution.setDataLength(length);
|
||||||
}
|
}
|
||||||
} catch(IOException e) {
|
} catch(IOException e) {
|
||||||
Log.e("UploadController", "IO Exception: ", e);
|
Timber.e(e, "IO Exception: ");
|
||||||
} catch(NullPointerException e) {
|
} catch(NullPointerException e) {
|
||||||
Log.e("UploadController", "Null Pointer Exception: ", e);
|
Timber.e(e, "Null Pointer Exception: ");
|
||||||
} catch(SecurityException e) {
|
} catch(SecurityException e) {
|
||||||
Log.e("UploadController", "Security Exception: ", e);
|
Timber.e(e, "Security Exception: ");
|
||||||
}
|
}
|
||||||
|
|
||||||
String mimeType = (String)contribution.getTag("mimeType");
|
String mimeType = (String)contribution.getTag("mimeType");
|
||||||
|
|
@ -132,7 +132,7 @@ public class UploadController {
|
||||||
if(mimeType != null) {
|
if(mimeType != null) {
|
||||||
contribution.setTag("mimeType", mimeType);
|
contribution.setTag("mimeType", mimeType);
|
||||||
imagePrefix = mimeType.startsWith("image/");
|
imagePrefix = mimeType.startsWith("image/");
|
||||||
Log.d("UploadController", "MimeType is: " + mimeType);
|
Timber.d("MimeType is: %s", mimeType);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(imagePrefix && contribution.getDateCreated() == null) {
|
if(imagePrefix && contribution.getDateCreated() == null) {
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,11 @@ import android.content.Intent;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.NotificationCompat;
|
import android.support.v4.app.NotificationCompat;
|
||||||
import android.util.Log;
|
|
||||||
import android.webkit.MimeTypeMap;
|
import android.webkit.MimeTypeMap;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.*;
|
||||||
import org.mediawiki.api.ApiResult;
|
import org.mediawiki.api.ApiResult;
|
||||||
import org.mediawiki.api.MWApi;
|
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
@ -26,16 +25,12 @@ import java.util.Set;
|
||||||
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.CommonsApplication;
|
|
||||||
import fr.free.nrw.commons.EventLog;
|
|
||||||
import fr.free.nrw.commons.HandlerService;
|
|
||||||
import fr.free.nrw.commons.R;
|
|
||||||
import fr.free.nrw.commons.Utils;
|
|
||||||
import fr.free.nrw.commons.contributions.Contribution;
|
import fr.free.nrw.commons.contributions.Contribution;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
|
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
|
||||||
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
||||||
import in.yuvi.http.fluent.ProgressListener;
|
import in.yuvi.http.fluent.ProgressListener;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class UploadService extends HandlerService<Contribution> {
|
public class UploadService extends HandlerService<Contribution> {
|
||||||
|
|
||||||
|
|
@ -87,7 +82,7 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onProgress(long transferred, long total) {
|
public void onProgress(long transferred, long total) {
|
||||||
Log.d("Commons", String.format("Uploaded %d of %d", transferred, total));
|
Timber.d("Uploaded %d of %d", transferred, total);
|
||||||
if(!notificationTitleChanged) {
|
if(!notificationTitleChanged) {
|
||||||
curProgressNotification.setContentTitle(notificationProgressTitle);
|
curProgressNotification.setContentTitle(notificationProgressTitle);
|
||||||
notificationTitleChanged = true;
|
notificationTitleChanged = true;
|
||||||
|
|
@ -112,7 +107,7 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
contributionsProviderClient.release();
|
contributionsProviderClient.release();
|
||||||
Log.d("Commons", "UploadService.onDestroy; " + unfinishedUploads + " are yet to be uploaded");
|
Timber.d("UploadService.onDestroy; %s are yet to be uploaded", unfinishedUploads);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -120,7 +115,7 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
|
||||||
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||||
app = (CommonsApplication) this.getApplicationContext();
|
app = CommonsApplication.getInstance();
|
||||||
contributionsProviderClient = this.getContentResolver().acquireContentProviderClient(ContributionsContentProvider.AUTHORITY);
|
contributionsProviderClient = this.getContentResolver().acquireContentProviderClient(ContributionsContentProvider.AUTHORITY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,7 +144,7 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
toUpload++;
|
toUpload++;
|
||||||
if (curProgressNotification != null && toUpload != 1) {
|
if (curProgressNotification != null && toUpload != 1) {
|
||||||
curProgressNotification.setContentText(getResources().getQuantityString(R.plurals.uploads_pending_notification_indicator, toUpload, toUpload));
|
curProgressNotification.setContentText(getResources().getQuantityString(R.plurals.uploads_pending_notification_indicator, toUpload, toUpload));
|
||||||
Log.d("Commons", String.format("%d uploads left", toUpload));
|
Timber.d("%d uploads left", toUpload);
|
||||||
this.startForeground(NOTIFICATION_UPLOAD_IN_PROGRESS, curProgressNotification.build());
|
this.startForeground(NOTIFICATION_UPLOAD_IN_PROGRESS, curProgressNotification.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,15 +168,15 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
Contribution.Table.COLUMN_STATE + " = ? OR " + Contribution.Table.COLUMN_STATE + " = ?",
|
Contribution.Table.COLUMN_STATE + " = ? OR " + Contribution.Table.COLUMN_STATE + " = ?",
|
||||||
new String[]{ String.valueOf(Contribution.STATE_QUEUED), String.valueOf(Contribution.STATE_IN_PROGRESS) }
|
new String[]{ String.valueOf(Contribution.STATE_QUEUED), String.valueOf(Contribution.STATE_IN_PROGRESS) }
|
||||||
);
|
);
|
||||||
Log.d("Commons", "Set " + updated + " uploads to failed");
|
Timber.d("Set %d uploads to failed", updated);
|
||||||
Log.d("Commons", "Flags is" + flags + " id is" + startId);
|
Timber.d("Flags is %d id is %d", flags, startId);
|
||||||
freshStart = false;
|
freshStart = false;
|
||||||
}
|
}
|
||||||
return START_REDELIVER_INTENT;
|
return START_REDELIVER_INTENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void uploadContribution(Contribution contribution) {
|
private void uploadContribution(Contribution contribution) {
|
||||||
MWApi api = app.getApi();
|
MWApi api = app.getMWApi();
|
||||||
|
|
||||||
ApiResult result;
|
ApiResult result;
|
||||||
InputStream file = null;
|
InputStream file = null;
|
||||||
|
|
@ -192,12 +187,12 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
//FIXME: Google Photos bug
|
//FIXME: Google Photos bug
|
||||||
file = this.getContentResolver().openInputStream(contribution.getLocalUri());
|
file = this.getContentResolver().openInputStream(contribution.getLocalUri());
|
||||||
} catch(FileNotFoundException e) {
|
} catch(FileNotFoundException e) {
|
||||||
Log.d("Exception", "File not found");
|
Timber.d("File not found");
|
||||||
Toast fileNotFound = Toast.makeText(this, R.string.upload_failed, Toast.LENGTH_LONG);
|
Toast fileNotFound = Toast.makeText(this, R.string.upload_failed, Toast.LENGTH_LONG);
|
||||||
fileNotFound.show();
|
fileNotFound.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d("Commons", "Before execution!");
|
Timber.d("Before execution!");
|
||||||
curProgressNotification = new NotificationCompat.Builder(this).setAutoCancel(true)
|
curProgressNotification = new NotificationCompat.Builder(this).setAutoCancel(true)
|
||||||
.setSmallIcon(R.drawable.ic_launcher)
|
.setSmallIcon(R.drawable.ic_launcher)
|
||||||
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher))
|
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher))
|
||||||
|
|
@ -206,7 +201,7 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
.setContentText(getResources().getQuantityString(R.plurals.uploads_pending_notification_indicator, toUpload, toUpload))
|
.setContentText(getResources().getQuantityString(R.plurals.uploads_pending_notification_indicator, toUpload, toUpload))
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.setProgress(100, 0, true)
|
.setProgress(100, 0, true)
|
||||||
.setContentIntent(PendingIntent.getActivity(getApplicationContext(), 0, new Intent(this, ContributionsActivity.class), 0))
|
.setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, ContributionsActivity.class), 0))
|
||||||
.setTicker(getString(R.string.upload_progress_notification_title_in_progress, contribution.getDisplayTitle()));
|
.setTicker(getString(R.string.upload_progress_notification_title_in_progress, contribution.getDisplayTitle()));
|
||||||
|
|
||||||
this.startForeground(NOTIFICATION_UPLOAD_IN_PROGRESS, curProgressNotification.build());
|
this.startForeground(NOTIFICATION_UPLOAD_IN_PROGRESS, curProgressNotification.build());
|
||||||
|
|
@ -218,16 +213,16 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
MimeTypeMap.getSingleton().getExtensionFromMimeType((String)contribution.getTag("mimeType")));
|
MimeTypeMap.getSingleton().getExtensionFromMimeType((String)contribution.getTag("mimeType")));
|
||||||
|
|
||||||
synchronized (unfinishedUploads) {
|
synchronized (unfinishedUploads) {
|
||||||
Log.d("Commons", "making sure of uniqueness of name: " + filename);
|
Timber.d("making sure of uniqueness of name: %s", filename);
|
||||||
filename = findUniqueFilename(filename);
|
filename = findUniqueFilename(filename);
|
||||||
unfinishedUploads.add(filename);
|
unfinishedUploads.add(filename);
|
||||||
}
|
}
|
||||||
if(!api.validateLogin()) {
|
if(!api.validateLogin()) {
|
||||||
// Need to revalidate!
|
// Need to revalidate!
|
||||||
if(app.revalidateAuthToken()) {
|
if(app.revalidateAuthToken()) {
|
||||||
Log.d("Commons", "Successfully revalidated token!");
|
Timber.d("Successfully revalidated token!");
|
||||||
} else {
|
} else {
|
||||||
Log.d("Commons", "Unable to revalidate :(");
|
Timber.d("Unable to revalidate :(");
|
||||||
// TODO: Put up a new notification, ask them to re-login
|
// TODO: Put up a new notification, ask them to re-login
|
||||||
stopForeground(true);
|
stopForeground(true);
|
||||||
Toast failureToast = Toast.makeText(this, R.string.authentication_failed, Toast.LENGTH_LONG);
|
Toast failureToast = Toast.makeText(this, R.string.authentication_failed, Toast.LENGTH_LONG);
|
||||||
|
|
@ -242,7 +237,7 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
);
|
);
|
||||||
result = api.upload(filename, file, contribution.getDataLength(), contribution.getPageContents(), contribution.getEditSummary(), notificationUpdater);
|
result = api.upload(filename, file, contribution.getDataLength(), contribution.getPageContents(), contribution.getEditSummary(), notificationUpdater);
|
||||||
|
|
||||||
Log.d("Commons", "Response is" + Utils.getStringFromDOM(result.getDocument()));
|
Timber.d("Response is %s", Utils.getStringFromDOM(result.getDocument()));
|
||||||
|
|
||||||
curProgressNotification = null;
|
curProgressNotification = null;
|
||||||
|
|
||||||
|
|
@ -277,7 +272,7 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
.log();
|
.log();
|
||||||
}
|
}
|
||||||
} catch(IOException e) {
|
} catch(IOException e) {
|
||||||
Log.d("Commons", "I have a network fuckup");
|
Timber.d("I have a network fuckup");
|
||||||
showFailedNotification(contribution);
|
showFailedNotification(contribution);
|
||||||
return;
|
return;
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -287,7 +282,7 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
toUpload--;
|
toUpload--;
|
||||||
if(toUpload == 0) {
|
if(toUpload == 0) {
|
||||||
// Sync modifications right after all uplaods are processed
|
// Sync modifications right after all uplaods are processed
|
||||||
ContentResolver.requestSync(((CommonsApplication) getApplicationContext()).getCurrentAccount(), ModificationsContentProvider.AUTHORITY, new Bundle());
|
ContentResolver.requestSync((CommonsApplication.getInstance()).getCurrentAccount(), ModificationsContentProvider.AUTHORITY, new Bundle());
|
||||||
stopForeground(true);
|
stopForeground(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -309,7 +304,7 @@ public class UploadService extends HandlerService<Contribution> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private String findUniqueFilename(String fileName) throws IOException {
|
private String findUniqueFilename(String fileName) throws IOException {
|
||||||
MWApi api = app.getApi();
|
MWApi api = app.getMWApi();
|
||||||
String sequenceFileName;
|
String sequenceFileName;
|
||||||
for ( int sequenceNumber = 1; true; sequenceNumber++ ) {
|
for ( int sequenceNumber = 1; true; sequenceNumber++ ) {
|
||||||
if (sequenceNumber == 1) {
|
if (sequenceNumber == 1) {
|
||||||
|
|
|
||||||
80
app/src/main/java/fr/free/nrw/commons/utils/DialogUtil.java
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
package fr.free.nrw.commons.utils;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.app.DialogFragment;
|
||||||
|
import android.support.v4.app.FragmentActivity;
|
||||||
|
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
public class DialogUtil {
|
||||||
|
|
||||||
|
public static void dismissSafely(@Nullable Activity activity, @Nullable DialogFragment dialog) {
|
||||||
|
boolean isActivityDestroyed = false;
|
||||||
|
|
||||||
|
if (activity == null || dialog == null) {
|
||||||
|
Timber.d("dismiss called with null activity / dialog. Ignoring.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||||
|
isActivityDestroyed = activity.isDestroyed();
|
||||||
|
}
|
||||||
|
if (activity.isFinishing() || isActivityDestroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
dialog.dismiss();
|
||||||
|
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
Timber.e(e, "Could not dismiss dialog.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void showSafely(Activity activity, Dialog dialog) {
|
||||||
|
if (activity == null || dialog == null) {
|
||||||
|
Timber.d("Show called with null activity / dialog. Ignoring.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isActivityDestroyed = false;
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||||
|
isActivityDestroyed = activity.isDestroyed();
|
||||||
|
}
|
||||||
|
if (activity.isFinishing() || isActivityDestroyed) {
|
||||||
|
Timber.e("Activity is not running. Could not show dialog. ");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
dialog.show();
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
Timber.e(e, "Could not show dialog.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void showSafely(FragmentActivity activity, DialogFragment dialog) {
|
||||||
|
boolean isActivityDestroyed = false;
|
||||||
|
|
||||||
|
if (activity == null || dialog == null) {
|
||||||
|
Timber.d("show called with null activity / dialog. Ignoring.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||||
|
isActivityDestroyed = activity.isDestroyed();
|
||||||
|
}
|
||||||
|
if (activity.isFinishing() || isActivityDestroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (dialog.getDialog() == null || !dialog.getDialog().isShowing()) {
|
||||||
|
dialog.show(activity.getSupportFragmentManager(), dialog.getClass().getSimpleName());
|
||||||
|
}
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
Timber.e(e, "Could not show dialog.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
57
app/src/main/java/fr/free/nrw/commons/utils/FileUtils.java
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
package fr.free.nrw.commons.utils;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
|
||||||
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
|
|
||||||
|
public class FileUtils {
|
||||||
|
/**
|
||||||
|
* Read and return the content of a resource file as string.
|
||||||
|
*
|
||||||
|
* @param fileName asset file's path (e.g. "/assets/queries/nearby_query.rq")
|
||||||
|
* @return the content of the file
|
||||||
|
*/
|
||||||
|
public static String readFromResource(String fileName) throws IOException {
|
||||||
|
StringBuffer buffer = new StringBuffer();
|
||||||
|
BufferedReader reader = null;
|
||||||
|
try {
|
||||||
|
reader = new BufferedReader(
|
||||||
|
new InputStreamReader(
|
||||||
|
CommonsApplication.class.getResourceAsStream(fileName), "UTF-8"));
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
buffer.append(line + "\n");
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (reader != null) {
|
||||||
|
reader.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes files.
|
||||||
|
* @param file context
|
||||||
|
*/
|
||||||
|
public static boolean deleteFile(File file) {
|
||||||
|
boolean deletedAll = true;
|
||||||
|
if (file != null) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
String[] children = file.list();
|
||||||
|
for (int i = 0; i < children.length; i++) {
|
||||||
|
deletedAll = deleteFile(new File(file, children[i])) && deletedAll;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
deletedAll = file.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return deletedAll;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package fr.free.nrw.commons.utils;
|
||||||
|
|
||||||
|
import android.support.annotation.IdRes;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
import android.support.v4.app.FragmentManager;
|
||||||
|
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
public class FragmentUtils {
|
||||||
|
|
||||||
|
public static boolean addAndCommitFragmentWithImmediateExecution(
|
||||||
|
@NonNull FragmentManager fragmentManager,
|
||||||
|
@IdRes int containerViewId,
|
||||||
|
@NonNull Fragment fragment) {
|
||||||
|
if (fragment.isAdded()) {
|
||||||
|
Timber.w("Could not add fragment. The fragment is already added.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
fragmentManager.beginTransaction()
|
||||||
|
.add(containerViewId, fragment)
|
||||||
|
.commitNow();
|
||||||
|
return true;
|
||||||
|
} catch (IllegalStateException e) {
|
||||||
|
Timber.e(e, "Could not add & commit fragment. " +
|
||||||
|
"Did you mean to call commitAllowingStateLoss?");
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
package fr.free.nrw.commons.utils;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import com.google.gson.JsonDeserializationContext;
|
||||||
|
import com.google.gson.JsonDeserializer;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonParseException;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
|
public class UriDeserializer implements JsonDeserializer<Uri> {
|
||||||
|
@Override
|
||||||
|
public Uri deserialize(final JsonElement src, final Type srcType,
|
||||||
|
final JsonDeserializationContext context) throws JsonParseException {
|
||||||
|
return Uri.parse(src.getAsString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package fr.free.nrw.commons.utils;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonPrimitive;
|
||||||
|
import com.google.gson.JsonSerializationContext;
|
||||||
|
import com.google.gson.JsonSerializer;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
|
|
||||||
|
public class UriSerializer implements JsonSerializer<Uri> {
|
||||||
|
public JsonElement serialize(Uri src, Type typeOfSrc, JsonSerializationContext context) {
|
||||||
|
return new JsonPrimitive(src.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
app/src/main/res/drawable-hdpi/ic_exit_to_app_black_24dp.png
Normal file
|
After Width: | Height: | Size: 251 B |
BIN
app/src/main/res/drawable-hdpi/ic_feedback_black_24dp.png
Normal file
|
After Width: | Height: | Size: 149 B |
BIN
app/src/main/res/drawable-hdpi/ic_file_upload_black_24dp.png
Normal file
|
After Width: | Height: | Size: 139 B |
BIN
app/src/main/res/drawable-hdpi/ic_home_black_24dp.png
Normal file
|
After Width: | Height: | Size: 214 B |
BIN
app/src/main/res/drawable-hdpi/ic_info_outline_black_24dp.png
Normal file
|
After Width: | Height: | Size: 487 B |
BIN
app/src/main/res/drawable-hdpi/ic_list_white_24dp.png
Normal file
|
After Width: | Height: | Size: 116 B |
BIN
app/src/main/res/drawable-hdpi/ic_location_on_black_24dp.png
Normal file
|
After Width: | Height: | Size: 376 B |
BIN
app/src/main/res/drawable-hdpi/ic_map_white_24dp.png
Normal file
|
After Width: | Height: | Size: 317 B |
BIN
app/src/main/res/drawable-hdpi/ic_settings_black_24dp.png
Normal file
|
After Width: | Height: | Size: 453 B |
BIN
app/src/main/res/drawable-mdpi/custom_map_marker.png
Normal file
|
After Width: | Height: | Size: 993 B |