Merge remote-tracking branch 'refs/remotes/commons-app/master'

This commit is contained in:
misaochan 2017-05-27 16:35:39 +10:00
commit 3dbbc428da
283 changed files with 6218 additions and 4209 deletions

4
.gitignore vendored
View file

@ -23,3 +23,7 @@ local.properties
# OS files # OS files
*.DS_Store *.DS_Store
Thumbs.db Thumbs.db
app/gradle/wrapper/gradle-wrapper.jar
app/gradlew
app/gradlew.bat
app/gradle/wrapper/gradle-wrapper.properties

View file

@ -1,4 +1,12 @@
language: android language: android
addons:
apt:
packages:
- w3m
env:
global:
- ANDROID_TARGET=android-22
- ANDROID_ABI=armeabi-v7a
android: android:
components: components:
- platform-tools - platform-tools
@ -6,8 +14,17 @@ android:
- build-tools-25.0.1 - build-tools-25.0.1
- extra-google-m2repository - extra-google-m2repository
- extra-android-m2repository - extra-android-m2repository
- ${ANDROID_TARGET}
- android-25 - android-25
- sys-img-x86-android-18 - sys-img-${ANDROID_ABI}-${ANDROID_TARGET}
before_script:
- echo no | android create avd --force -n test -t $ANDROID_TARGET --abi $ANDROID_ABI
- emulator -avd test -no-audio -no-window &
- android-wait-for-emulator
script:
- ./gradlew test connectedAndroidTest -stacktrace
after_failure:
- w3m -dump ${TRAVIS_BUILD_DIR}/app/build/reports/androidTests/connected/*Test.html
jdk: jdk:
# - openjdk8 # not yet available # - openjdk8 # not yet available
- oraclejdk8 - oraclejdk8

View file

@ -1,9 +1,25 @@
# Wikimedia Commons for Android # Wikimedia Commons for Android
## v2.2.2 beta ## v2.4 beta
- Fixed memory issue with loading contributions on main screen
- Deleted images don't show up on contributions list
- Added Fresco library for image loading and LeakCanary for memory profiling
- Added navigation drawer and overhauled action bar
- Added logout functionality
- Fixed various issues with map of Nearby places
## v2.3 beta
- Add map of Nearby places
- Add overlay dialog when a Nearby place is tapped
- Set default number of uploads to display in Main activity as 100, and add option in Settings to change it
- Detect when 2FA is used for login and display message
- Display date uploaded and image coordinates in image details page
- Display message when GPS is turned off, and when no Nearby items are found
## v2.2.2
- Hotfix for Nearby localization issue - Hotfix for Nearby localization issue
## v2.2.1 beta ## v2.2.1
- Hotfix for Settings crash - Hotfix for Settings crash
## v2.2 beta (will not be released to Production due to bugs with Settings) ## v2.2 beta (will not be released to Production due to bugs with Settings)

View file

@ -19,3 +19,9 @@ their contribution to the product.
* Veyndan Stuart * Veyndan Stuart
* Vivek Maskara * Vivek Maskara
* Neslihan Turan * Neslihan Turan
3rd party open source libraries used:
* Butterknife
* GSON
* Timber
* MapBox

View file

@ -6,7 +6,6 @@ dependencies {
compile 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar' compile 'fr.avianey.com.viewpagerindicator:library:2.4.1.1@aar'
compile 'in.yuvi:http.fluent:1.3' compile 'in.yuvi:http.fluent:1.3'
compile 'com.android.volley:volley:1.0.0' compile 'com.android.volley:volley:1.0.0'
compile 'com.nostra13.universalimageloader:universal-image-loader:1.8.4'
compile 'ch.acra:acra:4.7.0' compile 'ch.acra:acra:4.7.0'
compile 'org.mediawiki:api:1.3' compile 'org.mediawiki:api:1.3'
compile 'commons-codec:commons-codec:1.10' compile 'commons-codec:commons-codec:1.10'
@ -16,8 +15,21 @@ dependencies {
compile 'com.google.code.gson:gson:2.7' compile 'com.google.code.gson:gson:2.7'
compile "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION" compile "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION"
annotationProcessor "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION" annotationProcessor "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION"
compile 'com.jakewharton.timber:timber:4.5.1'
compile ('com.mapbox.mapboxsdk:mapbox-android-sdk:5.0.2@aar'){
transitive=true
}
compile 'com.facebook.fresco:fresco:1.3.0'
compile 'com.facebook.stetho:stetho:1.5.0'
compile "com.google.guava:guava:${GUAVA_VERSION}"
testCompile 'junit:junit:4.12' testCompile 'junit:junit:4.12'
androidTestCompile "com.android.support:support-annotations:${project.supportLibVersion}"
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
} }
android { android {
@ -28,10 +40,12 @@ android {
defaultConfig { defaultConfig {
applicationId 'fr.free.nrw.commons' applicationId 'fr.free.nrw.commons'
versionCode 69 versionCode 71
versionName '2.2.2' versionName '2.4'
minSdkVersion project.minSdkVersion minSdkVersion project.minSdkVersion
targetSdkVersion project.targetSdkVersion targetSdkVersion project.targetSdkVersion
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
} }
buildTypes { buildTypes {

View file

@ -25,7 +25,7 @@ task checkstyle(type: Checkstyle) {
task pmd(type: Pmd) { task pmd(type: Pmd) {
ignoreFailures = true ignoreFailures = true
ruleSetFiles = files("${project.rootDir}/ruleset.xml") ruleSetFiles = files("${project.rootDir}/script/style/ruleset.xml")
ruleSets = [] ruleSets = []
source 'src' source 'src'

View file

@ -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()));
}
}

View file

@ -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()
);
}
}

View file

@ -1,5 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="fr.free.nrw.commons"> package="fr.free.nrw.commons">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
@ -31,6 +30,10 @@
<activity <activity
android:name=".auth.LoginActivity" android:name=".auth.LoginActivity"
> >
<intent-filter>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
</activity> </activity>
<activity <activity
android:name=".WelcomeActivity" android:name=".WelcomeActivity"
@ -66,10 +69,6 @@
android:icon="@drawable/ic_launcher" android:icon="@drawable/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
> >
<intent-filter>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
</activity> </activity>
<activity <activity
android:name=".settings.SettingsActivity" android:name=".settings.SettingsActivity"

View 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

View file

@ -1,24 +1,16 @@
package fr.free.nrw.commons; package fr.free.nrw.commons;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.widget.TextView; import android.widget.TextView;
import fr.free.nrw.commons.theme.BaseActivity;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import fr.free.nrw.commons.theme.NavigationBaseActivity;
public class AboutActivity extends BaseActivity { public class AboutActivity extends NavigationBaseActivity {
@BindView(R.id.about_version) TextView versionText; @BindView(R.id.about_version) TextView versionText;
@BindView(R.id.about_license) TextView licenseText;
@BindView(R.id.about_improve) TextView improveText;
@BindView(R.id.about_privacy_policy) TextView privacyPolicyText;
@BindView(R.id.about_uploads_to) TextView uploadsToText;
@BindView(R.id.about_credits) TextView creditsText;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@ -27,24 +19,12 @@ public class AboutActivity extends BaseActivity {
ButterKnife.bind(this); ButterKnife.bind(this);
uploadsToText.setText(CommonsApplication.EVENTLOG_WIKI);
versionText.setText(BuildConfig.VERSION_NAME); versionText.setText(BuildConfig.VERSION_NAME);
initDrawer();
// We can't use formatted strings directly because it breaks with
// our localization tools. Grab an HTML string and turn it into
// a formatted string.
fixFormatting(licenseText, R.string.about_license);
fixFormatting(improveText, R.string.about_improve);
fixFormatting(privacyPolicyText, R.string.about_privacy_policy);
fixFormatting(creditsText, R.string.about_credits);
licenseText.setMovementMethod(LinkMovementMethod.getInstance());
improveText.setMovementMethod(LinkMovementMethod.getInstance());
privacyPolicyText.setMovementMethod(LinkMovementMethod.getInstance());
creditsText.setMovementMethod(LinkMovementMethod.getInstance());
} }
private void fixFormatting(TextView textView, int resource) { public static void startYourself(Context context) {
textView.setText(Html.fromHtml(getResources().getString(resource))); Intent settingsIntent = new Intent(context, AboutActivity.class);
context.startActivity(settingsIntent);
} }
} }

View file

@ -5,20 +5,26 @@ import android.accounts.AccountManager;
import android.accounts.AuthenticatorException; import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException; import android.accounts.OperationCanceledException;
import android.app.Application; import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.os.Build; import android.os.Build;
import android.database.sqlite.SQLiteDatabase;
import android.preference.PreferenceManager;
import android.support.v4.util.LruCache; import android.support.v4.util.LruCache;
import android.util.Log;
import com.android.volley.RequestQueue; import com.facebook.drawee.backends.pipeline.Fresco;
import com.android.volley.toolbox.BasicNetwork; import com.facebook.stetho.Stetho;
import com.android.volley.toolbox.DiskBasedCache;
import com.android.volley.toolbox.HurlStack; import fr.free.nrw.commons.caching.CacheController;
import com.nostra13.universalimageloader.cache.disc.impl.TotalSizeLimitedDiscCache; import fr.free.nrw.commons.category.Category;
import com.nostra13.universalimageloader.core.ImageLoader; import fr.free.nrw.commons.contributions.Contribution;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; import fr.free.nrw.commons.data.DBOpenHelper;
import com.nostra13.universalimageloader.utils.StorageUtils; import fr.free.nrw.commons.modifications.ModifierSequence;
import fr.free.nrw.commons.auth.AccountUtil;
import fr.free.nrw.commons.nearby.NearbyPlaces;
import com.squareup.leakcanary.LeakCanary;
import org.acra.ACRA; import org.acra.ACRA;
import org.acra.ReportingInteractionMode; import org.acra.ReportingInteractionMode;
@ -33,12 +39,12 @@ import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams; import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreProtocolPNames; import org.apache.http.params.CoreProtocolPNames;
import org.mediawiki.api.MWApi;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import fr.free.nrw.commons.auth.WikiAccountAuthenticator; import fr.free.nrw.commons.utils.FileUtils;
import fr.free.nrw.commons.caching.CacheController; import timber.log.Timber;
// TODO: Use ProGuard to rip out reporting when publishing // TODO: Use ProGuard to rip out reporting when publishing
@ReportsCrashes( @ReportsCrashes(
@ -51,7 +57,6 @@ import fr.free.nrw.commons.caching.CacheController;
) )
public class CommonsApplication extends Application { public class CommonsApplication extends Application {
private MWApi api;
private Account currentAccount = null; // Unlike a savings account... private Account currentAccount = null; // Unlike a savings account...
public static final String API_URL = "https://commons.wikimedia.org/w/api.php"; public static final String API_URL = "https://commons.wikimedia.org/w/api.php";
public static final String IMAGE_URL_BASE = "https://upload.wikimedia.org/wikipedia/commons"; public static final String IMAGE_URL_BASE = "https://upload.wikimedia.org/wikipedia/commons";
@ -70,11 +75,37 @@ public class CommonsApplication extends Application {
public static final String FEEDBACK_EMAIL = "commons-app-android@googlegroups.com"; public static final String FEEDBACK_EMAIL = "commons-app-android@googlegroups.com";
public static final String FEEDBACK_EMAIL_SUBJECT = "Commons Android App (%s) Feedback"; public static final String FEEDBACK_EMAIL_SUBJECT = "Commons Android App (%s) Feedback";
public RequestQueue volleyQueue; private static CommonsApplication instance = null;
private AbstractHttpClient httpClient = null;
private MWApi api = null;
LruCache<String, String> thumbnailUrlCache = new LruCache<>(1024);
private CacheController cacheData = null;
private DBOpenHelper dbOpenHelper = null;
private NearbyPlaces nearbyPlaces = null;
public CacheController cacheData; /**
* This should not be called by ANY application code (other than the magic Android glue)
* Use CommonsApplication.getInstance() instead to get the singleton.
*/
public CommonsApplication() {
CommonsApplication.instance = this;
}
public static AbstractHttpClient createHttpClient() { public static CommonsApplication getInstance() {
if (instance == null) {
instance = new CommonsApplication();
}
return instance;
}
public AbstractHttpClient getHttpClient() {
if (httpClient == null) {
httpClient = newHttpClient();
}
return httpClient;
}
private AbstractHttpClient newHttpClient() {
BasicHttpParams params = new BasicHttpParams(); BasicHttpParams params = new BasicHttpParams();
SchemeRegistry schemeRegistry = new SchemeRegistry(); SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
@ -85,87 +116,78 @@ public class CommonsApplication extends Application {
return new DefaultHttpClient(cm, params); return new DefaultHttpClient(cm, params);
} }
public static MWApi createMWApi() { public MWApi getMWApi() {
return new MWApi(API_URL, createHttpClient()); if (api == null) {
api = newMWApi();
}
return api;
}
private MWApi newMWApi() {
return new MWApi(API_URL, getHttpClient());
}
public CacheController getCacheData() {
if (cacheData == null) {
cacheData = new CacheController();
}
return cacheData;
}
public LruCache<String, String> getThumbnailUrlCache() {
return thumbnailUrlCache;
}
public synchronized DBOpenHelper getDBOpenHelper() {
if (dbOpenHelper == null) {
dbOpenHelper = new DBOpenHelper(this);
}
return dbOpenHelper;
}
public synchronized NearbyPlaces getNearbyPlaces() {
if (nearbyPlaces == null) {
nearbyPlaces = new NearbyPlaces();
}
return nearbyPlaces;
} }
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
Timber.plant(new Timber.DebugTree());
Stetho.initializeWithDefaults(this);
if (!BuildConfig.DEBUG) { if (!BuildConfig.DEBUG) {
ACRA.init(this); ACRA.init(this);
} }
// Fire progress callbacks for every 3% of uploaded content // Fire progress callbacks for every 3% of uploaded content
System.setProperty("in.yuvi.http.fluent.PROGRESS_TRIGGER_THRESHOLD", "3.0"); System.setProperty("in.yuvi.http.fluent.PROGRESS_TRIGGER_THRESHOLD", "3.0");
api = createMWApi();
ImageLoaderConfiguration imageLoaderConfiguration = new ImageLoaderConfiguration.Builder(getApplicationContext()) Fresco.initialize(this);
.discCache(new TotalSizeLimitedDiscCache(StorageUtils.getCacheDirectory(this), 128 * 1024 * 1024))
.build();
ImageLoader.getInstance().init(imageLoaderConfiguration);
// Initialize EventLogging // Initialize EventLogging
EventLog.setApp(this); EventLog.setApp(this);
// based off https://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
// Cache for 1/8th of available VM memory
long maxMem = Runtime.getRuntime().maxMemory();
if (maxMem < 48L * 1024L * 1024L) {
// Cache only one bitmap if VM memory is too small (such as Nexus One);
Log.d("Commons", "Skipping bitmap cache; max mem is: " + maxMem);
imageCache = new LruCache<>(1);
} else {
int cacheSize = (int) (maxMem / (1024 * 8));
Log.d("Commons", "Bitmap cache size " + cacheSize + " from max mem " + maxMem);
imageCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
int bitmapSize;
bitmapSize = bitmap.getByteCount();
// The cache size will be measured in kilobytes rather than number of items.
return bitmapSize / 1024;
}
};
}
//For caching area -> categories //For caching area -> categories
cacheData = new CacheController(); cacheData = new CacheController();
DiskBasedCache cache = new DiskBasedCache(getCacheDir(), 16 * 1024 * 1024);
volleyQueue = new RequestQueue(cache, new BasicNetwork(new HurlStack()));
volleyQueue.start();
}
private com.android.volley.toolbox.ImageLoader imageLoader;
private LruCache<String, Bitmap> imageCache;
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() { public Account getCurrentAccount() {
if(currentAccount == null) { if(currentAccount == null) {
AccountManager accountManager = AccountManager.get(this); AccountManager accountManager = AccountManager.get(this);
Account[] allAccounts = accountManager.getAccountsByType(WikiAccountAuthenticator.COMMONS_ACCOUNT_TYPE); Account[] allAccounts = accountManager.getAccountsByType(AccountUtil.accountType());
if(allAccounts.length != 0) { if(allAccounts.length != 0) {
currentAccount = allAccounts[0]; currentAccount = allAccounts[0];
} }
@ -181,21 +203,12 @@ public class CommonsApplication extends Application {
return false; // This should never happen return false; // This should never happen
} }
accountManager.invalidateAuthToken(WikiAccountAuthenticator.COMMONS_ACCOUNT_TYPE, api.getAuthCookie()); accountManager.invalidateAuthToken(AccountUtil.accountType(), getMWApi().getAuthCookie());
try { try {
String authCookie = accountManager.blockingGetAuthToken(curAccount, "", false); String authCookie = accountManager.blockingGetAuthToken(curAccount, "", false);
api.setAuthCookie(authCookie); getMWApi().setAuthCookie(authCookie);
return true; return true;
} catch (OperationCanceledException e) { } catch (OperationCanceledException | NullPointerException | IOException | AuthenticatorException e) {
e.printStackTrace();
return false;
} catch (AuthenticatorException e) {
e.printStackTrace();
return false;
} catch (IOException e) {
e.printStackTrace();
return false;
} catch (NullPointerException e) {
e.printStackTrace(); e.printStackTrace();
return false; return false;
} }
@ -206,4 +219,46 @@ public class CommonsApplication extends Application {
return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA) || return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA) ||
pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT); pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT);
} }
public void clearApplicationData(Context context) {
File cacheDirectory = context.getCacheDir();
File applicationDirectory = new File(cacheDirectory.getParent());
if (applicationDirectory.exists()) {
String[] fileNames = applicationDirectory.list();
for (String fileName : fileNames) {
if (!fileName.equals("lib")) {
FileUtils.deleteFile(new File(applicationDirectory, fileName));
}
}
}
AccountManager accountManager = AccountManager.get(this);
Account[] allAccounts = accountManager.getAccountsByType(AccountUtil.accountType());
for (int index = 0; index < allAccounts.length; index++) {
accountManager.removeAccount(allAccounts[index], null, null);
}
//TODO: fix preference manager
PreferenceManager.getDefaultSharedPreferences(getInstance()).edit().clear().commit();
SharedPreferences preferences = context
.getSharedPreferences("fr.free.nrw.commons", MODE_PRIVATE);
preferences.edit().clear().commit();
context.getSharedPreferences("prefs", Context.MODE_PRIVATE).edit().clear().commit();
preferences.edit().putBoolean("firstrun", false).apply();
updateAllDatabases();
currentAccount = null;
}
/**
* Deletes all tables and re-creates them.
*/
public void updateAllDatabases() {
DBOpenHelper dbOpenHelper = CommonsApplication.getInstance().getDBOpenHelper();
dbOpenHelper.getReadableDatabase().close();
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
ModifierSequence.Table.onDelete(db);
Category.Table.onDelete(db);
Contribution.Table.onDelete(db);
}
} }

View file

@ -4,19 +4,19 @@ import android.content.SharedPreferences;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build; import android.os.Build;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.Log;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.impl.client.AbstractHttpClient;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.IOException; import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import fr.free.nrw.commons.settings.Prefs; import fr.free.nrw.commons.settings.Prefs;
import in.yuvi.http.fluent.Http; import in.yuvi.http.fluent.Http;
import timber.log.Timber;
public class EventLog { public class EventLog {
@ -30,19 +30,19 @@ public class EventLog {
boolean allSuccess = true; boolean allSuccess = true;
// Not using the default URL connection, since that seems to have different behavior than the rest of the code // Not using the default URL connection, since that seems to have different behavior than the rest of the code
for(LogBuilder logBuilder: logBuilders) { for(LogBuilder logBuilder: logBuilders) {
HttpURLConnection conn;
try { try {
URL url = logBuilder.toUrl(); URL url = logBuilder.toUrl();
HttpResponse response = Http.get(url.toString()).use(CommonsApplication.createHttpClient()).asResponse(); AbstractHttpClient httpClient = CommonsApplication.getInstance().getHttpClient();
HttpResponse response = Http.get(url.toString()).use(httpClient).asResponse();
if(response.getStatusLine().getStatusCode() != 204) { if(response.getStatusLine().getStatusCode() != 204) {
allSuccess = false; allSuccess = false;
} }
Log.d("Commons", "EventLog hit " + url.toString()); Timber.d("EventLog hit %s", url);
} catch (IOException e) { } catch (IOException e) {
// Probably just ignore for now. Can be much more robust with a service, etc later on. // Probably just ignore for now. Can be much more robust with a service, etc later on.
Log.d("Commons", "IO Error, EventLog hit skipped"); Timber.d("IO Error, EventLog hit skipped");
} }
} }
@ -94,9 +94,7 @@ public class EventLog {
data.put("appversion", "Android/" + BuildConfig.VERSION_NAME); data.put("appversion", "Android/" + BuildConfig.VERSION_NAME);
fullData.put("event", data); fullData.put("event", data);
return new URL(CommonsApplication.EVENTLOG_URL + "?" + Utils.urlEncode(fullData.toString()) + ";"); return new URL(CommonsApplication.EVENTLOG_URL + "?" + Utils.urlEncode(fullData.toString()) + ";");
} catch (MalformedURLException e) { } catch (MalformedURLException | JSONException e) {
throw new RuntimeException(e);
} catch (JSONException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }

View file

@ -69,9 +69,8 @@ public class LicenseList {
int nameId = stringIdByName(stringId); int nameId = stringIdByName(stringId);
//Log.d("Commons", "LicenseList.nameForTemplate: nameId: " + nameId); //Log.d("Commons", "LicenseList.nameForTemplate: nameId: " + nameId);
if(nameId != 0) { if(nameId != 0) {
String name = res.getString(nameId);
//Log.d("Commons", "LicenseList.nameForTemplate: name: " + name); //Log.d("Commons", "LicenseList.nameForTemplate: name: " + name);
return name; return res.getString(nameId);
} }
return template; return template;
} }

View 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;
}
}

View file

@ -116,10 +116,6 @@ public class Media implements Parcelable {
this.creator = creator; this.creator = creator;
} }
public String getThumbnailUrl(int width) {
return Utils.makeThumbUrl(getImageUrl(), getFilename(), width);
}
public int getWidth() { public int getWidth() {
return width; return width;
} }
@ -144,6 +140,14 @@ public class Media implements Parcelable {
this.license = license; this.license = license;
} }
public String getCoordinates() {
return coordinates;
}
public void setCoordinates(String coordinates) {
this.coordinates = coordinates;
}
// Primary metadata fields // Primary metadata fields
protected Uri localUri; protected Uri localUri;
protected String imageUrl; protected String imageUrl;
@ -155,6 +159,7 @@ public class Media implements Parcelable {
protected int width; protected int width;
protected int height; protected int height;
protected String license; protected String license;
private String coordinates;
protected String creator; protected String creator;
protected ArrayList<String> categories; // as loaded at runtime? protected ArrayList<String> categories; // as loaded at runtime?
protected Map<String, String> descriptions; // multilingual descriptions as loaded protected Map<String, String> descriptions; // multilingual descriptions as loaded
@ -164,7 +169,7 @@ public class Media implements Parcelable {
} }
public void setCategories(List<String> categories) { public void setCategories(List<String> categories) {
this.categories.removeAll(this.categories); this.categories.clear();
this.categories.addAll(categories); this.categories.addAll(categories);
} }

View file

