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

View file

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

View file

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

View file

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

View file

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

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

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

View file

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

View file

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

View file

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

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

View file

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

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

View file

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

View file

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

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

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

View file

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

View file

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

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 final String DEFAULT_LICENSE = "defaultLicense";
public static final String UPLOADS_SHOWING = "uploadsshowing";
public static final String IS_CONTRIBUTION_COUNT_CHANGED = "ccontributionCountChanged";
public static class Licenses {
public static final String CC_BY_SA_3 = "CC BY-SA 3.0";

View file

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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