Merge remote-tracking branch 'refs/remotes/commons-app/master'
4
.gitignore
vendored
|
|
@ -23,3 +23,7 @@ local.properties
|
|||
# OS files
|
||||
*.DS_Store
|
||||
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
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- w3m
|
||||
env:
|
||||
global:
|
||||
- ANDROID_TARGET=android-22
|
||||
- ANDROID_ABI=armeabi-v7a
|
||||
android:
|
||||
components:
|
||||
- platform-tools
|
||||
|
|
@ -6,8 +14,17 @@ android:
|
|||
- build-tools-25.0.1
|
||||
- extra-google-m2repository
|
||||
- extra-android-m2repository
|
||||
- ${ANDROID_TARGET}
|
||||
- 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:
|
||||
# - openjdk8 # not yet available
|
||||
- oraclejdk8
|
||||
|
|
|
|||
20
CHANGELOG.md
|
|
@ -1,9 +1,25 @@
|
|||
# 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
|
||||
|
||||
## v2.2.1 beta
|
||||
## v2.2.1
|
||||
- Hotfix for Settings crash
|
||||
|
||||
## 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
|
||||
* Vivek Maskara
|
||||
* 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 'in.yuvi:http.fluent:1.3'
|
||||
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 'org.mediawiki:api:1.3'
|
||||
compile 'commons-codec:commons-codec:1.10'
|
||||
|
|
@ -16,8 +15,21 @@ dependencies {
|
|||
compile 'com.google.code.gson:gson:2.7'
|
||||
compile "com.jakewharton:butterknife:$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'
|
||||
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 {
|
||||
|
|
@ -28,10 +40,12 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
applicationId 'fr.free.nrw.commons'
|
||||
versionCode 69
|
||||
versionName '2.2.2'
|
||||
versionCode 71
|
||||
versionName '2.4'
|
||||
minSdkVersion project.minSdkVersion
|
||||
targetSdkVersion project.targetSdkVersion
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ task checkstyle(type: Checkstyle) {
|
|||
|
||||
task pmd(type: Pmd) {
|
||||
ignoreFailures = true
|
||||
ruleSetFiles = files("${project.rootDir}/ruleset.xml")
|
||||
ruleSetFiles = files("${project.rootDir}/script/style/ruleset.xml")
|
||||
ruleSets = []
|
||||
|
||||
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"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="fr.free.nrw.commons">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
|
@ -31,6 +30,10 @@
|
|||
<activity
|
||||
android:name=".auth.LoginActivity"
|
||||
>
|
||||
<intent-filter>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".WelcomeActivity"
|
||||
|
|
@ -66,10 +69,6 @@
|
|||
android:icon="@drawable/ic_launcher"
|
||||
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
|
||||
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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.Html;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.widget.TextView;
|
||||
|
||||
import fr.free.nrw.commons.theme.BaseActivity;
|
||||
|
||||
import butterknife.BindView;
|
||||
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_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
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
|
|
@ -27,24 +19,12 @@ public class AboutActivity extends BaseActivity {
|
|||
|
||||
ButterKnife.bind(this);
|
||||
|
||||
uploadsToText.setText(CommonsApplication.EVENTLOG_WIKI);
|
||||
versionText.setText(BuildConfig.VERSION_NAME);
|
||||
|
||||
// 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());
|
||||
initDrawer();
|
||||
}
|
||||
|
||||
private void fixFormatting(TextView textView, int resource) {
|
||||
textView.setText(Html.fromHtml(getResources().getString(resource)));
|
||||
public static void startYourself(Context context) {
|
||||
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.OperationCanceledException;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Build;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.util.LruCache;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.volley.RequestQueue;
|
||||
import com.android.volley.toolbox.BasicNetwork;
|
||||
import com.android.volley.toolbox.DiskBasedCache;
|
||||
import com.android.volley.toolbox.HurlStack;
|
||||
import com.nostra13.universalimageloader.cache.disc.impl.TotalSizeLimitedDiscCache;
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
|
||||
import com.nostra13.universalimageloader.utils.StorageUtils;
|
||||
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||
import com.facebook.stetho.Stetho;
|
||||
|
||||
import fr.free.nrw.commons.caching.CacheController;
|
||||
import fr.free.nrw.commons.category.Category;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
import fr.free.nrw.commons.data.DBOpenHelper;
|
||||
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.ReportingInteractionMode;
|
||||
|
|
@ -33,12 +39,12 @@ import org.apache.http.impl.client.DefaultHttpClient;
|
|||
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
|
||||
import org.apache.http.params.BasicHttpParams;
|
||||
import org.apache.http.params.CoreProtocolPNames;
|
||||
import org.mediawiki.api.MWApi;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import fr.free.nrw.commons.auth.WikiAccountAuthenticator;
|
||||
import fr.free.nrw.commons.caching.CacheController;
|
||||
import fr.free.nrw.commons.utils.FileUtils;
|
||||
import timber.log.Timber;
|
||||
|
||||
// TODO: Use ProGuard to rip out reporting when publishing
|
||||
@ReportsCrashes(
|
||||
|
|
@ -51,7 +57,6 @@ import fr.free.nrw.commons.caching.CacheController;
|
|||
)
|
||||
public class CommonsApplication extends Application {
|
||||
|
||||
private MWApi api;
|
||||
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 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_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();
|
||||
SchemeRegistry schemeRegistry = new SchemeRegistry();
|
||||
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
|
||||
|
|
@ -85,87 +116,78 @@ public class CommonsApplication extends Application {
|
|||
return new DefaultHttpClient(cm, params);
|
||||
}
|
||||
|
||||
public static MWApi createMWApi() {
|
||||
return new MWApi(API_URL, createHttpClient());
|
||||
public MWApi getMWApi() {
|
||||
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
|
||||
public void 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) {
|
||||
ACRA.init(this);
|
||||
}
|
||||
// Fire progress callbacks for every 3% of uploaded content
|
||||
System.setProperty("in.yuvi.http.fluent.PROGRESS_TRIGGER_THRESHOLD", "3.0");
|
||||
api = createMWApi();
|
||||
|
||||
ImageLoaderConfiguration imageLoaderConfiguration = new ImageLoaderConfiguration.Builder(getApplicationContext())
|
||||
.discCache(new TotalSizeLimitedDiscCache(StorageUtils.getCacheDirectory(this), 128 * 1024 * 1024))
|
||||
.build();
|
||||
ImageLoader.getInstance().init(imageLoaderConfiguration);
|
||||
Fresco.initialize(this);
|
||||
|
||||
// Initialize EventLogging
|
||||
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
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Account|null
|
||||
*/
|
||||
public Account getCurrentAccount() {
|
||||
if(currentAccount == null) {
|
||||
AccountManager accountManager = AccountManager.get(this);
|
||||
Account[] allAccounts = accountManager.getAccountsByType(WikiAccountAuthenticator.COMMONS_ACCOUNT_TYPE);
|
||||
Account[] allAccounts = accountManager.getAccountsByType(AccountUtil.accountType());
|
||||
if(allAccounts.length != 0) {
|
||||
currentAccount = allAccounts[0];
|
||||
}
|
||||
|
|
@ -181,21 +203,12 @@ public class CommonsApplication extends Application {
|
|||
return false; // This should never happen
|
||||
}
|
||||
|
||||
accountManager.invalidateAuthToken(WikiAccountAuthenticator.COMMONS_ACCOUNT_TYPE, api.getAuthCookie());
|
||||
accountManager.invalidateAuthToken(AccountUtil.accountType(), getMWApi().getAuthCookie());
|
||||
try {
|
||||
String authCookie = accountManager.blockingGetAuthToken(curAccount, "", false);
|
||||
api.setAuthCookie(authCookie);
|
||||
getMWApi().setAuthCookie(authCookie);
|
||||
return true;
|
||||
} catch (OperationCanceledException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
} catch (AuthenticatorException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
} catch (NullPointerException e) {
|
||||
} catch (OperationCanceledException | NullPointerException | IOException | AuthenticatorException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
|
|
@ -206,4 +219,46 @@ public class CommonsApplication extends Application {
|
|||
return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA) ||
|
||||
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.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.impl.client.AbstractHttpClient;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
import fr.free.nrw.commons.settings.Prefs;
|
||||
import in.yuvi.http.fluent.Http;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class EventLog {
|
||||
|
||||
|
|
@ -30,19 +30,19 @@ public class EventLog {
|
|||
boolean allSuccess = true;
|
||||
// Not using the default URL connection, since that seems to have different behavior than the rest of the code
|
||||
for(LogBuilder logBuilder: logBuilders) {
|
||||
HttpURLConnection conn;
|
||||
try {
|
||||
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) {
|
||||
allSuccess = false;
|
||||
}
|
||||
Log.d("Commons", "EventLog hit " + url.toString());
|
||||
Timber.d("EventLog hit %s", url);
|
||||
|
||||
} catch (IOException e) {
|
||||
// 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);
|
||||
fullData.put("event", data);
|
||||
return new URL(CommonsApplication.EVENTLOG_URL + "?" + Utils.urlEncode(fullData.toString()) + ";");
|
||||
} catch (MalformedURLException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (JSONException e) {
|
||||
} catch (MalformedURLException | JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,9 +69,8 @@ public class LicenseList {
|
|||
int nameId = stringIdByName(stringId);
|
||||
//Log.d("Commons", "LicenseList.nameForTemplate: nameId: " + nameId);
|
||||
if(nameId != 0) {
|
||||
String name = res.getString(nameId);
|
||||
//Log.d("Commons", "LicenseList.nameForTemplate: name: " + name);
|
||||
return name;
|
||||
return res.getString(nameId);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
public String getThumbnailUrl(int width) {
|
||||
return Utils.makeThumbUrl(getImageUrl(), getFilename(), width);
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
|
@ -144,6 +140,14 @@ public class Media implements Parcelable {
|
|||
this.license = license;
|
||||
}
|
||||
|
||||
public String getCoordinates() {
|
||||
return coordinates;
|
||||
}
|
||||
|
||||
public void setCoordinates(String coordinates) {
|
||||
this.coordinates = coordinates;
|
||||
}
|
||||
|
||||
// Primary metadata fields
|
||||
protected Uri localUri;
|
||||
protected String imageUrl;
|
||||
|
|
@ -155,6 +159,7 @@ public class Media implements Parcelable {
|
|||
protected int width;
|
||||
protected int height;
|
||||
protected String license;
|
||||
private String coordinates;
|
||||
protected String creator;
|
||||
protected ArrayList<String> categories; // as loaded at runtime?
|
||||
protected Map<String, String> descriptions; // multilingual descriptions as loaded
|
||||
|
|
@ -164,7 +169,7 @@ public class Media implements Parcelable {
|
|||
}
|
||||
|
||||
public void setCategories(List<String> categories) {
|
||||
this.categories.removeAll(this.categories);
|
||||
this.categories.clear();
|
||||
this.categories.addAll(categories);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
package fr.free.nrw.commons;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.mediawiki.api.ApiResult;
|
||||
import org.mediawiki.api.MWApi;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
|
|
@ -23,6 +20,9 @@ import javax.xml.parsers.DocumentBuilder;
|
|||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
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.
|
||||
*
|
||||
|
|
@ -31,14 +31,13 @@ import javax.xml.parsers.ParserConfigurationException;
|
|||
*/
|
||||
public class MediaDataExtractor {
|
||||
private boolean fetched;
|
||||
private boolean processed;
|
||||
|
||||
private String filename;
|
||||
private ArrayList<String> categories;
|
||||
private Map<String, String> descriptions;
|
||||
private String author;
|
||||
private Date date;
|
||||
private String license;
|
||||
private String coordinates;
|
||||
private LicenseList licenseList;
|
||||
|
||||
/**
|
||||
|
|
@ -49,7 +48,6 @@ public class MediaDataExtractor {
|
|||
categories = new ArrayList<>();
|
||||
descriptions = new HashMap<>();
|
||||
fetched = false;
|
||||
processed = false;
|
||||
this.licenseList = licenseList;
|
||||
}
|
||||
|
||||
|
|
@ -64,7 +62,7 @@ public class MediaDataExtractor {
|
|||
throw new IllegalStateException("Tried to call MediaDataExtractor.fetch() again.");
|
||||
}
|
||||
|
||||
MWApi api = CommonsApplication.createMWApi();
|
||||
MWApi api = CommonsApplication.getInstance().getMWApi();
|
||||
ApiResult result = api.action("query")
|
||||
.param("prop", "revisions")
|
||||
.param("titles", filename)
|
||||
|
|
@ -111,9 +109,7 @@ public class MediaDataExtractor {
|
|||
doc = docBuilder.parse(new ByteArrayInputStream(source.getBytes("UTF-8")));
|
||||
} catch (ParserConfigurationException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (IllegalStateException e) {
|
||||
throw new IOException(e);
|
||||
} catch (SAXException e) {
|
||||
} catch (IllegalStateException | SAXException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
Node templateNode = findTemplate(doc.getDocumentElement(), "information");
|
||||
|
|
@ -122,7 +118,14 @@ public class MediaDataExtractor {
|
|||
descriptions = getMultilingualText(descriptionNode);
|
||||
|
||||
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
|
||||
* 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");
|
||||
if (selfLicenseNode != null) {
|
||||
Node firstNode = findTemplateParameter(selfLicenseNode, 1);
|
||||
String licenseTemplate = getFlatText(firstNode);
|
||||
License license = licenseList.licenseForTemplate(licenseTemplate);
|
||||
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.
|
||||
} else {
|
||||
// fixme: record the self-ness in here too... sigh
|
||||
// all this needs better server-side metadata
|
||||
this.license = license.getKey();
|
||||
Log.d("Commons", "MediaDataExtractor found self-license " + this.license);
|
||||
Timber.d("MediaDataExtractor found self-license %s", this.license);
|
||||
}
|
||||
} else {
|
||||
for (License license : licenseList.values()) {
|
||||
|
|
@ -153,7 +156,7 @@ public class MediaDataExtractor {
|
|||
if (template != null) {
|
||||
// Found!
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -245,6 +248,25 @@ public class MediaDataExtractor {
|
|||
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.
|
||||
// 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.
|
||||
|
|
@ -290,6 +312,7 @@ public class MediaDataExtractor {
|
|||
|
||||
media.setCategories(categories);
|
||||
media.setDescriptions(descriptions);
|
||||
media.setCoordinates(coordinates);
|
||||
if (license != null) {
|
||||
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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import com.android.volley.VolleyError;
|
||||
import com.android.volley.toolbox.ImageLoader;
|
||||
import com.android.volley.toolbox.ImageLoader.ImageContainer;
|
||||
import com.android.volley.toolbox.ImageLoader.ImageListener;
|
||||
import com.facebook.drawee.view.SimpleDraweeView;
|
||||
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
|
||||
|
||||
public class MediaWikiImageView extends ImageView {
|
||||
|
||||
private Media mMedia;
|
||||
|
||||
private ImageLoader mImageLoader;
|
||||
|
||||
private ImageContainer mImageContainer;
|
||||
|
||||
private View loadingView;
|
||||
|
||||
private boolean isThumbnail;
|
||||
public class MediaWikiImageView extends SimpleDraweeView {
|
||||
private ThumbnailFetchTask currentThumbnailTask;
|
||||
|
||||
public MediaWikiImageView(Context context) {
|
||||
this(context, null);
|
||||
|
|
@ -49,179 +17,58 @@ public class MediaWikiImageView extends ImageView {
|
|||
|
||||
public MediaWikiImageView(Context context, AttributeSet attrs) {
|
||||
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) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
public void setMedia(Media media, ImageLoader imageLoader) {
|
||||
this.mMedia = media;
|
||||
mImageLoader = imageLoader;
|
||||
loadImageIfNecessary(false);
|
||||
public void setMedia(Media media) {
|
||||
if (currentThumbnailTask != null) {
|
||||
currentThumbnailTask.cancel(true);
|
||||
}
|
||||
|
||||
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) {
|
||||
if(media == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(mMedia == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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();
|
||||
if (CommonsApplication.getInstance().getThumbnailUrlCache().get(media.getFilename()) != null) {
|
||||
setImageUrl(CommonsApplication.getInstance().getThumbnailUrlCache().get(media.getFilename()));
|
||||
} else {
|
||||
// Round it to the nearest 320
|
||||
// Possible a similar size image has already been generated.
|
||||
// Reduces Server cache fragmentation, also increases chance of cache hit
|
||||
// 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);
|
||||
setImageUrl(null);
|
||||
currentThumbnailTask = new ThumbnailFetchTask(media);
|
||||
currentThumbnailTask.execute(media.getFilename());
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
protected void onDetachedFromWindow() {
|
||||
if (mImageContainer != null) {
|
||||
// If the view was bound to an image request, cancel it and clear
|
||||
// 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;
|
||||
if (currentThumbnailTask != null) {
|
||||
currentThumbnailTask.cancel(true);
|
||||
}
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
|
||||
private void setImageUrl(@Nullable String url) {
|
||||
setImageURI(url);
|
||||
}
|
||||
|
||||
private class ThumbnailFetchTask extends MediaThumbnailFetchTask {
|
||||
ThumbnailFetchTask(@NonNull Media media) {
|
||||
super(media);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void drawableStateChanged() {
|
||||
super.drawableStateChanged();
|
||||
invalidate();
|
||||
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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
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 android.preference.PreferenceManager;
|
||||
|
||||
import fr.free.nrw.commons.settings.Prefs;
|
||||
import timber.log.Timber;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
|
|
@ -44,8 +41,6 @@ import org.xmlpull.v1.XmlPullParserException;
|
|||
|
||||
public class Utils {
|
||||
|
||||
private static final String TAG = Utils.class.getName();
|
||||
|
||||
// Get SHA1 of file from input stream
|
||||
public static String getSHA1(InputStream is) {
|
||||
|
||||
|
|
@ -53,7 +48,7 @@ public class Utils {
|
|||
try {
|
||||
digest = MessageDigest.getInstance("SHA1");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.e(TAG, "Exception while getting Digest", e);
|
||||
Timber.e(e, "Exception while getting Digest");
|
||||
return "";
|
||||
}
|
||||
|
||||
|
|
@ -68,17 +63,17 @@ public class Utils {
|
|||
String output = bigInt.toString(16);
|
||||
// Fill to 40 chars
|
||||
output = String.format("%40s", output).replace(' ', '0');
|
||||
Log.i(TAG, "File SHA1: " + output);
|
||||
Timber.i("File SHA1: %s", output);
|
||||
|
||||
return output;
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "IO Exception", e);
|
||||
Timber.e(e, "IO Exception");
|
||||
return "";
|
||||
} finally {
|
||||
try {
|
||||
is.close();
|
||||
} 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;
|
||||
try {
|
||||
transformer = TransformerFactory.newInstance().newTransformer();
|
||||
} catch (TransformerConfigurationException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
} catch (TransformerFactoryConfigurationError e) {
|
||||
} catch (TransformerConfigurationException | TransformerFactoryConfigurationError e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
|
@ -145,26 +137,6 @@ public class Utils {
|
|||
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();
|
||||
|
||||
public static String urlEncode(String url) {
|
||||
|
|
@ -184,76 +156,64 @@ public class Utils {
|
|||
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) {
|
||||
return string.substring(0, 1).toUpperCase(Locale.getDefault()) + string.substring(1);
|
||||
}
|
||||
|
||||
public static String licenseTemplateFor(String license) {
|
||||
if (license.equals(Prefs.Licenses.CC_BY_3)) {
|
||||
switch (license) {
|
||||
case Prefs.Licenses.CC_BY_3:
|
||||
return "{{self|cc-by-3.0}}";
|
||||
} else if (license.equals(Prefs.Licenses.CC_BY_4)) {
|
||||
case Prefs.Licenses.CC_BY_4:
|
||||
return "{{self|cc-by-4.0}}";
|
||||
} else if (license.equals(Prefs.Licenses.CC_BY_SA_3)) {
|
||||
case Prefs.Licenses.CC_BY_SA_3:
|
||||
return "{{self|cc-by-sa-3.0}}";
|
||||
} else if (license.equals(Prefs.Licenses.CC_BY_SA_4)) {
|
||||
case Prefs.Licenses.CC_BY_SA_4:
|
||||
return "{{self|cc-by-sa-4.0}}";
|
||||
} else if (license.equals(Prefs.Licenses.CC0)) {
|
||||
case Prefs.Licenses.CC0:
|
||||
return "{{self|cc-zero}}";
|
||||
} else if (license.equals(Prefs.Licenses.CC_BY)) {
|
||||
case Prefs.Licenses.CC_BY:
|
||||
return "{{self|cc-by-3.0}}";
|
||||
} else if (license.equals(Prefs.Licenses.CC_BY_SA)) {
|
||||
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) {
|
||||
if (license.equals(Prefs.Licenses.CC_BY_3)) {
|
||||
switch (license) {
|
||||
case Prefs.Licenses.CC_BY_3:
|
||||
return R.string.license_name_cc_by;
|
||||
} else if (license.equals(Prefs.Licenses.CC_BY_4)) {
|
||||
case Prefs.Licenses.CC_BY_4:
|
||||
return R.string.license_name_cc_by_four;
|
||||
} else if (license.equals(Prefs.Licenses.CC_BY_SA_3)) {
|
||||
case Prefs.Licenses.CC_BY_SA_3:
|
||||
return R.string.license_name_cc_by_sa;
|
||||
} else if (license.equals(Prefs.Licenses.CC_BY_SA_4)) {
|
||||
case Prefs.Licenses.CC_BY_SA_4:
|
||||
return R.string.license_name_cc_by_sa_four;
|
||||
} else if (license.equals(Prefs.Licenses.CC0)) {
|
||||
case Prefs.Licenses.CC0:
|
||||
return R.string.license_name_cc0;
|
||||
} else if (license.equals(Prefs.Licenses.CC_BY)) { // for backward compatibility to v2.1
|
||||
case Prefs.Licenses.CC_BY: // for backward compatibility to v2.1
|
||||
return R.string.license_name_cc_by_3_0;
|
||||
} else if (license.equals(Prefs.Licenses.CC_BY_SA)) { // for backward compatibility to v2.1
|
||||
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) {
|
||||
if (license.equals(Prefs.Licenses.CC_BY_3)) {
|
||||
switch (license) {
|
||||
case Prefs.Licenses.CC_BY_3:
|
||||
return "https://creativecommons.org/licenses/by/3.0/";
|
||||
} else if (license.equals(Prefs.Licenses.CC_BY_4)) {
|
||||
case Prefs.Licenses.CC_BY_4:
|
||||
return "https://creativecommons.org/licenses/by/4.0/";
|
||||
} else if (license.equals(Prefs.Licenses.CC_BY_SA_3)) {
|
||||
case Prefs.Licenses.CC_BY_SA_3:
|
||||
return "https://creativecommons.org/licenses/by-sa/3.0/";
|
||||
} else if (license.equals(Prefs.Licenses.CC_BY_SA_4)) {
|
||||
case Prefs.Licenses.CC_BY_SA_4:
|
||||
return "https://creativecommons.org/licenses/by-sa/4.0/";
|
||||
} else if (license.equals(Prefs.Licenses.CC0)) {
|
||||
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) {
|
||||
|
|
@ -308,4 +268,12 @@ public class Utils {
|
|||
public static boolean isNullOrWhiteSpace(String value) {
|
||||
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;
|
||||
|
||||
public class WelcomeActivity extends BaseActivity {
|
||||
private WelcomePagerAdapter adapter;
|
||||
|
||||
@BindView(R.id.welcomePager) ViewPager pager;
|
||||
@BindView(R.id.welcomePagerIndicator) CirclePageIndicator indicator;
|
||||
|
|
@ -20,14 +19,16 @@ public class WelcomeActivity extends BaseActivity {
|
|||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_welcome);
|
||||
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().hide();
|
||||
}
|
||||
ButterKnife.bind(this);
|
||||
|
||||
setUpAdapter();
|
||||
}
|
||||
|
||||
private void setUpAdapter() {
|
||||
adapter = new WelcomePagerAdapter(this);
|
||||
WelcomePagerAdapter adapter = new WelcomePagerAdapter(this);
|
||||
pager.setAdapter(adapter);
|
||||
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 fr.free.nrw.commons.theme.BaseActivity;
|
||||
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;
|
||||
CommonsApplication app;
|
||||
|
||||
private String authCookie;
|
||||
|
||||
public AuthenticatedActivity(String accountType) {
|
||||
this.accountType = accountType;
|
||||
public AuthenticatedActivity() {
|
||||
this.accountType = AccountUtil.accountType();
|
||||
}
|
||||
|
||||
private class GetAuthCookieTask extends AsyncTask<Void, String, String> {
|
||||
|
|
@ -131,7 +131,7 @@ public abstract class AuthenticatedActivity extends BaseActivity {
|
|||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
app = (CommonsApplication)this.getApplicationContext();
|
||||
app = CommonsApplication.getInstance();
|
||||
if(savedInstanceState != null) {
|
||||
authCookie = savedInstanceState.getString("authCookie");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,171 +1,78 @@
|
|||
package fr.free.nrw.commons.auth;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountAuthenticatorActivity;
|
||||
import android.accounts.AccountAuthenticatorResponse;
|
||||
import android.accounts.AccountManager;
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
import android.widget.Toast;
|
||||
|
||||
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.*;
|
||||
import fr.free.nrw.commons.contributions.ContributionsActivity;
|
||||
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
|
||||
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
||||
public class LoginActivity extends AccountAuthenticatorActivity {
|
||||
|
||||
public static final String PARAM_USERNAME = "fr.free.nrw.commons.login.username";
|
||||
|
||||
private CommonsApplication app;
|
||||
|
||||
private SharedPreferences prefs = null;
|
||||
|
||||
Button loginButton;
|
||||
Button signupButton;
|
||||
EditText usernameEdit;
|
||||
private Button loginButton;
|
||||
private EditText usernameEdit;
|
||||
EditText passwordEdit;
|
||||
ProgressDialog dialog;
|
||||
EditText twoFactorEdit;
|
||||
ProgressDialog progressDialog;
|
||||
|
||||
private class LoginTask extends AsyncTask<String, String, String> {
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
||||
private CommonsApplication app;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
app = (CommonsApplication) this.getApplicationContext();
|
||||
|
||||
app = CommonsApplication.getInstance();
|
||||
|
||||
setContentView(R.layout.activity_login);
|
||||
final LoginActivity that = this;
|
||||
|
||||
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);
|
||||
passwordEdit = (EditText) findViewById(R.id.loginPassword);
|
||||
final LoginActivity that = this;
|
||||
twoFactorEdit = (EditText) findViewById(R.id.loginTwoFactor);
|
||||
|
||||
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
|
||||
public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) { }
|
||||
|
||||
|
|
@ -174,17 +81,21 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
|||
|
||||
@Override
|
||||
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);
|
||||
} else {
|
||||
loginButton.setEnabled(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
usernameEdit.addTextChangedListener(loginEnabler);
|
||||
passwordEdit.addTextChangedListener(loginEnabler);
|
||||
passwordEdit.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
private TextView.OnEditorActionListener newLoginInputActionListener() {
|
||||
return new TextView.OnEditorActionListener() {
|
||||
@Override
|
||||
public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
|
||||
if (loginButton.isEnabled()) {
|
||||
|
|
@ -198,36 +109,31 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
loginButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
that.performLogin();
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (prefs.getBoolean("firstrun", true)) {
|
||||
// Do first run stuff here then set 'firstrun' as false
|
||||
Intent welcomeIntent = new Intent(this, WelcomeActivity.class);
|
||||
startActivity(welcomeIntent);
|
||||
this.startWelcomeIntent();
|
||||
prefs.edit().putBoolean("firstrun", false).apply();
|
||||
}
|
||||
if (app.getCurrentAccount() != null) {
|
||||
startMainActivity();
|
||||
}
|
||||
}
|
||||
|
||||
private void startWelcomeIntent() {
|
||||
Intent welcomeIntent = new Intent(this, WelcomeActivity.class);
|
||||
startActivity(welcomeIntent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
try {
|
||||
// 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()) {
|
||||
dialog.dismiss();
|
||||
if (progressDialog != null && progressDialog.isShowing()) {
|
||||
progressDialog.dismiss();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
|
|
@ -236,15 +142,27 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
|||
}
|
||||
|
||||
private void performLogin() {
|
||||
String username = usernameEdit.getText().toString();
|
||||
// Because Mediawiki is upercase-first-char-then-case-sensitive :)
|
||||
String canonicalUsername = Utils.capitalize(username.substring(0,1)) + username.substring(1);
|
||||
Timber.d("Login to start!");
|
||||
LoginTask task = getLoginTask();
|
||||
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);
|
||||
task.execute(canonicalUsername, password);
|
||||
/**
|
||||
* Because Mediawiki is upercase-first-char-then-case-sensitive :)
|
||||
* @param username String
|
||||
* @return String canonicial username
|
||||
*/
|
||||
private String canonicializeUsername( String username ) {
|
||||
return Utils.capitalize(username.substring(0,1)) + username.substring(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -257,9 +175,48 @@ public class LoginActivity extends AccountAuthenticatorActivity {
|
|||
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) {
|
||||
Intent intent = new Intent(this, SignupActivity.class);
|
||||
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.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.Toast;
|
||||
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.theme.BaseActivity;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class SignupActivity extends BaseActivity {
|
||||
|
||||
|
|
@ -17,9 +18,7 @@ public class SignupActivity extends BaseActivity {
|
|||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Log.d("SignupActivity", "Signup Activity started");
|
||||
|
||||
getSupportActionBar().hide();
|
||||
Timber.d("Signup Activity started");
|
||||
|
||||
webView = new WebView(this);
|
||||
setContentView(webView);
|
||||
|
|
@ -37,17 +36,21 @@ public class SignupActivity extends BaseActivity {
|
|||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
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
|
||||
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();
|
||||
|
||||
Intent intent = new Intent(getApplicationContext(), LoginActivity.class);
|
||||
Intent intent = new Intent(CommonsApplication.getInstance(), LoginActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
} else {
|
||||
//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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,44 +9,76 @@ import android.content.Context;
|
|||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.mediawiki.api.MWApi;
|
||||
|
||||
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.MWApi;
|
||||
|
||||
public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
|
||||
|
||||
public static final String COMMONS_ACCOUNT_TYPE = "fr.free.nrw.commons";
|
||||
private Context context;
|
||||
|
||||
public WikiAccountAuthenticator(Context context) {
|
||||
super(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
|
||||
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
|
||||
final Intent intent = new Intent(context, LoginActivity.class);
|
||||
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
|
||||
final Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
|
||||
public Bundle addAccount(@NonNull AccountAuthenticatorResponse response,
|
||||
@NonNull String accountType, @Nullable String authTokenType,
|
||||
@Nullable String[] requiredFeatures, @Nullable Bundle options)
|
||||
throws NetworkErrorException {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException {
|
||||
return null;
|
||||
public Bundle confirmCredentials(@NonNull AccountAuthenticatorResponse response,
|
||||
@NonNull Account account, @Nullable Bundle options)
|
||||
throws NetworkErrorException {
|
||||
return unsupportedOperation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
|
||||
return null;
|
||||
return unsupportedOperation();
|
||||
}
|
||||
|
||||
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);
|
||||
if(result.equals("Success")) {
|
||||
if(result.equals("PASS")) {
|
||||
return api.getAuthCookie();
|
||||
} else {
|
||||
return null;
|
||||
|
|
@ -70,7 +102,7 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
|
|||
if (authCookie != null) {
|
||||
final Bundle result = new Bundle();
|
||||
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);
|
||||
return result;
|
||||
}
|
||||
|
|
@ -87,21 +119,31 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
|
|||
return bundle;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@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;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException {
|
||||
final Bundle result = new Bundle();
|
||||
result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
|
||||
return result;
|
||||
public Bundle hasFeatures(@NonNull AccountAuthenticatorResponse response,
|
||||
@NonNull Account account, @NonNull String[] features)
|
||||
throws NetworkErrorException {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
|
||||
return null;
|
||||
public Bundle updateCredentials(@NonNull AccountAuthenticatorResponse response,
|
||||
@NonNull Account account, @Nullable String authTokenType,
|
||||
@Nullable Bundle options)
|
||||
throws NetworkErrorException {
|
||||
return unsupportedOperation();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
package fr.free.nrw.commons.caching;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.github.varunpant.quadtree.Point;
|
||||
import com.github.varunpant.quadtree.QuadTree;
|
||||
|
||||
|
|
@ -10,15 +8,14 @@ import java.util.Arrays;
|
|||
import java.util.List;
|
||||
|
||||
import fr.free.nrw.commons.upload.MwVolleyApi;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class CacheController {
|
||||
|
||||
private double x, y;
|
||||
private QuadTree<List<String>> quadTree;
|
||||
private Point<List<String>>[] pointsFound;
|
||||
private double xMinus, xPlus, yMinus, yPlus;
|
||||
|
||||
private static final String TAG = CacheController.class.getName();
|
||||
private static final int EARTH_RADIUS = 6378137;
|
||||
|
||||
public CacheController() {
|
||||
|
|
@ -28,40 +25,41 @@ public class CacheController {
|
|||
public void setQtPoint(double decLongitude, double decLatitude) {
|
||||
x = decLongitude;
|
||||
y = decLatitude;
|
||||
Log.d(TAG, "New QuadTree created");
|
||||
Log.d(TAG, "X (longitude) value: " + x + ", Y (latitude) value: " + y);
|
||||
Timber.d("New QuadTree created");
|
||||
Timber.d("X (longitude) value: %f, Y (latitude) value: %f", x, y);
|
||||
}
|
||||
|
||||
public void cacheCategory() {
|
||||
List<String> pointCatList = new ArrayList<>();
|
||||
if (MwVolleyApi.GpsCatExists.getGpsCatExists() == true) {
|
||||
if (MwVolleyApi.GpsCatExists.getGpsCatExists()) {
|
||||
pointCatList.addAll(MwVolleyApi.getGpsCat());
|
||||
Log.d(TAG, "Categories being cached: " + pointCatList);
|
||||
Timber.d("Categories being cached: %s", pointCatList);
|
||||
} 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);
|
||||
}
|
||||
|
||||
public List<String> findCategory() {
|
||||
Point<List<String>>[] pointsFound;
|
||||
//Convert decLatitude and decLongitude to a coordinate offset range
|
||||
convertCoordRange();
|
||||
pointsFound = quadTree.searchWithin(xMinus, yMinus, xPlus, yPlus);
|
||||
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) {
|
||||
Log.d(TAG, "Entering for loop");
|
||||
Timber.d("Entering for loop");
|
||||
|
||||
for (Point<List<String>> point : pointsFound) {
|
||||
Log.d(TAG, "Nearby point: " + point.toString());
|
||||
Timber.d("Nearby point: %s", point);
|
||||
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 {
|
||||
Log.d(TAG, "No categories found in cache");
|
||||
Timber.d("No categories found in cache");
|
||||
}
|
||||
return displayCatList;
|
||||
}
|
||||
|
|
@ -84,6 +82,7 @@ public class CacheController {
|
|||
yMinus = lat - dLat * 180/Math.PI;
|
||||
xPlus = 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 {
|
||||
|
||||
private Context context;
|
||||
private LayoutInflater mInflater;
|
||||
|
||||
private ArrayList<CategorizationFragment.CategoryItem> items;
|
||||
|
||||
public CategoriesAdapter(Context context, ArrayList<CategorizationFragment.CategoryItem> items) {
|
||||
this.context = context;
|
||||
this.items = items;
|
||||
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.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
|
|
@ -44,6 +43,7 @@ import java.util.concurrent.TimeUnit;
|
|||
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.upload.MwVolleyApi;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* Displays the category suggestion and selection screen. Category search is initiated here.
|
||||
|
|
@ -79,7 +79,6 @@ public class CategorizationFragment extends Fragment {
|
|||
private ContentProviderClient client;
|
||||
|
||||
protected final static int SEARCH_CATS_LIMIT = 25;
|
||||
private static final String TAG = CategorizationFragment.class.getName();
|
||||
|
||||
public static class CategoryItem implements Parcelable {
|
||||
public String name;
|
||||
|
|
@ -130,28 +129,28 @@ public class CategorizationFragment extends Fragment {
|
|||
//Retrieve the title that was saved when user tapped submit icon
|
||||
SharedPreferences titleDesc = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
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
|
||||
titleCategoriesSub = new TitleCategories(title) {
|
||||
@Override
|
||||
protected void onPostExecute(ArrayList<String> result) {
|
||||
super.onPostExecute(result);
|
||||
Log.d(TAG, "Results in onPostExecute: " + result);
|
||||
Timber.d("Results in onPostExecute: %s", result);
|
||||
titleCatItems.addAll(result);
|
||||
Log.d(TAG, "TitleCatItems in onPostExecute: " + titleCatItems);
|
||||
Timber.d("TitleCatItems in onPostExecute: %s", titleCatItems);
|
||||
mergeLatch.countDown();
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
try {
|
||||
mergeLatch.await(5L, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(TAG, "Interrupted exception: ", e);
|
||||
Timber.e(e, "Interrupted exception: ");
|
||||
}
|
||||
return titleCatItems;
|
||||
}
|
||||
|
|
@ -191,7 +190,7 @@ public class CategorizationFragment extends Fragment {
|
|||
|
||||
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<>();
|
||||
if (MwVolleyApi.GpsCatExists.getGpsCatExists()) {
|
||||
|
|
@ -203,22 +202,22 @@ public class CategorizationFragment extends Fragment {
|
|||
//Await results of titleItems, which is likely to come in last
|
||||
try {
|
||||
mergeLatch.await(5L, TimeUnit.SECONDS);
|
||||
Log.d(TAG, "Waited for merge");
|
||||
Timber.d("Waited for merge");
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(TAG, "Interrupted Exception: ", e);
|
||||
Timber.e(e, "Interrupted Exception: ");
|
||||
}
|
||||
|
||||
mergedItems.addAll(gpsItems);
|
||||
Log.d(TAG, "Adding GPS items: " + gpsItems);
|
||||
Timber.d("Adding GPS items: %s", gpsItems);
|
||||
mergedItems.addAll(titleItems);
|
||||
Log.d(TAG, "Adding title items: " + titleItems);
|
||||
Timber.d("Adding title items: %s", titleItems);
|
||||
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
|
||||
ArrayList<String> mergedItemsList = new ArrayList<>(mergedItems);
|
||||
|
||||
Log.d(TAG, "Merged item list: " + mergedItemsList);
|
||||
Timber.d("Merged item list: %s", mergedItemsList);
|
||||
return mergedItemsList;
|
||||
}
|
||||
|
||||
|
|
@ -261,7 +260,7 @@ public class CategorizationFragment extends Fragment {
|
|||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Log.w(TAG, e);
|
||||
Timber.w(e);
|
||||
//Thread.currentThread().interrupt();
|
||||
}
|
||||
return result;
|
||||
|
|
@ -296,12 +295,12 @@ public class CategorizationFragment extends Fragment {
|
|||
super.onPostExecute(result);
|
||||
|
||||
results.addAll(result);
|
||||
Log.d(TAG, "Prefix result: " + result);
|
||||
Timber.d("Prefix result: %s", result);
|
||||
|
||||
String filter = categoriesFilter.getText().toString();
|
||||
ArrayList<String> resultsList = new ArrayList<>(results);
|
||||
categoriesCache.put(filter, resultsList);
|
||||
Log.d(TAG, "Final results List: " + resultsList);
|
||||
Timber.d("Final results List: %s", resultsList);
|
||||
|
||||
categoriesAdapter.notifyDataSetChanged();
|
||||
setCatsAfterAsync(resultsList, filter);
|
||||
|
|
@ -315,7 +314,7 @@ public class CategorizationFragment extends Fragment {
|
|||
super.onPostExecute(result);
|
||||
|
||||
results.addAll(result);
|
||||
Log.d(TAG, "Method A result: " + result);
|
||||
Timber.d("Method A result: %s", result);
|
||||
categoriesAdapter.notifyDataSetChanged();
|
||||
|
||||
latch.countDown();
|
||||
|
|
@ -358,8 +357,7 @@ public class CategorizationFragment extends Fragment {
|
|||
new String[] {name},
|
||||
null);
|
||||
if (cursor.moveToFirst()) {
|
||||
Category cat = Category.fromCursor(cursor);
|
||||
return cat;
|
||||
return Category.fromCursor(cursor);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
// 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());
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
if(from == to) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -8,9 +8,10 @@ import android.database.sqlite.SQLiteDatabase;
|
|||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.data.DBOpenHelper;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class CategoryContentProvider extends ContentProvider {
|
||||
|
||||
|
|
@ -36,7 +37,7 @@ public class CategoryContentProvider extends ContentProvider {
|
|||
private DBOpenHelper dbOpenHelper;
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
dbOpenHelper = DBOpenHelper.getInstance(getContext());
|
||||
dbOpenHelper = CommonsApplication.getInstance().getDBOpenHelper();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -101,14 +102,14 @@ public class CategoryContentProvider extends ContentProvider {
|
|||
|
||||
@Override
|
||||
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);
|
||||
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
|
||||
sqlDB.beginTransaction();
|
||||
switch (uriType) {
|
||||
case CATEGORIES:
|
||||
for(ContentValues value: values) {
|
||||
Log.d("Commons", "Inserting! " + value.toString());
|
||||
Timber.d("Inserting! %s", value);
|
||||
sqlDB.insert(Category.Table.TABLE_NAME, null, value);
|
||||
}
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
package fr.free.nrw.commons.category;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import fr.free.nrw.commons.MWApi;
|
||||
import org.mediawiki.api.ApiResult;
|
||||
import org.mediawiki.api.MWApi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -13,6 +12,7 @@ import java.util.Calendar;
|
|||
import java.util.Iterator;
|
||||
|
||||
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
|
||||
|
|
@ -22,7 +22,6 @@ import fr.free.nrw.commons.CommonsApplication;
|
|||
public class MethodAUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
||||
|
||||
private String filter;
|
||||
private static final String TAG = MethodAUpdater.class.getName();
|
||||
CategorizationFragment catFragment;
|
||||
|
||||
public MethodAUpdater(CategorizationFragment catFragment) {
|
||||
|
|
@ -54,11 +53,11 @@ public class MethodAUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
|||
Calendar now = Calendar.getInstance();
|
||||
int year = now.get(Calendar.YEAR);
|
||||
String yearInString = String.valueOf(year);
|
||||
Log.d(TAG, "Year: " + yearInString);
|
||||
Timber.d("Year: %s", yearInString);
|
||||
|
||||
int prevYear = year - 1;
|
||||
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
|
||||
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)
|
||||
//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)) {
|
||||
Log.d(TAG, "Filtering out year " + s);
|
||||
Timber.d("Filtering out year %s", s);
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(TAG, "Items: " + items.toString());
|
||||
Timber.d("Items: %s", items);
|
||||
return items;
|
||||
}
|
||||
|
||||
|
|
@ -80,7 +79,7 @@ public class MethodAUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
|||
protected ArrayList<String> doInBackground(Void... voids) {
|
||||
|
||||
//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;
|
||||
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("srsearch", filter)
|
||||
.get();
|
||||
Log.d(TAG, "Method A URL filter" + result.toString());
|
||||
Timber.d("Method A URL filter %s", result);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "IO Exception: ", e);
|
||||
Timber.e(e, "IO Exception: ");
|
||||
//Return empty arraylist
|
||||
return categories;
|
||||
}
|
||||
|
|
@ -108,8 +107,7 @@ public class MethodAUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
|||
categories.add(catString);
|
||||
}
|
||||
|
||||
Log.d(TAG, "Found categories from Method A search, waiting for filter");
|
||||
ArrayList<String> filteredItems = new ArrayList<>(filterYears(categories));
|
||||
return filteredItems;
|
||||
Timber.d("Found categories from Method A search, waiting for filter");
|
||||
return new ArrayList<>(filterYears(categories));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,10 @@ package fr.free.nrw.commons.category;
|
|||
|
||||
import android.os.AsyncTask;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import fr.free.nrw.commons.MWApi;
|
||||
import org.mediawiki.api.ApiResult;
|
||||
import org.mediawiki.api.MWApi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -14,6 +13,7 @@ import java.util.Calendar;
|
|||
import java.util.Iterator;
|
||||
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* 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>> {
|
||||
|
||||
private String filter;
|
||||
private static final String TAG = PrefixUpdater.class.getName();
|
||||
private CategorizationFragment catFragment;
|
||||
|
||||
public PrefixUpdater(CategorizationFragment catFragment) {
|
||||
|
|
@ -56,11 +55,11 @@ public class PrefixUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
|||
Calendar now = Calendar.getInstance();
|
||||
int year = now.get(Calendar.YEAR);
|
||||
String yearInString = String.valueOf(year);
|
||||
Log.d(TAG, "Year: " + yearInString);
|
||||
Timber.d("Year: %s", yearInString);
|
||||
|
||||
int prevYear = year - 1;
|
||||
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
|
||||
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)
|
||||
//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)) {
|
||||
Log.d(TAG, "Filtering out year " + s);
|
||||
Timber.d("Filtering out year %s", s);
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(TAG, "Items: " + items.toString());
|
||||
Timber.d("Items: %s", 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(TextUtils.isEmpty(filter)) {
|
||||
ArrayList<String> mergedItems = new ArrayList<>(catFragment.mergeItems());
|
||||
Log.d(TAG, "Merged items, waiting for filter");
|
||||
ArrayList<String> filteredItems = new ArrayList<>(filterYears(mergedItems));
|
||||
return filteredItems;
|
||||
Timber.d("Merged items, waiting for filter");
|
||||
return new ArrayList<>(filterYears(mergedItems));
|
||||
}
|
||||
|
||||
//if user types in something that is in cache, return cached category
|
||||
if(catFragment.categoriesCache.containsKey(filter)) {
|
||||
ArrayList<String> cachedItems = new ArrayList<>(catFragment.categoriesCache.get(filter));
|
||||
Log.d(TAG, "Found cache items, waiting for filter");
|
||||
ArrayList<String> filteredItems = new ArrayList<>(filterYears(cachedItems));
|
||||
return filteredItems;
|
||||
Timber.d("Found cache items, waiting for filter");
|
||||
return new ArrayList<>(filterYears(cachedItems));
|
||||
}
|
||||
|
||||
//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
|
||||
MWApi api = CommonsApplication.createMWApi();
|
||||
MWApi api = CommonsApplication.getInstance().getMWApi();
|
||||
ApiResult result;
|
||||
ArrayList<String> categories = new ArrayList<>();
|
||||
try {
|
||||
|
|
@ -107,9 +104,9 @@ public class PrefixUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
|||
.param("acprefix", filter)
|
||||
.param("aclimit", catFragment.SEARCH_CATS_LIMIT)
|
||||
.get();
|
||||
Log.d(TAG, "Prefix URL filter" + result.toString());
|
||||
Timber.d("Prefix URL filter %s", result);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "IO Exception: ", e);
|
||||
Timber.e(e, "IO Exception: ");
|
||||
//Return empty arraylist
|
||||
return categories;
|
||||
}
|
||||
|
|
@ -119,8 +116,7 @@ public class PrefixUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
|
|||
categories.add(categoryNode.getDocument().getTextContent());
|
||||
}
|
||||
|
||||
Log.d(TAG, "Found categories from Prefix search, waiting for filter");
|
||||
ArrayList<String> filteredItems = new ArrayList<>(filterYears(categories));
|
||||
return filteredItems;
|
||||
Timber.d("Found categories from Prefix search, waiting for filter");
|
||||
return new ArrayList<>(filterYears(categories));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
package fr.free.nrw.commons.category;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
import android.util.Log;
|
||||
|
||||
import fr.free.nrw.commons.MWApi;
|
||||
import org.mediawiki.api.ApiResult;
|
||||
import org.mediawiki.api.MWApi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
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
|
||||
|
|
@ -19,7 +19,7 @@ import fr.free.nrw.commons.CommonsApplication;
|
|||
public class TitleCategories extends AsyncTask<Void, Void, ArrayList<String>> {
|
||||
|
||||
private final static int SEARCH_CATS_LIMIT = 25;
|
||||
private static final String TAG = TitleCategories.class.getName();
|
||||
|
||||
private String title;
|
||||
|
||||
public TitleCategories(String title) {
|
||||
|
|
@ -34,7 +34,7 @@ public class TitleCategories extends AsyncTask<Void, Void, ArrayList<String>> {
|
|||
@Override
|
||||
protected ArrayList<String> doInBackground(Void... voids) {
|
||||
|
||||
MWApi api = CommonsApplication.createMWApi();
|
||||
MWApi api = CommonsApplication.getInstance().getMWApi();
|
||||
ApiResult result;
|
||||
ArrayList<String> items = new ArrayList<>();
|
||||
|
||||
|
|
@ -48,9 +48,9 @@ public class TitleCategories extends AsyncTask<Void, Void, ArrayList<String>> {
|
|||
.param("srlimit", SEARCH_CATS_LIMIT)
|
||||
.param("srsearch", title)
|
||||
.get();
|
||||
Log.d(TAG, "Searching for cats for title: " + result.toString());
|
||||
Timber.d("Searching for cats for title: %s", result);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "IO Exception: ", e);
|
||||
Timber.e(e, "IO Exception: ");
|
||||
//Return empty arraylist
|
||||
return items;
|
||||
}
|
||||
|
|
@ -62,7 +62,7 @@ public class TitleCategories extends AsyncTask<Void, Void, ArrayList<String>> {
|
|||
items.add(catString);
|
||||
}
|
||||
|
||||
Log.d(TAG, "Title cat query results: " + items);
|
||||
Timber.d("Title cat query results: %s", items);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,8 +44,6 @@ public class Contribution extends Media {
|
|||
public static final String SOURCE_GALLERY = "gallery";
|
||||
public static final String SOURCE_EXTERNAL = "external";
|
||||
|
||||
private static final String TAG = "Contribution";
|
||||
|
||||
private ContentProviderClient client;
|
||||
private Uri contentUri;
|
||||
private String source;
|
||||
|
|
@ -197,7 +195,7 @@ public class Contribution extends Media {
|
|||
cv.put(Table.COLUMN_LOCAL_URI, getLocalUri().toString());
|
||||
}
|
||||
if(getImageUrl() != null) {
|
||||
cv.put(Table.COLUMN_IMAGE_URL, getImageUrl().toString());
|
||||
cv.put(Table.COLUMN_IMAGE_URL, getImageUrl());
|
||||
}
|
||||
if(getDateUploaded() != null) {
|
||||
cv.put(Table.COLUMN_UPLOADED, getDateUploaded().getTime());
|
||||
|
|
@ -333,6 +331,11 @@ public class Contribution extends Media {
|
|||
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) {
|
||||
if(from == to) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import android.os.Bundle;
|
|||
import android.os.Environment;
|
||||
import android.provider.MediaStore;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
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.UploadService;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class ContributionController {
|
||||
private Fragment fragment;
|
||||
|
|
@ -38,13 +38,13 @@ public class ContributionController {
|
|||
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Commons/images/" + new Date().getTime() + ".jpg";
|
||||
File _photoFile = new File(path);
|
||||
try {
|
||||
if(_photoFile.exists() == false) {
|
||||
if(!_photoFile.exists()) {
|
||||
_photoFile.getParentFile().mkdirs();
|
||||
_photoFile.createNewFile();
|
||||
}
|
||||
|
||||
} 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);
|
||||
|
|
@ -84,11 +84,11 @@ public class ContributionController {
|
|||
shareIntent.putExtra(UploadService.EXTRA_SOURCE, Contribution.SOURCE_CAMERA);
|
||||
break;
|
||||
}
|
||||
Log.i("Image", "Image selected");
|
||||
Timber.i("Image selected");
|
||||
try {
|
||||
activity.startActivity(shareIntent);
|
||||
} catch (SecurityException e) {
|
||||
Log.e("ContributionController", "Security Exception", e);
|
||||
Timber.e(e, "Security Exception");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@ class ContributionViewHolder {
|
|||
final TextView seqNumView;
|
||||
final ProgressBar progressView;
|
||||
|
||||
String url;
|
||||
|
||||
ContributionViewHolder(View parent) {
|
||||
imageView = (MediaWikiImageView) parent.findViewById(R.id.contributionImage);
|
||||
titleView = (TextView)parent.findViewById(R.id.contributionTitle);
|
||||
|
|
|
|||
|
|
@ -5,16 +5,17 @@ import android.content.ContentResolver;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.database.DataSetObserver;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
|
@ -22,16 +23,19 @@ import android.widget.Adapter;
|
|||
import android.widget.AdapterView;
|
||||
|
||||
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.HandlerService;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
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.settings.Prefs;
|
||||
import fr.free.nrw.commons.upload.UploadService;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class ContributionsActivity
|
||||
extends AuthenticatedActivity
|
||||
|
|
@ -39,7 +43,8 @@ public class ContributionsActivity
|
|||
AdapterView.OnItemClickListener,
|
||||
MediaDetailPagerFragment.MediaDetailProvider,
|
||||
FragmentManager.OnBackStackChangedListener,
|
||||
ContributionsListFragment.SourceRefresher {
|
||||
ContributionsListFragment.SourceRefresher,
|
||||
HamburgerMenuContainer {
|
||||
|
||||
private Cursor allContributions;
|
||||
private ContributionsListFragment contributionsList;
|
||||
|
|
@ -48,6 +53,7 @@ public class ContributionsActivity
|
|||
private boolean isUploadServiceConnected;
|
||||
private ArrayList<DataSetObserver> observersWaitingForLoad = new ArrayList<>();
|
||||
private String CONTRIBUTION_SELECTION = "";
|
||||
|
||||
/*
|
||||
This sorts in the following order:
|
||||
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 + ")";
|
||||
|
||||
public ContributionsActivity() {
|
||||
super(WikiAccountAuthenticator.COMMONS_ACCOUNT_TYPE);
|
||||
}
|
||||
|
||||
private ServiceConnection uploadServiceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName componentName, IBinder binder) {
|
||||
|
|
@ -88,6 +90,15 @@ public class ContributionsActivity
|
|||
@Override
|
||||
protected void 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
|
||||
|
|
@ -98,7 +109,7 @@ public class ContributionsActivity
|
|||
@Override
|
||||
protected void onAuthCookieAcquired(String authCookie) {
|
||||
// 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);
|
||||
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
|
||||
startService(uploadServiceIntent);
|
||||
|
|
@ -112,23 +123,22 @@ public class ContributionsActivity
|
|||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setTitle(R.string.title_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()
|
||||
contributionsList = (ContributionsListFragment)getSupportFragmentManager().findFragmentById(R.id.contributionsListFragment);
|
||||
// Activity can call methods in the fragment by acquiring a
|
||||
// reference to the Fragment from FragmentManager, using findFragmentById()
|
||||
contributionsList = (ContributionsListFragment)getSupportFragmentManager()
|
||||
.findFragmentById(R.id.contributionsListFragment);
|
||||
|
||||
getSupportFragmentManager().addOnBackStackChangedListener(this);
|
||||
if (savedInstanceState != null) {
|
||||
mediaDetails = (MediaDetailPagerFragment)getSupportFragmentManager().findFragmentById(R.id.contributionsFragmentContainer);
|
||||
// onBackStackChanged uses mediaDetails.isVisible() but this returns false now.
|
||||
// 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);
|
||||
}
|
||||
mediaDetails = (MediaDetailPagerFragment)getSupportFragmentManager()
|
||||
.findFragmentById(R.id.contributionsFragmentContainer);
|
||||
}
|
||||
requestAuthToken();
|
||||
initDrawer();
|
||||
setTitle(getString(R.string.title_activity_contributions));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -158,9 +168,9 @@ public class ContributionsActivity
|
|||
Contribution c = Contribution.fromCursor(allContributions);
|
||||
if(c.getState() == Contribution.STATE_FAILED) {
|
||||
uploadService.queue(UploadService.ACTION_UPLOAD_FILE, c);
|
||||
Log.d("Commons", "Restarting for" + c.toContentValues().toString());
|
||||
Timber.d("Restarting for %s", c.toContentValues());
|
||||
} 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);
|
||||
Contribution c = Contribution.fromCursor(allContributions);
|
||||
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.delete();
|
||||
} 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
|
||||
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
|
||||
|
|
@ -217,7 +232,18 @@ public class ContributionsActivity
|
|||
((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();
|
||||
notifyAndMigrateDataSetObservers();
|
||||
|
|
@ -289,15 +315,16 @@ public class ContributionsActivity
|
|||
|
||||
@Override
|
||||
public void onBackStackChanged() {
|
||||
if(mediaDetails != null && mediaDetails.isVisible()) {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
} else {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
||||
}
|
||||
initBackButton();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshSource() {
|
||||
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.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.data.DBOpenHelper;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class ContributionsContentProvider extends ContentProvider{
|
||||
|
||||
|
|
@ -35,7 +36,7 @@ public class ContributionsContentProvider extends ContentProvider{
|
|||
private DBOpenHelper dbOpenHelper;
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
dbOpenHelper = DBOpenHelper.getInstance(getContext());
|
||||
dbOpenHelper = CommonsApplication.getInstance().getDBOpenHelper();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +103,7 @@ public class ContributionsContentProvider extends ContentProvider{
|
|||
|
||||
switch(uriType) {
|
||||
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,
|
||||
"_id = ?",
|
||||
new String[] { uri.getLastPathSegment() }
|
||||
|
|
@ -117,14 +118,14 @@ public class ContributionsContentProvider extends ContentProvider{
|
|||
|
||||
@Override
|
||||
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);
|
||||
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
|
||||
sqlDB.beginTransaction();
|
||||
switch (uriType) {
|
||||
case CONTRIBUTIONS:
|
||||
for(ContentValues value: values) {
|
||||
Log.d("Commons", "Inserting! " + value.toString());
|
||||
Timber.d("Inserting! %s", value);
|
||||
sqlDB.insert(Contribution.Table.TABLE_NAME, null, value);
|
||||
}
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -3,27 +3,13 @@ package fr.free.nrw.commons.contributions;
|
|||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
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.Utils;
|
||||
|
||||
class ContributionsListAdapter extends CursorAdapter {
|
||||
|
||||
private DisplayImageOptions contributionDisplayOptions = Utils.getGenericDisplayOptions().build();
|
||||
|
||||
private Activity activity;
|
||||
|
||||
public ContributionsListAdapter(Activity activity, Cursor c, int flags) {
|
||||
|
|
@ -38,48 +24,12 @@ class ContributionsListAdapter extends CursorAdapter {
|
|||
return parent;
|
||||
}
|
||||
|
||||
//FIXME: Potential cause of wrong image display bug
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
final ContributionViewHolder views = (ContributionViewHolder)view.getTag();
|
||||
final Contribution contribution = Contribution.fromCursor(cursor);
|
||||
|
||||
String actualUrl = (contribution.getLocalUri() != null && !TextUtils.isEmpty(contribution.getLocalUri().toString())) ? contribution.getLocalUri().toString() : contribution.getThumbnailUrl(640);
|
||||
|
||||
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.imageView.setMedia(contribution);
|
||||
views.titleView.setText(contribution.getDisplayTitle());
|
||||
|
||||
views.seqNumView.setText(String.valueOf(cursor.getPosition() + 1));
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import android.os.Build;
|
|||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
|
|
@ -21,16 +20,13 @@ import android.widget.AdapterView;
|
|||
import android.widget.GridView;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import butterknife.BindView;
|
||||
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.R;
|
||||
import fr.free.nrw.commons.settings.SettingsActivity;
|
||||
import fr.free.nrw.commons.nearby.NearbyActivity;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
|
||||
|
|
@ -45,7 +41,6 @@ public class ContributionsListFragment extends Fragment {
|
|||
@BindView(R.id.emptyMessage) TextView emptyMessage;
|
||||
|
||||
private ContributionController controller;
|
||||
private static final String TAG = "ContributionsList";
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
|
|
@ -54,14 +49,14 @@ public class ContributionsListFragment extends Fragment {
|
|||
|
||||
contributionsList.setOnItemClickListener((AdapterView.OnItemClickListener)getActivity());
|
||||
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"));
|
||||
}
|
||||
|
||||
//TODO: Should this be in onResume?
|
||||
SharedPreferences prefs = this.getActivity().getSharedPreferences("prefs", Context.MODE_PRIVATE);
|
||||
String lastModified = prefs.getString("lastSyncTimestamp", "");
|
||||
Log.d(TAG, "Last Sync Timestamp: " + lastModified);
|
||||
Timber.d("Last Sync Timestamp: %s", lastModified);
|
||||
|
||||
if (lastModified.equals("")) {
|
||||
waitingMessage.setVisibility(View.VISIBLE);
|
||||
|
|
@ -96,10 +91,12 @@ public class ContributionsListFragment extends Fragment {
|
|||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
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);
|
||||
} 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:
|
||||
controller.startCameraCapture();
|
||||
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:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
|
@ -176,7 +133,7 @@ public class ContributionsListFragment extends Fragment {
|
|||
// 1 = Storage allowed when gallery selected
|
||||
case 1: {
|
||||
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
Log.d("ContributionsList", "Call controller.startGalleryPick()");
|
||||
Timber.d("Call controller.startGalleryPick()");
|
||||
controller.startGalleryPick();
|
||||
}
|
||||
}
|
||||
|
|
@ -184,7 +141,7 @@ public class ContributionsListFragment extends Fragment {
|
|||
// 2 = Location allowed when 'nearby places' selected
|
||||
case 2: {
|
||||
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);
|
||||
startActivity(nearbyIntent);
|
||||
}
|
||||
|
|
@ -198,12 +155,9 @@ public class ContributionsListFragment extends Fragment {
|
|||
menu.clear(); // See http://stackoverflow.com/a/8495697/17865
|
||||
inflater.inflate(R.menu.fragment_contributions_list, menu);
|
||||
|
||||
CommonsApplication app = (CommonsApplication)getActivity().getApplicationContext();
|
||||
if (!app.deviceHasCamera()) {
|
||||
if (!CommonsApplication.getInstance().deviceHasCamera()) {
|
||||
menu.findItem(R.id.menu_from_camera).setEnabled(false);
|
||||
}
|
||||
|
||||
menu.findItem(R.id.menu_refresh).setVisible(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -11,17 +11,17 @@ import android.database.Cursor;
|
|||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.mediawiki.api.ApiResult;
|
||||
import org.mediawiki.api.MWApi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.MWApi;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
private static int COMMIT_THRESHOLD = 10;
|
||||
|
|
@ -30,7 +30,10 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
|
|||
}
|
||||
|
||||
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 };
|
||||
|
|
@ -58,7 +61,7 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
|
|||
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!
|
||||
String user = account.name;
|
||||
MWApi api = CommonsApplication.createMWApi();
|
||||
MWApi api = CommonsApplication.getInstance().getMWApi();
|
||||
SharedPreferences prefs = this.getContext().getSharedPreferences("prefs", Context.MODE_PRIVATE);
|
||||
String lastModified = prefs.getString("lastSyncTimestamp", "");
|
||||
Date curTime = new Date();
|
||||
|
|
@ -71,7 +74,7 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
|
|||
MWApi.RequestBuilder builder = api.action("query")
|
||||
.param("list", "logevents")
|
||||
.param("letype", "upload")
|
||||
.param("leprop", "title|timestamp")
|
||||
.param("leprop", "title|timestamp|ids")
|
||||
.param("leuser", user)
|
||||
.param("lelimit", getLimit());
|
||||
if(!TextUtils.isEmpty(lastModified)) {
|
||||
|
|
@ -85,18 +88,23 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
|
|||
// There isn't really much we can do, eh?
|
||||
// FIXME: Perhaps add EventLogging?
|
||||
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;
|
||||
}
|
||||
Log.d("Commons", "Last modified at " + lastModified);
|
||||
Timber.d("Last modified at %s", lastModified);
|
||||
|
||||
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<>();
|
||||
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");
|
||||
if(fileExists(contentProviderClient, filename)) {
|
||||
Log.d("Commons", "Skipping " + filename);
|
||||
Timber.d("Skipping %s", filename);
|
||||
continue;
|
||||
}
|
||||
String thumbUrl = Utils.makeThumbBaseUrl(filename);
|
||||
|
|
@ -128,6 +136,6 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
|
|||
}
|
||||
}
|
||||
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();
|
||||
synchronized (sSyncAdapterLock) {
|
||||
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 int DATABASE_VERSION = 6;
|
||||
private static DBOpenHelper singleton = null;
|
||||
|
||||
public static synchronized DBOpenHelper getInstance(Context context) {
|
||||
if ( singleton == null ) {
|
||||
singleton = new DBOpenHelper(context);
|
||||
}
|
||||
return singleton;
|
||||
}
|
||||
|
||||
private DBOpenHelper(Context context) {
|
||||
/**
|
||||
* Do not use, please call CommonsApplication.getDBOpenHelper()
|
||||
*/
|
||||
public DBOpenHelper(Context context) {
|
||||
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;
|
||||
|
||||
/** Accepts latitude and longitude.
|
||||
* North and South values are cut off at 90°
|
||||
*
|
||||
* @param latitude double value
|
||||
* @param longitude double value
|
||||
*/
|
||||
|
|
@ -42,4 +44,53 @@ public class LatLng {
|
|||
public String toString() {
|
||||
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.LocationManager;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class LocationServiceManager implements LocationListener {
|
||||
public static final String TAG = "LocationServiceManager";
|
||||
|
||||
private String provider;
|
||||
private LocationManager locationManager;
|
||||
private LatLng latestLocation;
|
||||
|
|
@ -31,14 +32,14 @@ public class LocationServiceManager implements LocationListener {
|
|||
Location location = locationManager.getLastKnownLocation(provider);
|
||||
//Location works, just need to 'send' GPS coords
|
||||
// via emulator extended controls if testing on emulator
|
||||
Log.d(TAG, "Checking for location...");
|
||||
Timber.d("Checking for location...");
|
||||
if (location != null) {
|
||||
this.onLocationChanged(location);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.e(TAG, "Illegal argument exception", e);
|
||||
Timber.e(e, "Illegal argument exception");
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Security exception", e);
|
||||
Timber.e(e, "Security exception");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -48,7 +49,7 @@ public class LocationServiceManager implements LocationListener {
|
|||
try {
|
||||
locationManager.removeUpdates(this);
|
||||
} 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) {
|
||||
double currentLatitude = location.getLatitude();
|
||||
double currentLongitude = location.getLongitude();
|
||||
Log.d(TAG, "Latitude: " + String.valueOf(currentLatitude)
|
||||
+ " Longitude: " + String.valueOf(currentLongitude));
|
||||
Timber.d("Latitude: %f Longitude: %f", currentLatitude, currentLongitude);
|
||||
|
||||
latestLocation = new LatLng(currentLatitude, currentLongitude);
|
||||
}
|
||||
|
||||
@Override
|
||||
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
|
||||
public void onProviderEnabled(String provider) {
|
||||
Log.d(TAG, "Provider " + provider + " enabled");
|
||||
Timber.d("Provider %s enabled", provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
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.database.DataSetObserver;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.ScrollView;
|
||||
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.text.SimpleDateFormat;
|
||||
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.LicenseList;
|
||||
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.R;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class MediaDetailFragment extends Fragment {
|
||||
|
||||
private boolean editable;
|
||||
private DisplayImageOptions displayOptions;
|
||||
private MediaDetailPagerFragment.MediaDetailProvider detailProvider;
|
||||
private int index;
|
||||
|
||||
public static MediaDetailFragment forMedia(int index) {
|
||||
return forMedia(index, false);
|
||||
}
|
||||
|
||||
public static MediaDetailFragment forMedia(int index, boolean editable) {
|
||||
MediaDetailFragment mf = new MediaDetailFragment();
|
||||
|
||||
|
|
@ -60,16 +48,15 @@ public class MediaDetailFragment extends Fragment {
|
|||
return mf;
|
||||
}
|
||||
|
||||
private ImageView image;
|
||||
//private EditText title;
|
||||
private ProgressBar loadingProgress;
|
||||
private ImageView loadingFailed;
|
||||
private MediaWikiImageView image;
|
||||
private MediaDetailSpacer spacer;
|
||||
private int initialListTop = 0;
|
||||
|
||||
private TextView title;
|
||||
private TextView desc;
|
||||
private TextView license;
|
||||
private TextView coordinates;
|
||||
private TextView uploadedDate;
|
||||
private LinearLayout categoryContainer;
|
||||
private ScrollView scrollView;
|
||||
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);
|
||||
|
||||
image = (ImageView) view.findViewById(R.id.mediaDetailImage);
|
||||
loadingProgress = (ProgressBar) view.findViewById(R.id.mediaDetailImageLoading);
|
||||
loadingFailed = (ImageView) view.findViewById(R.id.mediaDetailImageFailed);
|
||||
image = (MediaWikiImageView) view.findViewById(R.id.mediaDetailImage);
|
||||
scrollView = (ScrollView) view.findViewById(R.id.mediaDetailScrollView);
|
||||
|
||||
// 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);
|
||||
desc = (TextView) view.findViewById(R.id.mediaDetailDesc);
|
||||
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);
|
||||
|
||||
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
|
||||
scrollListener = new ViewTreeObserver.OnScrollChangedListener() {
|
||||
@Override
|
||||
|
|
@ -182,16 +150,34 @@ public class MediaDetailFragment extends Fragment {
|
|||
return view;
|
||||
}
|
||||
|
||||
@Override public void onResume() {
|
||||
super.onResume();
|
||||
Media media = detailProvider.getMediaAtPosition(index);
|
||||
if (media == null) {
|
||||
// Ask the detail provider to ping us when we're ready
|
||||
Timber.d("MediaDetailFragment not yet ready to display details; registering observer");
|
||||
dataObserver = new DataSetObserver() {
|
||||
@Override
|
||||
public void onChanged() {
|
||||
if (!isAdded()) {
|
||||
return;
|
||||
}
|
||||
Timber.d("MediaDetailFragment ready to display delayed details!");
|
||||
detailProvider.unregisterDataSetObserver(dataObserver);
|
||||
dataObserver = null;
|
||||
displayMediaDetails(detailProvider.getMediaAtPosition(index));
|
||||
}
|
||||
};
|
||||
detailProvider.registerDataSetObserver(dataObserver);
|
||||
} else {
|
||||
Timber.d("MediaDetailFragment ready to display details");
|
||||
displayMediaDetails(media);
|
||||
}
|
||||
}
|
||||
|
||||
private void displayMediaDetails(final Media media) {
|
||||
//Always load image from Internet to allow viewing the desc, license, and cats
|
||||
String actualUrl = media.getThumbnailUrl(640);
|
||||
if(actualUrl.startsWith("http")) {
|
||||
Log.d("Volley", "Actual URL starts with http and is: " + actualUrl);
|
||||
|
||||
ImageLoader loader = ((CommonsApplication)getActivity().getApplicationContext()).getImageLoader();
|
||||
MediaWikiImageView mwImage = (MediaWikiImageView)image;
|
||||
mwImage.setLoadingView(loadingProgress); //FIXME: Set this as an attribute
|
||||
mwImage.setMedia(media, loader);
|
||||
image.setMedia(media);
|
||||
|
||||
// FIXME: For transparent images
|
||||
// FIXME: keep the spinner going while we load data
|
||||
|
|
@ -219,15 +205,20 @@ public class MediaDetailFragment extends Fragment {
|
|||
@Override
|
||||
protected void onPostExecute(Boolean success) {
|
||||
detailFetchTask = null;
|
||||
if (!isAdded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (success.booleanValue()) {
|
||||
if (success) {
|
||||
extractor.fill(media);
|
||||
|
||||
// Set text of desc, license, and categories
|
||||
desc.setText(prettyDescription(media));
|
||||
license.setText(prettyLicense(media));
|
||||
coordinates.setText(prettyCoordinates(media));
|
||||
uploadedDate.setText(prettyUploadedDate(media));
|
||||
|
||||
categoryNames.removeAll(categoryNames);
|
||||
categoryNames.clear();
|
||||
categoryNames.addAll(media.getCategories());
|
||||
|
||||
categoriesLoaded = true;
|
||||
|
|
@ -238,71 +229,17 @@ public class MediaDetailFragment extends Fragment {
|
|||
}
|
||||
rebuildCatList();
|
||||
} else {
|
||||
Log.d("Commons", "Failed to load photo details.");
|
||||
Timber.d("Failed to load photo details.");
|
||||
}
|
||||
}
|
||||
};
|
||||
detailFetchTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
} else {
|
||||
//This should not usually happen, image along with associated details should always be loaded from Internet, but keeping this for now for backup.
|
||||
//Even if image is loaded from device storage, it will display, albeit with empty desc and cat.
|
||||
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) {
|
||||
loadingProgress.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadingFailed(String s, View view, FailReason failReason) {
|
||||
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
|
||||
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadingCancelled(String s, View view) {
|
||||
Log.e("Volley", "Image loading cancelled. But why?");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
title.setText(media.getDisplayTitle());
|
||||
desc.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
|
||||
public void onDestroyView() {
|
||||
if (detailFetchTask != null) {
|
||||
|
|
@ -377,7 +314,7 @@ public class MediaDetailFragment extends Fragment {
|
|||
|
||||
private String prettyLicense(Media media) {
|
||||
String licenseKey = media.getLicense();
|
||||
Log.d("Commons", "Media license is: " + licenseKey);
|
||||
Timber.d("Media license is: %s", licenseKey);
|
||||
if (licenseKey == null || licenseKey.equals("")) {
|
||||
return getString(R.string.detail_license_empty);
|
||||
}
|
||||
|
|
@ -388,4 +325,22 @@ public class MediaDetailFragment extends Fragment {
|
|||
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) {
|
||||
editable = savedInstanceState.getBoolean("editable");
|
||||
}
|
||||
app = (CommonsApplication)getActivity().getApplicationContext();
|
||||
app = CommonsApplication.getInstance();
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,9 +8,10 @@ import android.database.sqlite.SQLiteDatabase;
|
|||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.data.DBOpenHelper;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class ModificationsContentProvider extends ContentProvider{
|
||||
|
||||
|
|
@ -35,7 +36,7 @@ public class ModificationsContentProvider extends ContentProvider{
|
|||
private DBOpenHelper dbOpenHelper;
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
dbOpenHelper = DBOpenHelper.getInstance(getContext());
|
||||
dbOpenHelper = CommonsApplication.getInstance().getDBOpenHelper();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -101,14 +102,14 @@ public class ModificationsContentProvider extends ContentProvider{
|
|||
|
||||
@Override
|
||||
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);
|
||||
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
|
||||
sqlDB.beginTransaction();
|
||||
switch (uriType) {
|
||||
case MODIFICATIONS:
|
||||
for(ContentValues value: values) {
|
||||
Log.d("Commons", "Inserting! " + value.toString());
|
||||
Timber.d("Inserting! %s", value);
|
||||
sqlDB.insert(ModifierSequence.Table.TABLE_NAME, null, value);
|
||||
}
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -11,10 +11,9 @@ import android.content.SyncResult;
|
|||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import fr.free.nrw.commons.MWApi;
|
||||
import org.mediawiki.api.ApiResult;
|
||||
import org.mediawiki.api.MWApi;
|
||||
|
||||
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.contributions.Contribution;
|
||||
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
|
|||
|
||||
// Exit early if nothing to do
|
||||
if(allModifications == null || allModifications.getCount() == 0) {
|
||||
Log.d("Commons", "No modifications to perform");
|
||||
Timber.d("No modifications to perform");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -52,16 +52,16 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
|
|||
} catch (OperationCanceledException | AuthenticatorException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (IOException e) {
|
||||
Log.d("Commons", "Could not authenticate :(");
|
||||
Timber.d("Could not authenticate :(");
|
||||
return;
|
||||
}
|
||||
|
||||
if(Utils.isNullOrWhiteSpace(authCookie)) {
|
||||
Log.d("Commons", "Could not authenticate :(");
|
||||
Timber.d("Could not authenticate :(");
|
||||
return;
|
||||
}
|
||||
|
||||
MWApi api = CommonsApplication.createMWApi();
|
||||
MWApi api = CommonsApplication.getInstance().getMWApi();
|
||||
api.setAuthCookie(authCookie);
|
||||
String editToken;
|
||||
|
||||
|
|
@ -69,13 +69,13 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
|
|||
try {
|
||||
editToken = api.getEditToken();
|
||||
} catch (IOException e) {
|
||||
Log.d("Commons", "Can not retreive edit token!");
|
||||
Timber.d("Can not retreive edit token!");
|
||||
return;
|
||||
}
|
||||
|
||||
allModifications.moveToFirst();
|
||||
|
||||
Log.d("Commons", "Found " + allModifications.getCount() + " modifications to execute");
|
||||
Timber.d("Found %d modifications to execute", allModifications.getCount());
|
||||
|
||||
ContentProviderClient contributionsClient = null;
|
||||
try {
|
||||
|
|
@ -104,11 +104,11 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
|
|||
.param("titles", contrib.getFilename())
|
||||
.get();
|
||||
} catch (IOException e) {
|
||||
Log.d("Commons", "Network fuckup on modifications sync!");
|
||||
Timber.d("Network fuckup on modifications sync!");
|
||||
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 processedPageContent = sequence.executeModifications(contrib.getFilename(), pageContent);
|
||||
|
||||
|
|
@ -120,16 +120,16 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
|
|||
.param("summary", sequence.getEditSummary())
|
||||
.post();
|
||||
} catch (IOException e) {
|
||||
Log.d("Commons", "Network fuckup on modifications sync!");
|
||||
Timber.d("Network fuckup on modifications sync!");
|
||||
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");
|
||||
if(!result.equals("Success")) {
|
||||
// FIXME: Log this somewhere else
|
||||
Log.d("Commons", "Non success result!" + result);
|
||||
Timber.d("Non success result! %s", result);
|
||||
} else {
|
||||
sequence.delete();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ public class ModificationsSyncService extends Service {
|
|||
super.onCreate();
|
||||
synchronized (sSyncAdapterLock) {
|
||||
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);
|
||||
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;
|
||||
|
||||
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.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentTransaction;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
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.CommonsApplication;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
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 static final String TAG = NearbyActivity.class.getName();
|
||||
private LatLng curLatLang;
|
||||
private Bundle bundle;
|
||||
private NearbyAsyncTask nearbyAsyncTask;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_nearby);
|
||||
ButterKnife.bind(this);
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
bundle = new Bundle();
|
||||
locationManager = new LocationServiceManager(this);
|
||||
locationManager.registerLocationManager();
|
||||
|
||||
// Begin the transaction
|
||||
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
|
||||
NearbyListFragment fragment = new NearbyListFragment();
|
||||
ft.add(R.id.container, fragment);
|
||||
ft.commit();
|
||||
curLatLang = locationManager.getLatestLocation();
|
||||
nearbyAsyncTask = new NearbyAsyncTask(this);
|
||||
nearbyAsyncTask.execute();
|
||||
initDrawer();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -47,19 +76,87 @@ public class NearbyActivity extends BaseActivity {
|
|||
case R.id.action_refresh:
|
||||
refreshView();
|
||||
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:
|
||||
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
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
checkGps();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
nearbyAsyncTask.cancel(true);
|
||||
}
|
||||
|
||||
protected void refreshView() {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.container, new NearbyListFragment()).commit();
|
||||
nearbyAsyncTask = new NearbyAsyncTask(this);
|
||||
nearbyAsyncTask.execute();
|
||||
}
|
||||
|
||||
public LocationServiceManager getLocationManager() {
|
||||
|
|
@ -71,4 +168,93 @@ public class NearbyActivity extends BaseActivity {
|
|||
super.onDestroy();
|
||||
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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
|
||||
import fr.free.nrw.commons.R;
|
||||
|
||||
import java.util.List;
|
||||
import timber.log.Timber;
|
||||
|
||||
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.
|
||||
* @param context activity context
|
||||
* @param places list of places
|
||||
*/
|
||||
public NearbyAdapter(Context context, List<Place> places) {
|
||||
super(context, R.layout.item_place, places);
|
||||
this.context = context;
|
||||
placesList = places;
|
||||
public NearbyAdapter(Context context) {
|
||||
super(context, R.layout.item_place);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
// Get the data item for this 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
|
||||
if (convertView == null) {
|
||||
|
|
@ -42,13 +31,14 @@ public class NearbyAdapter extends ArrayAdapter<Place> {
|
|||
}
|
||||
|
||||
NearbyViewHolder viewHolder = new NearbyViewHolder(convertView);
|
||||
viewHolder.bindModel(context, place);
|
||||
viewHolder.bindModel(getContext(), place);
|
||||
// Return the completed view to render on screen
|
||||
return convertView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
// TODO: use Wikidata Q-ID instead?
|
||||
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;
|
||||
|
||||
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.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
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.ButterKnife;
|
||||
import butterknife.OnItemClick;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.utils.UriDeserializer;
|
||||
import timber.log.Timber;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
public class NearbyListFragment extends ListFragment {
|
||||
private List<Place> placeList;
|
||||
|
||||
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;
|
||||
@BindView(R.id.progressBar) ProgressBar progressBar;
|
||||
|
||||
private boolean isTaskRunning = false;
|
||||
|
||||
private static final String TAG = NearbyListFragment.class.getName();
|
||||
private NearbyAdapter adapter;
|
||||
|
||||
public NearbyListFragment() {
|
||||
}
|
||||
|
|
@ -55,118 +44,43 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
|
|||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
|
||||
Log.d(TAG, "NearbyListFragment created");
|
||||
Timber.d("NearbyListFragment created");
|
||||
View view = inflater.inflate(R.layout.fragment_nearby, container, false);
|
||||
ButterKnife.bind(this, view);
|
||||
adapter = new NearbyAdapter(getActivity());
|
||||
listview.setAdapter(adapter);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
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) {
|
||||
nearbyAsyncTask = new NearbyAsyncTask(this);
|
||||
nearbyAsyncTask.execute();
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
Log.d(TAG, "Saved instance state is null, populating ListView");
|
||||
} else {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
adapter.clear();
|
||||
Timber.d("Saved instance state is null, populating ListView");
|
||||
}
|
||||
|
||||
// If we are returning here from a screen orientation and the AsyncTask is still working,
|
||||
// re-create and display the progress dialog.
|
||||
if (isTaskRunning) {
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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.clear();
|
||||
adapter.addAll(placeList);
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@OnItemClick(R.id.listview)
|
||||
@OnItemClick(R.id.listView)
|
||||
void onItemClicked(int position) {
|
||||
Place place = (Place) listview.getItemAtPosition(position);
|
||||
LatLng placeLatLng = place.location;
|
||||
|
|
@ -174,53 +88,8 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
|
|||
double latitude = placeLatLng.latitude;
|
||||
double longitude = placeLatLng.longitude;
|
||||
|
||||
Log.d(TAG, "Item at position "
|
||||
+ position + " has coords: Lat: "
|
||||
+ latitude + " Long: "
|
||||
+ longitude);
|
||||
Timber.d("Item at position %d has coords: Lat: %f Long: %f", position, latitude, longitude);
|
||||
|
||||
//Open map app at given position
|
||||
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;
|
||||
NearbyInfoDialog.showYourself(getActivity(), place);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.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.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLEncoder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
|
@ -20,58 +15,31 @@ import java.util.Locale;
|
|||
import java.util.regex.Matcher;
|
||||
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 {
|
||||
|
||||
private static final String TAG = NearbyPlaces.class.getName();
|
||||
private static final int MIN_RESULTS = 40;
|
||||
private static final double INITIAL_RADIUS = 1.0;
|
||||
private static final double MAX_RADIUS = 300.0;
|
||||
private static final double INITIAL_RADIUS = 1.0; // in kilometers
|
||||
private static final double MAX_RADIUS = 300.0; // in kilometers
|
||||
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 String WIKIDATA_QUERY_TEMPLATE = "SELECT\n" +
|
||||
" (SAMPLE(?location) as ?location)\n" +
|
||||
" ?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 static final Uri WIKIDATA_QUERY_URL = Uri.parse("https://query.wikidata.org/sparql");
|
||||
private static final Uri WIKIDATA_QUERY_UI_URL = Uri.parse("https://query.wikidata.org/");
|
||||
private final String wikidataQuery;
|
||||
private double radius = INITIAL_RADIUS;
|
||||
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) {
|
||||
|
|
@ -81,7 +49,7 @@ public class NearbyPlaces {
|
|||
// increase the radius gradually to find a satisfactory number of nearby places
|
||||
while (radius < MAX_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) {
|
||||
break;
|
||||
} else {
|
||||
|
|
@ -89,33 +57,41 @@ public class NearbyPlaces {
|
|||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "" + e.toString());
|
||||
Timber.d(e.toString());
|
||||
// errors tend to be caused by too many results (and time out)
|
||||
// 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;
|
||||
}
|
||||
return places;
|
||||
}
|
||||
|
||||
private List<Place> getFromWikidataQuery(LatLng cur, String lang, double radius)
|
||||
private List<Place> getFromWikidataQuery(LatLng cur,
|
||||
String lang,
|
||||
double radius)
|
||||
throws IOException {
|
||||
List<Place> places = new ArrayList<>();
|
||||
String query = WIKIDATA_QUERY_TEMPLATE.replace("${RADIUS}", "" + radius)
|
||||
.replace("${LAT}", "" + String.format(Locale.ROOT, "%.3f", cur.latitude))
|
||||
.replace("${LONG}", "" + String.format(Locale.ROOT, "%.3f", cur.longitude))
|
||||
.replace("${LANG}", "" + lang);
|
||||
query = URLEncoder.encode(query, "utf-8").replace("+", "%20");
|
||||
String url = WIKIDATA_QUERY_URL.replace("${QUERY}", query);
|
||||
Log.d(TAG, url);
|
||||
|
||||
String query = wikidataQuery
|
||||
.replace("${RAD}", String.format(Locale.ROOT, "%.2f", radius))
|
||||
.replace("${LAT}", String.format(Locale.ROOT, "%.4f", cur.latitude))
|
||||
.replace("${LONG}", String.format(Locale.ROOT, "%.4f", cur.longitude))
|
||||
.replace("${LANG}", lang);
|
||||
|
||||
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();
|
||||
conn.setRequestProperty("Accept", "text/tab-separated-values");
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||
|
||||
String line;
|
||||
Log.d(TAG, "Reading from query result...");
|
||||
Timber.d("Reading from query result...");
|
||||
while ((line = in.readLine()) != null) {
|
||||
Log.v(TAG, line);
|
||||
Timber.v(line);
|
||||
line = line + "\n"; // to pad columns and make fields a fixed size
|
||||
if (!line.startsWith("\"Point")) {
|
||||
continue;
|
||||
|
|
@ -125,6 +101,9 @@ public class NearbyPlaces {
|
|||
String point = fields[0];
|
||||
String name = Utils.stripLocalizedString(fields[2]);
|
||||
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];
|
||||
|
||||
double latitude = 0;
|
||||
|
|
@ -146,7 +125,12 @@ public class NearbyPlaces {
|
|||
type, // list
|
||||
type, // details
|
||||
Uri.parse(icon),
|
||||
new LatLng(latitude, longitude)
|
||||
new LatLng(latitude, longitude),
|
||||
new Sitelinks.Builder()
|
||||
.setWikipediaLink(wikipediaSitelink)
|
||||
.setCommonsLink(commonsSitelink)
|
||||
.setWikidataLink(wikiDataLink)
|
||||
.build()
|
||||
));
|
||||
}
|
||||
in.close();
|
||||
|
|
@ -170,7 +154,7 @@ public class NearbyPlaces {
|
|||
|
||||
boolean firstLine = true;
|
||||
String line;
|
||||
Log.d(TAG, "Reading from CSV file...");
|
||||
Timber.d("Reading from CSV file...");
|
||||
|
||||
while ((line = in.readLine()) != null) {
|
||||
|
||||
|
|
@ -203,28 +187,16 @@ public class NearbyPlaces {
|
|||
type, // list
|
||||
type, // details
|
||||
null,
|
||||
new LatLng(latitude, longitude)
|
||||
new LatLng(latitude, longitude),
|
||||
new Sitelinks.Builder().build()
|
||||
));
|
||||
}
|
||||
in.close();
|
||||
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, e.toString());
|
||||
Timber.d(e.toString());
|
||||
}
|
||||
}
|
||||
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) {
|
||||
// Populate the data into the template view using the data object
|
||||
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);
|
||||
|
||||
icon.setImageResource(ResourceUtils.getDescriptionIcon(place.description));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,14 +16,17 @@ public class Place {
|
|||
public Bitmap image;
|
||||
public Bitmap secondaryImage;
|
||||
public String distance;
|
||||
public Sitelinks siteLinks;
|
||||
|
||||
|
||||
public Place(String name, String description, String longDescription,
|
||||
Uri secondaryImageUrl, LatLng location) {
|
||||
Uri secondaryImageUrl, LatLng location, Sitelinks siteLinks) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.longDescription = longDescription;
|
||||
this.secondaryImageUrl = secondaryImageUrl;
|
||||
this.location = location;
|
||||
this.siteLinks = siteLinks;
|
||||
}
|
||||
|
||||
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 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 final String CC_BY_SA_3 = "CC BY-SA 3.0";
|
||||
|
|
|
|||
|
|
@ -1,13 +1,19 @@
|
|||
package fr.free.nrw.commons.settings;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceActivity;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v7.app.AppCompatDelegate;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import butterknife.ButterKnife;
|
||||
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;
|
||||
|
||||
@Override
|
||||
|
|
@ -19,11 +25,13 @@ public class SettingsActivity extends PreferenceActivity {
|
|||
setTheme(R.style.LightAppTheme);
|
||||
}
|
||||
|
||||
// Display the fragment as the main content.
|
||||
getFragmentManager().beginTransaction()
|
||||
.replace(android.R.id.content, new SettingsFragment()).commit();
|
||||
settingsFragment = (SettingsFragment) getFragmentManager().findFragmentById(R.id.settingsFragment);
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_settings);
|
||||
|
||||
ButterKnife.bind(this);
|
||||
initDrawer();
|
||||
}
|
||||
|
||||
// Get an action bar
|
||||
|
|
@ -34,5 +42,25 @@ public class SettingsActivity extends PreferenceActivity {
|
|||
settingsDelegate = AppCompatDelegate.create(this, null);
|
||||
}
|
||||
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;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
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.Utils;
|
||||
|
||||
|
|
@ -19,23 +25,9 @@ public class SettingsFragment extends PreferenceFragment {
|
|||
|
||||
// Update spinner to show selected value as summary
|
||||
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())));
|
||||
|
||||
// Keep summary updated when changing value
|
||||
licensePreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
|
|
@ -52,5 +44,45 @@ public class SettingsFragment extends PreferenceFragment {
|
|||
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,13 +6,15 @@ import android.preference.PreferenceManager;
|
|||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
|
||||
public class BaseActivity extends AppCompatActivity {
|
||||
boolean currentTheme;
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("theme",true)) {
|
||||
if(Utils.isDarkTheme(this)){
|
||||
currentTheme = true;
|
||||
setTheme(R.style.DarkAppTheme);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -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.Intent;
|
||||
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.MWApi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import android.support.v7.app.AlertDialog;
|
||||
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.R;
|
||||
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
|
||||
|
|
@ -24,8 +23,6 @@ import fr.free.nrw.commons.contributions.ContributionsActivity;
|
|||
*/
|
||||
public class ExistingFileAsync extends AsyncTask<Void, Void, Boolean> {
|
||||
|
||||
private static final String TAG = ExistingFileAsync.class.getName();
|
||||
|
||||
private String fileSHA1;
|
||||
private Context context;
|
||||
|
||||
|
|
@ -41,7 +38,7 @@ public class ExistingFileAsync extends AsyncTask<Void, Void, Boolean> {
|
|||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... voids) {
|
||||
MWApi api = CommonsApplication.createMWApi();
|
||||
MWApi api = CommonsApplication.getInstance().getMWApi();
|
||||
ApiResult result;
|
||||
|
||||
// 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("aisha1", fileSHA1)
|
||||
.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) {
|
||||
Log.e(TAG, "IO Exception: ", e);
|
||||
Timber.e(e, "IO Exception: ");
|
||||
return false;
|
||||
}
|
||||
|
||||
ArrayList<ApiResult> resultNodes = result.getNodes("/api/query/allimages/img");
|
||||
Log.d(TAG, "Result nodes: " + resultNodes);
|
||||
Timber.d("Result nodes: %s", resultNodes);
|
||||
|
||||
boolean fileExists;
|
||||
if (!resultNodes.isEmpty()) {
|
||||
|
|
@ -67,7 +64,7 @@ public class ExistingFileAsync extends AsyncTask<Void, Void, Boolean> {
|
|||
fileExists = false;
|
||||
}
|
||||
|
||||
Log.d(TAG, "File already exists in Commons:" + fileExists);
|
||||
Timber.d("File already exists in Commons: %s", fileExists);
|
||||
return fileExists;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package fr.free.nrw.commons.upload;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
|
|
@ -20,6 +21,8 @@ public class FileUtils {
|
|||
* @param uri The Uri to query.
|
||||
* @author paulburke
|
||||
*/
|
||||
// Can be safely suppressed, checks for isKitKat before running isDocumentUri
|
||||
@SuppressLint("NewApi")
|
||||
public static String getPath(final Context context, final Uri uri) {
|
||||
|
||||
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.preference.PreferenceManager;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
|
@ -21,8 +22,6 @@ import java.io.IOException;
|
|||
*/
|
||||
public class GPSExtractor {
|
||||
|
||||
private static final String TAG = GPSExtractor.class.getName();
|
||||
|
||||
private String filePath;
|
||||
private double decLatitude, decLongitude;
|
||||
private Double currentLatitude = null;
|
||||
|
|
@ -31,8 +30,7 @@ public class GPSExtractor {
|
|||
public boolean imageCoordsExists;
|
||||
private MyLocationListener myLocationListener;
|
||||
private LocationManager locationManager;
|
||||
private String provider;
|
||||
private Criteria criteria;
|
||||
|
||||
|
||||
public GPSExtractor(String filePath, Context context){
|
||||
this.filePath = filePath;
|
||||
|
|
@ -46,7 +44,7 @@ public class GPSExtractor {
|
|||
private boolean gpsPreferenceEnabled() {
|
||||
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
boolean gpsPref = sharedPref.getBoolean("allowGps", false);
|
||||
Log.d(TAG, "Gps pref set to: " + gpsPref);
|
||||
Timber.d("Gps pref set to: %b", gpsPref);
|
||||
return gpsPref;
|
||||
}
|
||||
|
||||
|
|
@ -55,8 +53,8 @@ public class GPSExtractor {
|
|||
*/
|
||||
protected void registerLocationManager() {
|
||||
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
||||
criteria = new Criteria();
|
||||
provider = locationManager.getBestProvider(criteria, true);
|
||||
Criteria criteria = new Criteria();
|
||||
String provider = locationManager.getBestProvider(criteria, true);
|
||||
myLocationListener = new MyLocationListener();
|
||||
|
||||
try {
|
||||
|
|
@ -66,9 +64,9 @@ public class GPSExtractor {
|
|||
myLocationListener.onLocationChanged(location);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.e(TAG, "Illegal argument exception", e);
|
||||
Timber.e(e, "Illegal argument exception");
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Security exception", e);
|
||||
Timber.e(e, "Security exception");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -76,7 +74,7 @@ public class GPSExtractor {
|
|||
try {
|
||||
locationManager.removeUpdates(myLocationListener);
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Security exception", e);
|
||||
Timber.e(e, "Security exception");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -98,10 +96,10 @@ public class GPSExtractor {
|
|||
try {
|
||||
exif = new ExifInterface(filePath);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
Timber.w(e);
|
||||
return null;
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.w(TAG, e);
|
||||
Timber.w(e);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -110,14 +108,15 @@ public class GPSExtractor {
|
|||
registerLocationManager();
|
||||
|
||||
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
|
||||
boolean gpsPrefEnabled = gpsPreferenceEnabled();
|
||||
|
||||
//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) {
|
||||
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);
|
||||
} else {
|
||||
// No coords found
|
||||
|
|
@ -128,7 +127,7 @@ public class GPSExtractor {
|
|||
} else {
|
||||
//If image has EXIF data, extract image coords
|
||||
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_ref = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF);
|
||||
|
|
@ -136,8 +135,8 @@ public class GPSExtractor {
|
|||
longitude_ref = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF);
|
||||
|
||||
if (latitude!=null && latitude_ref!=null && longitude!=null && longitude_ref!=null) {
|
||||
Log.d(TAG, "Latitude: " + latitude + " " + latitude_ref);
|
||||
Log.d(TAG, "Longitude: " + longitude + " " + longitude_ref);
|
||||
Timber.d("Latitude: %s %s", latitude, latitude_ref);
|
||||
Timber.d("Longitude: %s %s", longitude, longitude_ref);
|
||||
|
||||
decimalCoords = getDecimalCoords(latitude, latitude_ref, longitude, longitude_ref);
|
||||
return decimalCoords;
|
||||
|
|
@ -160,17 +159,17 @@ public class GPSExtractor {
|
|||
|
||||
@Override
|
||||
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
|
||||
public void onProviderEnabled(String provider) {
|
||||
Log.d(TAG, "Provider " + provider + " enabled");
|
||||
Timber.d("Provider %s enabled", provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
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);
|
||||
Log.d(TAG, "Latitude and Longitude are " + decimalCoords);
|
||||
Timber.d("Latitude and Longitude are %s", decimalCoords);
|
||||
return decimalCoords;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,13 +14,14 @@ import android.os.Bundle;
|
|||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
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.R;
|
||||
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.contributions.Contribution;
|
||||
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.ModifierSequence;
|
||||
import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class MultipleShareActivity
|
||||
extends AuthenticatedActivity
|
||||
|
|
@ -53,10 +54,6 @@ public class MultipleShareActivity
|
|||
|
||||
private UploadController uploadController;
|
||||
|
||||
public MultipleShareActivity() {
|
||||
super(WikiAccountAuthenticator.COMMONS_ACCOUNT_TYPE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Media getMediaAtPosition(int i) {
|
||||
return photosList.get(i);
|
||||
|
|
@ -116,7 +113,7 @@ public class MultipleShareActivity
|
|||
|
||||
private void multipleUploadBegins() {
|
||||
|
||||
Log.d("MultipleShareActivity", "Multiple upload begins");
|
||||
Timber.d("Multiple upload begins");
|
||||
|
||||
final ProgressDialog dialog = new ProgressDialog(MultipleShareActivity.this);
|
||||
dialog.setIndeterminate(false);
|
||||
|
|
@ -135,7 +132,11 @@ public class MultipleShareActivity
|
|||
dialog.setProgress(uploadCount);
|
||||
if(uploadCount == photosList.size()) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
@ -205,7 +206,9 @@ public class MultipleShareActivity
|
|||
uploadController = new UploadController(this);
|
||||
|
||||
setContentView(R.layout.activity_multiple_uploads);
|
||||
app = (CommonsApplication)this.getApplicationContext();
|
||||
app = CommonsApplication.getInstance();
|
||||
ButterKnife.bind(this);
|
||||
initDrawer();
|
||||
|
||||
if(savedInstanceState != null) {
|
||||
photosList = savedInstanceState.getParcelableArrayList("uploadsList");
|
||||
|
|
@ -242,7 +245,7 @@ public class MultipleShareActivity
|
|||
|
||||
@Override
|
||||
protected void onAuthCookieAcquired(String authCookie) {
|
||||
app.getApi().setAuthCookie(authCookie);
|
||||
app.getMWApi().setAuthCookie(authCookie);
|
||||
Intent intent = getIntent();
|
||||
|
||||
if(intent.getAction().equals(Intent.ACTION_SEND_MULTIPLE)) {
|
||||
|
|
|
|||
|
|
@ -21,19 +21,15 @@ import android.widget.BaseAdapter;
|
|||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.GridView;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
import com.facebook.drawee.view.SimpleDraweeView;
|
||||
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment;
|
||||
|
||||
|
||||
public class MultipleUploadListFragment extends Fragment {
|
||||
|
||||
public interface OnMultipleUploadInitiatedHandler {
|
||||
|
|
@ -48,17 +44,13 @@ public class MultipleUploadListFragment extends Fragment {
|
|||
private MediaDetailPagerFragment.MediaDetailProvider detailProvider;
|
||||
private OnMultipleUploadInitiatedHandler multipleUploadInitiatedHandler;
|
||||
|
||||
private DisplayImageOptions uploadDisplayOptions;
|
||||
|
||||
private boolean imageOnlyMode;
|
||||
|
||||
private static class UploadHolderView {
|
||||
Uri imageUri;
|
||||
|
||||
ImageView image;
|
||||
TextView title;
|
||||
|
||||
RelativeLayout overlay;
|
||||
private Uri imageUri;
|
||||
private SimpleDraweeView image;
|
||||
private TextView title;
|
||||
private RelativeLayout overlay;
|
||||
}
|
||||
|
||||
private class PhotoDisplayAdapter extends BaseAdapter {
|
||||
|
|
@ -85,7 +77,7 @@ public class MultipleUploadListFragment extends Fragment {
|
|||
if(view == null) {
|
||||
view = getLayoutInflater(null).inflate(R.layout.layout_upload_item, null);
|
||||
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.overlay = (RelativeLayout) view.findViewById(R.id.uploadOverlay);
|
||||
|
||||
|
|
@ -100,7 +92,7 @@ public class MultipleUploadListFragment extends Fragment {
|
|||
Contribution up = (Contribution)this.getItem(i);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
|
@ -221,7 +213,6 @@ public class MultipleUploadListFragment extends Fragment {
|
|||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
uploadDisplayOptions = Utils.getGenericDisplayOptions().build();
|
||||
detailProvider = (MediaDetailPagerFragment.MediaDetailProvider)getActivity();
|
||||
multipleUploadInitiatedHandler = (OnMultipleUploadInitiatedHandler) getActivity();
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package fr.free.nrw.commons.upload;
|
|||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.volley.Cache;
|
||||
import com.android.volley.NetworkResponse;
|
||||
|
|
@ -22,6 +21,8 @@ import java.util.HashSet;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
|
@ -32,13 +33,11 @@ public class MwVolleyApi {
|
|||
private static RequestQueue REQUEST_QUEUE;
|
||||
private static final Gson GSON = new GsonBuilder().create();
|
||||
private Context context;
|
||||
private String coordsLog;
|
||||
|
||||
protected static Set<String> categorySet;
|
||||
private static List<String> categoryList;
|
||||
|
||||
private static final String MWURL = "https://commons.wikimedia.org/";
|
||||
private static final String TAG = MwVolleyApi.class.getName();
|
||||
|
||||
public MwVolleyApi(Context context) {
|
||||
this.context = context;
|
||||
|
|
@ -52,13 +51,12 @@ public class MwVolleyApi {
|
|||
public static void setGpsCat(List<String> cachedList) {
|
||||
categoryList = new ArrayList<>();
|
||||
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) {
|
||||
coordsLog = coords;
|
||||
String apiUrl = buildUrl(coords);
|
||||
Log.d(TAG, "URL: " + apiUrl);
|
||||
Timber.d("URL: %s", apiUrl);
|
||||
|
||||
JsonRequest<QueryResponse> request = new QueryRequest(apiUrl,
|
||||
new LogResponseListener<QueryResponse>(), new LogResponseErrorListener());
|
||||
|
|
@ -101,7 +99,7 @@ public class MwVolleyApi {
|
|||
|
||||
private static RequestQueue getQueue(Context context) {
|
||||
if (REQUEST_QUEUE == null) {
|
||||
REQUEST_QUEUE = Volley.newRequestQueue(context.getApplicationContext());
|
||||
REQUEST_QUEUE = Volley.newRequestQueue(context);
|
||||
}
|
||||
return REQUEST_QUEUE;
|
||||
}
|
||||
|
|
@ -110,7 +108,7 @@ public class MwVolleyApi {
|
|||
|
||||
@Override
|
||||
public void onResponse(T response) {
|
||||
Log.d(TAG, response.toString());
|
||||
Timber.d(response.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -118,12 +116,11 @@ public class MwVolleyApi {
|
|||
|
||||
@Override
|
||||
public void onErrorResponse(VolleyError error) {
|
||||
Log.e(TAG, error.toString());
|
||||
Timber.e(error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static class QueryRequest extends JsonRequest<QueryResponse> {
|
||||
private static final String TAG = QueryRequest.class.getName();
|
||||
|
||||
public QueryRequest(String url,
|
||||
Response.Listener<QueryResponse> listener,
|
||||
|
|
@ -169,11 +166,11 @@ public class MwVolleyApi {
|
|||
private String printSet() {
|
||||
if (categorySet == null || categorySet.isEmpty()) {
|
||||
GpsCatExists.setGpsCatExists(false);
|
||||
Log.d(TAG, "gpsCatExists=" + GpsCatExists.getGpsCatExists());
|
||||
Timber.d("gpsCatExists=%b", GpsCatExists.getGpsCatExists());
|
||||
return "No collection of categories";
|
||||
} else {
|
||||
GpsCatExists.setGpsCatExists(true);
|
||||
Log.d(TAG, "gpsCatExists=" + GpsCatExists.getGpsCatExists());
|
||||
Timber.d("gpsCatExists=%b", GpsCatExists.getGpsCatExists());
|
||||
return "CATEGORIES FOUND" + categorySet.toString();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,34 +10,32 @@ import android.os.Bundle;
|
|||
import android.os.Environment;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
import com.facebook.drawee.view.SimpleDraweeView;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import butterknife.ButterKnife;
|
||||
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.auth.AuthenticatedActivity;
|
||||
import fr.free.nrw.commons.auth.WikiAccountAuthenticator;
|
||||
import fr.free.nrw.commons.category.CategorizationFragment;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
import fr.free.nrw.commons.modifications.CategoryModifier;
|
||||
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
||||
import fr.free.nrw.commons.modifications.ModifierSequence;
|
||||
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
|
||||
|
|
@ -48,9 +46,6 @@ public class ShareActivity
|
|||
implements SingleUploadFragment.OnUploadActionInitiated,
|
||||
CategorizationFragment.OnCategoriesSaveHandler {
|
||||
|
||||
private static final String TAG = ShareActivity.class.getName();
|
||||
|
||||
private SingleUploadFragment shareView;
|
||||
private CategorizationFragment categorizationFragment;
|
||||
|
||||
private CommonsApplication app;
|
||||
|
|
@ -61,14 +56,14 @@ public class ShareActivity
|
|||
|
||||
private Uri mediaUri;
|
||||
private Contribution contribution;
|
||||
private ImageView backgroundImageView;
|
||||
private SimpleDraweeView backgroundImageView;
|
||||
|
||||
private UploadController uploadController;
|
||||
|
||||
private CommonsApplication cacheObj;
|
||||
private boolean cacheFound;
|
||||
|
||||
private GPSExtractor imageObj;
|
||||
private String filePath;
|
||||
private String decimalCoords;
|
||||
|
||||
private boolean useNewPermissions = false;
|
||||
|
|
@ -79,10 +74,6 @@ public class ShareActivity
|
|||
private String description;
|
||||
private Snackbar snackbar;
|
||||
|
||||
public ShareActivity() {
|
||||
super(WikiAccountAuthenticator.COMMONS_ACCOUNT_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when user taps the submit button
|
||||
*/
|
||||
|
|
@ -112,13 +103,17 @@ public class ShareActivity
|
|||
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();
|
||||
|
||||
if (cacheFound == false) {
|
||||
if (!cacheFound) {
|
||||
//Has to be called after apiCall.request()
|
||||
app.cacheData.cacheCategory();
|
||||
Log.d(TAG, "Cache the categories found");
|
||||
app.getCacheData().cacheCategory();
|
||||
Timber.d("Cache the categories found");
|
||||
}
|
||||
|
||||
uploadController.startUpload(title, mediaUri, description, mimeType, source, decimalCoords, new UploadController.ContributionUploadProgress() {
|
||||
|
|
@ -195,9 +190,9 @@ public class ShareActivity
|
|||
|
||||
@Override
|
||||
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");
|
||||
if(shareView == null && categorizationFragment == null) {
|
||||
shareView = new SingleUploadFragment();
|
||||
|
|
@ -221,9 +216,10 @@ public class ShareActivity
|
|||
super.onCreate(savedInstanceState);
|
||||
uploadController = new UploadController(this);
|
||||
setContentView(R.layout.activity_share);
|
||||
|
||||
app = (CommonsApplication)this.getApplicationContext();
|
||||
backgroundImageView = (ImageView)findViewById(R.id.backgroundImage);
|
||||
ButterKnife.bind(this);
|
||||
initBack();
|
||||
app = CommonsApplication.getInstance();
|
||||
backgroundImageView = (SimpleDraweeView)findViewById(R.id.backgroundImage);
|
||||
|
||||
//Receive intent from ContributionController.java when user selects picture to upload
|
||||
Intent intent = getIntent();
|
||||
|
|
@ -240,20 +236,20 @@ public class ShareActivity
|
|||
|
||||
if (mediaUri != null) {
|
||||
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
|
||||
try {
|
||||
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);
|
||||
Log.d(TAG, "File SHA1 is: " + fileSHA1);
|
||||
Timber.d("File SHA1 is: %s", fileSHA1);
|
||||
|
||||
ExistingFileAsync fileAsyncTask = new ExistingFileAsync(fileSHA1, this);
|
||||
fileAsyncTask.execute();
|
||||
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "IO Exception: ", e);
|
||||
Timber.d(e, "IO Exception: ");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -263,8 +259,8 @@ public class ShareActivity
|
|||
|
||||
requestAuthToken();
|
||||
|
||||
Log.d(TAG, "Uri: " + mediaUriString);
|
||||
Log.d(TAG, "Ext storage dir: " + Environment.getExternalStorageDirectory());
|
||||
Timber.d("Uri: %s", mediaUriString);
|
||||
Timber.d("Ext storage dir: %s", Environment.getExternalStorageDirectory());
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
useNewPermissions = true;
|
||||
|
|
@ -377,9 +373,9 @@ public class ShareActivity
|
|||
* @param gpsEnabled
|
||||
*/
|
||||
public void getFileMetadata(boolean gpsEnabled) {
|
||||
filePath = FileUtils.getPath(this, mediaUri);
|
||||
Log.d(TAG, "Filepath: " + filePath);
|
||||
Log.d(TAG, "Calling GPSExtractor");
|
||||
String filePath = FileUtils.getPath(this, mediaUri);
|
||||
Timber.d("Filepath: %s", filePath);
|
||||
Timber.d("Calling GPSExtractor");
|
||||
if(imageObj == null) {
|
||||
imageObj = new GPSExtractor(filePath, this);
|
||||
}
|
||||
|
|
@ -397,28 +393,28 @@ public class ShareActivity
|
|||
*/
|
||||
public void useImageCoords() {
|
||||
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
|
||||
if (imageObj.imageCoordsExists) {
|
||||
double decLongitude = imageObj.getDecLongitude();
|
||||
double decLatitude = imageObj.getDecLatitude();
|
||||
app.cacheData.setQtPoint(decLongitude, decLatitude);
|
||||
app.getCacheData().setQtPoint(decLongitude, decLatitude);
|
||||
}
|
||||
|
||||
MwVolleyApi apiCall = new MwVolleyApi(this);
|
||||
|
||||
List<String> displayCatList = app.cacheData.findCategory();
|
||||
List<String> displayCatList = app.getCacheData().findCategory();
|
||||
boolean catListEmpty = displayCatList.isEmpty();
|
||||
|
||||
// If no categories found in cache, call MediaWiki API to match image coords with nearby Commons categories
|
||||
if (catListEmpty) {
|
||||
cacheFound = false;
|
||||
apiCall.request(decimalCoords);
|
||||
Log.d(TAG, "displayCatList size 0, calling MWAPI" + displayCatList.toString());
|
||||
Timber.d("displayCatList size 0, calling MWAPI %s", displayCatList);
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -429,10 +425,10 @@ public class ShareActivity
|
|||
super.onPause();
|
||||
try {
|
||||
imageObj.unregisterLocationManager();
|
||||
Log.d(TAG, "Unregistered locationManager");
|
||||
Timber.d("Unregistered locationManager");
|
||||
}
|
||||
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) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
NavUtils.navigateUpFromSameTask(this);
|
||||
if(categorizationFragment!=null && categorizationFragment.isVisible()) {
|
||||
categorizationFragment.backButtonDialog();
|
||||
} else {
|
||||
onBackPressed();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import android.preference.PreferenceManager;
|
|||
import android.support.v4.app.Fragment;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
|
|
@ -37,6 +36,7 @@ import butterknife.OnTouch;
|
|||
import fr.free.nrw.commons.settings.Prefs;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.Utils;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class SingleUploadFragment extends Fragment {
|
||||
private SharedPreferences prefs;
|
||||
|
|
@ -54,8 +54,6 @@ public class SingleUploadFragment extends Fragment {
|
|||
|
||||
private OnUploadActionInitiated uploadActionInitiatedHandler;
|
||||
|
||||
private static final String TAG = SingleUploadFragment.class.getName();
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.activity_share, menu);
|
||||
|
|
@ -103,7 +101,7 @@ public class SingleUploadFragment extends Fragment {
|
|||
prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
license = prefs.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3);
|
||||
|
||||
Log.d("Single Upload fragment", license);
|
||||
Timber.d(license);
|
||||
|
||||
ArrayAdapter<String> adapter;
|
||||
if (PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean("theme",true)) {
|
||||
|
|
@ -117,7 +115,14 @@ public class SingleUploadFragment extends Fragment {
|
|||
licenseSpinner.setAdapter(adapter);
|
||||
|
||||
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);
|
||||
|
||||
TextWatcher uploadEnabler = new TextWatcher() {
|
||||
|
|
@ -190,7 +195,7 @@ public class SingleUploadFragment extends Fragment {
|
|||
SharedPreferences titleDesc = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
String title = titleDesc.getString("Title", "");
|
||||
String desc = titleDesc.getString("Desc", "");
|
||||
Log.d(TAG, "Title: " + title + ", Desc: " + desc);
|
||||
Timber.d("Title: %s, Desc: %s", title, desc);
|
||||
|
||||
titleEdit.setText(title);
|
||||
descEdit.setText(desc);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import android.os.IBinder;
|
|||
import android.preference.PreferenceManager;
|
||||
import android.provider.MediaStore;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
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.Utils;
|
||||
import fr.free.nrw.commons.contributions.Contribution;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class UploadController {
|
||||
private UploadService uploadService;
|
||||
|
|
@ -36,7 +36,7 @@ public class UploadController {
|
|||
|
||||
public UploadController(Activity activity) {
|
||||
this.activity = activity;
|
||||
app = (CommonsApplication)activity.getApplicationContext();
|
||||
app = CommonsApplication.getInstance();
|
||||
}
|
||||
|
||||
private boolean isUploadServiceConnected;
|
||||
|
|
@ -55,7 +55,7 @@ public class UploadController {
|
|||
};
|
||||
|
||||
public void prepareService() {
|
||||
Intent uploadServiceIntent = new Intent(activity.getApplicationContext(), UploadService.class);
|
||||
Intent uploadServiceIntent = new Intent(activity, UploadService.class);
|
||||
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
|
||||
activity.startService(uploadServiceIntent);
|
||||
activity.bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE);
|
||||
|
|
@ -115,11 +115,11 @@ public class UploadController {
|
|||
contribution.setDataLength(length);
|
||||
}
|
||||
} catch(IOException e) {
|
||||
Log.e("UploadController", "IO Exception: ", e);
|
||||
Timber.e(e, "IO Exception: ");
|
||||
} catch(NullPointerException e) {
|
||||
Log.e("UploadController", "Null Pointer Exception: ", e);
|
||||
Timber.e(e, "Null Pointer Exception: ");
|
||||
} catch(SecurityException e) {
|
||||
Log.e("UploadController", "Security Exception: ", e);
|
||||
Timber.e(e, "Security Exception: ");
|
||||
}
|
||||
|
||||
String mimeType = (String)contribution.getTag("mimeType");
|
||||
|
|
@ -132,7 +132,7 @@ public class UploadController {
|
|||
if(mimeType != null) {
|
||||
contribution.setTag("mimeType", mimeType);
|
||||
imagePrefix = mimeType.startsWith("image/");
|
||||
Log.d("UploadController", "MimeType is: " + mimeType);
|
||||
Timber.d("MimeType is: %s", mimeType);
|
||||
}
|
||||
|
||||
if(imagePrefix && contribution.getDateCreated() == null) {
|
||||
|
|
|
|||
|
|
@ -10,12 +10,11 @@ import android.content.Intent;
|
|||
import android.graphics.BitmapFactory;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.util.Log;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import android.widget.Toast;
|
||||
|
||||
import fr.free.nrw.commons.*;
|
||||
import org.mediawiki.api.ApiResult;
|
||||
import org.mediawiki.api.MWApi;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
|
@ -26,16 +25,12 @@ import java.util.Set;
|
|||
import java.util.regex.Matcher;
|
||||
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.ContributionsActivity;
|
||||
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
|
||||
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
|
||||
import in.yuvi.http.fluent.ProgressListener;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class UploadService extends HandlerService<Contribution> {
|
||||
|
||||
|
|
@ -87,7 +82,7 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
|
||||
@Override
|
||||
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) {
|
||||
curProgressNotification.setContentTitle(notificationProgressTitle);
|
||||
notificationTitleChanged = true;
|
||||
|
|
@ -112,7 +107,7 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
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
|
||||
|
|
@ -120,7 +115,7 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
super.onCreate();
|
||||
|
||||
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
app = (CommonsApplication) this.getApplicationContext();
|
||||
app = CommonsApplication.getInstance();
|
||||
contributionsProviderClient = this.getContentResolver().acquireContentProviderClient(ContributionsContentProvider.AUTHORITY);
|
||||
}
|
||||
|
||||
|
|
@ -149,7 +144,7 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
toUpload++;
|
||||
if (curProgressNotification != null && toUpload != 1) {
|
||||
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());
|
||||
}
|
||||
|
||||
|
|
@ -173,15 +168,15 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
Contribution.Table.COLUMN_STATE + " = ? OR " + Contribution.Table.COLUMN_STATE + " = ?",
|
||||
new String[]{ String.valueOf(Contribution.STATE_QUEUED), String.valueOf(Contribution.STATE_IN_PROGRESS) }
|
||||
);
|
||||
Log.d("Commons", "Set " + updated + " uploads to failed");
|
||||
Log.d("Commons", "Flags is" + flags + " id is" + startId);
|
||||
Timber.d("Set %d uploads to failed", updated);
|
||||
Timber.d("Flags is %d id is %d", flags, startId);
|
||||
freshStart = false;
|
||||
}
|
||||
return START_REDELIVER_INTENT;
|
||||
}
|
||||
|
||||
private void uploadContribution(Contribution contribution) {
|
||||
MWApi api = app.getApi();
|
||||
MWApi api = app.getMWApi();
|
||||
|
||||
ApiResult result;
|
||||
InputStream file = null;
|
||||
|
|
@ -192,12 +187,12 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
//FIXME: Google Photos bug
|
||||
file = this.getContentResolver().openInputStream(contribution.getLocalUri());
|
||||
} 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);
|
||||
fileNotFound.show();
|
||||
}
|
||||
|
||||
Log.d("Commons", "Before execution!");
|
||||
Timber.d("Before execution!");
|
||||
curProgressNotification = new NotificationCompat.Builder(this).setAutoCancel(true)
|
||||
.setSmallIcon(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))
|
||||
.setOngoing(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()));
|
||||
|
||||
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")));
|
||||
|
||||
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);
|
||||
unfinishedUploads.add(filename);
|
||||
}
|
||||
if(!api.validateLogin()) {
|
||||
// Need to revalidate!
|
||||
if(app.revalidateAuthToken()) {
|
||||
Log.d("Commons", "Successfully revalidated token!");
|
||||
Timber.d("Successfully revalidated token!");
|
||||
} else {
|
||||
Log.d("Commons", "Unable to revalidate :(");
|
||||
Timber.d("Unable to revalidate :(");
|
||||
// TODO: Put up a new notification, ask them to re-login
|
||||
stopForeground(true);
|
||||
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);
|
||||
|
||||
Log.d("Commons", "Response is" + Utils.getStringFromDOM(result.getDocument()));
|
||||
Timber.d("Response is %s", Utils.getStringFromDOM(result.getDocument()));
|
||||
|
||||
curProgressNotification = null;
|
||||
|
||||
|
|
@ -277,7 +272,7 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
.log();
|
||||
}
|
||||
} catch(IOException e) {
|
||||
Log.d("Commons", "I have a network fuckup");
|
||||
Timber.d("I have a network fuckup");
|
||||
showFailedNotification(contribution);
|
||||
return;
|
||||
} finally {
|
||||
|
|
@ -287,7 +282,7 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
toUpload--;
|
||||
if(toUpload == 0) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -309,7 +304,7 @@ public class UploadService extends HandlerService<Contribution> {
|
|||
}
|
||||
|
||||
private String findUniqueFilename(String fileName) throws IOException {
|
||||
MWApi api = app.getApi();
|
||||
MWApi api = app.getMWApi();
|
||||
String sequenceFileName;
|
||||
for ( int sequenceNumber = 1; true; sequenceNumber++ ) {
|
||||
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 |