@ -1,9 +1,6 @@
package fr.free.nrw.commons; package fr.free.nrw.commons;
import android.util.Log;
import org.mediawiki.api.ApiResult; import org.mediawiki.api.ApiResult;
import org.mediawiki.api.MWApi;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import org.w3c.dom.Node; import org.w3c.dom.Node;
@ -23,6 +20,9 @@ import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import fr.free.nrw.commons.location.LatLng;
import timber.log.Timber;
/** /**
* Fetch additional media data from the network that we don't store locally. * Fetch additional media data from the network that we don't store locally.
* *
@ -31,14 +31,13 @@ import javax.xml.parsers.ParserConfigurationException;
*/ */
public class MediaDataExtractor { public class MediaDataExtractor {
private boolean fetched; private boolean fetched;
private boolean processed;
private String filename; private String filename;
private ArrayList<String> categories; private ArrayList<String> categories;
private Map<String, String> descriptions; private Map<String, String> descriptions;
private String author;
private Date date; private Date date;
private String license; private String license;
private String coordinates;
private LicenseList licenseList; private LicenseList licenseList;
/** /**
@ -49,7 +48,6 @@ public class MediaDataExtractor {
categories = new ArrayList<>(); categories = new ArrayList<>();
descriptions = new HashMap<>(); descriptions = new HashMap<>();
fetched = false; fetched = false;
processed = false;
this.licenseList = licenseList; this.licenseList = licenseList;
} }
@ -64,7 +62,7 @@ public class MediaDataExtractor {
throw new IllegalStateException("Tried to call MediaDataExtractor.fetch() again."); throw new IllegalStateException("Tried to call MediaDataExtractor.fetch() again.");
} }
MWApi api = CommonsApplication.createMWApi(); MWApi api = CommonsApplication.getInstance().getMWApi();
ApiResult result = api.action("query") ApiResult result = api.action("query")
.param("prop", "revisions") .param("prop", "revisions")
.param("titles", filename) .param("titles", filename)
@ -111,9 +109,7 @@ public class MediaDataExtractor {
doc = docBuilder.parse(new ByteArrayInputStream(source.getBytes("UTF-8"))); doc = docBuilder.parse(new ByteArrayInputStream(source.getBytes("UTF-8")));
} catch (ParserConfigurationException e) { } catch (ParserConfigurationException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} catch (IllegalStateException e) { } catch (IllegalStateException | SAXException e) {
throw new IOException(e);
} catch (SAXException e) {
throw new IOException(e); throw new IOException(e);
} }
Node templateNode = findTemplate(doc.getDocumentElement(), "information"); Node templateNode = findTemplate(doc.getDocumentElement(), "information");
@ -122,7 +118,14 @@ public class MediaDataExtractor {
descriptions = getMultilingualText(descriptionNode); descriptions = getMultilingualText(descriptionNode);
Node authorNode = findTemplateParameter(templateNode, "author"); Node authorNode = findTemplateParameter(templateNode, "author");
author = getFlatText(authorNode); }
Node coordinateTemplateNode = findTemplate(doc.getDocumentElement(), "location");
if (coordinateTemplateNode != null) {
coordinates = getCoordinates(coordinateTemplateNode);
} else {
coordinates = "No coordinates found";
} }
/* /*
@ -131,20 +134,20 @@ public class MediaDataExtractor {
* look for 'self' template and check its first parameter * look for 'self' template and check its first parameter
* if none, look for any of the known templates * if none, look for any of the known templates
*/ */
Log.d("Commons", "MediaDataExtractor searching for license"); Timber.d("MediaDataExtractor searching for license");
Node selfLicenseNode = findTemplate(doc.getDocumentElement(), "self"); Node selfLicenseNode = findTemplate(doc.getDocumentElement(), "self");
if (selfLicenseNode != null) { if (selfLicenseNode != null) {
Node firstNode = findTemplateParameter(selfLicenseNode, 1); Node firstNode = findTemplateParameter(selfLicenseNode, 1);
String licenseTemplate = getFlatText(firstNode); String licenseTemplate = getFlatText(firstNode);
License license = licenseList.licenseForTemplate(licenseTemplate); License license = licenseList.licenseForTemplate(licenseTemplate);
if (license == null) { if (license == null) {
Log.d("Commons", "MediaDataExtractor found no matching license for self parameter: " + licenseTemplate + "; faking it"); Timber.d("MediaDataExtractor found no matching license for self parameter: %s; faking it", licenseTemplate);
this.license = licenseTemplate; // hack hack! For non-selectable licenses that are still in the system. this.license = licenseTemplate; // hack hack! For non-selectable licenses that are still in the system.
} else { } else {
// fixme: record the self-ness in here too... sigh // fixme: record the self-ness in here too... sigh
// all this needs better server-side metadata // all this needs better server-side metadata
this.license = license.getKey(); this.license = license.getKey();
Log.d("Commons", "MediaDataExtractor found self-license " + this.license); Timber.d("MediaDataExtractor found self-license %s", this.license);
} }
} else { } else {
for (License license : licenseList.values()) { for (License license : licenseList.values()) {
@ -153,7 +156,7 @@ public class MediaDataExtractor {
if (template != null) { if (template != null) {
// Found! // Found!
this.license = license.getKey(); this.license = license.getKey();
Log.d("Commons", "MediaDataExtractor found non-self license " + this.license); Timber.d("MediaDataExtractor found non-self license %s", this.license);
break; break;
} }
} }
@ -245,6 +248,25 @@ public class MediaDataExtractor {
return parentNode.getTextContent(); return parentNode.getTextContent();
} }
/**
* Extracts the coordinates from the template and returns them as pretty formatted string.
* Loops over the children of the coordinate template:
* {{Location|47.50111007666667|19.055700301944444}}
* and extracts the latitude and longitude as a pretty string.
*
* @param parentNode The node of the coordinates template.
* @return Pretty formatted coordinates.
* @throws IOException Parsing failed.
*/
private String getCoordinates(Node parentNode) throws IOException {
NodeList childNodes = parentNode.getChildNodes();
double latitudeText = Double.parseDouble(childNodes.item(1).getTextContent());
double longitudeText = Double.parseDouble(childNodes.item(2).getTextContent());
LatLng coordinates = new LatLng(latitudeText, longitudeText);
return coordinates.getPrettyCoordinateString();
}
// Extract a dictionary of multilingual texts from a subset of the parse tree. // Extract a dictionary of multilingual texts from a subset of the parse tree.
// Texts are wrapped in things like {{en|foo} or {{en|1=foo bar}}. // Texts are wrapped in things like {{en|foo} or {{en|1=foo bar}}.
// Text outside those wrappers is stuffed into a 'default' faux language key if present. // Text outside those wrappers is stuffed into a 'default' faux language key if present.
@ -290,6 +312,7 @@ public class MediaDataExtractor {
media.setCategories(categories); media.setCategories(categories);
media.setDescriptions(descriptions); media.setDescriptions(descriptions);
media.setCoordinates(coordinates);
if (license != null) { if (license != null) {
media.setLicense(license); media.setLicense(license);
} }

View file

@ -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;
}
}

View file

@ -1,47 +1,15 @@
/**
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package fr.free.nrw.commons; package fr.free.nrw.commons;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray; import android.support.annotation.NonNull;
import android.graphics.drawable.BitmapDrawable; import android.support.annotation.Nullable;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import com.android.volley.VolleyError; import com.facebook.drawee.view.SimpleDraweeView;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.ImageLoader.ImageContainer;
import com.android.volley.toolbox.ImageLoader.ImageListener;
import fr.free.nrw.commons.contributions.Contribution; public class MediaWikiImageView extends SimpleDraweeView {
import fr.free.nrw.commons.contributions.ContributionsContentProvider; private ThumbnailFetchTask currentThumbnailTask;
public class MediaWikiImageView extends ImageView {
private Media mMedia;
private ImageLoader mImageLoader;
private ImageContainer mImageContainer;
private View loadingView;
private boolean isThumbnail;
public MediaWikiImageView(Context context) { public MediaWikiImageView(Context context) {
this(context, null); this(context, null);
@ -49,179 +17,58 @@ public class MediaWikiImageView extends ImageView {
public MediaWikiImageView(Context context, AttributeSet attrs) { public MediaWikiImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0); this(context, attrs, 0);
TypedArray actualAttrs = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MediaWikiImageView, 0, 0);
isThumbnail = actualAttrs.getBoolean(0, false);
actualAttrs.recycle();
} }
public MediaWikiImageView(Context context, AttributeSet attrs, int defStyle) { public MediaWikiImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle); super(context, attrs, defStyle);
} }
public void setMedia(Media media, ImageLoader imageLoader) { public void setMedia(Media media) {
this.mMedia = media; if (currentThumbnailTask != null) {
mImageLoader = imageLoader; currentThumbnailTask.cancel(true);
loadImageIfNecessary(false); }
} if(media == null) {
public void setLoadingView(View loadingView) {
this.loadingView = loadingView;
}
public View getLoadingView() {
return loadingView;
}
private void loadImageIfNecessary(final boolean isInLayoutPass) {
loadImageIfNecessary(isInLayoutPass, false);
}
private void loadImageIfNecessary(final boolean isInLayoutPass, final boolean tryOriginal) {
int width = getWidth();
int height = getHeight();
// if the view's bounds aren't known yet, hold off on loading the image.
if (width == 0 && height == 0) {
return; return;
} }
if(mMedia == null) { if (CommonsApplication.getInstance().getThumbnailUrlCache().get(media.getFilename()) != null) {
return; setImageUrl(CommonsApplication.getInstance().getThumbnailUrlCache().get(media.getFilename()));
}
// Do not count for density when loading thumbnails.
// FIXME: Use another 'algorithm' that doesn't punish low res devices
if(isThumbnail) {
float dpFactor = Math.max(getResources().getDisplayMetrics().density, 1.0f);
width = (int) (width / dpFactor);
height = (int) (height / dpFactor);
}
final String mUrl;
if(tryOriginal) {
mUrl = mMedia.getImageUrl();
} else { } else {
// Round it to the nearest 320 setImageUrl(null);
// Possible a similar size image has already been generated. currentThumbnailTask = new ThumbnailFetchTask(media);
// Reduces Server cache fragmentation, also increases chance of cache hit currentThumbnailTask.execute(media.getFilename());
// If width is less than 320, we round up to 320
int bucketedWidth = width <= 320 ? 320 : Math.round((float)width / 320.0f) * 320;
if(mMedia.getWidth() != 0 && mMedia.getWidth() < bucketedWidth) {
// If we know that the width of the image is lesser than the required width
// We don't even try to load the thumbnai, go directly to the source
loadImageIfNecessary(isInLayoutPass, true);
return;
} else {
mUrl = mMedia.getThumbnailUrl(bucketedWidth);
}
} }
// if the URL to be loaded in this view is empty, cancel any old requests and clear the
// currently loaded image.
if (TextUtils.isEmpty(mUrl)) {
if (mImageContainer != null) {
mImageContainer.cancelRequest();
mImageContainer = null;
}
setImageBitmap(null);
return;
}
// Don't repeat work. Prevents onLayout cascades
// We ignore it if the image request was for either the current URL of for the full URL
// Since the full URL is always the second, and
if (mImageContainer != null && mImageContainer.getRequestUrl() != null) {
if (mImageContainer.getRequestUrl().equals(mMedia.getImageUrl()) || mImageContainer.getRequestUrl().equals(mUrl)) {
return;
} else {
// if there is a pre-existing request, cancel it if it's fetching a different URL.
mImageContainer.cancelRequest();
BitmapDrawable actualDrawable = (BitmapDrawable)getDrawable();
if(actualDrawable != null && actualDrawable.getBitmap() != null) {
setImageBitmap(null);
if(loadingView != null) {
loadingView.setVisibility(View.VISIBLE);
}
}
}
}
// The pre-existing content of this view didn't match the current URL. Load the new image
// from the network.
ImageContainer newContainer = mImageLoader.get(mUrl,
new ImageListener() {
@Override
public void onErrorResponse(final VolleyError error) {
if(!tryOriginal) {
post(new Runnable() {
@Override
public void run() {
loadImageIfNecessary(false, true);
}
});
}
}
@Override
public void onResponse(final ImageContainer response, boolean isImmediate) {
// If this was an immediate response that was delivered inside of a layout
// pass do not set the image immediately as it will trigger a requestLayout
// inside of a layout. Instead, defer setting the image by posting back to
// the main thread.
if (isImmediate && isInLayoutPass) {
post(new Runnable() {
@Override
public void run() {
onResponse(response, false);
}
});
return;
}
if (response.getBitmap() != null) {
setImageBitmap(response.getBitmap());
if(tryOriginal && mMedia instanceof Contribution && (response.getBitmap().getWidth() > mMedia.getWidth() || response.getBitmap().getHeight() > mMedia.getHeight())) {
// If there is no width information for this image, save it. This speeds up image loading massively for smaller images
mMedia.setHeight(response.getBitmap().getHeight());
mMedia.setWidth(response.getBitmap().getWidth());
((Contribution)mMedia).setContentProviderClient(MediaWikiImageView.this.getContext().getContentResolver().acquireContentProviderClient(ContributionsContentProvider.AUTHORITY));
((Contribution)mMedia).save();
}
if(loadingView != null) {
loadingView.setVisibility(View.GONE);
}
} else {
// I'm not really sure where this would hit but not onError
}
}
});
// update the ImageContainer to be the new bitmap container.
mImageContainer = newContainer;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
loadImageIfNecessary(true);
} }
@Override @Override
protected void onDetachedFromWindow() { protected void onDetachedFromWindow() {
if (mImageContainer != null) { if (currentThumbnailTask != null) {
// If the view was bound to an image request, cancel it and clear currentThumbnailTask.cancel(true);
// out the image from the view.
mImageContainer.cancelRequest();
setImageBitmap(null);
// also clear out the container so we can reload the image if necessary.
mImageContainer = null;
} }
super.onDetachedFromWindow(); super.onDetachedFromWindow();
} }
@Override private void setImageUrl(@Nullable String url) {
protected void drawableStateChanged() { setImageURI(url);
super.drawableStateChanged(); }
invalidate();
private class ThumbnailFetchTask extends MediaThumbnailFetchTask {
ThumbnailFetchTask(@NonNull Media media) {
super(media);
}
@Override
protected void onPostExecute(String result) {
if (isCancelled()) {
return;
}
if (TextUtils.isEmpty(result) && media.getLocalUri() != null) {
result = media.getLocalUri().toString();
} else {
// only cache meaningful thumbnails received from network.
CommonsApplication.getInstance().getThumbnailUrlCache().put(media.getFilename(), result);
}
setImageUrl(result);
}
} }
} }

View file

@ -1,14 +1,11 @@
package fr.free.nrw.commons; package fr.free.nrw.commons;
import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.preference.PreferenceManager;
import android.util.Log;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
import fr.free.nrw.commons.settings.Prefs; import fr.free.nrw.commons.settings.Prefs;
import timber.log.Timber;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
@ -44,8 +41,6 @@ import org.xmlpull.v1.XmlPullParserException;
public class Utils { public class Utils {
private static final String TAG = Utils.class.getName();
// Get SHA1 of file from input stream // Get SHA1 of file from input stream
public static String getSHA1(InputStream is) { public static String getSHA1(InputStream is) {
@ -53,7 +48,7 @@ public class Utils {
try { try {
digest = MessageDigest.getInstance("SHA1"); digest = MessageDigest.getInstance("SHA1");
} catch (NoSuchAlgorithmException e) { } catch (NoSuchAlgorithmException e) {
Log.e(TAG, "Exception while getting Digest", e); Timber.e(e, "Exception while getting Digest");
return ""; return "";
} }
@ -68,17 +63,17 @@ public class Utils {
String output = bigInt.toString(16); String output = bigInt.toString(16);
// Fill to 40 chars // Fill to 40 chars
output = String.format("%40s", output).replace(' ', '0'); output = String.format("%40s", output).replace(' ', '0');
Log.i(TAG, "File SHA1: " + output); Timber.i("File SHA1: %s", output);
return output; return output;
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "IO Exception", e); Timber.e(e, "IO Exception");
return ""; return "";
} finally { } finally {
try { try {
is.close(); is.close();
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "Exception on closing MD5 input stream", e); Timber.e(e, "Exception on closing MD5 input stream");
} }
} }
} }
@ -124,10 +119,7 @@ public class Utils {
Transformer transformer = null; Transformer transformer = null;
try { try {
transformer = TransformerFactory.newInstance().newTransformer(); transformer = TransformerFactory.newInstance().newTransformer();
} catch (TransformerConfigurationException e) { } catch (TransformerConfigurationException | TransformerFactoryConfigurationError e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (TransformerFactoryConfigurationError e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
@ -145,26 +137,6 @@ public class Utils {
return outputStream.toString(); return outputStream.toString();
} }
private static DisplayImageOptions.Builder defaultImageOptionsBuilder;
public static DisplayImageOptions.Builder getGenericDisplayOptions() {
if (defaultImageOptionsBuilder == null) {
defaultImageOptionsBuilder = new DisplayImageOptions.Builder().cacheInMemory()
.imageScaleType(ImageScaleType.IN_SAMPLE_POWER_OF_2);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
// List views flicker badly during data updates on Android 2.3; we
// haven't quite figured out why but cells seem to be rearranged oddly.
// Disable the fade-in on 2.3 to reduce the effect.
defaultImageOptionsBuilder = defaultImageOptionsBuilder
.displayer(new FadeInBitmapDisplayer(300));
}
defaultImageOptionsBuilder = defaultImageOptionsBuilder
.cacheInMemory()
.resetViewBeforeLoading();
}
return defaultImageOptionsBuilder;
}
private static final URLCodec urlCodec = new URLCodec(); private static final URLCodec urlCodec = new URLCodec();
public static String urlEncode(String url) { public static String urlEncode(String url) {
@ -184,76 +156,64 @@ public class Utils {
return count; return count;
} }
public static String makeThumbUrl(String imageUrl, String filename, int width) {
// Ugly Hack!
// Update: OH DEAR GOD WHAT A HORRIBLE HACK I AM SO SORRY
if (imageUrl.endsWith("webm")) {
return imageUrl.replaceFirst("test/", "test/thumb/").replace("commons/", "commons/thumb/") + "/" + width + "px--" + filename.replaceAll("File:", "").replaceAll(" ", "_") + ".jpg";
} else {
String thumbUrl = imageUrl.replaceFirst("test/", "test/thumb/").replace("commons/", "commons/thumb/") + "/" + width + "px-" + filename.replaceAll("File:", "").replaceAll(" ", "_");
if (thumbUrl.endsWith("jpg") || thumbUrl.endsWith("png") || thumbUrl.endsWith("jpeg")) {
return thumbUrl;
} else {
return thumbUrl + ".png";
}
}
}
public static String capitalize(String string) { public static String capitalize(String string) {
return string.substring(0, 1).toUpperCase(Locale.getDefault()) + string.substring(1); return string.substring(0, 1).toUpperCase(Locale.getDefault()) + string.substring(1);
} }
public static String licenseTemplateFor(String license) { public static String licenseTemplateFor(String license) {
if (license.equals(Prefs.Licenses.CC_BY_3)) { switch (license) {
return "{{self|cc-by-3.0}}"; case Prefs.Licenses.CC_BY_3:
} else if (license.equals(Prefs.Licenses.CC_BY_4)) { return "{{self|cc-by-3.0}}";
return "{{self|cc-by-4.0}}"; case Prefs.Licenses.CC_BY_4:
} else if (license.equals(Prefs.Licenses.CC_BY_SA_3)) { return "{{self|cc-by-4.0}}";
return "{{self|cc-by-sa-3.0}}"; case Prefs.Licenses.CC_BY_SA_3:
} else if (license.equals(Prefs.Licenses.CC_BY_SA_4)) { return "{{self|cc-by-sa-3.0}}";
return "{{self|cc-by-sa-4.0}}"; case Prefs.Licenses.CC_BY_SA_4:
} else if (license.equals(Prefs.Licenses.CC0)) { return "{{self|cc-by-sa-4.0}}";
return "{{self|cc-zero}}"; case Prefs.Licenses.CC0:
} else if (license.equals(Prefs.Licenses.CC_BY)) { return "{{self|cc-zero}}";
return "{{self|cc-by-3.0}}"; case Prefs.Licenses.CC_BY:
} else if (license.equals(Prefs.Licenses.CC_BY_SA)) { return "{{self|cc-by-3.0}}";
return "{{self|cc-by-sa-3.0}}"; case Prefs.Licenses.CC_BY_SA:
return "{{self|cc-by-sa-3.0}}";
} }
throw new RuntimeException("Unrecognized license value"); throw new RuntimeException("Unrecognized license value: " + license);
} }
public static int licenseNameFor(String license) { public static int licenseNameFor(String license) {
if (license.equals(Prefs.Licenses.CC_BY_3)) { switch (license) {
return R.string.license_name_cc_by; case Prefs.Licenses.CC_BY_3:
} else if (license.equals(Prefs.Licenses.CC_BY_4)) { return R.string.license_name_cc_by;
return R.string.license_name_cc_by_four; case Prefs.Licenses.CC_BY_4:
} else if (license.equals(Prefs.Licenses.CC_BY_SA_3)) { return R.string.license_name_cc_by_four;
return R.string.license_name_cc_by_sa; case Prefs.Licenses.CC_BY_SA_3:
} else if (license.equals(Prefs.Licenses.CC_BY_SA_4)) { return R.string.license_name_cc_by_sa;
return R.string.license_name_cc_by_sa_four; case Prefs.Licenses.CC_BY_SA_4:
} else if (license.equals(Prefs.Licenses.CC0)) { return R.string.license_name_cc_by_sa_four;
return R.string.license_name_cc0; case Prefs.Licenses.CC0:
} else if (license.equals(Prefs.Licenses.CC_BY)) { // for backward compatibility to v2.1 return R.string.license_name_cc0;
return R.string.license_name_cc_by_3_0; case Prefs.Licenses.CC_BY: // for backward compatibility to v2.1
} else if (license.equals(Prefs.Licenses.CC_BY_SA)) { // for backward compatibility to v2.1 return R.string.license_name_cc_by_3_0;
return R.string.license_name_cc_by_sa_3_0; case Prefs.Licenses.CC_BY_SA: // for backward compatibility to v2.1
return R.string.license_name_cc_by_sa_3_0;
} }
throw new RuntimeException("Unrecognized license value"); throw new RuntimeException("Unrecognized license value: " + license);
} }
public static String licenseUrlFor(String license) { public static String licenseUrlFor(String license) {
if (license.equals(Prefs.Licenses.CC_BY_3)) { switch (license) {
return "https://creativecommons.org/licenses/by/3.0/"; case Prefs.Licenses.CC_BY_3:
} else if (license.equals(Prefs.Licenses.CC_BY_4)) { return "https://creativecommons.org/licenses/by/3.0/";
return "https://creativecommons.org/licenses/by/4.0/"; case Prefs.Licenses.CC_BY_4:
} else if (license.equals(Prefs.Licenses.CC_BY_SA_3)) { return "https://creativecommons.org/licenses/by/4.0/";
return "https://creativecommons.org/licenses/by-sa/3.0/"; case Prefs.Licenses.CC_BY_SA_3:
} else if (license.equals(Prefs.Licenses.CC_BY_SA_4)) { return "https://creativecommons.org/licenses/by-sa/3.0/";
return "https://creativecommons.org/licenses/by-sa/4.0/"; case Prefs.Licenses.CC_BY_SA_4:
} else if (license.equals(Prefs.Licenses.CC0)) { return "https://creativecommons.org/licenses/by-sa/4.0/";
return "https://creativecommons.org/publicdomain/zero/1.0/"; case Prefs.Licenses.CC0:
return "https://creativecommons.org/publicdomain/zero/1.0/";
} }
throw new RuntimeException("Unrecognized license value"); throw new RuntimeException("Unrecognized license value: " + license);
} }
public static Uri uriForWikiPage(String name) { public static Uri uriForWikiPage(String name) {
@ -308,4 +268,12 @@ public class Utils {
public static boolean isNullOrWhiteSpace(String value) { public static boolean isNullOrWhiteSpace(String value) {
return value == null || value.trim().isEmpty(); return value == null || value.trim().isEmpty();
} }
public static boolean isDarkTheme(Context context) {
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean("theme",true)) {
return true;
}else {
return false;
}
}
} }

View file

@ -10,7 +10,6 @@ import butterknife.ButterKnife;
import fr.free.nrw.commons.theme.BaseActivity; import fr.free.nrw.commons.theme.BaseActivity;
public class WelcomeActivity extends BaseActivity { public class WelcomeActivity extends BaseActivity {
private WelcomePagerAdapter adapter;
@BindView(R.id.welcomePager) ViewPager pager; @BindView(R.id.welcomePager) ViewPager pager;
@BindView(R.id.welcomePagerIndicator) CirclePageIndicator indicator; @BindView(R.id.welcomePagerIndicator) CirclePageIndicator indicator;
@ -20,14 +19,16 @@ public class WelcomeActivity extends BaseActivity {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_welcome); setContentView(R.layout.activity_welcome);
getSupportActionBar().hide(); if (getSupportActionBar() != null) {
getSupportActionBar().hide();
}
ButterKnife.bind(this); ButterKnife.bind(this);
setUpAdapter(); setUpAdapter();
} }
private void setUpAdapter() { private void setUpAdapter() {
adapter = new WelcomePagerAdapter(this); WelcomePagerAdapter adapter = new WelcomePagerAdapter(this);
pager.setAdapter(adapter); pager.setAdapter(adapter);
indicator.setViewPager(pager); indicator.setViewPager(pager);
} }

View 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();
}
}

View file

@ -10,18 +10,18 @@ import android.os.Bundle;
import java.io.IOException; import java.io.IOException;
import fr.free.nrw.commons.theme.BaseActivity;
import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.theme.NavigationBaseActivity;
public abstract class AuthenticatedActivity extends BaseActivity { public abstract class AuthenticatedActivity extends NavigationBaseActivity {
String accountType; String accountType;
CommonsApplication app; CommonsApplication app;
private String authCookie; private String authCookie;
public AuthenticatedActivity(String accountType) { public AuthenticatedActivity() {
this.accountType = accountType; this.accountType = AccountUtil.accountType();
} }
private class GetAuthCookieTask extends AsyncTask<Void, String, String> { private class GetAuthCookieTask extends AsyncTask<Void, String, String> {
@ -131,7 +131,7 @@ public abstract class AuthenticatedActivity extends BaseActivity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
app = (CommonsApplication)this.getApplicationContext(); app = CommonsApplication.getInstance();
if(savedInstanceState != null) { if(savedInstanceState != null) {
authCookie = savedInstanceState.getString("authCookie"); authCookie = savedInstanceState.getString("authCookie");
} }

View file

@ -1,171 +1,78 @@
package fr.free.nrw.commons.auth; package fr.free.nrw.commons.auth;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorActivity; import android.accounts.AccountAuthenticatorActivity;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager;
import android.app.Activity;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.NavUtils; import android.support.v4.app.NavUtils;
import android.text.Editable; import android.text.Editable;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import fr.free.nrw.commons.*;
import java.io.IOException;
import java.util.Locale;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.EventLog;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.WelcomeActivity;
import fr.free.nrw.commons.contributions.ContributionsActivity; import fr.free.nrw.commons.contributions.ContributionsActivity;
import fr.free.nrw.commons.contributions.ContributionsContentProvider; import timber.log.Timber;
import fr.free.nrw.commons.modifications.ModificationsContentProvider;
public class LoginActivity extends AccountAuthenticatorActivity { public class LoginActivity extends AccountAuthenticatorActivity {
public static final String PARAM_USERNAME = "fr.free.nrw.commons.login.username"; public static final String PARAM_USERNAME = "fr.free.nrw.commons.login.username";
private CommonsApplication app;
private SharedPreferences prefs = null; private SharedPreferences prefs = null;
Button loginButton; private Button loginButton;
Button signupButton; private EditText usernameEdit;
EditText usernameEdit;
EditText passwordEdit; EditText passwordEdit;
ProgressDialog dialog; EditText twoFactorEdit;
ProgressDialog progressDialog;
private class LoginTask extends AsyncTask<String, String, String> { private CommonsApplication app;
Activity context;
String username;
String password;
@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
Log.d("Commons", "Login done!");
EventLog.schema(CommonsApplication.EVENT_LOGIN_ATTEMPT)
.param("username", username)
.param("result", result)
.log();
if (result.equals("Success")) {
if (dialog != null && dialog.isShowing()) {
dialog.dismiss();
}
Toast successToast = Toast.makeText(context, R.string.login_success, Toast.LENGTH_SHORT);
successToast.show();
Account account = new Account(username, WikiAccountAuthenticator.COMMONS_ACCOUNT_TYPE);
boolean accountCreated = AccountManager.get(context).addAccountExplicitly(account, password, null);
Bundle extras = context.getIntent().getExtras();
if (extras != null) {
Log.d("LoginActivity", "Bundle of extras: " + extras.toString());
if (accountCreated) { // Pass the new account back to the account manager
AccountAuthenticatorResponse response = extras.getParcelable(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE);
Bundle authResult = new Bundle();
authResult.putString(AccountManager.KEY_ACCOUNT_NAME, username);
authResult.putString(AccountManager.KEY_ACCOUNT_TYPE, WikiAccountAuthenticator.COMMONS_ACCOUNT_TYPE);
if (response != null) {
response.onResult(authResult);
}
}
}
// FIXME: If the user turns it off, it shouldn't be auto turned back on
ContentResolver.setSyncAutomatically(account, ContributionsContentProvider.AUTHORITY, true); // Enable sync by default!
ContentResolver.setSyncAutomatically(account, ModificationsContentProvider.AUTHORITY, true); // Enable sync by default!
Intent intent = new Intent(context, ContributionsActivity.class);
startActivity(intent);
finish();
} else {
int response;
if(result.equals("NetworkFailure")) {
response = R.string.login_failed_network;
} else if(result.equals("NotExists") || result.equals("Illegal") || result.equals("NotExists")) {
response = R.string.login_failed_username;
passwordEdit.setText("");
} else if(result.equals("EmptyPass") || result.equals("WrongPass") || result.equals("WrongPluginPass")) {
response = R.string.login_failed_password;
passwordEdit.setText("");
} else if(result.equals("Throttled")) {
response = R.string.login_failed_throttled;
} else if(result.equals("Blocked")) {
response = R.string.login_failed_blocked;
} else {
// Should never really happen
Log.d("Commons", "Login failed with reason: " + result);
response = R.string.login_failed_generic;
}
Toast.makeText(getApplicationContext(), response, Toast.LENGTH_LONG).show();
dialog.cancel();
}
}
@Override
protected void onPreExecute() {
super.onPreExecute();
dialog = new ProgressDialog(context);
dialog.setIndeterminate(true);
dialog.setTitle(getString(R.string.logging_in_title));
dialog.setMessage(getString(R.string.logging_in_message));
dialog.setCanceledOnTouchOutside(false);
dialog.show();
}
LoginTask(Activity context) {
this.context = context;
}
@Override
protected String doInBackground(String... params) {
username = params[0];
password = params[1];
try {
return app.getApi().login(username, password);
} catch (IOException e) {
// Do something better!
return "NetworkFailure";
}
}
}
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
app = (CommonsApplication) this.getApplicationContext();
app = CommonsApplication.getInstance();
setContentView(R.layout.activity_login); setContentView(R.layout.activity_login);
final LoginActivity that = this;
loginButton = (Button) findViewById(R.id.loginButton); loginButton = (Button) findViewById(R.id.loginButton);
signupButton = (Button) findViewById(R.id.signupButton); Button signupButton = (Button) findViewById(R.id.signupButton);
usernameEdit = (EditText) findViewById(R.id.loginUsername); usernameEdit = (EditText) findViewById(R.id.loginUsername);
passwordEdit = (EditText) findViewById(R.id.loginPassword); passwordEdit = (EditText) findViewById(R.id.loginPassword);
final LoginActivity that = this; twoFactorEdit = (EditText) findViewById(R.id.loginTwoFactor);
prefs = getSharedPreferences("fr.free.nrw.commons", MODE_PRIVATE); prefs = getSharedPreferences("fr.free.nrw.commons", MODE_PRIVATE);
TextWatcher loginEnabler = new TextWatcher() { TextWatcher loginEnabler = newLoginTextWatcher();
usernameEdit.addTextChangedListener(loginEnabler);
passwordEdit.addTextChangedListener(loginEnabler);
twoFactorEdit.addTextChangedListener(loginEnabler);
passwordEdit.setOnEditorActionListener( newLoginInputActionListener() );
loginButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
that.performLogin();
}
});
signupButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) { that.signUp(v); }
});
}
private TextWatcher newLoginTextWatcher() {
return new TextWatcher() {
@Override @Override
public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) { } public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) { }
@ -174,17 +81,21 @@ public class LoginActivity extends AccountAuthenticatorActivity {
@Override @Override
public void afterTextChanged(Editable editable) { public void afterTextChanged(Editable editable) {
if(usernameEdit.getText().length() != 0 && passwordEdit.getText().length() != 0) { if(
usernameEdit.getText().length() != 0 &&
passwordEdit.getText().length() != 0 &&
( BuildConfig.DEBUG || twoFactorEdit.getText().length() != 0 || twoFactorEdit.getVisibility() != View.VISIBLE )
) {
loginButton.setEnabled(true); loginButton.setEnabled(true);
} else { } else {
loginButton.setEnabled(false); loginButton.setEnabled(false);
} }
} }
}; };
}
usernameEdit.addTextChangedListener(loginEnabler); private TextView.OnEditorActionListener newLoginInputActionListener() {
passwordEdit.addTextChangedListener(loginEnabler); return new TextView.OnEditorActionListener() {
passwordEdit.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override @Override
public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) { public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
if (loginButton.isEnabled()) { if (loginButton.isEnabled()) {
@ -198,36 +109,31 @@ public class LoginActivity extends AccountAuthenticatorActivity {
} }
return false; return false;
} }
}); };
loginButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
that.performLogin();
}
});
} }
@Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
if (prefs.getBoolean("firstrun", true)) { if (prefs.getBoolean("firstrun", true)) {
// Do first run stuff here then set 'firstrun' as false this.startWelcomeIntent();
Intent welcomeIntent = new Intent(this, WelcomeActivity.class);
startActivity(welcomeIntent);
prefs.edit().putBoolean("firstrun", false).apply(); prefs.edit().putBoolean("firstrun", false).apply();
} }
if (app.getCurrentAccount() != null) {
startMainActivity();
}
} }
private void startWelcomeIntent() {
Intent welcomeIntent = new Intent(this, WelcomeActivity.class);
startActivity(welcomeIntent);
}
@Override @Override
protected void onDestroy() { protected void onDestroy() {
try { try {
// To prevent leaked window when finish() is called, see http://stackoverflow.com/questions/32065854/activity-has-leaked-window-at-alertdialog-show-method // To prevent leaked window when finish() is called, see http://stackoverflow.com/questions/32065854/activity-has-leaked-window-at-alertdialog-show-method
if (dialog != null && dialog.isShowing()) { if (progressDialog != null && progressDialog.isShowing()) {
dialog.dismiss(); progressDialog.dismiss();
} }
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
@ -236,15 +142,27 @@ public class LoginActivity extends AccountAuthenticatorActivity {
} }
private void performLogin() { private void performLogin() {
String username = usernameEdit.getText().toString(); Timber.d("Login to start!");
// Because Mediawiki is upercase-first-char-then-case-sensitive :) LoginTask task = getLoginTask();
String canonicalUsername = Utils.capitalize(username.substring(0,1)) + username.substring(1); task.execute();
}
String password = passwordEdit.getText().toString(); private LoginTask getLoginTask() {
return new LoginTask(
this,
canonicializeUsername( usernameEdit.getText().toString() ),
passwordEdit.getText().toString(),
twoFactorEdit.getText().toString()
);
}
Log.d("Commons", "Login to start!"); /**
LoginTask task = new LoginTask(this); * Because Mediawiki is upercase-first-char-then-case-sensitive :)
task.execute(canonicalUsername, password); * @param username String
* @return String canonicial username
*/
private String canonicializeUsername( String username ) {
return Utils.capitalize(username.substring(0,1)) + username.substring(1);
} }
@Override @Override
@ -257,9 +175,48 @@ public class LoginActivity extends AccountAuthenticatorActivity {
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
//Called when Sign Up button is clicked /**
* Called when Sign Up button is clicked.
* @param view View
*/
public void signUp(View view) { public void signUp(View view) {
Intent intent = new Intent(this, SignupActivity.class); Intent intent = new Intent(this, SignupActivity.class);
startActivity(intent); startActivity(intent);
} }
public void askUserForTwoFactorAuth() {
if(BuildConfig.DEBUG) {
twoFactorEdit.setVisibility(View.VISIBLE);
showUserToastAndCancelDialog( R.string.login_failed_2fa_needed );
}else{
showUserToastAndCancelDialog( R.string.login_failed_2fa_not_supported );
}
}
public void showUserToastAndCancelDialog( int resId ) {
showUserToast( resId );
progressDialog.cancel();
}
private void showUserToast( int resId ) {
Toast.makeText(this, resId, Toast.LENGTH_LONG).show();
}
public void showSuccessToastAndDismissDialog() {
Toast successToast = Toast.makeText(this, R.string.login_success, Toast.LENGTH_SHORT);
successToast.show();
progressDialog.dismiss();
}
public void emptySensitiveEditFields() {
passwordEdit.setText("");
twoFactorEdit.setText("");
}
public void startMainActivity() {
Intent intent = new Intent(this, ContributionsActivity.class);
startActivity(intent);
finish();
}
} }

View 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 );
}
}
}

View file

@ -2,13 +2,14 @@ package fr.free.nrw.commons.auth;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.webkit.WebSettings; import android.webkit.WebSettings;
import android.webkit.WebView; import android.webkit.WebView;
import android.webkit.WebViewClient; import android.webkit.WebViewClient;
import android.widget.Toast; import android.widget.Toast;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.theme.BaseActivity; import fr.free.nrw.commons.theme.BaseActivity;
import timber.log.Timber;
public class SignupActivity extends BaseActivity { public class SignupActivity extends BaseActivity {
@ -17,9 +18,7 @@ public class SignupActivity extends BaseActivity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
Log.d("SignupActivity", "Signup Activity started"); Timber.d("Signup Activity started");
getSupportActionBar().hide();
webView = new WebView(this); webView = new WebView(this);
setContentView(webView); setContentView(webView);
@ -37,17 +36,21 @@ public class SignupActivity extends BaseActivity {
public boolean shouldOverrideUrlLoading(WebView view, String url) { public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.equals("https://commons.m.wikimedia.org/w/index.php?title=Main_Page&welcome=yes")) { if (url.equals("https://commons.m.wikimedia.org/w/index.php?title=Main_Page&welcome=yes")) {
//Signup success, so clear cookies, notify user, and load LoginActivity again //Signup success, so clear cookies, notify user, and load LoginActivity again
Log.d("SignupActivity", "Overriding URL" + url); Timber.d("Overriding URL %s", url);
Toast toast = Toast.makeText(getApplicationContext(), "Account created!", Toast.LENGTH_LONG); Toast toast = Toast.makeText(
CommonsApplication.getInstance(),
"Account created!",
Toast.LENGTH_LONG
);
toast.show(); toast.show();
Intent intent = new Intent(getApplicationContext(), LoginActivity.class); Intent intent = new Intent(CommonsApplication.getInstance(), LoginActivity.class);
startActivity(intent); startActivity(intent);
return true; return true;
} else { } else {
//If user clicks any other links in the webview //If user clicks any other links in the webview
Log.d("SignupActivity", "Not overriding URL, URL is: " + url); Timber.d("Not overriding URL, URL is: %s", url);
return false; return false;
} }
} }

View file

@ -9,44 +9,76 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import org.mediawiki.api.MWApi;
import java.io.IOException; import java.io.IOException;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.MWApi;
public class WikiAccountAuthenticator extends AbstractAccountAuthenticator { public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
public static final String COMMONS_ACCOUNT_TYPE = "fr.free.nrw.commons";
private Context context; private Context context;
public WikiAccountAuthenticator(Context context) { public WikiAccountAuthenticator(Context context) {
super(context); super(context);
this.context = context; this.context = context;
} }
private Bundle unsupportedOperation() {
Bundle bundle = new Bundle();
bundle.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION);
// HACK: the docs indicate that this is a required key bit it's not displayed to the user.
bundle.putString(AccountManager.KEY_ERROR_MESSAGE, "");
return bundle;
}
private boolean supportedAccountType(@Nullable String type) {
return AccountUtil.accountType().equals(type);
}
@Override @Override
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException { public Bundle addAccount(@NonNull AccountAuthenticatorResponse response,
final Intent intent = new Intent(context, LoginActivity.class); @NonNull String accountType, @Nullable String authTokenType,
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); @Nullable String[] requiredFeatures, @Nullable Bundle options)
final Bundle bundle = new Bundle(); throws NetworkErrorException {
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
if (!supportedAccountType(accountType)) {
return unsupportedOperation();
}
return addAccount(response);
}
private Bundle addAccount(AccountAuthenticatorResponse response) {
Intent Intent = new Intent(context, LoginActivity.class);
Intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, Intent);
return bundle; return bundle;
} }
@Override @Override
public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException { public Bundle confirmCredentials(@NonNull AccountAuthenticatorResponse response,
return null; @NonNull Account account, @Nullable Bundle options)
throws NetworkErrorException {
return unsupportedOperation();
} }
@Override @Override
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) { public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
return null; return unsupportedOperation();
} }
private String getAuthCookie(String username, String password) throws IOException { private String getAuthCookie(String username, String password) throws IOException {
MWApi api = CommonsApplication.createMWApi(); MWApi api = CommonsApplication.getInstance().getMWApi();
//TODO add 2fa support here
String result = api.login(username, password); String result = api.login(username, password);
if(result.equals("Success")) { if(result.equals("PASS")) {
return api.getAuthCookie(); return api.getAuthCookie();
} else { } else {
return null; return null;
@ -70,7 +102,7 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
if (authCookie != null) { if (authCookie != null) {
final Bundle result = new Bundle(); final Bundle result = new Bundle();
result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
result.putString(AccountManager.KEY_ACCOUNT_TYPE, COMMONS_ACCOUNT_TYPE); result.putString(AccountManager.KEY_ACCOUNT_TYPE, AccountUtil.accountType());
result.putString(AccountManager.KEY_AUTHTOKEN, authCookie); result.putString(AccountManager.KEY_AUTHTOKEN, authCookie);
return result; return result;
} }
@ -87,21 +119,31 @@ public class WikiAccountAuthenticator extends AbstractAccountAuthenticator {
return bundle; return bundle;
} }
@Nullable
@Override @Override
public String getAuthTokenLabel(String authTokenType) { public String getAuthTokenLabel(@NonNull String authTokenType) {
//Note: the wikipedia app actually returns a string here....
//return supportedAccountType(authTokenType) ? context.getString(R.string.wikimedia) : null;
return null; return null;
} }
@Nullable
@Override @Override
public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException { public Bundle hasFeatures(@NonNull AccountAuthenticatorResponse response,
final Bundle result = new Bundle(); @NonNull Account account, @NonNull String[] features)
result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false); throws NetworkErrorException {
return result; Bundle bundle = new Bundle();
bundle.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
return bundle;
} }
@Nullable
@Override @Override
public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException { public Bundle updateCredentials(@NonNull AccountAuthenticatorResponse response,
return null; @NonNull Account account, @Nullable String authTokenType,
@Nullable Bundle options)
throws NetworkErrorException {
return unsupportedOperation();
} }
} }

View file

@ -1,7 +1,5 @@
package fr.free.nrw.commons.caching; package fr.free.nrw.commons.caching;
import android.util.Log;
import com.github.varunpant.quadtree.Point; import com.github.varunpant.quadtree.Point;
import com.github.varunpant.quadtree.QuadTree; import com.github.varunpant.quadtree.QuadTree;
@ -10,15 +8,14 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import fr.free.nrw.commons.upload.MwVolleyApi; import fr.free.nrw.commons.upload.MwVolleyApi;
import timber.log.Timber;
public class CacheController { public class CacheController {
private double x, y; private double x, y;
private QuadTree<List<String>> quadTree; private QuadTree<List<String>> quadTree;
private Point<List<String>>[] pointsFound;
private double xMinus, xPlus, yMinus, yPlus; private double xMinus, xPlus, yMinus, yPlus;
private static final String TAG = CacheController.class.getName();
private static final int EARTH_RADIUS = 6378137; private static final int EARTH_RADIUS = 6378137;
public CacheController() { public CacheController() {
@ -28,40 +25,41 @@ public class CacheController {
public void setQtPoint(double decLongitude, double decLatitude) { public void setQtPoint(double decLongitude, double decLatitude) {
x = decLongitude; x = decLongitude;
y = decLatitude; y = decLatitude;
Log.d(TAG, "New QuadTree created"); Timber.d("New QuadTree created");
Log.d(TAG, "X (longitude) value: " + x + ", Y (latitude) value: " + y); Timber.d("X (longitude) value: %f, Y (latitude) value: %f", x, y);
} }
public void cacheCategory() { public void cacheCategory() {
List<String> pointCatList = new ArrayList<>(); List<String> pointCatList = new ArrayList<>();
if (MwVolleyApi.GpsCatExists.getGpsCatExists() == true) { if (MwVolleyApi.GpsCatExists.getGpsCatExists()) {
pointCatList.addAll(MwVolleyApi.getGpsCat()); pointCatList.addAll(MwVolleyApi.getGpsCat());
Log.d(TAG, "Categories being cached: " + pointCatList); Timber.d("Categories being cached: %s", pointCatList);
} else { } else {
Log.d(TAG, "No categories found, so no categories cached"); Timber.d("No categories found, so no categories cached");
} }
quadTree.set(x, y, pointCatList); quadTree.set(x, y, pointCatList);
} }
public List<String> findCategory() { public List<String> findCategory() {
Point<List<String>>[] pointsFound;
//Convert decLatitude and decLongitude to a coordinate offset range //Convert decLatitude and decLongitude to a coordinate offset range
convertCoordRange(); convertCoordRange();
pointsFound = quadTree.searchWithin(xMinus, yMinus, xPlus, yPlus); pointsFound = quadTree.searchWithin(xMinus, yMinus, xPlus, yPlus);
List<String> displayCatList = new ArrayList<>(); List<String> displayCatList = new ArrayList<>();
Log.d(TAG, "Points found in quadtree: " + Arrays.asList(pointsFound)); Timber.d("Points found in quadtree: %s", Arrays.toString(pointsFound));
if (pointsFound.length != 0) { if (pointsFound.length != 0) {
Log.d(TAG, "Entering for loop"); Timber.d("Entering for loop");
for (Point<List<String>> point : pointsFound) { for (Point<List<String>> point : pointsFound) {
Log.d(TAG, "Nearby point: " + point.toString()); Timber.d("Nearby point: %s", point);
displayCatList = point.getValue(); displayCatList = point.getValue();
Log.d(TAG, "Nearby cat: " + point.getValue()); Timber.d("Nearby cat: %s", point.getValue());
} }
Log.d(TAG, "Categories found in cache: " + displayCatList.toString()); Timber.d("Categories found in cache: %s", displayCatList);
} else { } else {
Log.d(TAG, "No categories found in cache"); Timber.d("No categories found in cache");
} }
return displayCatList; return displayCatList;
} }
@ -84,6 +82,7 @@ public class CacheController {
yMinus = lat - dLat * 180/Math.PI; yMinus = lat - dLat * 180/Math.PI;
xPlus = lon + dLon * 180/Math.PI; xPlus = lon + dLon * 180/Math.PI;
xMinus = lon - dLon * 180/Math.PI; xMinus = lon - dLon * 180/Math.PI;
Log.d(TAG, "Search within: xMinus=" + xMinus + ", yMinus=" + yMinus + ", xPlus=" + xPlus + ", yPlus=" + yPlus); Timber.d("Search within: xMinus=%s, yMinus=%s, xPlus=%s, yPlus=%s",
xMinus, yMinus, xPlus, yPlus);
} }
} }

View file

@ -14,13 +14,11 @@ import fr.free.nrw.commons.R;
public class CategoriesAdapter extends BaseAdapter { public class CategoriesAdapter extends BaseAdapter {
private Context context;
private LayoutInflater mInflater; private LayoutInflater mInflater;
private ArrayList<CategorizationFragment.CategoryItem> items; private ArrayList<CategorizationFragment.CategoryItem> items;
public CategoriesAdapter(Context context, ArrayList<CategorizationFragment.CategoryItem> items) { public CategoriesAdapter(Context context, ArrayList<CategorizationFragment.CategoryItem> items) {
this.context = context;
this.items = items; this.items = items;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
} }

View file

@ -16,7 +16,6 @@ import android.support.v7.app.AlertDialog;
import android.text.Editable; import android.text.Editable;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
@ -44,6 +43,7 @@ import java.util.concurrent.TimeUnit;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.upload.MwVolleyApi; import fr.free.nrw.commons.upload.MwVolleyApi;
import timber.log.Timber;
/** /**
* Displays the category suggestion and selection screen. Category search is initiated here. * Displays the category suggestion and selection screen. Category search is initiated here.
@ -79,7 +79,6 @@ public class CategorizationFragment extends Fragment {
private ContentProviderClient client; private ContentProviderClient client;
protected final static int SEARCH_CATS_LIMIT = 25; protected final static int SEARCH_CATS_LIMIT = 25;
private static final String TAG = CategorizationFragment.class.getName();
public static class CategoryItem implements Parcelable { public static class CategoryItem implements Parcelable {
public String name; public String name;
@ -130,28 +129,28 @@ public class CategorizationFragment extends Fragment {
//Retrieve the title that was saved when user tapped submit icon //Retrieve the title that was saved when user tapped submit icon
SharedPreferences titleDesc = PreferenceManager.getDefaultSharedPreferences(getActivity()); SharedPreferences titleDesc = PreferenceManager.getDefaultSharedPreferences(getActivity());
String title = titleDesc.getString("Title", ""); String title = titleDesc.getString("Title", "");
Log.d(TAG, "Title: " + title); Timber.d("Title: %s", title);
//Override onPostExecute to access the results of async API call //Override onPostExecute to access the results of async API call
titleCategoriesSub = new TitleCategories(title) { titleCategoriesSub = new TitleCategories(title) {
@Override @Override
protected void onPostExecute(ArrayList<String> result) { protected void onPostExecute(ArrayList<String> result) {
super.onPostExecute(result); super.onPostExecute(result);
Log.d(TAG, "Results in onPostExecute: " + result); Timber.d("Results in onPostExecute: %s", result);
titleCatItems.addAll(result); titleCatItems.addAll(result);
Log.d(TAG, "TitleCatItems in onPostExecute: " + titleCatItems); Timber.d("TitleCatItems in onPostExecute: %s", titleCatItems);
mergeLatch.countDown(); mergeLatch.countDown();
} }
}; };
titleCategoriesSub.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); titleCategoriesSub.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
Log.d(TAG, "TitleCatItems in titleCatQuery: " + titleCatItems); Timber.d("TitleCatItems in titleCatQuery: %s", titleCatItems);
//Only return titleCatItems after API call has finished //Only return titleCatItems after API call has finished
try { try {
mergeLatch.await(5L, TimeUnit.SECONDS); mergeLatch.await(5L, TimeUnit.SECONDS);
} catch (InterruptedException e) { } catch (InterruptedException e) {
Log.e(TAG, "Interrupted exception: ", e); Timber.e(e, "Interrupted exception: ");
} }
return titleCatItems; return titleCatItems;
} }
@ -191,7 +190,7 @@ public class CategorizationFragment extends Fragment {
Set<String> mergedItems = new LinkedHashSet<>(); Set<String> mergedItems = new LinkedHashSet<>();
Log.d(TAG, "Calling APIs for GPS cats, title cats and recent cats..."); Timber.d("Calling APIs for GPS cats, title cats and recent cats...");
List<String> gpsItems = new ArrayList<>(); List<String> gpsItems = new ArrayList<>();
if (MwVolleyApi.GpsCatExists.getGpsCatExists()) { if (MwVolleyApi.GpsCatExists.getGpsCatExists()) {
@ -203,22 +202,22 @@ public class CategorizationFragment extends Fragment {
//Await results of titleItems, which is likely to come in last //Await results of titleItems, which is likely to come in last
try { try {
mergeLatch.await(5L, TimeUnit.SECONDS); mergeLatch.await(5L, TimeUnit.SECONDS);
Log.d(TAG, "Waited for merge"); Timber.d("Waited for merge");
} catch (InterruptedException e) { } catch (InterruptedException e) {
Log.e(TAG, "Interrupted Exception: ", e); Timber.e(e, "Interrupted Exception: ");
} }
mergedItems.addAll(gpsItems); mergedItems.addAll(gpsItems);
Log.d(TAG, "Adding GPS items: " + gpsItems); Timber.d("Adding GPS items: %s", gpsItems);
mergedItems.addAll(titleItems); mergedItems.addAll(titleItems);
Log.d(TAG, "Adding title items: " + titleItems); Timber.d("Adding title items: %s", titleItems);
mergedItems.addAll(recentItems); mergedItems.addAll(recentItems);
Log.d(TAG, "Adding recent items: " + recentItems); Timber.d("Adding recent items: %s", recentItems);
//Needs to be an ArrayList and not a List unless we want to modify a big portion of preexisting code //Needs to be an ArrayList and not a List unless we want to modify a big portion of preexisting code
ArrayList<String> mergedItemsList = new ArrayList<>(mergedItems); ArrayList<String> mergedItemsList = new ArrayList<>(mergedItems);
Log.d(TAG, "Merged item list: " + mergedItemsList); Timber.d("Merged item list: %s", mergedItemsList);
return mergedItemsList; return mergedItemsList;
} }
@ -261,7 +260,7 @@ public class CategorizationFragment extends Fragment {
} }
} }
else { else {
Log.e(TAG, "Error: Fragment is null"); Timber.e("Error: Fragment is null");
} }
} }
@ -285,7 +284,7 @@ public class CategorizationFragment extends Fragment {
latch.await(); latch.await();
} }
catch (InterruptedException e) { catch (InterruptedException e) {
Log.w(TAG, e); Timber.w(e);
//Thread.currentThread().interrupt(); //Thread.currentThread().interrupt();
} }
return result; return result;
@ -296,12 +295,12 @@ public class CategorizationFragment extends Fragment {
super.onPostExecute(result); super.onPostExecute(result);
results.addAll(result); results.addAll(result);
Log.d(TAG, "Prefix result: " + result); Timber.d("Prefix result: %s", result);
String filter = categoriesFilter.getText().toString(); String filter = categoriesFilter.getText().toString();
ArrayList<String> resultsList = new ArrayList<>(results); ArrayList<String> resultsList = new ArrayList<>(results);
categoriesCache.put(filter, resultsList); categoriesCache.put(filter, resultsList);
Log.d(TAG, "Final results List: " + resultsList); Timber.d("Final results List: %s", resultsList);
categoriesAdapter.notifyDataSetChanged(); categoriesAdapter.notifyDataSetChanged();
setCatsAfterAsync(resultsList, filter); setCatsAfterAsync(resultsList, filter);
@ -315,7 +314,7 @@ public class CategorizationFragment extends Fragment {
super.onPostExecute(result); super.onPostExecute(result);
results.addAll(result); results.addAll(result);
Log.d(TAG, "Method A result: " + result); Timber.d("Method A result: %s", result);
categoriesAdapter.notifyDataSetChanged(); categoriesAdapter.notifyDataSetChanged();
latch.countDown(); latch.countDown();
@ -358,8 +357,7 @@ public class CategorizationFragment extends Fragment {
new String[] {name}, new String[] {name},
null); null);
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
Category cat = Category.fromCursor(cursor); return Category.fromCursor(cursor);
return cat;
} }
} catch (RemoteException e) { } catch (RemoteException e) {
// This feels lazy, but to hell with checked exceptions. :) // This feels lazy, but to hell with checked exceptions. :)
@ -500,7 +498,7 @@ public class CategorizationFragment extends Fragment {
} }
} }
private void backButtonDialog() { public void backButtonDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage("Are you sure you want to go back? The image will not have any categories saved.") builder.setMessage("Are you sure you want to go back? The image will not have any categories saved.")

View file

@ -115,6 +115,11 @@ public class Category {
db.execSQL(CREATE_TABLE_STATEMENT); db.execSQL(CREATE_TABLE_STATEMENT);
} }
public static void onDelete(SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
onCreate(db);
}
public static void onUpdate(SQLiteDatabase db, int from, int to) { public static void onUpdate(SQLiteDatabase db, int from, int to) {
if(from == to) { if(from == to) {
return; return;

View file

@ -8,9 +8,10 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder; import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri; import android.net.Uri;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.data.DBOpenHelper; import fr.free.nrw.commons.data.DBOpenHelper;
import timber.log.Timber;
public class CategoryContentProvider extends ContentProvider { public class CategoryContentProvider extends ContentProvider {
@ -36,7 +37,7 @@ public class CategoryContentProvider extends ContentProvider {
private DBOpenHelper dbOpenHelper; private DBOpenHelper dbOpenHelper;
@Override @Override
public boolean onCreate() { public boolean onCreate() {
dbOpenHelper = DBOpenHelper.getInstance(getContext()); dbOpenHelper = CommonsApplication.getInstance().getDBOpenHelper();
return false; return false;
} }
@ -101,14 +102,14 @@ public class CategoryContentProvider extends ContentProvider {
@Override @Override
public int bulkInsert(Uri uri, ContentValues[] values) { public int bulkInsert(Uri uri, ContentValues[] values) {
Log.d("Commons", "Hello, bulk insert! (CategoryContentProvider)"); Timber.d("Hello, bulk insert! (CategoryContentProvider)");
int uriType = uriMatcher.match(uri); int uriType = uriMatcher.match(uri);
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
sqlDB.beginTransaction(); sqlDB.beginTransaction();
switch (uriType) { switch (uriType) {
case CATEGORIES: case CATEGORIES:
for(ContentValues value: values) { for(ContentValues value: values) {
Log.d("Commons", "Inserting! " + value.toString()); Timber.d("Inserting! %s", value);
sqlDB.insert(Category.Table.TABLE_NAME, null, value); sqlDB.insert(Category.Table.TABLE_NAME, null, value);
} }
break; break;

View file

@ -1,11 +1,10 @@
package fr.free.nrw.commons.category; package fr.free.nrw.commons.category;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.util.Log;
import android.view.View; import android.view.View;
import fr.free.nrw.commons.MWApi;
import org.mediawiki.api.ApiResult; import org.mediawiki.api.ApiResult;
import org.mediawiki.api.MWApi;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@ -13,6 +12,7 @@ import java.util.Calendar;
import java.util.Iterator; import java.util.Iterator;
import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.CommonsApplication;
import timber.log.Timber;
/** /**
* Sends asynchronous queries to the Commons MediaWiki API to retrieve categories that are close to * Sends asynchronous queries to the Commons MediaWiki API to retrieve categories that are close to
@ -22,7 +22,6 @@ import fr.free.nrw.commons.CommonsApplication;
public class MethodAUpdater extends AsyncTask<Void, Void, ArrayList<String>> { public class MethodAUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
private String filter; private String filter;
private static final String TAG = MethodAUpdater.class.getName();
CategorizationFragment catFragment; CategorizationFragment catFragment;
public MethodAUpdater(CategorizationFragment catFragment) { public MethodAUpdater(CategorizationFragment catFragment) {
@ -54,11 +53,11 @@ public class MethodAUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
Calendar now = Calendar.getInstance(); Calendar now = Calendar.getInstance();
int year = now.get(Calendar.YEAR); int year = now.get(Calendar.YEAR);
String yearInString = String.valueOf(year); String yearInString = String.valueOf(year);
Log.d(TAG, "Year: " + yearInString); Timber.d("Year: %s", yearInString);
int prevYear = year - 1; int prevYear = year - 1;
String prevYearInString = String.valueOf(prevYear); String prevYearInString = String.valueOf(prevYear);
Log.d(TAG, "Previous year: " + prevYearInString); Timber.d("Previous year: %s", prevYearInString);
//Copy to Iterator to prevent ConcurrentModificationException when removing item //Copy to Iterator to prevent ConcurrentModificationException when removing item
for(iterator = items.iterator(); iterator.hasNext();) { for(iterator = items.iterator(); iterator.hasNext();) {
@ -67,12 +66,12 @@ public class MethodAUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
//Check if s contains a 4-digit word anywhere within the string (.* is wildcard) //Check if s contains a 4-digit word anywhere within the string (.* is wildcard)
//And that s does not equal the current year or previous year //And that s does not equal the current year or previous year
if(s.matches(".*(19|20)\\d{2}.*") && !s.contains(yearInString) && !s.contains(prevYearInString)) { if(s.matches(".*(19|20)\\d{2}.*") && !s.contains(yearInString) && !s.contains(prevYearInString)) {
Log.d(TAG, "Filtering out year " + s); Timber.d("Filtering out year %s", s);
iterator.remove(); iterator.remove();
} }
} }
Log.d(TAG, "Items: " + items.toString()); Timber.d("Items: %s", items);
return items; return items;
} }
@ -80,7 +79,7 @@ public class MethodAUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
protected ArrayList<String> doInBackground(Void... voids) { protected ArrayList<String> doInBackground(Void... voids) {
//otherwise if user has typed something in that isn't in cache, search API for matching categories //otherwise if user has typed something in that isn't in cache, search API for matching categories
MWApi api = CommonsApplication.createMWApi(); MWApi api = CommonsApplication.getInstance().getMWApi();
ApiResult result; ApiResult result;
ArrayList<String> categories = new ArrayList<>(); ArrayList<String> categories = new ArrayList<>();
@ -94,9 +93,9 @@ public class MethodAUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
.param("srlimit", catFragment.SEARCH_CATS_LIMIT) .param("srlimit", catFragment.SEARCH_CATS_LIMIT)
.param("srsearch", filter) .param("srsearch", filter)
.get(); .get();
Log.d(TAG, "Method A URL filter" + result.toString()); Timber.d("Method A URL filter %s", result);
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "IO Exception: ", e); Timber.e(e, "IO Exception: ");
//Return empty arraylist //Return empty arraylist
return categories; return categories;
} }
@ -108,8 +107,7 @@ public class MethodAUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
categories.add(catString); categories.add(catString);
} }
Log.d(TAG, "Found categories from Method A search, waiting for filter"); Timber.d("Found categories from Method A search, waiting for filter");
ArrayList<String> filteredItems = new ArrayList<>(filterYears(categories)); return new ArrayList<>(filterYears(categories));
return filteredItems;
} }
} }

View file

@ -2,11 +2,10 @@ package fr.free.nrw.commons.category;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import android.view.View; import android.view.View;
import fr.free.nrw.commons.MWApi;
import org.mediawiki.api.ApiResult; import org.mediawiki.api.ApiResult;
import org.mediawiki.api.MWApi;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@ -14,6 +13,7 @@ import java.util.Calendar;
import java.util.Iterator; import java.util.Iterator;
import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.CommonsApplication;
import timber.log.Timber;
/** /**
* Sends asynchronous queries to the Commons MediaWiki API to retrieve categories that share the * Sends asynchronous queries to the Commons MediaWiki API to retrieve categories that share the
@ -24,7 +24,6 @@ import fr.free.nrw.commons.CommonsApplication;
public class PrefixUpdater extends AsyncTask<Void, Void, ArrayList<String>> { public class PrefixUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
private String filter; private String filter;
private static final String TAG = PrefixUpdater.class.getName();
private CategorizationFragment catFragment; private CategorizationFragment catFragment;
public PrefixUpdater(CategorizationFragment catFragment) { public PrefixUpdater(CategorizationFragment catFragment) {
@ -56,11 +55,11 @@ public class PrefixUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
Calendar now = Calendar.getInstance(); Calendar now = Calendar.getInstance();
int year = now.get(Calendar.YEAR); int year = now.get(Calendar.YEAR);
String yearInString = String.valueOf(year); String yearInString = String.valueOf(year);
Log.d(TAG, "Year: " + yearInString); Timber.d("Year: %s", yearInString);
int prevYear = year - 1; int prevYear = year - 1;
String prevYearInString = String.valueOf(prevYear); String prevYearInString = String.valueOf(prevYear);
Log.d(TAG, "Previous year: " + prevYearInString); Timber.d("Previous year: %s", prevYearInString);
//Copy to Iterator to prevent ConcurrentModificationException when removing item //Copy to Iterator to prevent ConcurrentModificationException when removing item
for(iterator = items.iterator(); iterator.hasNext();) { for(iterator = items.iterator(); iterator.hasNext();) {
@ -69,12 +68,12 @@ public class PrefixUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
//Check if s contains a 4-digit word anywhere within the string (.* is wildcard) //Check if s contains a 4-digit word anywhere within the string (.* is wildcard)
//And that s does not equal the current year or previous year //And that s does not equal the current year or previous year
if(s.matches(".*(19|20)\\d{2}.*") && !s.contains(yearInString) && !s.contains(prevYearInString)) { if(s.matches(".*(19|20)\\d{2}.*") && !s.contains(yearInString) && !s.contains(prevYearInString)) {
Log.d(TAG, "Filtering out year " + s); Timber.d("Filtering out year %s", s);
iterator.remove(); iterator.remove();
} }
} }
Log.d(TAG, "Items: " + items.toString()); Timber.d("Items: %s", items);
return items; return items;
} }
@ -83,22 +82,20 @@ public class PrefixUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
//If user hasn't typed anything in yet, get GPS and recent items //If user hasn't typed anything in yet, get GPS and recent items
if(TextUtils.isEmpty(filter)) { if(TextUtils.isEmpty(filter)) {
ArrayList<String> mergedItems = new ArrayList<>(catFragment.mergeItems()); ArrayList<String> mergedItems = new ArrayList<>(catFragment.mergeItems());
Log.d(TAG, "Merged items, waiting for filter"); Timber.d("Merged items, waiting for filter");
ArrayList<String> filteredItems = new ArrayList<>(filterYears(mergedItems)); return new ArrayList<>(filterYears(mergedItems));
return filteredItems;
} }
//if user types in something that is in cache, return cached category //if user types in something that is in cache, return cached category
if(catFragment.categoriesCache.containsKey(filter)) { if(catFragment.categoriesCache.containsKey(filter)) {
ArrayList<String> cachedItems = new ArrayList<>(catFragment.categoriesCache.get(filter)); ArrayList<String> cachedItems = new ArrayList<>(catFragment.categoriesCache.get(filter));
Log.d(TAG, "Found cache items, waiting for filter"); Timber.d("Found cache items, waiting for filter");
ArrayList<String> filteredItems = new ArrayList<>(filterYears(cachedItems)); return new ArrayList<>(filterYears(cachedItems));
return filteredItems;
} }
//otherwise if user has typed something in that isn't in cache, search API for matching categories //otherwise if user has typed something in that isn't in cache, search API for matching categories
//URL: https://commons.wikimedia.org/w/api.php?action=query&list=allcategories&acprefix=filter&aclimit=25 //URL: https://commons.wikimedia.org/w/api.php?action=query&list=allcategories&acprefix=filter&aclimit=25
MWApi api = CommonsApplication.createMWApi(); MWApi api = CommonsApplication.getInstance().getMWApi();
ApiResult result; ApiResult result;
ArrayList<String> categories = new ArrayList<>(); ArrayList<String> categories = new ArrayList<>();
try { try {
@ -107,9 +104,9 @@ public class PrefixUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
.param("acprefix", filter) .param("acprefix", filter)
.param("aclimit", catFragment.SEARCH_CATS_LIMIT) .param("aclimit", catFragment.SEARCH_CATS_LIMIT)
.get(); .get();
Log.d(TAG, "Prefix URL filter" + result.toString()); Timber.d("Prefix URL filter %s", result);
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "IO Exception: ", e); Timber.e(e, "IO Exception: ");
//Return empty arraylist //Return empty arraylist
return categories; return categories;
} }
@ -119,8 +116,7 @@ public class PrefixUpdater extends AsyncTask<Void, Void, ArrayList<String>> {
categories.add(categoryNode.getDocument().getTextContent()); categories.add(categoryNode.getDocument().getTextContent());
} }
Log.d(TAG, "Found categories from Prefix search, waiting for filter"); Timber.d("Found categories from Prefix search, waiting for filter");
ArrayList<String> filteredItems = new ArrayList<>(filterYears(categories)); return new ArrayList<>(filterYears(categories));
return filteredItems;
} }
} }

View file

@ -1,15 +1,15 @@
package fr.free.nrw.commons.category; package fr.free.nrw.commons.category;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.util.Log;
import fr.free.nrw.commons.MWApi;
import org.mediawiki.api.ApiResult; import org.mediawiki.api.ApiResult;
import org.mediawiki.api.MWApi;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.CommonsApplication;
import timber.log.Timber;
/** /**
* Sends asynchronous queries to the Commons MediaWiki API to retrieve categories that are related to * Sends asynchronous queries to the Commons MediaWiki API to retrieve categories that are related to
@ -19,7 +19,7 @@ import fr.free.nrw.commons.CommonsApplication;
public class TitleCategories extends AsyncTask<Void, Void, ArrayList<String>> { public class TitleCategories extends AsyncTask<Void, Void, ArrayList<String>> {
private final static int SEARCH_CATS_LIMIT = 25; private final static int SEARCH_CATS_LIMIT = 25;
private static final String TAG = TitleCategories.class.getName();
private String title; private String title;
public TitleCategories(String title) { public TitleCategories(String title) {
@ -34,7 +34,7 @@ public class TitleCategories extends AsyncTask<Void, Void, ArrayList<String>> {
@Override @Override
protected ArrayList<String> doInBackground(Void... voids) { protected ArrayList<String> doInBackground(Void... voids) {
MWApi api = CommonsApplication.createMWApi(); MWApi api = CommonsApplication.getInstance().getMWApi();
ApiResult result; ApiResult result;
ArrayList<String> items = new ArrayList<>(); ArrayList<String> items = new ArrayList<>();
@ -48,9 +48,9 @@ public class TitleCategories extends AsyncTask<Void, Void, ArrayList<String>> {
.param("srlimit", SEARCH_CATS_LIMIT) .param("srlimit", SEARCH_CATS_LIMIT)
.param("srsearch", title) .param("srsearch", title)
.get(); .get();
Log.d(TAG, "Searching for cats for title: " + result.toString()); Timber.d("Searching for cats for title: %s", result);
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "IO Exception: ", e); Timber.e(e, "IO Exception: ");
//Return empty arraylist //Return empty arraylist
return items; return items;
} }
@ -62,7 +62,7 @@ public class TitleCategories extends AsyncTask<Void, Void, ArrayList<String>> {
items.add(catString); items.add(catString);
} }
Log.d(TAG, "Title cat query results: " + items); Timber.d("Title cat query results: %s", items);
return items; return items;
} }

View file

@ -44,8 +44,6 @@ public class Contribution extends Media {
public static final String SOURCE_GALLERY = "gallery"; public static final String SOURCE_GALLERY = "gallery";
public static final String SOURCE_EXTERNAL = "external"; public static final String SOURCE_EXTERNAL = "external";
private static final String TAG = "Contribution";
private ContentProviderClient client; private ContentProviderClient client;
private Uri contentUri; private Uri contentUri;
private String source; private String source;
@ -197,7 +195,7 @@ public class Contribution extends Media {
cv.put(Table.COLUMN_LOCAL_URI, getLocalUri().toString()); cv.put(Table.COLUMN_LOCAL_URI, getLocalUri().toString());
} }
if(getImageUrl() != null) { if(getImageUrl() != null) {
cv.put(Table.COLUMN_IMAGE_URL, getImageUrl().toString()); cv.put(Table.COLUMN_IMAGE_URL, getImageUrl());
} }
if(getDateUploaded() != null) { if(getDateUploaded() != null) {
cv.put(Table.COLUMN_UPLOADED, getDateUploaded().getTime()); cv.put(Table.COLUMN_UPLOADED, getDateUploaded().getTime());
@ -333,6 +331,11 @@ public class Contribution extends Media {
db.execSQL(CREATE_TABLE_STATEMENT); db.execSQL(CREATE_TABLE_STATEMENT);
} }
public static void onDelete(SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
onCreate(db);
}
public static void onUpdate(SQLiteDatabase db, int from, int to) { public static void onUpdate(SQLiteDatabase db, int from, int to) {
if(from == to) { if(from == to) {
return; return;

View file

@ -7,7 +7,6 @@ import android.os.Bundle;
import android.os.Environment; import android.os.Environment;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.util.Log;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -15,6 +14,7 @@ import java.util.Date;
import fr.free.nrw.commons.upload.ShareActivity; import fr.free.nrw.commons.upload.ShareActivity;
import fr.free.nrw.commons.upload.UploadService; import fr.free.nrw.commons.upload.UploadService;
import timber.log.Timber;
public class ContributionController { public class ContributionController {
private Fragment fragment; private Fragment fragment;
@ -38,13 +38,13 @@ public class ContributionController {
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Commons/images/" + new Date().getTime() + ".jpg"; String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Commons/images/" + new Date().getTime() + ".jpg";
File _photoFile = new File(path); File _photoFile = new File(path);
try { try {
if(_photoFile.exists() == false) { if(!_photoFile.exists()) {
_photoFile.getParentFile().mkdirs(); _photoFile.getParentFile().mkdirs();
_photoFile.createNewFile(); _photoFile.createNewFile();
} }
} catch (IOException e) { } catch (IOException e) {
Log.e("Commons", "Could not create file: " + path, e); Timber.e(e, "Could not create file: %s", path);
} }
return Uri.fromFile(_photoFile); return Uri.fromFile(_photoFile);
@ -84,11 +84,11 @@ public class ContributionController {
shareIntent.putExtra(UploadService.EXTRA_SOURCE, Contribution.SOURCE_CAMERA); shareIntent.putExtra(UploadService.EXTRA_SOURCE, Contribution.SOURCE_CAMERA);
break; break;
} }
Log.i("Image", "Image selected"); Timber.i("Image selected");
try { try {
activity.startActivity(shareIntent); activity.startActivity(shareIntent);
} catch (SecurityException e) { } catch (SecurityException e) {
Log.e("ContributionController", "Security Exception", e); Timber.e(e, "Security Exception");
} }
} }

View file

@ -14,8 +14,6 @@ class ContributionViewHolder {
final TextView seqNumView; final TextView seqNumView;
final ProgressBar progressView; final ProgressBar progressView;
String url;
ContributionViewHolder(View parent) { ContributionViewHolder(View parent) {
imageView = (MediaWikiImageView) parent.findViewById(R.id.contributionImage); imageView = (MediaWikiImageView) parent.findViewById(R.id.contributionImage);
titleView = (TextView)parent.findViewById(R.id.contributionTitle); titleView = (TextView)parent.findViewById(R.id.contributionTitle);

View file

@ -5,16 +5,17 @@ import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.database.Cursor; import android.database.Cursor;
import android.database.DataSetObserver; import android.database.DataSetObserver;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
import android.support.v4.app.LoaderManager; import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader; import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader; import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter; import android.support.v4.widget.CursorAdapter;
import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
@ -22,16 +23,19 @@ import android.widget.Adapter;
import android.widget.AdapterView; import android.widget.AdapterView;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Objects; import java.util.Locale;
import butterknife.ButterKnife;
import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.HandlerService; import fr.free.nrw.commons.HandlerService;
import fr.free.nrw.commons.Media; import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.AuthenticatedActivity; import fr.free.nrw.commons.auth.AuthenticatedActivity;
import fr.free.nrw.commons.auth.WikiAccountAuthenticator; import fr.free.nrw.commons.hamburger.HamburgerMenuContainer;
import fr.free.nrw.commons.media.MediaDetailPagerFragment; import fr.free.nrw.commons.media.MediaDetailPagerFragment;
import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.upload.UploadService; import fr.free.nrw.commons.upload.UploadService;
import timber.log.Timber;
public class ContributionsActivity public class ContributionsActivity
extends AuthenticatedActivity extends AuthenticatedActivity
@ -39,7 +43,8 @@ public class ContributionsActivity
AdapterView.OnItemClickListener, AdapterView.OnItemClickListener,
MediaDetailPagerFragment.MediaDetailProvider, MediaDetailPagerFragment.MediaDetailProvider,
FragmentManager.OnBackStackChangedListener, FragmentManager.OnBackStackChangedListener,
ContributionsListFragment.SourceRefresher { ContributionsListFragment.SourceRefresher,
HamburgerMenuContainer {
private Cursor allContributions; private Cursor allContributions;
private ContributionsListFragment contributionsList; private ContributionsListFragment contributionsList;
@ -48,6 +53,7 @@ public class ContributionsActivity
private boolean isUploadServiceConnected; private boolean isUploadServiceConnected;
private ArrayList<DataSetObserver> observersWaitingForLoad = new ArrayList<>(); private ArrayList<DataSetObserver> observersWaitingForLoad = new ArrayList<>();
private String CONTRIBUTION_SELECTION = ""; private String CONTRIBUTION_SELECTION = "";
/* /*
This sorts in the following order: This sorts in the following order:
Currently Uploading Currently Uploading
@ -59,10 +65,6 @@ public class ContributionsActivity
*/ */
private String CONTRIBUTION_SORT = Contribution.Table.COLUMN_STATE + " DESC, " + Contribution.Table.COLUMN_UPLOADED + " DESC , (" + Contribution.Table.COLUMN_TIMESTAMP + " * " + Contribution.Table.COLUMN_STATE + ")"; private String CONTRIBUTION_SORT = Contribution.Table.COLUMN_STATE + " DESC, " + Contribution.Table.COLUMN_UPLOADED + " DESC , (" + Contribution.Table.COLUMN_TIMESTAMP + " * " + Contribution.Table.COLUMN_STATE + ")";
public ContributionsActivity() {
super(WikiAccountAuthenticator.COMMONS_ACCOUNT_TYPE);
}
private ServiceConnection uploadServiceConnection = new ServiceConnection() { private ServiceConnection uploadServiceConnection = new ServiceConnection() {
@Override @Override
public void onServiceConnected(ComponentName componentName, IBinder binder) { public void onServiceConnected(ComponentName componentName, IBinder binder) {
@ -88,6 +90,15 @@ public class ContributionsActivity
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
boolean isSettingsChanged =
sharedPreferences.getBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED,false);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED,false);
editor.apply();
if (isSettingsChanged) {
refreshSource();
}
} }
@Override @Override
@ -98,7 +109,7 @@ public class ContributionsActivity
@Override @Override
protected void onAuthCookieAcquired(String authCookie) { protected void onAuthCookieAcquired(String authCookie) {
// Do a sync everytime we get here! // Do a sync everytime we get here!
ContentResolver.requestSync(((CommonsApplication) getApplicationContext()).getCurrentAccount(), ContributionsContentProvider.AUTHORITY, new Bundle()); ContentResolver.requestSync(CommonsApplication.getInstance().getCurrentAccount(), ContributionsContentProvider.AUTHORITY, new Bundle());
Intent uploadServiceIntent = new Intent(this, UploadService.class); Intent uploadServiceIntent = new Intent(this, UploadService.class);
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE); uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
startService(uploadServiceIntent); startService(uploadServiceIntent);
@ -112,23 +123,22 @@ public class ContributionsActivity
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setTitle(R.string.title_activity_contributions);
setContentView(R.layout.activity_contributions); setContentView(R.layout.activity_contributions);
ButterKnife.bind(this);
// Activity can call methods in the fragment by acquiring a reference to the Fragment from FragmentManager, using findFragmentById() // Activity can call methods in the fragment by acquiring a
contributionsList = (ContributionsListFragment)getSupportFragmentManager().findFragmentById(R.id.contributionsListFragment); // reference to the Fragment from FragmentManager, using findFragmentById()
contributionsList = (ContributionsListFragment)getSupportFragmentManager()
.findFragmentById(R.id.contributionsListFragment);
getSupportFragmentManager().addOnBackStackChangedListener(this); getSupportFragmentManager().addOnBackStackChangedListener(this);
if (savedInstanceState != null) { if (savedInstanceState != null) {
mediaDetails = (MediaDetailPagerFragment)getSupportFragmentManager().findFragmentById(R.id.contributionsFragmentContainer); mediaDetails = (MediaDetailPagerFragment)getSupportFragmentManager()
// onBackStackChanged uses mediaDetails.isVisible() but this returns false now. .findFragmentById(R.id.contributionsFragmentContainer);
// Use the saved value from before pause or orientation change.
if (mediaDetails != null && savedInstanceState.getBoolean("mediaDetailsVisible")) {
// Feels awful that we have to reset this manually!
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
} }
requestAuthToken(); requestAuthToken();
initDrawer();
setTitle(getString(R.string.title_activity_contributions));
} }
@Override @Override
@ -158,9 +168,9 @@ public class ContributionsActivity
Contribution c = Contribution.fromCursor(allContributions); Contribution c = Contribution.fromCursor(allContributions);
if(c.getState() == Contribution.STATE_FAILED) { if(c.getState() == Contribution.STATE_FAILED) {
uploadService.queue(UploadService.ACTION_UPLOAD_FILE, c); uploadService.queue(UploadService.ACTION_UPLOAD_FILE, c);
Log.d("Commons", "Restarting for" + c.toContentValues().toString()); Timber.d("Restarting for %s", c.toContentValues());
} else { } else {
Log.d("Commons", "Skipping re-upload for non-failed " + c.toContentValues().toString()); Timber.d("Skipping re-upload for non-failed %s", c.toContentValues());
} }
} }
@ -168,11 +178,11 @@ public class ContributionsActivity
allContributions.moveToPosition(i); allContributions.moveToPosition(i);
Contribution c = Contribution.fromCursor(allContributions); Contribution c = Contribution.fromCursor(allContributions);
if(c.getState() == Contribution.STATE_FAILED) { if(c.getState() == Contribution.STATE_FAILED) {
Log.d("Commons", "Deleting failed contrib " + c.toContentValues().toString()); Timber.d("Deleting failed contrib %s", c.toContentValues());
c.setContentProviderClient(getContentResolver().acquireContentProviderClient(ContributionsContentProvider.AUTHORITY)); c.setContentProviderClient(getContentResolver().acquireContentProviderClient(ContributionsContentProvider.AUTHORITY));
c.delete(); c.delete();
} else { } else {
Log.d("Commons", "Skipping deletion for non-failed contrib " + c.toContentValues().toString()); Timber.d("Skipping deletion for non-failed contrib %s", c.toContentValues());
} }
} }
@ -206,7 +216,12 @@ public class ContributionsActivity
@Override @Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) { public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
return new CursorLoader(this, ContributionsContentProvider.BASE_URI, Contribution.Table.ALL_FIELDS, CONTRIBUTION_SELECTION, null, CONTRIBUTION_SORT); SharedPreferences sharedPref =
PreferenceManager.getDefaultSharedPreferences(this);
int uploads = sharedPref.getInt(Prefs.UPLOADS_SHOWING, 100);
return new CursorLoader(this, ContributionsContentProvider.BASE_URI,
Contribution.Table.ALL_FIELDS, CONTRIBUTION_SELECTION, null,
CONTRIBUTION_SORT + "LIMIT " + uploads);
} }
@Override @Override
@ -217,7 +232,18 @@ public class ContributionsActivity
((CursorAdapter)contributionsList.getAdapter()).swapCursor(cursor); ((CursorAdapter)contributionsList.getAdapter()).swapCursor(cursor);
} }
getSupportActionBar().setSubtitle(getResources().getQuantityString(R.plurals.contributions_subtitle, cursor.getCount(), cursor.getCount())); if (cursor.getCount() == 0
&& Locale.getDefault().getISO3Language().equals(Locale.ENGLISH.getISO3Language())) {
//cursor count is zero and language is english -
// we need to set the message for 0 case explicitly.
getSupportActionBar().setSubtitle(getResources()
.getString(R.string.contributions_subtitle_zero));
} else {
getSupportActionBar().setSubtitle(getResources()
.getQuantityString(R.plurals.contributions_subtitle,
cursor.getCount(),
cursor.getCount()));
}
contributionsList.clearSyncMessage(); contributionsList.clearSyncMessage();
notifyAndMigrateDataSetObservers(); notifyAndMigrateDataSetObservers();
@ -289,15 +315,16 @@ public class ContributionsActivity
@Override @Override
public void onBackStackChanged() { public void onBackStackChanged() {
if(mediaDetails != null && mediaDetails.isVisible()) { initBackButton();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
} else {
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
}
} }
@Override @Override
public void refreshSource() { public void refreshSource() {
getSupportLoaderManager().restartLoader(0, null, this); getSupportLoaderManager().restartLoader(0, null, this);
} }
public static void startYourself(Context context) {
Intent settingsIntent = new Intent(context, ContributionsActivity.class);
context.startActivity(settingsIntent);
}
} }

View file

@ -8,9 +8,10 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder; import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri; import android.net.Uri;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.data.DBOpenHelper; import fr.free.nrw.commons.data.DBOpenHelper;
import timber.log.Timber;
public class ContributionsContentProvider extends ContentProvider{ public class ContributionsContentProvider extends ContentProvider{
@ -35,7 +36,7 @@ public class ContributionsContentProvider extends ContentProvider{
private DBOpenHelper dbOpenHelper; private DBOpenHelper dbOpenHelper;
@Override @Override
public boolean onCreate() { public boolean onCreate() {
dbOpenHelper = DBOpenHelper.getInstance(getContext()); dbOpenHelper = CommonsApplication.getInstance().getDBOpenHelper();
return false; return false;
} }
@ -102,7 +103,7 @@ public class ContributionsContentProvider extends ContentProvider{
switch(uriType) { switch(uriType) {
case CONTRIBUTIONS_ID: case CONTRIBUTIONS_ID:
Log.d("Commons", "Deleting contribution id " + uri.getLastPathSegment()); Timber.d("Deleting contribution id %s", uri.getLastPathSegment());
rows = db.delete(Contribution.Table.TABLE_NAME, rows = db.delete(Contribution.Table.TABLE_NAME,
"_id = ?", "_id = ?",
new String[] { uri.getLastPathSegment() } new String[] { uri.getLastPathSegment() }
@ -117,14 +118,14 @@ public class ContributionsContentProvider extends ContentProvider{
@Override @Override
public int bulkInsert(Uri uri, ContentValues[] values) { public int bulkInsert(Uri uri, ContentValues[] values) {
Log.d("Commons", "Hello, bulk insert! (ContributionsContentProvider)"); Timber.d("Hello, bulk insert! (ContributionsContentProvider)");
int uriType = uriMatcher.match(uri); int uriType = uriMatcher.match(uri);
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
sqlDB.beginTransaction(); sqlDB.beginTransaction();
switch (uriType) { switch (uriType) {
case CONTRIBUTIONS: case CONTRIBUTIONS:
for(ContentValues value: values) { for(ContentValues value: values) {
Log.d("Commons", "Inserting! " + value.toString()); Timber.d("Inserting! %s", value);
sqlDB.insert(Contribution.Table.TABLE_NAME, null, value); sqlDB.insert(Contribution.Table.TABLE_NAME, null, value);
} }
break; break;

View file

@ -3,27 +3,13 @@ package fr.free.nrw.commons.contributions;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.support.v4.widget.CursorAdapter; import android.support.v4.widget.CursorAdapter;
import android.text.TextUtils;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.assist.SimpleImageLoadingListener;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.MediaWikiImageView;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
class ContributionsListAdapter extends CursorAdapter { class ContributionsListAdapter extends CursorAdapter {
private DisplayImageOptions contributionDisplayOptions = Utils.getGenericDisplayOptions().build();
private Activity activity; private Activity activity;
public ContributionsListAdapter(Activity activity, Cursor c, int flags) { public ContributionsListAdapter(Activity activity, Cursor c, int flags) {
@ -38,48 +24,12 @@ class ContributionsListAdapter extends CursorAdapter {
return parent; return parent;
} }
//FIXME: Potential cause of wrong image display bug
@Override @Override
public void bindView(View view, Context context, Cursor cursor) { public void bindView(View view, Context context, Cursor cursor) {
final ContributionViewHolder views = (ContributionViewHolder)view.getTag(); final ContributionViewHolder views = (ContributionViewHolder)view.getTag();
final Contribution contribution = Contribution.fromCursor(cursor); final Contribution contribution = Contribution.fromCursor(cursor);
String actualUrl = (contribution.getLocalUri() != null && !TextUtils.isEmpty(contribution.getLocalUri().toString())) ? contribution.getLocalUri().toString() : contribution.getThumbnailUrl(640); views.imageView.setMedia(contribution);
if(views.url == null || !views.url.equals(actualUrl)) {
if(actualUrl.startsWith("http")) {
MediaWikiImageView mwImageView = views.imageView;
mwImageView.setMedia(contribution, ((CommonsApplication) activity.getApplicationContext()).getImageLoader());
// FIXME: For transparent images
} else {
ImageLoader.getInstance().displayImage(actualUrl, views.imageView, contributionDisplayOptions, new SimpleImageLoadingListener() {
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
if(loadedImage.hasAlpha()) {
views.imageView.setBackgroundResource(android.R.color.white);
}
views.seqNumView.setVisibility(View.GONE);
}
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
super.onLoadingFailed(imageUri, view, failReason);
MediaWikiImageView mwImageView = views.imageView;
mwImageView.setMedia(contribution, ((CommonsApplication) activity.getApplicationContext()).getImageLoader());
}
});
}
views.url = actualUrl;
}
BitmapDrawable actualImageDrawable = (BitmapDrawable)views.imageView.getDrawable();
if(actualImageDrawable != null && actualImageDrawable.getBitmap() != null && actualImageDrawable.getBitmap().hasAlpha()) {
views.imageView.setBackgroundResource(android.R.color.white);
} else {
views.imageView.setBackgroundDrawable(null);
}
views.titleView.setText(contribution.getDisplayTitle()); views.titleView.setText(contribution.getDisplayTitle());
views.seqNumView.setText(String.valueOf(cursor.getPosition() + 1)); views.seqNumView.setText(String.valueOf(cursor.getPosition() + 1));

View file

@ -10,7 +10,6 @@ import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
@ -21,16 +20,13 @@ import android.widget.AdapterView;
import android.widget.GridView; import android.widget.GridView;
import android.widget.ListAdapter; import android.widget.ListAdapter;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import fr.free.nrw.commons.AboutActivity;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.settings.SettingsActivity;
import fr.free.nrw.commons.nearby.NearbyActivity; import fr.free.nrw.commons.nearby.NearbyActivity;
import timber.log.Timber;
import static android.app.Activity.RESULT_OK; import static android.app.Activity.RESULT_OK;
@ -45,7 +41,6 @@ public class ContributionsListFragment extends Fragment {
@BindView(R.id.emptyMessage) TextView emptyMessage; @BindView(R.id.emptyMessage) TextView emptyMessage;
private ContributionController controller; private ContributionController controller;
private static final String TAG = "ContributionsList";
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
@ -54,14 +49,14 @@ public class ContributionsListFragment extends Fragment {
contributionsList.setOnItemClickListener((AdapterView.OnItemClickListener)getActivity()); contributionsList.setOnItemClickListener((AdapterView.OnItemClickListener)getActivity());
if(savedInstanceState != null) { if(savedInstanceState != null) {
Log.d(TAG, "Scrolling to " + savedInstanceState.getInt("grid-position")); Timber.d("Scrolling to %d", savedInstanceState.getInt("grid-position"));
contributionsList.setSelection(savedInstanceState.getInt("grid-position")); contributionsList.setSelection(savedInstanceState.getInt("grid-position"));
} }
//TODO: Should this be in onResume? //TODO: Should this be in onResume?
SharedPreferences prefs = this.getActivity().getSharedPreferences("prefs", Context.MODE_PRIVATE); SharedPreferences prefs = this.getActivity().getSharedPreferences("prefs", Context.MODE_PRIVATE);
String lastModified = prefs.getString("lastSyncTimestamp", ""); String lastModified = prefs.getString("lastSyncTimestamp", "");
Log.d(TAG, "Last Sync Timestamp: " + lastModified); Timber.d("Last Sync Timestamp: %s", lastModified);
if (lastModified.equals("")) { if (lastModified.equals("")) {
waitingMessage.setVisibility(View.VISIBLE); waitingMessage.setVisibility(View.VISIBLE);
@ -96,10 +91,12 @@ public class ContributionsListFragment extends Fragment {
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);
if ( resultCode == RESULT_OK ) { if ( resultCode == RESULT_OK ) {
Log.d("Contributions", "OnActivityResult() parameters: Req code: " + requestCode + " Result code: " + resultCode + " Data: " + data); Timber.d("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data);
controller.handleImagePicked(requestCode, data); controller.handleImagePicked(requestCode, data);
} else { } else {
Log.e("Contributions", "OnActivityResult() parameters: Req code: " + requestCode + " Result code: " + resultCode + " Data: " + data); Timber.e("OnActivityResult() parameters: Req code: %d Result code: %d Data: %s",
requestCode, resultCode, data);
} }
} }
@ -125,46 +122,6 @@ public class ContributionsListFragment extends Fragment {
case R.id.menu_from_camera: case R.id.menu_from_camera:
controller.startCameraCapture(); controller.startCameraCapture();
return true; return true;
case R.id.menu_settings:
Intent settingsIntent = new Intent(getActivity(), SettingsActivity.class);
startActivity(settingsIntent);
return true;
case R.id.menu_about:
Intent aboutIntent = new Intent(getActivity(), AboutActivity.class);
startActivity(aboutIntent);
return true;
case R.id.menu_feedback:
Intent feedbackIntent = new Intent(Intent.ACTION_SEND);
feedbackIntent.setType("message/rfc822");
feedbackIntent.putExtra(Intent.EXTRA_EMAIL, new String[] { CommonsApplication.FEEDBACK_EMAIL });
feedbackIntent.putExtra(Intent.EXTRA_SUBJECT, String.format(CommonsApplication.FEEDBACK_EMAIL_SUBJECT, BuildConfig.VERSION_NAME));
try {
startActivity(feedbackIntent);
}
catch (ActivityNotFoundException e) {
Toast.makeText(getActivity(), R.string.no_email_client, Toast.LENGTH_SHORT).show();
}
return true;
case R.id.menu_nearby:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(this.getActivity(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
//See http://stackoverflow.com/questions/33169455/onrequestpermissionsresult-not-being-called-in-dialog-fragment
requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 2);
return true;
} else {
Intent nearbyIntent = new Intent(getActivity(), NearbyActivity.class);
startActivity(nearbyIntent);
return true;
}
}
else {
Intent nearbyIntent = new Intent(getActivity(), NearbyActivity.class);
startActivity(nearbyIntent);
return true;
}
case R.id.menu_refresh:
((SourceRefresher)getActivity()).refreshSource();
return true;
default: default:
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
@ -176,7 +133,7 @@ public class ContributionsListFragment extends Fragment {
// 1 = Storage allowed when gallery selected // 1 = Storage allowed when gallery selected
case 1: { case 1: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d("ContributionsList", "Call controller.startGalleryPick()"); Timber.d("Call controller.startGalleryPick()");
controller.startGalleryPick(); controller.startGalleryPick();
} }
} }
@ -184,7 +141,7 @@ public class ContributionsListFragment extends Fragment {
// 2 = Location allowed when 'nearby places' selected // 2 = Location allowed when 'nearby places' selected
case 2: { case 2: {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d("ContributionsList", "Location permission granted"); Timber.d("Location permission granted");
Intent nearbyIntent = new Intent(getActivity(), NearbyActivity.class); Intent nearbyIntent = new Intent(getActivity(), NearbyActivity.class);
startActivity(nearbyIntent); startActivity(nearbyIntent);
} }
@ -198,12 +155,9 @@ public class ContributionsListFragment extends Fragment {
menu.clear(); // See http://stackoverflow.com/a/8495697/17865 menu.clear(); // See http://stackoverflow.com/a/8495697/17865
inflater.inflate(R.menu.fragment_contributions_list, menu); inflater.inflate(R.menu.fragment_contributions_list, menu);
CommonsApplication app = (CommonsApplication)getActivity().getApplicationContext(); if (!CommonsApplication.getInstance().deviceHasCamera()) {
if (!app.deviceHasCamera()) {
menu.findItem(R.id.menu_from_camera).setEnabled(false); menu.findItem(R.id.menu_from_camera).setEnabled(false);
} }
menu.findItem(R.id.menu_refresh).setVisible(false);
} }
@Override @Override

View file

@ -11,17 +11,17 @@ import android.database.Cursor;
import android.os.Bundle; import android.os.Bundle;
import android.os.RemoteException; import android.os.RemoteException;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import org.mediawiki.api.ApiResult; import org.mediawiki.api.ApiResult;
import org.mediawiki.api.MWApi;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.MWApi;
import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.Utils;
import timber.log.Timber;
public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter { public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
private static int COMMIT_THRESHOLD = 10; private static int COMMIT_THRESHOLD = 10;
@ -30,7 +30,10 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
} }
private int getLimit() { private int getLimit() {
return 500; // FIXME: Parameterize!
int limit = 500;
Timber.d("Max number of uploads set to %d", limit);
return limit; // FIXME: Parameterize!
} }
private static final String[] existsQuery = { Contribution.Table.COLUMN_FILENAME }; private static final String[] existsQuery = { Contribution.Table.COLUMN_FILENAME };
@ -58,7 +61,7 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
public void onPerformSync(Account account, Bundle bundle, String s, ContentProviderClient contentProviderClient, SyncResult syncResult) { public void onPerformSync(Account account, Bundle bundle, String s, ContentProviderClient contentProviderClient, SyncResult syncResult) {
// This code is fraught with possibilities of race conditions, but lalalalala I can't hear you! // This code is fraught with possibilities of race conditions, but lalalalala I can't hear you!
String user = account.name; String user = account.name;
MWApi api = CommonsApplication.createMWApi(); MWApi api = CommonsApplication.getInstance().getMWApi();
SharedPreferences prefs = this.getContext().getSharedPreferences("prefs", Context.MODE_PRIVATE); SharedPreferences prefs = this.getContext().getSharedPreferences("prefs", Context.MODE_PRIVATE);
String lastModified = prefs.getString("lastSyncTimestamp", ""); String lastModified = prefs.getString("lastSyncTimestamp", "");
Date curTime = new Date(); Date curTime = new Date();
@ -71,7 +74,7 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
MWApi.RequestBuilder builder = api.action("query") MWApi.RequestBuilder builder = api.action("query")
.param("list", "logevents") .param("list", "logevents")
.param("letype", "upload") .param("letype", "upload")
.param("leprop", "title|timestamp") .param("leprop", "title|timestamp|ids")
.param("leuser", user) .param("leuser", user)
.param("lelimit", getLimit()); .param("lelimit", getLimit());
if(!TextUtils.isEmpty(lastModified)) { if(!TextUtils.isEmpty(lastModified)) {
@ -85,18 +88,23 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
// There isn't really much we can do, eh? // There isn't really much we can do, eh?
// FIXME: Perhaps add EventLogging? // FIXME: Perhaps add EventLogging?
syncResult.stats.numIoExceptions += 1; // Not sure if this does anything. Shitty docs syncResult.stats.numIoExceptions += 1; // Not sure if this does anything. Shitty docs
Log.d("Commons", "Syncing failed due to " + e.toString()); Timber.d("Syncing failed due to %s", e);
return; return;
} }
Log.d("Commons", "Last modified at " + lastModified); Timber.d("Last modified at %s", lastModified);
ArrayList<ApiResult> uploads = result.getNodes("/api/query/logevents/item"); ArrayList<ApiResult> uploads = result.getNodes("/api/query/logevents/item");
Log.d("Commons", uploads.size() + " results!"); Timber.d("%d results!", uploads.size());
ArrayList<ContentValues> imageValues = new ArrayList<>(); ArrayList<ContentValues> imageValues = new ArrayList<>();
for(ApiResult image: uploads) { for(ApiResult image: uploads) {
String pageId = image.getString("@pageid");
if (pageId.equals("0")) {
// means that this upload was deleted.
continue;
}
String filename = image.getString("@title"); String filename = image.getString("@title");
if(fileExists(contentProviderClient, filename)) { if(fileExists(contentProviderClient, filename)) {
Log.d("Commons", "Skipping " + filename); Timber.d("Skipping %s", filename);
continue; continue;
} }
String thumbUrl = Utils.makeThumbBaseUrl(filename); String thumbUrl = Utils.makeThumbBaseUrl(filename);
@ -128,6 +136,6 @@ public class ContributionsSyncAdapter extends AbstractThreadedSyncAdapter {
} }
} }
prefs.edit().putString("lastSyncTimestamp", Utils.toMWDate(curTime)).apply(); prefs.edit().putString("lastSyncTimestamp", Utils.toMWDate(curTime)).apply();
Log.d("Commons", "Oh hai, everyone! Look, a kitty!"); Timber.d("Oh hai, everyone! Look, a kitty!");
} }
} }

View file

@ -15,7 +15,7 @@ public class ContributionsSyncService extends Service {
super.onCreate(); super.onCreate();
synchronized (sSyncAdapterLock) { synchronized (sSyncAdapterLock) {
if (sSyncAdapter == null) { if (sSyncAdapter == null) {
sSyncAdapter = new ContributionsSyncAdapter(getApplicationContext(), true); sSyncAdapter = new ContributionsSyncAdapter(this, true);
} }
} }
} }

View file

@ -12,16 +12,11 @@ public class DBOpenHelper extends SQLiteOpenHelper{
private static final String DATABASE_NAME = "commons.db"; private static final String DATABASE_NAME = "commons.db";
private static final int DATABASE_VERSION = 6; private static final int DATABASE_VERSION = 6;
private static DBOpenHelper singleton = null;
public static synchronized DBOpenHelper getInstance(Context context) { /**
if ( singleton == null ) { * Do not use, please call CommonsApplication.getDBOpenHelper()
singleton = new DBOpenHelper(context); */
} public DBOpenHelper(Context context) {
return singleton;
}
private DBOpenHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION); super(context, DATABASE_NAME, null, DATABASE_VERSION);
} }

View file

@ -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();
}

View file

@ -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;
}
}

View file

@ -6,6 +6,8 @@ public class LatLng {
public final double longitude; public final double longitude;
/** Accepts latitude and longitude. /** Accepts latitude and longitude.
* North and South values are cut off at 90°
*
* @param latitude double value * @param latitude double value
* @param longitude double value * @param longitude double value
*/ */
@ -42,4 +44,53 @@ public class LatLng {
public String toString() { public String toString() {
return "lat/lng: (" + this.latitude + "," + this.longitude + ")"; return "lat/lng: (" + this.latitude + "," + this.longitude + ")";
} }
/**
* Rounds the float to 4 digits and returns absolute value.
*
* @param coordinate A coordinate value as string.
* @return String of the rounded number.
*/
private String formatCoordinate(double coordinate) {
double roundedNumber = Math.round(coordinate * 10000d) / 10000d;
double absoluteNumber = Math.abs(roundedNumber);
return String.valueOf(absoluteNumber);
}
/**
* Returns "N" or "S" depending on the latitude.
*
* @return "N" or "S".
*/
private String getNorthSouth() {
if (this.latitude < 0) {
return "S";
}
return "N";
}
/**
* Returns "E" or "W" depending on the longitude.
*
* @return "E" or "W".
*/
private String getEastWest() {
if (this.longitude >= 0 && this.longitude < 180) {
return "E";
}
return "W";
}
/**
* Returns a nicely formatted coordinate string. Used e.g. in
* the detail view.
*
* @return The formatted string.
*/
public String getPrettyCoordinateString() {
return formatCoordinate(this.latitude) + " " + this.getNorthSouth() + ", "
+ formatCoordinate(this.longitude) + " " + this.getEastWest();
}
} }

View file

@ -6,10 +6,11 @@ import android.location.Location;
import android.location.LocationListener; import android.location.LocationListener;
import android.location.LocationManager; import android.location.LocationManager;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import timber.log.Timber;
public class LocationServiceManager implements LocationListener { public class LocationServiceManager implements LocationListener {
public static final String TAG = "LocationServiceManager";
private String provider; private String provider;
private LocationManager locationManager; private LocationManager locationManager;
private LatLng latestLocation; private LatLng latestLocation;
@ -31,14 +32,14 @@ public class LocationServiceManager implements LocationListener {
Location location = locationManager.getLastKnownLocation(provider); Location location = locationManager.getLastKnownLocation(provider);
//Location works, just need to 'send' GPS coords //Location works, just need to 'send' GPS coords
// via emulator extended controls if testing on emulator // via emulator extended controls if testing on emulator
Log.d(TAG, "Checking for location..."); Timber.d("Checking for location...");
if (location != null) { if (location != null) {
this.onLocationChanged(location); this.onLocationChanged(location);
} }
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
Log.e(TAG, "Illegal argument exception", e); Timber.e(e, "Illegal argument exception");
} catch (SecurityException e) { } catch (SecurityException e) {
Log.e(TAG, "Security exception", e); Timber.e(e, "Security exception");
} }
} }
@ -48,7 +49,7 @@ public class LocationServiceManager implements LocationListener {
try { try {
locationManager.removeUpdates(this); locationManager.removeUpdates(this);
} catch (SecurityException e) { } catch (SecurityException e) {
Log.e(TAG, "Security exception", e); Timber.e(e, "Security exception");
} }
} }
@ -56,24 +57,23 @@ public class LocationServiceManager implements LocationListener {
public void onLocationChanged(Location location) { public void onLocationChanged(Location location) {
double currentLatitude = location.getLatitude(); double currentLatitude = location.getLatitude();
double currentLongitude = location.getLongitude(); double currentLongitude = location.getLongitude();
Log.d(TAG, "Latitude: " + String.valueOf(currentLatitude) Timber.d("Latitude: %f Longitude: %f", currentLatitude, currentLongitude);
+ " Longitude: " + String.valueOf(currentLongitude));
latestLocation = new LatLng(currentLatitude, currentLongitude); latestLocation = new LatLng(currentLatitude, currentLongitude);
} }
@Override @Override
public void onStatusChanged(String provider, int status, Bundle extras) { public void onStatusChanged(String provider, int status, Bundle extras) {
Log.d(TAG, provider + "'s status changed to " + status); Timber.d("%s's status changed to %d", provider, status);
} }
@Override @Override
public void onProviderEnabled(String provider) { public void onProviderEnabled(String provider) {
Log.d(TAG, "Provider " + provider + " enabled"); Timber.d("Provider %s enabled", provider);
} }
@Override @Override
public void onProviderDisabled(String provider) { public void onProviderDisabled(String provider) {
Log.d(TAG, "Provider " + provider + " disabled"); Timber.d("Provider %s disabled", provider);
} }
} }

View file

@ -2,31 +2,23 @@ package fr.free.nrw.commons.media;
import android.content.Intent; import android.content.Intent;
import android.database.DataSetObserver; import android.database.DataSetObserver;
import android.graphics.Bitmap;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.util.Log;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewTreeObserver; import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.ScrollView; import android.widget.ScrollView;
import android.widget.TextView; import android.widget.TextView;
import com.android.volley.toolbox.ImageLoader;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.assist.ImageLoadingListener;
import java.io.IOException; import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.License; import fr.free.nrw.commons.License;
import fr.free.nrw.commons.LicenseList; import fr.free.nrw.commons.LicenseList;
import fr.free.nrw.commons.Media; import fr.free.nrw.commons.Media;
@ -34,18 +26,14 @@ import fr.free.nrw.commons.MediaDataExtractor;
import fr.free.nrw.commons.MediaWikiImageView; import fr.free.nrw.commons.MediaWikiImageView;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.Utils;
import timber.log.Timber;
public class MediaDetailFragment extends Fragment { public class MediaDetailFragment extends Fragment {
private boolean editable; private boolean editable;
private DisplayImageOptions displayOptions;
private MediaDetailPagerFragment.MediaDetailProvider detailProvider; private MediaDetailPagerFragment.MediaDetailProvider detailProvider;
private int index; private int index;
public static MediaDetailFragment forMedia(int index) {
return forMedia(index, false);
}
public static MediaDetailFragment forMedia(int index, boolean editable) { public static MediaDetailFragment forMedia(int index, boolean editable) {
MediaDetailFragment mf = new MediaDetailFragment(); MediaDetailFragment mf = new MediaDetailFragment();
@ -60,16 +48,15 @@ public class MediaDetailFragment extends Fragment {
return mf; return mf;
} }
private ImageView image; private MediaWikiImageView image;
//private EditText title;
private ProgressBar loadingProgress;
private ImageView loadingFailed;
private MediaDetailSpacer spacer; private MediaDetailSpacer spacer;
private int initialListTop = 0; private int initialListTop = 0;
private TextView title; private TextView title;
private TextView desc; private TextView desc;
private TextView license; private TextView license;
private TextView coordinates;
private TextView uploadedDate;
private LinearLayout categoryContainer; private LinearLayout categoryContainer;
private ScrollView scrollView; private ScrollView scrollView;
private ArrayList<String> categoryNames; private ArrayList<String> categoryNames;
@ -113,9 +100,7 @@ public class MediaDetailFragment extends Fragment {
final View view = inflater.inflate(R.layout.fragment_media_detail, container, false); final View view = inflater.inflate(R.layout.fragment_media_detail, container, false);
image = (ImageView) view.findViewById(R.id.mediaDetailImage); image = (MediaWikiImageView) view.findViewById(R.id.mediaDetailImage);
loadingProgress = (ProgressBar) view.findViewById(R.id.mediaDetailImageLoading);
loadingFailed = (ImageView) view.findViewById(R.id.mediaDetailImageFailed);
scrollView = (ScrollView) view.findViewById(R.id.mediaDetailScrollView); scrollView = (ScrollView) view.findViewById(R.id.mediaDetailScrollView);
// Detail consists of a list view with main pane in header view, plus category list. // Detail consists of a list view with main pane in header view, plus category list.
@ -123,29 +108,12 @@ public class MediaDetailFragment extends Fragment {
title = (TextView) view.findViewById(R.id.mediaDetailTitle); title = (TextView) view.findViewById(R.id.mediaDetailTitle);
desc = (TextView) view.findViewById(R.id.mediaDetailDesc); desc = (TextView) view.findViewById(R.id.mediaDetailDesc);
license = (TextView) view.findViewById(R.id.mediaDetailLicense); license = (TextView) view.findViewById(R.id.mediaDetailLicense);
coordinates = (TextView) view.findViewById(R.id.mediaDetailCoordinates);
uploadedDate = (TextView) view.findViewById(R.id.mediaDetailuploadeddate);
categoryContainer = (LinearLayout) view.findViewById(R.id.mediaDetailCategoryContainer); categoryContainer = (LinearLayout) view.findViewById(R.id.mediaDetailCategoryContainer);
licenseList = new LicenseList(getActivity()); licenseList = new LicenseList(getActivity());
Media media = detailProvider.getMediaAtPosition(index);
if (media == null) {
// Ask the detail provider to ping us when we're ready
Log.d("Commons", "MediaDetailFragment not yet ready to display details; registering observer");
dataObserver = new DataSetObserver() {
@Override
public void onChanged() {
Log.d("Commons", "MediaDetailFragment ready to display delayed details!");
detailProvider.unregisterDataSetObserver(dataObserver);
dataObserver = null;
displayMediaDetails(detailProvider.getMediaAtPosition(index));
}
};
detailProvider.registerDataSetObserver(dataObserver);
} else {
Log.d("Commons", "MediaDetailFragment ready to display details");
displayMediaDetails(media);
}
// Progressively darken the image in the background when we scroll detail pane up // Progressively darken the image in the background when we scroll detail pane up
scrollListener = new ViewTreeObserver.OnScrollChangedListener() { scrollListener = new ViewTreeObserver.OnScrollChangedListener() {
@Override @Override
@ -182,97 +150,75 @@ public class MediaDetailFragment extends Fragment {
return view; return view;
} }
private void displayMediaDetails(final Media media) { @Override public void onResume() {
//Always load image from Internet to allow viewing the desc, license, and cats super.onResume();
String actualUrl = media.getThumbnailUrl(640); Media media = detailProvider.getMediaAtPosition(index);
if(actualUrl.startsWith("http")) { if (media == null) {
Log.d("Volley", "Actual URL starts with http and is: " + actualUrl); // Ask the detail provider to ping us when we're ready
Timber.d("MediaDetailFragment not yet ready to display details; registering observer");
ImageLoader loader = ((CommonsApplication)getActivity().getApplicationContext()).getImageLoader(); dataObserver = new DataSetObserver() {
MediaWikiImageView mwImage = (MediaWikiImageView)image;
mwImage.setLoadingView(loadingProgress); //FIXME: Set this as an attribute
mwImage.setMedia(media, loader);
// FIXME: For transparent images
// FIXME: keep the spinner going while we load data
// FIXME: cache this data
// Load image metadata: desc, license, categories
detailFetchTask = new AsyncTask<Void, Void, Boolean>() {
private MediaDataExtractor extractor;
@Override @Override
protected void onPreExecute() { public void onChanged() {
extractor = new MediaDataExtractor(media.getFilename(), licenseList); if (!isAdded()) {
} return;
@Override
protected Boolean doInBackground(Void... voids) {
try {
extractor.fetch();
return Boolean.TRUE;
} catch (IOException e) {
e.printStackTrace();
}
return Boolean.FALSE;
}
@Override
protected void onPostExecute(Boolean success) {
detailFetchTask = null;
if (success.booleanValue()) {
extractor.fill(media);
// Set text of desc, license, and categories
desc.setText(prettyDescription(media));
license.setText(prettyLicense(media));
categoryNames.removeAll(categoryNames);
categoryNames.addAll(media.getCategories());
categoriesLoaded = true;
categoriesPresent = (categoryNames.size() > 0);
if (!categoriesPresent) {
// Stick in a filler element.
categoryNames.add(getString(R.string.detail_panel_cats_none));
}
rebuildCatList();
} else {
Log.d("Commons", "Failed to load photo details.");
} }
Timber.d("MediaDetailFragment ready to display delayed details!");
detailProvider.unregisterDataSetObserver(dataObserver);
dataObserver = null;
displayMediaDetails(detailProvider.getMediaAtPosition(index));
} }
}; };
detailFetchTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); detailProvider.registerDataSetObserver(dataObserver);
} else { } else {
//This should not usually happen, image along with associated details should always be loaded from Internet, but keeping this for now for backup. Timber.d("MediaDetailFragment ready to display details");
//Even if image is loaded from device storage, it will display, albeit with empty desc and cat. displayMediaDetails(media);
Log.d("Volley", "Actual URL does not start with http and is: " + actualUrl); }
com.nostra13.universalimageloader.core.ImageLoader.getInstance().displayImage(actualUrl, image, displayOptions, new ImageLoadingListener() { }
@Override
public void onLoadingStarted(String s, View view) { private void displayMediaDetails(final Media media) {
loadingProgress.setVisibility(View.VISIBLE); //Always load image from Internet to allow viewing the desc, license, and cats
image.setMedia(media);
// FIXME: For transparent images
// FIXME: keep the spinner going while we load data
// FIXME: cache this data
// Load image metadata: desc, license, categories
detailFetchTask = new AsyncTask<Void, Void, Boolean>() {
private MediaDataExtractor extractor;
@Override
protected void onPreExecute() {
extractor = new MediaDataExtractor(media.getFilename(), licenseList);
}
@Override
protected Boolean doInBackground(Void... voids) {
try {
extractor.fetch();
return Boolean.TRUE;
} catch (IOException e) {
e.printStackTrace();
}
return Boolean.FALSE;
}
@Override
protected void onPostExecute(Boolean success) {
detailFetchTask = null;
if (!isAdded()) {
return;
} }
@Override if (success) {
public void onLoadingFailed(String s, View view, FailReason failReason) { extractor.fill(media);
loadingProgress.setVisibility(View.GONE);
loadingFailed.setVisibility(View.VISIBLE);
}
@Override
public void onLoadingComplete(String s, View view, Bitmap bitmap) {
loadingProgress.setVisibility(View.GONE);
loadingFailed.setVisibility(View.GONE);
image.setVisibility(View.VISIBLE);
if(bitmap.hasAlpha()) {
image.setBackgroundResource(android.R.color.white);
}
// Set text of desc, license, and categories // Set text of desc, license, and categories
desc.setText(prettyDescription(media)); desc.setText(prettyDescription(media));
license.setText(prettyLicense(media)); license.setText(prettyLicense(media));
coordinates.setText(prettyCoordinates(media));
uploadedDate.setText(prettyUploadedDate(media));
categoryNames.removeAll(categoryNames); categoryNames.clear();
categoryNames.addAll(media.getCategories()); categoryNames.addAll(media.getCategories());
categoriesLoaded = true; categoriesLoaded = true;
@ -282,27 +228,18 @@ public class MediaDetailFragment extends Fragment {
categoryNames.add(getString(R.string.detail_panel_cats_none)); categoryNames.add(getString(R.string.detail_panel_cats_none));
} }
rebuildCatList(); rebuildCatList();
} else {
Timber.d("Failed to load photo details.");
} }
}
@Override };
public void onLoadingCancelled(String s, View view) { detailFetchTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
Log.e("Volley", "Image loading cancelled. But why?");
}
});
}
title.setText(media.getDisplayTitle()); title.setText(media.getDisplayTitle());
desc.setText(""); // fill in from network... desc.setText(""); // fill in from network...
license.setText(""); // fill in from network... license.setText(""); // fill in from network...
} }
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
displayOptions = Utils.getGenericDisplayOptions().build();
}
@Override @Override
public void onDestroyView() { public void onDestroyView() {
if (detailFetchTask != null) { if (detailFetchTask != null) {
@ -377,7 +314,7 @@ public class MediaDetailFragment extends Fragment {
private String prettyLicense(Media media) { private String prettyLicense(Media media) {
String licenseKey = media.getLicense(); String licenseKey = media.getLicense();
Log.d("Commons", "Media license is: " + licenseKey); Timber.d("Media license is: %s", licenseKey);
if (licenseKey == null || licenseKey.equals("")) { if (licenseKey == null || licenseKey.equals("")) {
return getString(R.string.detail_license_empty); return getString(R.string.detail_license_empty);
} }
@ -388,4 +325,22 @@ public class MediaDetailFragment extends Fragment {
return licenseObj.getName(); return licenseObj.getName();
} }
} }
private String prettyUploadedDate(Media media) {
Date date = media.getDateUploaded();
if (date.toString() == null || date.toString().isEmpty()) {
return "Uploaded date not available";
}
SimpleDateFormat formatter = new SimpleDateFormat("dd MMM yyyy");
return formatter.format(date);
}
/**
* Returns the coordinates nicely formatted.
*
* @return Coordinates as text.
*/
private String prettyCoordinates(Media media) {
return media.getCoordinates();
}
} }

View file

@ -123,7 +123,7 @@ public class MediaDetailPagerFragment extends Fragment implements ViewPager.OnPa
if(savedInstanceState != null) { if(savedInstanceState != null) {
editable = savedInstanceState.getBoolean("editable"); editable = savedInstanceState.getBoolean("editable");
} }
app = (CommonsApplication)getActivity().getApplicationContext(); app = CommonsApplication.getInstance();
setHasOptionsMenu(true); setHasOptionsMenu(true);
} }

View file

@ -8,9 +8,10 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder; import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri; import android.net.Uri;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.data.DBOpenHelper; import fr.free.nrw.commons.data.DBOpenHelper;
import timber.log.Timber;
public class ModificationsContentProvider extends ContentProvider{ public class ModificationsContentProvider extends ContentProvider{
@ -35,7 +36,7 @@ public class ModificationsContentProvider extends ContentProvider{
private DBOpenHelper dbOpenHelper; private DBOpenHelper dbOpenHelper;
@Override @Override
public boolean onCreate() { public boolean onCreate() {
dbOpenHelper = DBOpenHelper.getInstance(getContext()); dbOpenHelper = CommonsApplication.getInstance().getDBOpenHelper();
return false; return false;
} }
@ -101,14 +102,14 @@ public class ModificationsContentProvider extends ContentProvider{
@Override @Override
public int bulkInsert(Uri uri, ContentValues[] values) { public int bulkInsert(Uri uri, ContentValues[] values) {
Log.d("Commons", "Hello, bulk insert! (ModificationsContentProvider)"); Timber.d("Hello, bulk insert! (ModificationsContentProvider)");
int uriType = uriMatcher.match(uri); int uriType = uriMatcher.match(uri);
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
sqlDB.beginTransaction(); sqlDB.beginTransaction();
switch (uriType) { switch (uriType) {
case MODIFICATIONS: case MODIFICATIONS:
for(ContentValues value: values) { for(ContentValues value: values) {
Log.d("Commons", "Inserting! " + value.toString()); Timber.d("Inserting! %s", value);
sqlDB.insert(ModifierSequence.Table.TABLE_NAME, null, value); sqlDB.insert(ModifierSequence.Table.TABLE_NAME, null, value);
} }
break; break;

View file

@ -11,10 +11,9 @@ import android.content.SyncResult;
import android.database.Cursor; import android.database.Cursor;
import android.os.Bundle; import android.os.Bundle;
import android.os.RemoteException; import android.os.RemoteException;
import android.util.Log;
import fr.free.nrw.commons.MWApi;
import org.mediawiki.api.ApiResult; import org.mediawiki.api.ApiResult;
import org.mediawiki.api.MWApi;
import java.io.IOException; import java.io.IOException;
@ -22,6 +21,7 @@ import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.contributions.Contribution; import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.contributions.ContributionsContentProvider; import fr.free.nrw.commons.contributions.ContributionsContentProvider;
import timber.log.Timber;
public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter { public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
@ -42,7 +42,7 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
// Exit early if nothing to do // Exit early if nothing to do
if(allModifications == null || allModifications.getCount() == 0) { if(allModifications == null || allModifications.getCount() == 0) {
Log.d("Commons", "No modifications to perform"); Timber.d("No modifications to perform");
return; return;
} }
@ -52,16 +52,16 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
} catch (OperationCanceledException | AuthenticatorException e) { } catch (OperationCanceledException | AuthenticatorException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} catch (IOException e) { } catch (IOException e) {
Log.d("Commons", "Could not authenticate :("); Timber.d("Could not authenticate :(");
return; return;
} }
if(Utils.isNullOrWhiteSpace(authCookie)) { if(Utils.isNullOrWhiteSpace(authCookie)) {
Log.d("Commons", "Could not authenticate :("); Timber.d("Could not authenticate :(");
return; return;
} }
MWApi api = CommonsApplication.createMWApi(); MWApi api = CommonsApplication.getInstance().getMWApi();
api.setAuthCookie(authCookie); api.setAuthCookie(authCookie);
String editToken; String editToken;
@ -69,13 +69,13 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
try { try {
editToken = api.getEditToken(); editToken = api.getEditToken();
} catch (IOException e) { } catch (IOException e) {
Log.d("Commons", "Can not retreive edit token!"); Timber.d("Can not retreive edit token!");
return; return;
} }
allModifications.moveToFirst(); allModifications.moveToFirst();
Log.d("Commons", "Found " + allModifications.getCount() + " modifications to execute"); Timber.d("Found %d modifications to execute", allModifications.getCount());
ContentProviderClient contributionsClient = null; ContentProviderClient contributionsClient = null;
try { try {
@ -104,11 +104,11 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
.param("titles", contrib.getFilename()) .param("titles", contrib.getFilename())
.get(); .get();
} catch (IOException e) { } catch (IOException e) {
Log.d("Commons", "Network fuckup on modifications sync!"); Timber.d("Network fuckup on modifications sync!");
continue; continue;
} }
Log.d("Commons", "Page content is " + Utils.getStringFromDOM(requestResult.getDocument())); Timber.d("Page content is %s", Utils.getStringFromDOM(requestResult.getDocument()));
String pageContent = requestResult.getString("/api/query/pages/page/revisions/rev"); String pageContent = requestResult.getString("/api/query/pages/page/revisions/rev");
String processedPageContent = sequence.executeModifications(contrib.getFilename(), pageContent); String processedPageContent = sequence.executeModifications(contrib.getFilename(), pageContent);
@ -120,16 +120,16 @@ public class ModificationsSyncAdapter extends AbstractThreadedSyncAdapter {
.param("summary", sequence.getEditSummary()) .param("summary", sequence.getEditSummary())
.post(); .post();
} catch (IOException e) { } catch (IOException e) {
Log.d("Commons", "Network fuckup on modifications sync!"); Timber.d("Network fuckup on modifications sync!");
continue; continue;
} }
Log.d("Commons", "Response is" + Utils.getStringFromDOM(responseResult.getDocument())); Timber.d("Response is %s", Utils.getStringFromDOM(responseResult.getDocument()));
String result = responseResult.getString("/api/edit/@result"); String result = responseResult.getString("/api/edit/@result");
if(!result.equals("Success")) { if(!result.equals("Success")) {
// FIXME: Log this somewhere else // FIXME: Log this somewhere else
Log.d("Commons", "Non success result!" + result); Timber.d("Non success result! %s", result);
} else { } else {
sequence.delete(); sequence.delete();
} }

View file

@ -15,7 +15,7 @@ public class ModificationsSyncService extends Service {
super.onCreate(); super.onCreate();
synchronized (sSyncAdapterLock) { synchronized (sSyncAdapterLock) {
if (sSyncAdapter == null) { if (sSyncAdapter == null) {
sSyncAdapter = new ModificationsSyncAdapter(getApplicationContext(), true); sSyncAdapter = new ModificationsSyncAdapter(this, true);
} }
} }
} }

View file

@ -142,5 +142,10 @@ public class ModifierSequence {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME); db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
onCreate(db); onCreate(db);
} }
public static void onDelete(SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
onCreate(db);
}
} }
} }

View file

@ -1,36 +1,65 @@
package fr.free.nrw.commons.nearby; package fr.free.nrw.commons.nearby;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.location.LocationManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentTransaction; import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AlertDialog;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.Toast;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.location.LocationServiceManager; import fr.free.nrw.commons.location.LocationServiceManager;
import fr.free.nrw.commons.theme.BaseActivity; import fr.free.nrw.commons.theme.NavigationBaseActivity;
import fr.free.nrw.commons.utils.UriSerializer;
import timber.log.Timber;
public class NearbyActivity extends BaseActivity {
public class NearbyActivity extends NavigationBaseActivity {
@BindView(R.id.progressBar)
ProgressBar progressBar;
private boolean isMapViewActive = false;
private LocationServiceManager locationManager; private LocationServiceManager locationManager;
private LatLng curLatLang;
private static final String TAG = NearbyActivity.class.getName(); private Bundle bundle;
private NearbyAsyncTask nearbyAsyncTask;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_nearby); setContentView(R.layout.activity_nearby);
ButterKnife.bind(this);
if (getSupportActionBar() != null) { if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
} }
bundle = new Bundle();
locationManager = new LocationServiceManager(this); locationManager = new LocationServiceManager(this);
locationManager.registerLocationManager(); locationManager.registerLocationManager();
curLatLang = locationManager.getLatestLocation();
// Begin the transaction nearbyAsyncTask = new NearbyAsyncTask(this);
FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); nearbyAsyncTask.execute();
NearbyListFragment fragment = new NearbyListFragment(); initDrawer();
ft.add(R.id.container, fragment);
ft.commit();
} }
@Override @Override
@ -47,19 +76,87 @@ public class NearbyActivity extends BaseActivity {
case R.id.action_refresh: case R.id.action_refresh:
refreshView(); refreshView();
return true; return true;
case R.id.action_map:
showMapView();
if (isMapViewActive) {
item.setIcon(R.drawable.ic_list_white_24dp);
} else {
item.setIcon(R.drawable.ic_map_white_24dp);
}
return true;
default: default:
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
} }
protected void checkGps() {
LocationManager manager = (LocationManager) getSystemService(LOCATION_SERVICE);
if (!manager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
Timber.d("GPS is not enabled");
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
alertDialogBuilder.setMessage(R.string.gps_disabled)
.setCancelable(false)
.setPositiveButton(R.string.enable_gps,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
Intent callGPSSettingIntent = new Intent(
android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
Timber.d("Loaded settings page");
startActivityForResult(callGPSSettingIntent, 1);
}
});
alertDialogBuilder.setNegativeButton(R.string.menu_cancel_upload,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
AlertDialog alert = alertDialogBuilder.create();
alert.show();
} else {
Timber.d("GPS is enabled");
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1) {
Timber.d("User is back from Settings page");
refreshView();
}
}
private void showMapView() {
if (!isMapViewActive) {
isMapViewActive = true;
if (nearbyAsyncTask.getStatus() == AsyncTask.Status.FINISHED) {
setMapFragment();
}
} else {
isMapViewActive = false;
if (nearbyAsyncTask.getStatus() == AsyncTask.Status.FINISHED) {
setListFragment();
}
}
}
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
checkGps();
}
@Override
protected void onPause() {
super.onPause();
nearbyAsyncTask.cancel(true);
} }
protected void refreshView() { protected void refreshView() {
getSupportFragmentManager().beginTransaction() nearbyAsyncTask = new NearbyAsyncTask(this);
.replace(R.id.container, new NearbyListFragment()).commit(); nearbyAsyncTask.execute();
} }
public LocationServiceManager getLocationManager() { public LocationServiceManager getLocationManager() {
@ -71,4 +168,93 @@ public class NearbyActivity extends BaseActivity {
super.onDestroy(); super.onDestroy();
locationManager.unregisterLocationManager(); locationManager.unregisterLocationManager();
} }
private class NearbyAsyncTask extends AsyncTask<Void, Integer, List<Place>> {
private Context mContext;
private NearbyAsyncTask (Context context) {
mContext = context;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
}
@Override
protected List<Place> doInBackground(Void... params) {
return NearbyController
.loadAttractionsFromLocation(curLatLang, CommonsApplication.getInstance()
);
}
@Override
protected void onPostExecute(List<Place> placeList) {
super.onPostExecute(placeList);
if (isCancelled()) {
return;
}
Gson gson = new GsonBuilder()
.registerTypeAdapter(Uri.class, new UriSerializer())
.create();
String gsonPlaceList = gson.toJson(placeList);
String gsonCurLatLng = gson.toJson(curLatLang);
if (placeList.size() == 0) {
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(mContext, R.string.no_nearby, duration);
toast.show();
}
bundle.clear();
bundle.putString("PlaceList", gsonPlaceList);
bundle.putString("CurLatLng", gsonCurLatLng);
// Begin the transaction
if (isMapViewActive) {
setMapFragment();
} else {
setListFragment();
}
if (progressBar != null) {
progressBar.setVisibility(View.GONE);
}
}
}
/**
* Calls fragment for map view.
*/
public void setMapFragment() {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
Fragment fragment = new NearbyMapFragment();
fragment.setArguments(bundle);
fragmentTransaction.replace(R.id.container, fragment);
fragmentTransaction.commit();
}
/**
* Calls fragment for list view.
*/
public void setListFragment() {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
Fragment fragment = new NearbyListFragment();
fragment.setArguments(bundle);
fragmentTransaction.replace(R.id.container, fragment);
fragmentTransaction.commit();
}
public static void startYourself(Context context) {
Intent settingsIntent = new Intent(context, NearbyActivity.class);
context.startActivity(settingsIntent);
}
} }

View file

@ -1,39 +1,28 @@
package fr.free.nrw.commons.nearby; package fr.free.nrw.commons.nearby;
import android.content.Context; import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import timber.log.Timber;
import java.util.List;
public class NearbyAdapter extends ArrayAdapter<Place> { public class NearbyAdapter extends ArrayAdapter<Place> {
private List<Place> placesList;
private Context context;
public List<Place> getPlacesList() {
return placesList;
}
/** Accepts activity context and list of places. /** Accepts activity context and list of places.
* @param context activity context * @param context activity context
* @param places list of places
*/ */
public NearbyAdapter(Context context, List<Place> places) { public NearbyAdapter(Context context) {
super(context, R.layout.item_place, places); super(context, R.layout.item_place);
this.context = context;
placesList = places;
} }
@Override @Override
public View getView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, ViewGroup parent) {
// Get the data item for this position // Get the data item for this position
Place place = getItem(position); Place place = getItem(position);
Log.v("NearbyAdapter", "" + place); Timber.v(String.valueOf(place));
// Check if an existing view is being reused, otherwise inflate the view // Check if an existing view is being reused, otherwise inflate the view
if (convertView == null) { if (convertView == null) {
@ -42,13 +31,14 @@ public class NearbyAdapter extends ArrayAdapter<Place> {
} }
NearbyViewHolder viewHolder = new NearbyViewHolder(convertView); NearbyViewHolder viewHolder = new NearbyViewHolder(convertView);
viewHolder.bindModel(context, place); viewHolder.bindModel(getContext(), place);
// Return the completed view to render on screen // Return the completed view to render on screen
return convertView; return convertView;
} }
@Override @Override
public long getItemId(int position) { public long getItemId(int position) {
// TODO: use Wikidata Q-ID instead?
return position; return position;
} }
} }

View file

@ -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];
}
};
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -1,46 +1,35 @@
package fr.free.nrw.commons.nearby; package fr.free.nrw.commons.nearby;
import static fr.free.nrw.commons.utils.LengthUtils.computeDistanceBetween;
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.ListFragment; import android.support.v4.app.ListFragment;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ListView; import android.widget.ListView;
import android.widget.ProgressBar;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.List;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import butterknife.OnItemClick; import butterknife.OnItemClick;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.location.LatLng; import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.utils.UriDeserializer;
import timber.log.Timber;
import java.util.Collections; public class NearbyListFragment extends ListFragment {
import java.util.Comparator; private List<Place> placeList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class NearbyListFragment extends ListFragment implements TaskListener { @BindView(R.id.listView) ListView listview;
private static final int MAX_RESULTS = 1000;
private NearbyAsyncTask nearbyAsyncTask;
@BindView(R.id.listview) ListView listview; private NearbyAdapter adapter;
@BindView(R.id.progressBar) ProgressBar progressBar;
private boolean isTaskRunning = false;
private static final String TAG = NearbyListFragment.class.getName();
public NearbyListFragment() { public NearbyListFragment() {
} }
@ -55,118 +44,43 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
Log.d(TAG, "NearbyListFragment created"); Timber.d("NearbyListFragment created");
View view = inflater.inflate(R.layout.fragment_nearby, container, false); View view = inflater.inflate(R.layout.fragment_nearby, container, false);
ButterKnife.bind(this, view); ButterKnife.bind(this, view);
adapter = new NearbyAdapter(getActivity());
listview.setAdapter(adapter);
return view; return view;
} }
@Override @Override
public void onViewCreated(View view, Bundle savedInstanceState) { public void onViewCreated(View view, Bundle savedInstanceState) {
//Check that this is the first time view is created, to avoid double list when screen orientation changed // Check that this is the first time view is created,
// to avoid double list when screen orientation changed
Bundle bundle = this.getArguments();
Gson gson = new GsonBuilder()
.registerTypeAdapter(Uri.class, new UriDeserializer())
.create();
if (bundle != null) {
String gsonPlaceList = bundle.getString("PlaceList");
String gsonLatLng = bundle.getString("CurLatLng");
Type listType = new TypeToken<List<Place>>() {}.getType();
placeList = gson.fromJson(gsonPlaceList, listType);
Type curLatLngType = new TypeToken<LatLng>() {}.getType();
LatLng curLatLng = gson.fromJson(gsonLatLng, curLatLngType);
placeList = NearbyController.loadAttractionsFromLocationToPlaces(curLatLng, placeList);
}
if (savedInstanceState == null) { if (savedInstanceState == null) {
nearbyAsyncTask = new NearbyAsyncTask(this); adapter.clear();
nearbyAsyncTask.execute(); Timber.d("Saved instance state is null, populating ListView");
progressBar.setVisibility(View.VISIBLE);
Log.d(TAG, "Saved instance state is null, populating ListView");
} else {
progressBar.setVisibility(View.GONE);
} }
// If we are returning here from a screen orientation and the AsyncTask is still working, adapter.clear();
// re-create and display the progress dialog. adapter.addAll(placeList);
if (isTaskRunning) { adapter.notifyDataSetChanged();
progressBar.setVisibility(View.VISIBLE);
}
} }
@Override @OnItemClick(R.id.listView)
public void onSaveInstanceState(Bundle outInstanceState) {
// See http://stackoverflow.com/questions/8942135/listview-added-dublicate-item-in-list-when-screen-orientation-changes
outInstanceState.putInt("value", 1);
}
@Override
public void onTaskStarted() {
isTaskRunning = true;
progressBar.setVisibility(View.VISIBLE);
}
@Override
public void onTaskFinished(List<Place> result) {
if (progressBar != null) {
progressBar.setVisibility(View.GONE);
}
isTaskRunning = false;
}
@Override
public void onDetach() {
// All dialogs should be closed before leaving the activity in order to avoid
// the: Activity has leaked window com.android.internal.policy... exception
if (progressBar != null && progressBar.isShown()) {
progressBar.setVisibility(View.GONE);
}
super.onDetach();
}
@Override
public void onDestroy() {
super.onDestroy();
// See http://stackoverflow.com/questions/18264408/incomplete-asynctask-crashes-my-app
if (nearbyAsyncTask != null && nearbyAsyncTask.getStatus() != AsyncTask.Status.FINISHED) {
nearbyAsyncTask.cancel(true);
}
}
private class NearbyAsyncTask extends AsyncTask<Void, Integer, List<Place>> {
private final TaskListener listener;
public NearbyAsyncTask(TaskListener listener) {
this.listener = listener;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
listener.onTaskStarted();
}
@Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
progressBar.setProgress(values[0]);
}
@Override
protected List<Place> doInBackground(Void... params) {
return loadAttractionsFromLocation(
((NearbyActivity)getActivity()).getLocationManager().getLatestLocation()
);
}
@Override
protected void onPostExecute(List<Place> places) {
super.onPostExecute(places);
if (isCancelled()) {
return;
}
progressBar.setVisibility(View.GONE);
NearbyAdapter adapter = new NearbyAdapter(getActivity(), places);
listview.setAdapter(adapter);
listener.onTaskFinished(places);
adapter.notifyDataSetChanged();
}
}
@OnItemClick(R.id.listview)
void onItemClicked(int position) { void onItemClicked(int position) {
Place place = (Place) listview.getItemAtPosition(position); Place place = (Place) listview.getItemAtPosition(position);
LatLng placeLatLng = place.location; LatLng placeLatLng = place.location;
@ -174,53 +88,8 @@ public class NearbyListFragment extends ListFragment implements TaskListener {
double latitude = placeLatLng.latitude; double latitude = placeLatLng.latitude;
double longitude = placeLatLng.longitude; double longitude = placeLatLng.longitude;
Log.d(TAG, "Item at position " Timber.d("Item at position %d has coords: Lat: %f Long: %f", position, latitude, longitude);
+ position + " has coords: Lat: "
+ latitude + " Long: "
+ longitude);
//Open map app at given position NearbyInfoDialog.showYourself(getActivity(), place);
Uri gmmIntentUri = Uri.parse("geo:0,0?q=" + latitude + "," + longitude);
Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri);
if (mapIntent.resolveActivity(getActivity().getPackageManager()) != null) {
startActivity(mapIntent);
}
}
private List<Place> loadAttractionsFromLocation(LatLng curLatLng) {
Log.d(TAG, "Loading attractions near " + curLatLng);
if (curLatLng == null) {
return Collections.emptyList();
}
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
List<Place> places = prefs.getBoolean("useWikidata", true)
? NearbyPlaces.getInstance().getFromWikidataQuery(
curLatLng, Locale.getDefault().getLanguage())
: NearbyPlaces.getInstance().getFromWikiNeedsPictures();
if (curLatLng != null) {
Log.d(TAG, "Sorting places by distance...");
final Map<Place, Double> distances = new HashMap<>();
for (Place place: places) {
distances.put(place, computeDistanceBetween(place.location, curLatLng));
}
Collections.sort(places,
new Comparator<Place>() {
@Override
public int compare(Place lhs, Place rhs) {
double lhsDistance = distances.get(lhs);
double rhsDistance = distances.get(rhs);
return (int) (lhsDistance - rhsDistance);
}
}
);
}
places = places.subList(0, Math.min(places.size(), MAX_RESULTS));
for (Place place: places) {
String distance = formatDistanceBetween(curLatLng, place.location);
place.setDistance(distance);
}
return places;
} }
} }

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -2,17 +2,12 @@ package fr.free.nrw.commons.nearby;
import android.net.Uri; import android.net.Uri;
import android.os.StrictMode; import android.os.StrictMode;
import android.util.Log;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.location.LatLng;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -20,58 +15,31 @@ import java.util.Locale;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.utils.FileUtils;
import timber.log.Timber;
public class NearbyPlaces { public class NearbyPlaces {
private static final String TAG = NearbyPlaces.class.getName();
private static final int MIN_RESULTS = 40; private static final int MIN_RESULTS = 40;
private static final double INITIAL_RADIUS = 1.0; private static final double INITIAL_RADIUS = 1.0; // in kilometers
private static final double MAX_RADIUS = 300.0; private static final double MAX_RADIUS = 300.0; // in kilometers
private static final double RADIUS_MULTIPLIER = 1.618; private static final double RADIUS_MULTIPLIER = 1.618;
private static final String WIKIDATA_QUERY_URL = "https://query.wikidata.org/sparql?query=${QUERY}"; private static final Uri WIKIDATA_QUERY_URL = Uri.parse("https://query.wikidata.org/sparql");
private static final String WIKIDATA_QUERY_TEMPLATE = "SELECT\n" + private static final Uri WIKIDATA_QUERY_UI_URL = Uri.parse("https://query.wikidata.org/");
" (SAMPLE(?location) as ?location)\n" + private final String wikidataQuery;
" ?item\n" +
" (SAMPLE(COALESCE(?item_label_preferred_language, ?item_label_any_language)) as ?label)\n" +
" (SAMPLE(?classId) as ?class)\n" +
" (SAMPLE(COALESCE(?class_label_preferred_language, ?class_label_any_language, \"?\")) as ?class_label)\n" +
" (SAMPLE(COALESCE(?icon0, ?icon1)) as ?icon)\n" +
" (SAMPLE(COALESCE(?emoji0, ?emoji1)) as ?emoji)\n" +
"WHERE {\n" +
" # Around given location...\n" +
" SERVICE wikibase:around {\n" +
" ?item wdt:P625 ?location.\n" +
" bd:serviceParam wikibase:center \"Point(${LONG} ${LAT})\"^^geo:wktLiteral. \n" +
" bd:serviceParam wikibase:radius \"${RADIUS}\" . # Radius in kilometers.\n" +
" }\n" +
" \n" +
" # ... and without an image.\n" +
" MINUS {?item wdt:P18 []}\n" +
" \n" +
" # Get the label in the preferred language of the user, or any other language if no label is available in that language.\n" +
" OPTIONAL {?item rdfs:label ?item_label_preferred_language. FILTER (lang(?item_label_preferred_language) = \"${LANG}\")}\n" +
" OPTIONAL {?item rdfs:label ?item_label_any_language}\n" +
" \n" +
" # Get the class label in the preferred language of the user, or any other language if no label is available in that language.\n" +
" OPTIONAL {\n" +
" ?item p:P31/ps:P31 ?classId.\n" +
" OPTIONAL {?classId rdfs:label ?class_label_preferred_language. FILTER (lang(?class_label_preferred_language) = \"${LANG}\")}\n" +
" OPTIONAL {?classId rdfs:label ?class_label_any_language}\n" +
"\n" +
" # Get icon\n" +
" OPTIONAL { ?classId wdt:P2910 ?icon0. }\n" +
" OPTIONAL { ?classId wdt:P279*/wdt:P2910 ?icon1. }\n" +
" # Get emoji\n" +
" OPTIONAL { ?classId wdt:P487 ?emoji0. }\n" +
" OPTIONAL { ?classId wdt:P279*/wdt:P487 ?emoji1. }\n" +
" }\n" +
"}\n" +
"GROUP BY ?item\n";
private static NearbyPlaces singleton;
private double radius = INITIAL_RADIUS; private double radius = INITIAL_RADIUS;
private List<Place> places; private List<Place> places;
private NearbyPlaces(){ public NearbyPlaces() {
try {
String query = FileUtils.readFromResource("/assets/queries/nearby_query.rq");
wikidataQuery = query;
Timber.v(wikidataQuery);
} catch (IOException e) {
throw new RuntimeException(e);
}
} }
List<Place> getFromWikidataQuery(LatLng curLatLng, String lang) { List<Place> getFromWikidataQuery(LatLng curLatLng, String lang) {
@ -81,7 +49,7 @@ public class NearbyPlaces {
// increase the radius gradually to find a satisfactory number of nearby places // increase the radius gradually to find a satisfactory number of nearby places
while (radius < MAX_RADIUS) { while (radius < MAX_RADIUS) {
places = getFromWikidataQuery(curLatLng, lang, radius); places = getFromWikidataQuery(curLatLng, lang, radius);
Log.d(TAG, places.size() + " results at radius: " + radius); Timber.d("%d results at radius: %f", places.size(), radius);
if (places.size() >= MIN_RESULTS) { if (places.size() >= MIN_RESULTS) {
break; break;
} else { } else {
@ -89,33 +57,41 @@ public class NearbyPlaces {
} }
} }
} catch (IOException e) { } catch (IOException e) {
Log.d(TAG, "" + e.toString()); Timber.d(e.toString());
// errors tend to be caused by too many results (and time out) // errors tend to be caused by too many results (and time out)
// try a small radius next time // try a small radius next time
Log.d(TAG, "back to initial radius: " + radius); Timber.d("back to initial radius: %f", radius);
radius = INITIAL_RADIUS; radius = INITIAL_RADIUS;
} }
return places; return places;
} }
private List<Place> getFromWikidataQuery(LatLng cur, String lang, double radius) private List<Place> getFromWikidataQuery(LatLng cur,
String lang,
double radius)
throws IOException { throws IOException {
List<Place> places = new ArrayList<>(); List<Place> places = new ArrayList<>();
String query = WIKIDATA_QUERY_TEMPLATE.replace("${RADIUS}", "" + radius)
.replace("${LAT}", "" + String.format(Locale.ROOT, "%.3f", cur.latitude)) String query = wikidataQuery
.replace("${LONG}", "" + String.format(Locale.ROOT, "%.3f", cur.longitude)) .replace("${RAD}", String.format(Locale.ROOT, "%.2f", radius))
.replace("${LANG}", "" + lang); .replace("${LAT}", String.format(Locale.ROOT, "%.4f", cur.latitude))
query = URLEncoder.encode(query, "utf-8").replace("+", "%20"); .replace("${LONG}", String.format(Locale.ROOT, "%.4f", cur.longitude))
String url = WIKIDATA_QUERY_URL.replace("${QUERY}", query); .replace("${LANG}", lang);
Log.d(TAG, url);
Timber.v("# Wikidata query: \n" + query);
// format as a URL
Timber.d(WIKIDATA_QUERY_UI_URL.buildUpon().fragment(query).build().toString());
String url = WIKIDATA_QUERY_URL.buildUpon()
.appendQueryParameter("query", query).build().toString();
URLConnection conn = new URL(url).openConnection(); URLConnection conn = new URL(url).openConnection();
conn.setRequestProperty("Accept", "text/tab-separated-values"); conn.setRequestProperty("Accept", "text/tab-separated-values");
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line; String line;
Log.d(TAG, "Reading from query result..."); Timber.d("Reading from query result...");
while ((line = in.readLine()) != null) { while ((line = in.readLine()) != null) {
Log.v(TAG, line); Timber.v(line);
line = line + "\n"; // to pad columns and make fields a fixed size line = line + "\n"; // to pad columns and make fields a fixed size
if (!line.startsWith("\"Point")) { if (!line.startsWith("\"Point")) {
continue; continue;
@ -125,6 +101,9 @@ public class NearbyPlaces {
String point = fields[0]; String point = fields[0];
String name = Utils.stripLocalizedString(fields[2]); String name = Utils.stripLocalizedString(fields[2]);
String type = Utils.stripLocalizedString(fields[4]); String type = Utils.stripLocalizedString(fields[4]);
String wikipediaSitelink = Utils.stripLocalizedString(fields[7]);
String commonsSitelink = Utils.stripLocalizedString(fields[8]);
String wikiDataLink = Utils.stripLocalizedString(fields[1]);
String icon = fields[5]; String icon = fields[5];
double latitude = 0; double latitude = 0;
@ -146,7 +125,12 @@ public class NearbyPlaces {
type, // list type, // list
type, // details type, // details
Uri.parse(icon), Uri.parse(icon),
new LatLng(latitude, longitude) new LatLng(latitude, longitude),
new Sitelinks.Builder()
.setWikipediaLink(wikipediaSitelink)
.setCommonsLink(commonsSitelink)
.setWikidataLink(wikiDataLink)
.build()
)); ));
} }
in.close(); in.close();
@ -170,7 +154,7 @@ public class NearbyPlaces {
boolean firstLine = true; boolean firstLine = true;
String line; String line;
Log.d(TAG, "Reading from CSV file..."); Timber.d("Reading from CSV file...");
while ((line = in.readLine()) != null) { while ((line = in.readLine()) != null) {
@ -203,28 +187,16 @@ public class NearbyPlaces {
type, // list type, // list
type, // details type, // details
null, null,
new LatLng(latitude, longitude) new LatLng(latitude, longitude),
new Sitelinks.Builder().build()
)); ));
} }
in.close(); in.close();
} catch (IOException e) { } catch (IOException e) {
Log.d(TAG, e.toString()); Timber.d(e.toString());
} }
} }
return places; return places;
} }
/**
* Get the singleton instance of this class.
* The instance is created upon the first invocation of this method, and then reused.
*
* @return The singleton instance
*/
public static synchronized NearbyPlaces getInstance() {
if (singleton == null) {
singleton = new NearbyPlaces();
}
return singleton;
}
} }

View file

@ -25,9 +25,12 @@ public class NearbyViewHolder implements ViewHolder<Place> {
public void bindModel(Context context, Place place) { public void bindModel(Context context, Place place) {
// Populate the data into the template view using the data object // Populate the data into the template view using the data object
tvName.setText(place.name); tvName.setText(place.name);
tvDesc.setText(place.description); String description = place.description;
if ( description == null || description.isEmpty() || description.equals("?")) {
description = "No Description Found";
}
tvDesc.setText(description);
distance.setText(place.distance); distance.setText(place.distance);
icon.setImageResource(ResourceUtils.getDescriptionIcon(place.description)); icon.setImageResource(ResourceUtils.getDescriptionIcon(place.description));
} }
} }

View file

@ -16,14 +16,17 @@ public class Place {
public Bitmap image; public Bitmap image;
public Bitmap secondaryImage; public Bitmap secondaryImage;
public String distance; public String distance;
public Sitelinks siteLinks;
public Place(String name, String description, String longDescription, public Place(String name, String description, String longDescription,
Uri secondaryImageUrl, LatLng location) { Uri secondaryImageUrl, LatLng location, Sitelinks siteLinks) {
this.name = name; this.name = name;
this.description = description; this.description = description;
this.longDescription = longDescription; this.longDescription = longDescription;
this.secondaryImageUrl = secondaryImageUrl; this.secondaryImageUrl = secondaryImageUrl;
this.location = location; this.location = location;
this.siteLinks = siteLinks;
} }
public void setDistance(String distance) { public void setDistance(String distance) {

View 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);
}
}
}

View file

@ -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);
}

View file

@ -5,6 +5,8 @@ public class Prefs {
public static String TRACKING_ENABLED = "eventLogging"; public static String TRACKING_ENABLED = "eventLogging";
public static final String DEFAULT_LICENSE = "defaultLicense"; public static final String DEFAULT_LICENSE = "defaultLicense";
public static final String UPLOADS_SHOWING = "uploadsshowing";
public static final String IS_CONTRIBUTION_COUNT_CHANGED = "ccontributionCountChanged";
public static class Licenses { public static class Licenses {
public static final String CC_BY_SA_3 = "CC BY-SA 3.0"; public static final String CC_BY_SA_3 = "CC BY-SA 3.0";

View file

@ -1,13 +1,19 @@
package fr.free.nrw.commons.settings; package fr.free.nrw.commons.settings;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatDelegate; import android.support.v7.app.AppCompatDelegate;
import android.view.MenuItem;
import butterknife.ButterKnife;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.theme.NavigationBaseActivity;
public class SettingsActivity extends NavigationBaseActivity {
private SettingsFragment settingsFragment;
public class SettingsActivity extends PreferenceActivity {
private AppCompatDelegate settingsDelegate; private AppCompatDelegate settingsDelegate;
@Override @Override
@ -19,11 +25,13 @@ public class SettingsActivity extends PreferenceActivity {
setTheme(R.style.LightAppTheme); setTheme(R.style.LightAppTheme);
} }
// Display the fragment as the main content. settingsFragment = (SettingsFragment) getFragmentManager().findFragmentById(R.id.settingsFragment);
getFragmentManager().beginTransaction()
.replace(android.R.id.content, new SettingsFragment()).commit();
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
ButterKnife.bind(this);
initDrawer();
} }
// Get an action bar // Get an action bar
@ -34,5 +42,25 @@ public class SettingsActivity extends PreferenceActivity {
settingsDelegate = AppCompatDelegate.create(this, null); settingsDelegate = AppCompatDelegate.create(this, null);
} }
settingsDelegate.onPostCreate(savedInstanceState); settingsDelegate.onPostCreate(savedInstanceState);
//Get an up button
//settingsDelegate.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
//Handle action-bar clicks
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
public static void startYourself(Context context) {
Intent settingsIntent = new Intent(context, SettingsActivity.class);
context.startActivity(settingsIntent);
} }
} }

View file

@ -1,11 +1,17 @@
package fr.free.nrw.commons.settings; package fr.free.nrw.commons.settings;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.preference.CheckBoxPreference; import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.ListPreference; import android.preference.ListPreference;
import android.preference.Preference; import android.preference.Preference;
import android.preference.PreferenceFragment; import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.Utils;
@ -19,23 +25,9 @@ public class SettingsFragment extends PreferenceFragment {
// Update spinner to show selected value as summary // Update spinner to show selected value as summary
ListPreference licensePreference = (ListPreference) findPreference(Prefs.DEFAULT_LICENSE); ListPreference licensePreference = (ListPreference) findPreference(Prefs.DEFAULT_LICENSE);
// WARNING: ORDERING NEEDS TO MATCH FOR THE LICENSE NAMES AND DISPLAY VALUES
licensePreference.setEntries(new String[]{
getString(R.string.license_name_cc0),
getString(R.string.license_name_cc_by_3_0),
getString(R.string.license_name_cc_by_4_0),
getString(R.string.license_name_cc_by_sa_3_0),
getString(R.string.license_name_cc_by_sa_4_0)
});
licensePreference.setEntryValues(new String[]{
Prefs.Licenses.CC0,
Prefs.Licenses.CC_BY_3,
Prefs.Licenses.CC_BY_4,
Prefs.Licenses.CC_BY_SA_3,
Prefs.Licenses.CC_BY_SA_4
});
licensePreference.setSummary(getString(Utils.licenseNameFor(licensePreference.getValue()))); licensePreference.setSummary(getString(Utils.licenseNameFor(licensePreference.getValue())));
// Keep summary updated when changing value
licensePreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { licensePreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override @Override
public boolean onPreferenceChange(Preference preference, Object newValue) { public boolean onPreferenceChange(Preference preference, Object newValue) {
@ -52,5 +44,45 @@ public class SettingsFragment extends PreferenceFragment {
return true; return true;
} }
}); });
final EditTextPreference uploadLimit = (EditTextPreference) findPreference("uploads");
final SharedPreferences sharedPref = PreferenceManager
.getDefaultSharedPreferences(CommonsApplication.getInstance());
int uploads = sharedPref.getInt(Prefs.UPLOADS_SHOWING, 100);
uploadLimit.setText(uploads + "");
uploadLimit.setSummary(uploads + "");
uploadLimit.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
int value = Integer.parseInt(newValue.toString());
final SharedPreferences.Editor editor = sharedPref.edit();
if (value > 500) {
new AlertDialog.Builder(getActivity())
.setTitle(R.string.maximum_limit)
.setMessage(R.string.maximum_limit_alert)
.setPositiveButton(android.R.string.yes,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
}
})
.setIcon(android.R.drawable.ic_dialog_alert)
.show();
editor.putInt(Prefs.UPLOADS_SHOWING, 500);
editor.putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED,true);
uploadLimit.setSummary(500 + "");
uploadLimit.setText(500 + "");
} else {
editor.putInt(Prefs.UPLOADS_SHOWING, Integer.parseInt(newValue.toString()));
editor.putBoolean(Prefs.IS_CONTRIBUTION_COUNT_CHANGED,true);
uploadLimit.setSummary(newValue.toString());
}
editor.apply();
return true;
}
});
} }
} }

View file

@ -6,16 +6,18 @@ import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
public class BaseActivity extends AppCompatActivity { public class BaseActivity extends AppCompatActivity {
boolean currentTheme; boolean currentTheme;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("theme",true)) { if(Utils.isDarkTheme(this)){
currentTheme = true; currentTheme = true;
setTheme(R.style.DarkAppTheme); setTheme(R.style.DarkAppTheme);
}else { } else {
currentTheme = false; currentTheme = false;
setTheme(R.style.LightAppTheme); // default setTheme(R.style.LightAppTheme); // default
} }

View file

@ -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);
}
}

View file

@ -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()));
}
}

View file

@ -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;
}
}

View file

@ -4,19 +4,18 @@ import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.util.Log; import android.support.v7.app.AlertDialog;
import fr.free.nrw.commons.MWApi;
import org.mediawiki.api.ApiResult; import org.mediawiki.api.ApiResult;
import org.mediawiki.api.MWApi;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import android.support.v7.app.AlertDialog;
import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.ContributionsActivity; import fr.free.nrw.commons.contributions.ContributionsActivity;
import timber.log.Timber;
/** /**
* Sends asynchronous queries to the Commons MediaWiki API to check that file doesn't already exist * Sends asynchronous queries to the Commons MediaWiki API to check that file doesn't already exist
@ -24,8 +23,6 @@ import fr.free.nrw.commons.contributions.ContributionsActivity;
*/ */
public class ExistingFileAsync extends AsyncTask<Void, Void, Boolean> { public class ExistingFileAsync extends AsyncTask<Void, Void, Boolean> {
private static final String TAG = ExistingFileAsync.class.getName();
private String fileSHA1; private String fileSHA1;
private Context context; private Context context;
@ -41,7 +38,7 @@ public class ExistingFileAsync extends AsyncTask<Void, Void, Boolean> {
@Override @Override
protected Boolean doInBackground(Void... voids) { protected Boolean doInBackground(Void... voids) {
MWApi api = CommonsApplication.createMWApi(); MWApi api = CommonsApplication.getInstance().getMWApi();
ApiResult result; ApiResult result;
// https://commons.wikimedia.org/w/api.php?action=query&list=allimages&format=xml&aisha1=801957214aba50cb63bb6eb1b0effa50188900ba // https://commons.wikimedia.org/w/api.php?action=query&list=allimages&format=xml&aisha1=801957214aba50cb63bb6eb1b0effa50188900ba
@ -51,14 +48,14 @@ public class ExistingFileAsync extends AsyncTask<Void, Void, Boolean> {
.param("list", "allimages") .param("list", "allimages")
.param("aisha1", fileSHA1) .param("aisha1", fileSHA1)
.get(); .get();
Log.d(TAG, "Searching Commons API for existing file: " + result.toString()); Timber.d("Searching Commons API for existing file: %s", result);
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "IO Exception: ", e); Timber.e(e, "IO Exception: ");
return false; return false;
} }
ArrayList<ApiResult> resultNodes = result.getNodes("/api/query/allimages/img"); ArrayList<ApiResult> resultNodes = result.getNodes("/api/query/allimages/img");
Log.d(TAG, "Result nodes: " + resultNodes); Timber.d("Result nodes: %s", resultNodes);
boolean fileExists; boolean fileExists;
if (!resultNodes.isEmpty()) { if (!resultNodes.isEmpty()) {
@ -67,7 +64,7 @@ public class ExistingFileAsync extends AsyncTask<Void, Void, Boolean> {
fileExists = false; fileExists = false;
} }
Log.d(TAG, "File already exists in Commons:" + fileExists); Timber.d("File already exists in Commons: %s", fileExists);
return fileExists; return fileExists;
} }

View file

@ -1,5 +1,6 @@
package fr.free.nrw.commons.upload; package fr.free.nrw.commons.upload;
import android.annotation.SuppressLint;
import android.content.ContentUris; import android.content.ContentUris;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
@ -20,6 +21,8 @@ public class FileUtils {
* @param uri The Uri to query. * @param uri The Uri to query.
* @author paulburke * @author paulburke
*/ */
// Can be safely suppressed, checks for isKitKat before running isDocumentUri
@SuppressLint("NewApi")
public static String getPath(final Context context, final Uri uri) { public static String getPath(final Context context, final Uri uri) {
final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;

View file

@ -10,10 +10,11 @@ import android.media.ExifInterface;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.util.Log;
import java.io.IOException; import java.io.IOException;
import timber.log.Timber;
/** /**
* Extracts geolocation to be passed to API for category suggestions. If a picture with geolocation * Extracts geolocation to be passed to API for category suggestions. If a picture with geolocation
* is uploaded, extract latitude and longitude from EXIF data of image. If a picture without * is uploaded, extract latitude and longitude from EXIF data of image. If a picture without
@ -21,8 +22,6 @@ import java.io.IOException;
*/ */
public class GPSExtractor { public class GPSExtractor {
private static final String TAG = GPSExtractor.class.getName();
private String filePath; private String filePath;
private double decLatitude, decLongitude; private double decLatitude, decLongitude;
private Double currentLatitude = null; private Double currentLatitude = null;
@ -31,8 +30,7 @@ public class GPSExtractor {
public boolean imageCoordsExists; public boolean imageCoordsExists;
private MyLocationListener myLocationListener; private MyLocationListener myLocationListener;
private LocationManager locationManager; private LocationManager locationManager;
private String provider;
private Criteria criteria;
public GPSExtractor(String filePath, Context context){ public GPSExtractor(String filePath, Context context){
this.filePath = filePath; this.filePath = filePath;
@ -46,7 +44,7 @@ public class GPSExtractor {
private boolean gpsPreferenceEnabled() { private boolean gpsPreferenceEnabled() {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
boolean gpsPref = sharedPref.getBoolean("allowGps", false); boolean gpsPref = sharedPref.getBoolean("allowGps", false);
Log.d(TAG, "Gps pref set to: " + gpsPref); Timber.d("Gps pref set to: %b", gpsPref);
return gpsPref; return gpsPref;
} }
@ -55,8 +53,8 @@ public class GPSExtractor {
*/ */
protected void registerLocationManager() { protected void registerLocationManager() {
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
criteria = new Criteria(); Criteria criteria = new Criteria();
provider = locationManager.getBestProvider(criteria, true); String provider = locationManager.getBestProvider(criteria, true);
myLocationListener = new MyLocationListener(); myLocationListener = new MyLocationListener();
try { try {
@ -66,9 +64,9 @@ public class GPSExtractor {
myLocationListener.onLocationChanged(location); myLocationListener.onLocationChanged(location);
} }
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
Log.e(TAG, "Illegal argument exception", e); Timber.e(e, "Illegal argument exception");
} catch (SecurityException e) { } catch (SecurityException e) {
Log.e(TAG, "Security exception", e); Timber.e(e, "Security exception");
} }
} }
@ -76,7 +74,7 @@ public class GPSExtractor {
try { try {
locationManager.removeUpdates(myLocationListener); locationManager.removeUpdates(myLocationListener);
} catch (SecurityException e) { } catch (SecurityException e) {
Log.e(TAG, "Security exception", e); Timber.e(e, "Security exception");
} }
} }
@ -98,10 +96,10 @@ public class GPSExtractor {
try { try {
exif = new ExifInterface(filePath); exif = new ExifInterface(filePath);
} catch (IOException e) { } catch (IOException e) {
Log.w(TAG, e); Timber.w(e);
return null; return null;
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
Log.w(TAG, e); Timber.w(e);
return null; return null;
} }
@ -110,14 +108,15 @@ public class GPSExtractor {
registerLocationManager(); registerLocationManager();
imageCoordsExists = false; imageCoordsExists = false;
Log.d(TAG, "EXIF data has no location info"); Timber.d("EXIF data has no location info");
//Check what user's preference is for automatic location detection //Check what user's preference is for automatic location detection
boolean gpsPrefEnabled = gpsPreferenceEnabled(); boolean gpsPrefEnabled = gpsPreferenceEnabled();
//Check that currentLatitude and currentLongitude have been explicitly set by MyLocationListener and do not default to (0.0,0.0) //Check that currentLatitude and currentLongitude have been explicitly set by MyLocationListener and do not default to (0.0,0.0)
if (gpsPrefEnabled && currentLatitude != null && currentLongitude != null) { if (gpsPrefEnabled && currentLatitude != null && currentLongitude != null) {
Log.d(TAG, "Current location values: Lat = " + currentLatitude + " Long = " + currentLongitude); Timber.d("Current location values: Lat = %f Long = %f",
currentLatitude, currentLongitude);
return String.valueOf(currentLatitude) + "|" + String.valueOf(currentLongitude); return String.valueOf(currentLatitude) + "|" + String.valueOf(currentLongitude);
} else { } else {
// No coords found // No coords found
@ -128,7 +127,7 @@ public class GPSExtractor {
} else { } else {
//If image has EXIF data, extract image coords //If image has EXIF data, extract image coords
imageCoordsExists = true; imageCoordsExists = true;
Log.d(TAG, "EXIF data has location info"); Timber.d("EXIF data has location info");
latitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE); latitude = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE);
latitude_ref = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF); latitude_ref = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF);
@ -136,8 +135,8 @@ public class GPSExtractor {
longitude_ref = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF); longitude_ref = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF);
if (latitude!=null && latitude_ref!=null && longitude!=null && longitude_ref!=null) { if (latitude!=null && latitude_ref!=null && longitude!=null && longitude_ref!=null) {
Log.d(TAG, "Latitude: " + latitude + " " + latitude_ref); Timber.d("Latitude: %s %s", latitude, latitude_ref);
Log.d(TAG, "Longitude: " + longitude + " " + longitude_ref); Timber.d("Longitude: %s %s", longitude, longitude_ref);
decimalCoords = getDecimalCoords(latitude, latitude_ref, longitude, longitude_ref); decimalCoords = getDecimalCoords(latitude, latitude_ref, longitude, longitude_ref);
return decimalCoords; return decimalCoords;
@ -160,17 +159,17 @@ public class GPSExtractor {
@Override @Override
public void onStatusChanged(String provider, int status, Bundle extras) { public void onStatusChanged(String provider, int status, Bundle extras) {
Log.d(TAG, provider + "'s status changed to " + status); Timber.d("%s's status changed to %d", provider, status);
} }
@Override @Override
public void onProviderEnabled(String provider) { public void onProviderEnabled(String provider) {
Log.d(TAG, "Provider " + provider + " enabled"); Timber.d("Provider %s enabled", provider);
} }
@Override @Override
public void onProviderDisabled(String provider) { public void onProviderDisabled(String provider) {
Log.d(TAG, "Provider " + provider + " disabled"); Timber.d("Provider %s disabled", provider);
} }
} }
@ -201,7 +200,7 @@ public class GPSExtractor {
} }
String decimalCoords = String.valueOf(decLatitude) + "|" + String.valueOf(decLongitude); String decimalCoords = String.valueOf(decLatitude) + "|" + String.valueOf(decLongitude);
Log.d(TAG, "Latitude and Longitude are " + decimalCoords); Timber.d("Latitude and Longitude are %s", decimalCoords);
return decimalCoords; return decimalCoords;
} }

View file

@ -14,13 +14,14 @@ import android.os.Bundle;
import android.support.v4.app.ActivityCompat; import android.support.v4.app.ActivityCompat;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.Toast; import android.widget.Toast;
import butterknife.ButterKnife;
import java.util.ArrayList; import java.util.ArrayList;
import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.CommonsApplication;
@ -28,7 +29,6 @@ import fr.free.nrw.commons.EventLog;
import fr.free.nrw.commons.Media; import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.AuthenticatedActivity; import fr.free.nrw.commons.auth.AuthenticatedActivity;
import fr.free.nrw.commons.auth.WikiAccountAuthenticator;
import fr.free.nrw.commons.category.CategorizationFragment; import fr.free.nrw.commons.category.CategorizationFragment;
import fr.free.nrw.commons.contributions.Contribution; import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.media.MediaDetailPagerFragment; import fr.free.nrw.commons.media.MediaDetailPagerFragment;
@ -36,6 +36,7 @@ import fr.free.nrw.commons.modifications.CategoryModifier;
import fr.free.nrw.commons.modifications.ModificationsContentProvider; import fr.free.nrw.commons.modifications.ModificationsContentProvider;
import fr.free.nrw.commons.modifications.ModifierSequence; import fr.free.nrw.commons.modifications.ModifierSequence;
import fr.free.nrw.commons.modifications.TemplateRemoveModifier; import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
import timber.log.Timber;
public class MultipleShareActivity public class MultipleShareActivity
extends AuthenticatedActivity extends AuthenticatedActivity
@ -53,10 +54,6 @@ public class MultipleShareActivity
private UploadController uploadController; private UploadController uploadController;
public MultipleShareActivity() {
super(WikiAccountAuthenticator.COMMONS_ACCOUNT_TYPE);
}
@Override @Override
public Media getMediaAtPosition(int i) { public Media getMediaAtPosition(int i) {
return photosList.get(i); return photosList.get(i);
@ -116,7 +113,7 @@ public class MultipleShareActivity
private void multipleUploadBegins() { private void multipleUploadBegins() {
Log.d("MultipleShareActivity", "Multiple upload begins"); Timber.d("Multiple upload begins");
final ProgressDialog dialog = new ProgressDialog(MultipleShareActivity.this); final ProgressDialog dialog = new ProgressDialog(MultipleShareActivity.this);
dialog.setIndeterminate(false); dialog.setIndeterminate(false);
@ -135,7 +132,11 @@ public class MultipleShareActivity
dialog.setProgress(uploadCount); dialog.setProgress(uploadCount);
if(uploadCount == photosList.size()) { if(uploadCount == photosList.size()) {
dialog.dismiss(); dialog.dismiss();
Toast startingToast = Toast.makeText(getApplicationContext(), R.string.uploading_started, Toast.LENGTH_LONG); Toast startingToast = Toast.makeText(
CommonsApplication.getInstance(),
R.string.uploading_started,
Toast.LENGTH_LONG
);
startingToast.show(); startingToast.show();
} }
} }
@ -205,7 +206,9 @@ public class MultipleShareActivity
uploadController = new UploadController(this); uploadController = new UploadController(this);
setContentView(R.layout.activity_multiple_uploads); setContentView(R.layout.activity_multiple_uploads);
app = (CommonsApplication)this.getApplicationContext(); app = CommonsApplication.getInstance();
ButterKnife.bind(this);
initDrawer();
if(savedInstanceState != null) { if(savedInstanceState != null) {
photosList = savedInstanceState.getParcelableArrayList("uploadsList"); photosList = savedInstanceState.getParcelableArrayList("uploadsList");
@ -242,7 +245,7 @@ public class MultipleShareActivity
@Override @Override
protected void onAuthCookieAcquired(String authCookie) { protected void onAuthCookieAcquired(String authCookie) {
app.getApi().setAuthCookie(authCookie); app.getMWApi().setAuthCookie(authCookie);
Intent intent = getIntent(); Intent intent = getIntent();
if(intent.getAction().equals(Intent.ACTION_SEND_MULTIPLE)) { if(intent.getAction().equals(Intent.ACTION_SEND_MULTIPLE)) {

View file

@ -21,19 +21,15 @@ import android.widget.BaseAdapter;
import android.widget.EditText; import android.widget.EditText;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.GridView; import android.widget.GridView;
import android.widget.ImageView;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.TextView; import android.widget.TextView;
import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.facebook.drawee.view.SimpleDraweeView;
import com.nostra13.universalimageloader.core.ImageLoader;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.contributions.Contribution; import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.media.MediaDetailPagerFragment; import fr.free.nrw.commons.media.MediaDetailPagerFragment;
public class MultipleUploadListFragment extends Fragment { public class MultipleUploadListFragment extends Fragment {
public interface OnMultipleUploadInitiatedHandler { public interface OnMultipleUploadInitiatedHandler {
@ -48,17 +44,13 @@ public class MultipleUploadListFragment extends Fragment {
private MediaDetailPagerFragment.MediaDetailProvider detailProvider; private MediaDetailPagerFragment.MediaDetailProvider detailProvider;
private OnMultipleUploadInitiatedHandler multipleUploadInitiatedHandler; private OnMultipleUploadInitiatedHandler multipleUploadInitiatedHandler;
private DisplayImageOptions uploadDisplayOptions;
private boolean imageOnlyMode; private boolean imageOnlyMode;
private static class UploadHolderView { private static class UploadHolderView {
Uri imageUri; private Uri imageUri;
private SimpleDraweeView image;
ImageView image; private TextView title;
TextView title; private RelativeLayout overlay;
RelativeLayout overlay;
} }
private class PhotoDisplayAdapter extends BaseAdapter { private class PhotoDisplayAdapter extends BaseAdapter {
@ -85,7 +77,7 @@ public class MultipleUploadListFragment extends Fragment {
if(view == null) { if(view == null) {
view = getLayoutInflater(null).inflate(R.layout.layout_upload_item, null); view = getLayoutInflater(null).inflate(R.layout.layout_upload_item, null);
holder = new UploadHolderView(); holder = new UploadHolderView();
holder.image = (ImageView) view.findViewById(R.id.uploadImage); holder.image = (SimpleDraweeView) view.findViewById(R.id.uploadImage);
holder.title = (TextView) view.findViewById(R.id.uploadTitle); holder.title = (TextView) view.findViewById(R.id.uploadTitle);
holder.overlay = (RelativeLayout) view.findViewById(R.id.uploadOverlay); holder.overlay = (RelativeLayout) view.findViewById(R.id.uploadOverlay);
@ -100,7 +92,7 @@ public class MultipleUploadListFragment extends Fragment {
Contribution up = (Contribution)this.getItem(i); Contribution up = (Contribution)this.getItem(i);
if(holder.imageUri == null || !holder.imageUri.equals(up.getLocalUri())) { if(holder.imageUri == null || !holder.imageUri.equals(up.getLocalUri())) {
ImageLoader.getInstance().displayImage(up.getLocalUri().toString(), holder.image, uploadDisplayOptions); holder.image.setImageURI(up.getLocalUri().toString());
holder.imageUri = up.getLocalUri(); holder.imageUri = up.getLocalUri();
} }
@ -221,7 +213,6 @@ public class MultipleUploadListFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
uploadDisplayOptions = Utils.getGenericDisplayOptions().build();
detailProvider = (MediaDetailPagerFragment.MediaDetailProvider)getActivity(); detailProvider = (MediaDetailPagerFragment.MediaDetailProvider)getActivity();
multipleUploadInitiatedHandler = (OnMultipleUploadInitiatedHandler) getActivity(); multipleUploadInitiatedHandler = (OnMultipleUploadInitiatedHandler) getActivity();

View file

@ -2,7 +2,6 @@ package fr.free.nrw.commons.upload;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.util.Log;
import com.android.volley.Cache; import com.android.volley.Cache;
import com.android.volley.NetworkResponse; import com.android.volley.NetworkResponse;
@ -22,6 +21,8 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import timber.log.Timber;
/** /**
* Uses the Volley library to implement asynchronous calls to the Commons MediaWiki API to match * Uses the Volley library to implement asynchronous calls to the Commons MediaWiki API to match
* GPS coordinates with nearby Commons categories. Parses the results using GSON to obtain a list * GPS coordinates with nearby Commons categories. Parses the results using GSON to obtain a list
@ -32,13 +33,11 @@ public class MwVolleyApi {
private static RequestQueue REQUEST_QUEUE; private static RequestQueue REQUEST_QUEUE;
private static final Gson GSON = new GsonBuilder().create(); private static final Gson GSON = new GsonBuilder().create();
private Context context; private Context context;
private String coordsLog;
protected static Set<String> categorySet; protected static Set<String> categorySet;
private static List<String> categoryList; private static List<String> categoryList;
private static final String MWURL = "https://commons.wikimedia.org/"; private static final String MWURL = "https://commons.wikimedia.org/";
private static final String TAG = MwVolleyApi.class.getName();
public MwVolleyApi(Context context) { public MwVolleyApi(Context context) {
this.context = context; this.context = context;
@ -52,13 +51,12 @@ public class MwVolleyApi {
public static void setGpsCat(List<String> cachedList) { public static void setGpsCat(List<String> cachedList) {
categoryList = new ArrayList<>(); categoryList = new ArrayList<>();
categoryList.addAll(cachedList); categoryList.addAll(cachedList);
Log.d(TAG, "Setting GPS cats from cache: " + categoryList.toString()); Timber.d("Setting GPS cats from cache: %s", categoryList);
} }
public void request(String coords) { public void request(String coords) {
coordsLog = coords;
String apiUrl = buildUrl(coords); String apiUrl = buildUrl(coords);
Log.d(TAG, "URL: " + apiUrl); Timber.d("URL: %s", apiUrl);
JsonRequest<QueryResponse> request = new QueryRequest(apiUrl, JsonRequest<QueryResponse> request = new QueryRequest(apiUrl,
new LogResponseListener<QueryResponse>(), new LogResponseErrorListener()); new LogResponseListener<QueryResponse>(), new LogResponseErrorListener());
@ -101,7 +99,7 @@ public class MwVolleyApi {
private static RequestQueue getQueue(Context context) { private static RequestQueue getQueue(Context context) {
if (REQUEST_QUEUE == null) { if (REQUEST_QUEUE == null) {
REQUEST_QUEUE = Volley.newRequestQueue(context.getApplicationContext()); REQUEST_QUEUE = Volley.newRequestQueue(context);
} }
return REQUEST_QUEUE; return REQUEST_QUEUE;
} }
@ -110,7 +108,7 @@ public class MwVolleyApi {
@Override @Override
public void onResponse(T response) { public void onResponse(T response) {
Log.d(TAG, response.toString()); Timber.d(response.toString());
} }
} }
@ -118,12 +116,11 @@ public class MwVolleyApi {
@Override @Override
public void onErrorResponse(VolleyError error) { public void onErrorResponse(VolleyError error) {
Log.e(TAG, error.toString()); Timber.e(error.toString());
} }
} }
private static class QueryRequest extends JsonRequest<QueryResponse> { private static class QueryRequest extends JsonRequest<QueryResponse> {
private static final String TAG = QueryRequest.class.getName();
public QueryRequest(String url, public QueryRequest(String url,
Response.Listener<QueryResponse> listener, Response.Listener<QueryResponse> listener,
@ -169,11 +166,11 @@ public class MwVolleyApi {
private String printSet() { private String printSet() {
if (categorySet == null || categorySet.isEmpty()) { if (categorySet == null || categorySet.isEmpty()) {
GpsCatExists.setGpsCatExists(false); GpsCatExists.setGpsCatExists(false);
Log.d(TAG, "gpsCatExists=" + GpsCatExists.getGpsCatExists()); Timber.d("gpsCatExists=%b", GpsCatExists.getGpsCatExists());
return "No collection of categories"; return "No collection of categories";
} else { } else {
GpsCatExists.setGpsCatExists(true); GpsCatExists.setGpsCatExists(true);
Log.d(TAG, "gpsCatExists=" + GpsCatExists.getGpsCatExists()); Timber.d("gpsCatExists=%b", GpsCatExists.getGpsCatExists());
return "CATEGORIES FOUND" + categorySet.toString(); return "CATEGORIES FOUND" + categorySet.toString();
} }

View file

@ -10,34 +10,32 @@ import android.os.Bundle;
import android.os.Environment; import android.os.Environment;
import android.support.design.widget.Snackbar; import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat; import android.support.v4.app.ActivityCompat;
import android.support.v4.app.NavUtils;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.nostra13.universalimageloader.core.ImageLoader; import com.facebook.drawee.view.SimpleDraweeView;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import butterknife.ButterKnife;
import fr.free.nrw.commons.CommonsApplication; import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.EventLog; import fr.free.nrw.commons.EventLog;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.auth.AuthenticatedActivity; import fr.free.nrw.commons.auth.AuthenticatedActivity;
import fr.free.nrw.commons.auth.WikiAccountAuthenticator;
import fr.free.nrw.commons.category.CategorizationFragment; import fr.free.nrw.commons.category.CategorizationFragment;
import fr.free.nrw.commons.contributions.Contribution; import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.modifications.CategoryModifier; import fr.free.nrw.commons.modifications.CategoryModifier;
import fr.free.nrw.commons.modifications.ModificationsContentProvider; import fr.free.nrw.commons.modifications.ModificationsContentProvider;
import fr.free.nrw.commons.modifications.ModifierSequence; import fr.free.nrw.commons.modifications.ModifierSequence;
import fr.free.nrw.commons.modifications.TemplateRemoveModifier; import fr.free.nrw.commons.modifications.TemplateRemoveModifier;
import timber.log.Timber;
/** /**
* Activity for the title/desc screen after image is selected. Also starts processing image * Activity for the title/desc screen after image is selected. Also starts processing image
@ -48,9 +46,6 @@ public class ShareActivity
implements SingleUploadFragment.OnUploadActionInitiated, implements SingleUploadFragment.OnUploadActionInitiated,
CategorizationFragment.OnCategoriesSaveHandler { CategorizationFragment.OnCategoriesSaveHandler {
private static final String TAG = ShareActivity.class.getName();
private SingleUploadFragment shareView;
private CategorizationFragment categorizationFragment; private CategorizationFragment categorizationFragment;
private CommonsApplication app; private CommonsApplication app;
@ -61,14 +56,14 @@ public class ShareActivity
private Uri mediaUri; private Uri mediaUri;
private Contribution contribution; private Contribution contribution;
private ImageView backgroundImageView; private SimpleDraweeView backgroundImageView;
private UploadController uploadController; private UploadController uploadController;
private CommonsApplication cacheObj; private CommonsApplication cacheObj;
private boolean cacheFound; private boolean cacheFound;
private GPSExtractor imageObj; private GPSExtractor imageObj;
private String filePath;
private String decimalCoords; private String decimalCoords;
private boolean useNewPermissions = false; private boolean useNewPermissions = false;
@ -79,10 +74,6 @@ public class ShareActivity
private String description; private String description;
private Snackbar snackbar; private Snackbar snackbar;
public ShareActivity() {
super(WikiAccountAuthenticator.COMMONS_ACCOUNT_TYPE);
}
/** /**
* Called when user taps the submit button * Called when user taps the submit button
*/ */
@ -112,13 +103,17 @@ public class ShareActivity
getFileMetadata(false); getFileMetadata(false);
} }
Toast startingToast = Toast.makeText(getApplicationContext(), R.string.uploading_started, Toast.LENGTH_LONG); Toast startingToast = Toast.makeText(
CommonsApplication.getInstance(),
R.string.uploading_started,
Toast.LENGTH_LONG
);
startingToast.show(); startingToast.show();
if (cacheFound == false) { if (!cacheFound) {
//Has to be called after apiCall.request() //Has to be called after apiCall.request()
app.cacheData.cacheCategory(); app.getCacheData().cacheCategory();
Log.d(TAG, "Cache the categories found"); Timber.d("Cache the categories found");
} }
uploadController.startUpload(title, mediaUri, description, mimeType, source, decimalCoords, new UploadController.ContributionUploadProgress() { uploadController.startUpload(title, mediaUri, description, mimeType, source, decimalCoords, new UploadController.ContributionUploadProgress() {
@ -195,9 +190,9 @@ public class ShareActivity
@Override @Override
protected void onAuthCookieAcquired(String authCookie) { protected void onAuthCookieAcquired(String authCookie) {
app.getApi().setAuthCookie(authCookie); app.getMWApi().setAuthCookie(authCookie);
shareView = (SingleUploadFragment) getSupportFragmentManager().findFragmentByTag("shareView"); SingleUploadFragment shareView = (SingleUploadFragment) getSupportFragmentManager().findFragmentByTag("shareView");
categorizationFragment = (CategorizationFragment) getSupportFragmentManager().findFragmentByTag("categorization"); categorizationFragment = (CategorizationFragment) getSupportFragmentManager().findFragmentByTag("categorization");
if(shareView == null && categorizationFragment == null) { if(shareView == null && categorizationFragment == null) {
shareView = new SingleUploadFragment(); shareView = new SingleUploadFragment();
@ -221,9 +216,10 @@ public class ShareActivity
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
uploadController = new UploadController(this); uploadController = new UploadController(this);
setContentView(R.layout.activity_share); setContentView(R.layout.activity_share);
ButterKnife.bind(this);
app = (CommonsApplication)this.getApplicationContext(); initBack();
backgroundImageView = (ImageView)findViewById(R.id.backgroundImage); app = CommonsApplication.getInstance();
backgroundImageView = (SimpleDraweeView)findViewById(R.id.backgroundImage);
//Receive intent from ContributionController.java when user selects picture to upload //Receive intent from ContributionController.java when user selects picture to upload
Intent intent = getIntent(); Intent intent = getIntent();
@ -240,20 +236,20 @@ public class ShareActivity
if (mediaUri != null) { if (mediaUri != null) {
mediaUriString = mediaUri.toString(); mediaUriString = mediaUri.toString();
ImageLoader.getInstance().displayImage(mediaUriString, backgroundImageView); backgroundImageView.setImageURI(mediaUriString);
//Test SHA1 of image to see if it matches SHA1 of a file on Commons //Test SHA1 of image to see if it matches SHA1 of a file on Commons
try { try {
InputStream inputStream = getContentResolver().openInputStream(mediaUri); InputStream inputStream = getContentResolver().openInputStream(mediaUri);
Log.d(TAG, "Input stream created from " + mediaUriString); Timber.d("Input stream created from %s", mediaUriString);
String fileSHA1 = Utils.getSHA1(inputStream); String fileSHA1 = Utils.getSHA1(inputStream);
Log.d(TAG, "File SHA1 is: " + fileSHA1); Timber.d("File SHA1 is: %s", fileSHA1);
ExistingFileAsync fileAsyncTask = new ExistingFileAsync(fileSHA1, this); ExistingFileAsync fileAsyncTask = new ExistingFileAsync(fileSHA1, this);
fileAsyncTask.execute(); fileAsyncTask.execute();
} catch (IOException e) { } catch (IOException e) {
Log.d(TAG, "IO Exception: ", e); Timber.d(e, "IO Exception: ");
} }
} }
@ -263,8 +259,8 @@ public class ShareActivity
requestAuthToken(); requestAuthToken();
Log.d(TAG, "Uri: " + mediaUriString); Timber.d("Uri: %s", mediaUriString);
Log.d(TAG, "Ext storage dir: " + Environment.getExternalStorageDirectory()); Timber.d("Ext storage dir: %s", Environment.getExternalStorageDirectory());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
useNewPermissions = true; useNewPermissions = true;
@ -377,9 +373,9 @@ public class ShareActivity
* @param gpsEnabled * @param gpsEnabled
*/ */
public void getFileMetadata(boolean gpsEnabled) { public void getFileMetadata(boolean gpsEnabled) {
filePath = FileUtils.getPath(this, mediaUri); String filePath = FileUtils.getPath(this, mediaUri);
Log.d(TAG, "Filepath: " + filePath); Timber.d("Filepath: %s", filePath);
Log.d(TAG, "Calling GPSExtractor"); Timber.d("Calling GPSExtractor");
if(imageObj == null) { if(imageObj == null) {
imageObj = new GPSExtractor(filePath, this); imageObj = new GPSExtractor(filePath, this);
} }
@ -397,28 +393,28 @@ public class ShareActivity
*/ */
public void useImageCoords() { public void useImageCoords() {
if(decimalCoords != null) { if(decimalCoords != null) {
Log.d(TAG, "Decimal coords of image: " + decimalCoords); Timber.d("Decimal coords of image: %s", decimalCoords);
// Only set cache for this point if image has coords // Only set cache for this point if image has coords
if (imageObj.imageCoordsExists) { if (imageObj.imageCoordsExists) {
double decLongitude = imageObj.getDecLongitude(); double decLongitude = imageObj.getDecLongitude();
double decLatitude = imageObj.getDecLatitude(); double decLatitude = imageObj.getDecLatitude();
app.cacheData.setQtPoint(decLongitude, decLatitude); app.getCacheData().setQtPoint(decLongitude, decLatitude);
} }
MwVolleyApi apiCall = new MwVolleyApi(this); MwVolleyApi apiCall = new MwVolleyApi(this);
List<String> displayCatList = app.cacheData.findCategory(); List<String> displayCatList = app.getCacheData().findCategory();
boolean catListEmpty = displayCatList.isEmpty(); boolean catListEmpty = displayCatList.isEmpty();
// If no categories found in cache, call MediaWiki API to match image coords with nearby Commons categories // If no categories found in cache, call MediaWiki API to match image coords with nearby Commons categories
if (catListEmpty) { if (catListEmpty) {
cacheFound = false; cacheFound = false;
apiCall.request(decimalCoords); apiCall.request(decimalCoords);
Log.d(TAG, "displayCatList size 0, calling MWAPI" + displayCatList.toString()); Timber.d("displayCatList size 0, calling MWAPI %s", displayCatList);
} else { } else {
cacheFound = true; cacheFound = true;
Log.d(TAG, "Cache found, setting categoryList in MwVolleyApi to " + displayCatList.toString()); Timber.d("Cache found, setting categoryList in MwVolleyApi to %s", displayCatList);
MwVolleyApi.setGpsCat(displayCatList); MwVolleyApi.setGpsCat(displayCatList);
} }
} }
@ -429,10 +425,10 @@ public class ShareActivity
super.onPause(); super.onPause();
try { try {
imageObj.unregisterLocationManager(); imageObj.unregisterLocationManager();
Log.d(TAG, "Unregistered locationManager"); Timber.d("Unregistered locationManager");
} }
catch (NullPointerException e) { catch (NullPointerException e) {
Log.d(TAG, "locationManager does not exist, not unregistered"); Timber.d("locationManager does not exist, not unregistered");
} }
} }
@ -446,7 +442,11 @@ public class ShareActivity
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case android.R.id.home: case android.R.id.home:
NavUtils.navigateUpFromSameTask(this); if(categorizationFragment!=null && categorizationFragment.isVisible()) {
categorizationFragment.backButtonDialog();
} else {
onBackPressed();
}
return true; return true;
} }
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);

View file

@ -11,7 +11,6 @@ import android.preference.PreferenceManager;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.text.Editable; import android.text.Editable;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
@ -37,6 +36,7 @@ import butterknife.OnTouch;
import fr.free.nrw.commons.settings.Prefs; import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.R; import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.Utils;
import timber.log.Timber;
public class SingleUploadFragment extends Fragment { public class SingleUploadFragment extends Fragment {
private SharedPreferences prefs; private SharedPreferences prefs;
@ -54,8 +54,6 @@ public class SingleUploadFragment extends Fragment {
private OnUploadActionInitiated uploadActionInitiatedHandler; private OnUploadActionInitiated uploadActionInitiatedHandler;
private static final String TAG = SingleUploadFragment.class.getName();
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.activity_share, menu); inflater.inflate(R.menu.activity_share, menu);
@ -103,7 +101,7 @@ public class SingleUploadFragment extends Fragment {
prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
license = prefs.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3); license = prefs.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3);
Log.d("Single Upload fragment", license); Timber.d(license);
ArrayAdapter<String> adapter; ArrayAdapter<String> adapter;
if (PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean("theme",true)) { if (PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean("theme",true)) {
@ -117,7 +115,14 @@ public class SingleUploadFragment extends Fragment {
licenseSpinner.setAdapter(adapter); licenseSpinner.setAdapter(adapter);
int position = licenseItems.indexOf(getString(Utils.licenseNameFor(license))); int position = licenseItems.indexOf(getString(Utils.licenseNameFor(license)));
Log.d("Single Upload fragment", "Position:"+position+" "+getString(Utils.licenseNameFor(license)));
// Check position is valid
if (position < 0) {
Timber.d("Invalid position: %d. Using default license", position);
position = 4;
}
Timber.d("Position: %d %s", position, getString(Utils.licenseNameFor(license)));
licenseSpinner.setSelection(position); licenseSpinner.setSelection(position);
TextWatcher uploadEnabler = new TextWatcher() { TextWatcher uploadEnabler = new TextWatcher() {
@ -190,7 +195,7 @@ public class SingleUploadFragment extends Fragment {
SharedPreferences titleDesc = PreferenceManager.getDefaultSharedPreferences(getActivity()); SharedPreferences titleDesc = PreferenceManager.getDefaultSharedPreferences(getActivity());
String title = titleDesc.getString("Title", ""); String title = titleDesc.getString("Title", "");
String desc = titleDesc.getString("Desc", ""); String desc = titleDesc.getString("Desc", "");
Log.d(TAG, "Title: " + title + ", Desc: " + desc); Timber.d("Title: %s, Desc: %s", title, desc);
titleEdit.setText(title); titleEdit.setText(title);
descEdit.setText(desc); descEdit.setText(desc);

View file

@ -13,7 +13,6 @@ import android.os.IBinder;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import java.io.IOException; import java.io.IOException;
import java.util.Date; import java.util.Date;
@ -23,6 +22,7 @@ import fr.free.nrw.commons.HandlerService;
import fr.free.nrw.commons.settings.Prefs; import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.Utils; import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.contributions.Contribution; import fr.free.nrw.commons.contributions.Contribution;
import timber.log.Timber;
public class UploadController { public class UploadController {
private UploadService uploadService; private UploadService uploadService;
@ -36,7 +36,7 @@ public class UploadController {
public UploadController(Activity activity) { public UploadController(Activity activity) {
this.activity = activity; this.activity = activity;
app = (CommonsApplication)activity.getApplicationContext(); app = CommonsApplication.getInstance();
} }
private boolean isUploadServiceConnected; private boolean isUploadServiceConnected;
@ -55,7 +55,7 @@ public class UploadController {
}; };
public void prepareService() { public void prepareService() {
Intent uploadServiceIntent = new Intent(activity.getApplicationContext(), UploadService.class); Intent uploadServiceIntent = new Intent(activity, UploadService.class);
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE); uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
activity.startService(uploadServiceIntent); activity.startService(uploadServiceIntent);
activity.bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE); activity.bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE);
@ -115,11 +115,11 @@ public class UploadController {
contribution.setDataLength(length); contribution.setDataLength(length);
} }
} catch(IOException e) { } catch(IOException e) {
Log.e("UploadController", "IO Exception: ", e); Timber.e(e, "IO Exception: ");
} catch(NullPointerException e) { } catch(NullPointerException e) {
Log.e("UploadController", "Null Pointer Exception: ", e); Timber.e(e, "Null Pointer Exception: ");
} catch(SecurityException e) { } catch(SecurityException e) {
Log.e("UploadController", "Security Exception: ", e); Timber.e(e, "Security Exception: ");
} }
String mimeType = (String)contribution.getTag("mimeType"); String mimeType = (String)contribution.getTag("mimeType");
@ -132,7 +132,7 @@ public class UploadController {
if(mimeType != null) { if(mimeType != null) {
contribution.setTag("mimeType", mimeType); contribution.setTag("mimeType", mimeType);
imagePrefix = mimeType.startsWith("image/"); imagePrefix = mimeType.startsWith("image/");
Log.d("UploadController", "MimeType is: " + mimeType); Timber.d("MimeType is: %s", mimeType);
} }
if(imagePrefix && contribution.getDateCreated() == null) { if(imagePrefix && contribution.getDateCreated() == null) {

View file

@ -10,12 +10,11 @@ import android.content.Intent;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
import android.widget.Toast; import android.widget.Toast;
import fr.free.nrw.commons.*;
import org.mediawiki.api.ApiResult; import org.mediawiki.api.ApiResult;
import org.mediawiki.api.MWApi;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
@ -26,16 +25,12 @@ import java.util.Set;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.EventLog;
import fr.free.nrw.commons.HandlerService;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.contributions.Contribution; import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.contributions.ContributionsActivity; import fr.free.nrw.commons.contributions.ContributionsActivity;
import fr.free.nrw.commons.contributions.ContributionsContentProvider; import fr.free.nrw.commons.contributions.ContributionsContentProvider;
import fr.free.nrw.commons.modifications.ModificationsContentProvider; import fr.free.nrw.commons.modifications.ModificationsContentProvider;
import in.yuvi.http.fluent.ProgressListener; import in.yuvi.http.fluent.ProgressListener;
import timber.log.Timber;
public class UploadService extends HandlerService<Contribution> { public class UploadService extends HandlerService<Contribution> {
@ -87,7 +82,7 @@ public class UploadService extends HandlerService<Contribution> {
@Override @Override
public void onProgress(long transferred, long total) { public void onProgress(long transferred, long total) {
Log.d("Commons", String.format("Uploaded %d of %d", transferred, total)); Timber.d("Uploaded %d of %d", transferred, total);
if(!notificationTitleChanged) { if(!notificationTitleChanged) {
curProgressNotification.setContentTitle(notificationProgressTitle); curProgressNotification.setContentTitle(notificationProgressTitle);
notificationTitleChanged = true; notificationTitleChanged = true;
@ -112,7 +107,7 @@ public class UploadService extends HandlerService<Contribution> {
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
contributionsProviderClient.release(); contributionsProviderClient.release();
Log.d("Commons", "UploadService.onDestroy; " + unfinishedUploads + " are yet to be uploaded"); Timber.d("UploadService.onDestroy; %s are yet to be uploaded", unfinishedUploads);
} }
@Override @Override
@ -120,7 +115,7 @@ public class UploadService extends HandlerService<Contribution> {
super.onCreate(); super.onCreate();
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
app = (CommonsApplication) this.getApplicationContext(); app = CommonsApplication.getInstance();
contributionsProviderClient = this.getContentResolver().acquireContentProviderClient(ContributionsContentProvider.AUTHORITY); contributionsProviderClient = this.getContentResolver().acquireContentProviderClient(ContributionsContentProvider.AUTHORITY);
} }
@ -149,7 +144,7 @@ public class UploadService extends HandlerService<Contribution> {
toUpload++; toUpload++;
if (curProgressNotification != null && toUpload != 1) { if (curProgressNotification != null && toUpload != 1) {
curProgressNotification.setContentText(getResources().getQuantityString(R.plurals.uploads_pending_notification_indicator, toUpload, toUpload)); curProgressNotification.setContentText(getResources().getQuantityString(R.plurals.uploads_pending_notification_indicator, toUpload, toUpload));
Log.d("Commons", String.format("%d uploads left", toUpload)); Timber.d("%d uploads left", toUpload);
this.startForeground(NOTIFICATION_UPLOAD_IN_PROGRESS, curProgressNotification.build()); this.startForeground(NOTIFICATION_UPLOAD_IN_PROGRESS, curProgressNotification.build());
} }
@ -173,15 +168,15 @@ public class UploadService extends HandlerService<Contribution> {
Contribution.Table.COLUMN_STATE + " = ? OR " + Contribution.Table.COLUMN_STATE + " = ?", Contribution.Table.COLUMN_STATE + " = ? OR " + Contribution.Table.COLUMN_STATE + " = ?",
new String[]{ String.valueOf(Contribution.STATE_QUEUED), String.valueOf(Contribution.STATE_IN_PROGRESS) } new String[]{ String.valueOf(Contribution.STATE_QUEUED), String.valueOf(Contribution.STATE_IN_PROGRESS) }
); );
Log.d("Commons", "Set " + updated + " uploads to failed"); Timber.d("Set %d uploads to failed", updated);
Log.d("Commons", "Flags is" + flags + " id is" + startId); Timber.d("Flags is %d id is %d", flags, startId);
freshStart = false; freshStart = false;
} }
return START_REDELIVER_INTENT; return START_REDELIVER_INTENT;
} }
private void uploadContribution(Contribution contribution) { private void uploadContribution(Contribution contribution) {
MWApi api = app.getApi(); MWApi api = app.getMWApi();
ApiResult result; ApiResult result;
InputStream file = null; InputStream file = null;
@ -192,12 +187,12 @@ public class UploadService extends HandlerService<Contribution> {
//FIXME: Google Photos bug //FIXME: Google Photos bug
file = this.getContentResolver().openInputStream(contribution.getLocalUri()); file = this.getContentResolver().openInputStream(contribution.getLocalUri());
} catch(FileNotFoundException e) { } catch(FileNotFoundException e) {
Log.d("Exception", "File not found"); Timber.d("File not found");
Toast fileNotFound = Toast.makeText(this, R.string.upload_failed, Toast.LENGTH_LONG); Toast fileNotFound = Toast.makeText(this, R.string.upload_failed, Toast.LENGTH_LONG);
fileNotFound.show(); fileNotFound.show();
} }
Log.d("Commons", "Before execution!"); Timber.d("Before execution!");
curProgressNotification = new NotificationCompat.Builder(this).setAutoCancel(true) curProgressNotification = new NotificationCompat.Builder(this).setAutoCancel(true)
.setSmallIcon(R.drawable.ic_launcher) .setSmallIcon(R.drawable.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher)) .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher))
@ -206,7 +201,7 @@ public class UploadService extends HandlerService<Contribution> {
.setContentText(getResources().getQuantityString(R.plurals.uploads_pending_notification_indicator, toUpload, toUpload)) .setContentText(getResources().getQuantityString(R.plurals.uploads_pending_notification_indicator, toUpload, toUpload))
.setOngoing(true) .setOngoing(true)
.setProgress(100, 0, true) .setProgress(100, 0, true)
.setContentIntent(PendingIntent.getActivity(getApplicationContext(), 0, new Intent(this, ContributionsActivity.class), 0)) .setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, ContributionsActivity.class), 0))
.setTicker(getString(R.string.upload_progress_notification_title_in_progress, contribution.getDisplayTitle())); .setTicker(getString(R.string.upload_progress_notification_title_in_progress, contribution.getDisplayTitle()));
this.startForeground(NOTIFICATION_UPLOAD_IN_PROGRESS, curProgressNotification.build()); this.startForeground(NOTIFICATION_UPLOAD_IN_PROGRESS, curProgressNotification.build());
@ -218,16 +213,16 @@ public class UploadService extends HandlerService<Contribution> {
MimeTypeMap.getSingleton().getExtensionFromMimeType((String)contribution.getTag("mimeType"))); MimeTypeMap.getSingleton().getExtensionFromMimeType((String)contribution.getTag("mimeType")));
synchronized (unfinishedUploads) { synchronized (unfinishedUploads) {
Log.d("Commons", "making sure of uniqueness of name: " + filename); Timber.d("making sure of uniqueness of name: %s", filename);
filename = findUniqueFilename(filename); filename = findUniqueFilename(filename);
unfinishedUploads.add(filename); unfinishedUploads.add(filename);
} }
if(!api.validateLogin()) { if(!api.validateLogin()) {
// Need to revalidate! // Need to revalidate!
if(app.revalidateAuthToken()) { if(app.revalidateAuthToken()) {
Log.d("Commons", "Successfully revalidated token!"); Timber.d("Successfully revalidated token!");
} else { } else {
Log.d("Commons", "Unable to revalidate :("); Timber.d("Unable to revalidate :(");
// TODO: Put up a new notification, ask them to re-login // TODO: Put up a new notification, ask them to re-login
stopForeground(true); stopForeground(true);
Toast failureToast = Toast.makeText(this, R.string.authentication_failed, Toast.LENGTH_LONG); Toast failureToast = Toast.makeText(this, R.string.authentication_failed, Toast.LENGTH_LONG);
@ -242,7 +237,7 @@ public class UploadService extends HandlerService<Contribution> {
); );
result = api.upload(filename, file, contribution.getDataLength(), contribution.getPageContents(), contribution.getEditSummary(), notificationUpdater); result = api.upload(filename, file, contribution.getDataLength(), contribution.getPageContents(), contribution.getEditSummary(), notificationUpdater);
Log.d("Commons", "Response is" + Utils.getStringFromDOM(result.getDocument())); Timber.d("Response is %s", Utils.getStringFromDOM(result.getDocument()));
curProgressNotification = null; curProgressNotification = null;
@ -277,7 +272,7 @@ public class UploadService extends HandlerService<Contribution> {
.log(); .log();
} }
} catch(IOException e) { } catch(IOException e) {
Log.d("Commons", "I have a network fuckup"); Timber.d("I have a network fuckup");
showFailedNotification(contribution); showFailedNotification(contribution);
return; return;
} finally { } finally {
@ -287,7 +282,7 @@ public class UploadService extends HandlerService<Contribution> {
toUpload--; toUpload--;
if(toUpload == 0) { if(toUpload == 0) {
// Sync modifications right after all uplaods are processed // Sync modifications right after all uplaods are processed
ContentResolver.requestSync(((CommonsApplication) getApplicationContext()).getCurrentAccount(), ModificationsContentProvider.AUTHORITY, new Bundle()); ContentResolver.requestSync((CommonsApplication.getInstance()).getCurrentAccount(), ModificationsContentProvider.AUTHORITY, new Bundle());
stopForeground(true); stopForeground(true);
} }
} }
@ -309,7 +304,7 @@ public class UploadService extends HandlerService<Contribution> {
} }
private String findUniqueFilename(String fileName) throws IOException { private String findUniqueFilename(String fileName) throws IOException {
MWApi api = app.getApi(); MWApi api = app.getMWApi();
String sequenceFileName; String sequenceFileName;
for ( int sequenceNumber = 1; true; sequenceNumber++ ) { for ( int sequenceNumber = 1; true; sequenceNumber++ ) {
if (sequenceNumber == 1) { if (sequenceNumber == 1) {

View 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.");
}
}
}

View 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;
}
}

View file

@ -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;
}
}

View file

@ -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());
}
}

View file

@ -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());
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 993 B

Some files were not shown because too many files have changed in this diff Show more