Merge branch 'campaigns'

Conflicts:
	commons/res/values/strings.xml
	commons/src/main/java/org/wikimedia/commons/Media.java

Change-Id: Ib8c74cbf88630f11b3fbfee903800e7acf7b2fd4
This commit is contained in:
YuviPanda 2013-10-15 22:54:03 +05:30
commit 064dbb0df6
32 changed files with 1374 additions and 462 deletions

View file

@ -82,6 +82,15 @@
android:label="@string/title_activity_settings"
/>
<activity android:name=".AboutActivity" android:label="@string/title_activity_about"/>
<activity android:name=".campaigns.CampaignActivity" android:label="Campaigns"
android:icon="@drawable/ic_launcher"
>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<service android:name=".upload.UploadService" >
</service>
<service
@ -96,6 +105,17 @@
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator" />
</service>
<service
android:name=".campaigns.CampaignsSyncService"
android:exported="true">
<intent-filter>
<action
android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/campaigns_sync_adapter" />
</service>
<service
android:name=".contributions.ContributionsSyncService"
android:exported="true">
@ -127,6 +147,13 @@
android:authorities="org.wikimedia.commons.contributions.contentprovider"
android:exported="false">
</provider>
<provider
android:name=".campaigns.CampaignsContentProvider"
android:label="@string/provider_campaigns"
android:syncable="true"
android:authorities="org.wikimedia.commons.campaigns.contentprovider"
android:exported="false">
</provider>
<provider
android:name=".modifications.ModificationsContentProvider"
android:label="@string/provider_modifications"

View file

@ -24,14 +24,8 @@
<dependency>
<groupId>com.actionbarsherlock</groupId>
<artifactId>actionbarsherlock</artifactId>
<version>4.2.0</version>
<version>4.4.0</version>
<type>apklib</type>
<exclusions>
<exclusion>
<groupId>com.google.android</groupId>
<artifactId>support-v4</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>android</groupId>

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/campaignsList"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</FrameLayout>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<GridView android:id="@+id/campaignsList"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:stretchMode="columnWidth"
android:columnWidth="240dp"
android:numColumns="auto_fit"
android:listSelector="@null"
android:fadingEdge="none"
android:fastScrollEnabled="false"
/>
</LinearLayout>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/campaignItemName"
android:layout_height="wrap_content"
android:layout_width="match_parent"
/>
</LinearLayout>

View file

@ -138,4 +138,5 @@
<string name="detail_panel_cats_label">Categories</string>
<string name="detail_panel_cats_loading">Loading...</string>
<string name="detail_panel_cats_none">None selected</string>
<string name="provider_campaigns">Campaigns</string>
</resources>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:contentAuthority="org.wikimedia.commons.campaigns.contentprovider"
android:accountType="org.wikimedia.commons"
android:supportsUploading="false"
android:userVisible="true"
android:isAlwaysSyncable="true"
/>

View file

@ -59,6 +59,9 @@ public class Media implements Parcelable {
}
public String getImageUrl() {
if(imageUrl == null) {
imageUrl = Utils.makeThumbBaseUrl(this.getFilename());
}
return imageUrl;
}
@ -103,7 +106,7 @@ public class Media implements Parcelable {
}
public String getThumbnailUrl(int width) {
return Utils.makeThumbUrl(imageUrl, filename, width);
return Utils.makeThumbUrl(getImageUrl(), getFilename(), width);
}
public int getWidth() {
@ -182,6 +185,10 @@ public class Media implements Parcelable {
}
}
public Media(String filename) {
this.filename = filename;
}
public Media(Uri localUri, String imageUrl, String filename, String description, long dataLength, Date dateCreated, Date dateUploaded, String creator) {
this();
this.localUri = localUri;

View file

@ -182,7 +182,7 @@ public class MediaWikiImageView extends ImageView {
if (response.getBitmap() != null) {
setImageBitmap(response.getBitmap());
if(tryOriginal && mMedia instanceof Contribution && response.getBitmap().getWidth() > mMedia.getWidth() || response.getBitmap().getHeight() > mMedia.getHeight()) {
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());

View file

@ -2,6 +2,7 @@ package org.wikimedia.commons;
import android.net.Uri;
import android.os.*;
import android.util.Log;
import com.nostra13.universalimageloader.core.*;
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;

View file

@ -16,6 +16,7 @@ import android.support.v4.app.NavUtils;
import org.wikimedia.commons.*;
import org.wikimedia.commons.EventLog;
import org.wikimedia.commons.campaigns.CampaignsContentProvider;
import org.wikimedia.commons.contributions.*;
import org.wikimedia.commons.modifications.ModificationsContentProvider;
@ -67,6 +68,7 @@ public class LoginActivity extends AccountAuthenticatorActivity {
// 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!
ContentResolver.setSyncAutomatically(account, CampaignsContentProvider.AUTHORITY, true); // Enable sync by default!
context.finish();
} else {
int response;

View file

@ -0,0 +1,190 @@
package org.wikimedia.commons.campaigns;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.Serializable;
import java.util.ArrayList;
// FIXME: Implement Parcelable
public class Campaign implements Serializable {
private boolean enabled;
private String autoAddWikitext;
private ArrayList<String> autoAddCategories;
private String name;
private String ownWorkLicenseDefault;
private String defaultDescription;
private JSONObject config;
private String body;
private boolean isParsed;
private String trackingCategory;
private String description;
private String title;
public boolean isEnabled() {
return enabled;
}
public String getAutoAddWikitext() {
if(!this.isParsed) {
this.parseConfig();
}
return autoAddWikitext;
}
public ArrayList<String> getAutoAddCategories() {
if(!this.isParsed) {
this.parseConfig();
}
return autoAddCategories;
}
public String getName() {
return name;
}
public String getOwnWorkLicenseDefault() {
if(!this.isParsed) {
this.parseConfig();
}
return ownWorkLicenseDefault;
}
public String getDefaultDescription() {
if(!this.isParsed) {
this.parseConfig();
}
return defaultDescription;
}
public JSONObject getConfig() {
if(!this.isParsed) {
this.parseConfig();
}
return config;
}
private void parseConfig() {
try {
this.config = new JSONObject(body);
} catch (JSONException e) {
throw new RuntimeException(e); // because what else are you gonna do?
}
if(config.has("autoAdd")) {
this.autoAddWikitext = config.optJSONObject("autoAdd").optString("wikitext", null);
if(config.optJSONObject("autoAdd").has("categories")) {
this.autoAddCategories = new ArrayList<String>();
JSONArray catsArray = config.optJSONObject("autoAdd").optJSONArray("categories");
for(int i=0; i < catsArray.length(); i++) {
autoAddCategories.add(catsArray.optString(i));
}
}
}
this.title = config.optString("title", name);
this.description = config.optString("description", "");
this.isParsed = true;
}
private Campaign(String name, String body, String trackingCategory) {
this.name = name;
this.body = body;
this.trackingCategory = trackingCategory;
}
public ContentValues toContentValues() {
ContentValues cv = new ContentValues();
cv.put(Table.COLUMN_NAME, this.getName());
cv.put(Table.COLUMN_ENABLED, this.isEnabled() ? 1 : 0);
cv.put(Table.COLUMN_TITLE, this.getTitle());
cv.put(Table.COLUMN_DESCRIPTION, this.getDescription());
cv.put(Table.COLUMN_TRACKING_CATEGORY, this.getTrackingCategory());
cv.put(Table.COLUMN_BODY, this.body);
return cv;
}
public static Campaign parse(String name, String body, String trackingCategory) {
Campaign c = new Campaign(name, body, trackingCategory);
c.parseConfig();
return c;
}
public static Campaign fromCursor(Cursor cursor) {
String name = cursor.getString(1);
Boolean enabled = cursor.getInt(2) == 1;
String title = cursor.getString(3);
String description = cursor.getString(4);
String trackingCategory = cursor.getString(5);
String body = cursor.getString(6);
Campaign c = new Campaign(name, body, trackingCategory);
c.title = title;
c.description = description;
c.enabled = enabled;
return c;
}
public String getTrackingCategory() {
return trackingCategory;
}
public String getDescription() {
return description;
}
public String getTitle() {
return title;
}
public static class Table {
public static final String TABLE_NAME = "campaigns";
public static final String COLUMN_ID = "_id";
public static final String COLUMN_NAME = "name";
public static final String COLUMN_ENABLED = "enabled";
public static final String COLUMN_TITLE = "title";
public static final String COLUMN_DESCRIPTION = "description";
public static final String COLUMN_TRACKING_CATEGORY = "tracking_category";
public static final String COLUMN_BODY = "body";
// NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES.
public static final String[] ALL_FIELDS = {
COLUMN_ID,
COLUMN_NAME,
COLUMN_ENABLED,
COLUMN_TITLE,
COLUMN_DESCRIPTION,
COLUMN_TRACKING_CATEGORY,
COLUMN_BODY
};
private static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " ("
+ "_id INTEGER PRIMARY KEY,"
+ "name STRING,"
+ "enabled INTEGER,"
+ "title STRING,"
+ "description STRING,"
+ "tracking_category STRING,"
+ "body STRING"
+ ");";
public static void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE_STATEMENT);
}
public static void onUpdate(SQLiteDatabase db, int from, int to) {
if(to <= 6) {
onCreate(db);
return;
}
return;
}
}
}

View file

@ -0,0 +1,60 @@
package org.wikimedia.commons.campaigns;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import org.wikimedia.commons.CommonsApplication;
import org.wikimedia.commons.R;
import org.wikimedia.commons.contributions.ContributionsActivity;
public class CampaignActivity
extends SherlockFragmentActivity
implements LoaderManager.LoaderCallbacks<Cursor> {
private ListView campaignsListView;
private CampaignsListAdapter campaignsListAdapter;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_campaigns);
ContentResolver.setSyncAutomatically(((CommonsApplication)getApplicationContext()).getCurrentAccount(), CampaignsContentProvider.AUTHORITY, true); // Enable sync by default!
campaignsListView = (ListView) findViewById(R.id.campaignsList);
campaignsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
Campaign c = Campaign.fromCursor((Cursor) adapterView.getItemAtPosition(i));
Intent intent = new Intent(CampaignActivity.this, ContributionsActivity.class);
intent.putExtra("campaign", c);
startActivity(intent);
}
});
getSupportLoaderManager().initLoader(0, null, this);
}
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
return new CursorLoader(this, CampaignsContentProvider.BASE_URI, Campaign.Table.ALL_FIELDS, "", null, "");
}
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
if(campaignsListAdapter == null) {
campaignsListAdapter = new CampaignsListAdapter(this, cursor, 0);
campaignsListView.setAdapter(campaignsListAdapter);
} else {
campaignsListAdapter.swapCursor(cursor);
}
}
public void onLoaderReset(Loader<Cursor> cursorLoader) {
campaignsListAdapter.swapCursor(null);
}
}

View file

@ -0,0 +1,49 @@
package org.wikimedia.commons.campaigns;
import android.net.Uri;
import org.wikimedia.commons.contributions.Contribution;
import java.util.ArrayList;
import java.util.Date;
public class CampaignContribution extends Contribution {
private Campaign campaign;
private ArrayList<String> fieldValues;
public CampaignContribution(Uri localUri, String remoteUri, String filename, String description, long dataLength, Date dateCreated, Date dateUploaded, String creator, String editSummary, Campaign campaign) {
super(localUri, remoteUri, filename, description, dataLength, dateCreated, dateUploaded, creator, editSummary);
this.campaign = campaign;
}
public Campaign getCampaign() {
return campaign;
}
public void setCampaign(Campaign campaign) {
this.campaign = campaign;
}
@Override
public String getTrackingTemplates() {
StringBuffer buffer = new StringBuffer();
if(campaign.getAutoAddWikitext() != null) {
buffer.append(campaign.getAutoAddWikitext()).append("\n");
}
if(campaign.getAutoAddCategories() != null && campaign.getAutoAddCategories().size() != 0) {
for(String cat : campaign.getAutoAddCategories()) {
buffer.append("[[Category:").append(cat).append("]]").append("\n");
}
} else {
buffer.append("{{subst:unc}}\n");
}
buffer.append("[[Category:").append(campaign.getTrackingCategory()).append("\n");
return buffer.toString();
}
@Override
public String getDescription() {
return super.getDescription();
}
}

View file

@ -0,0 +1,206 @@
package org.wikimedia.commons.campaigns;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import org.wikimedia.commons.CommonsApplication;
import org.wikimedia.commons.data.DBOpenHelper;
public class CampaignsContentProvider extends ContentProvider{
private static final int CAMPAIGNS = 1;
private static final int CAMPAIGNS_ID = 2;
public static final String AUTHORITY = "org.wikimedia.commons.campaigns.contentprovider";
private static final String BASE_PATH = "campiagns";
public static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY + "/" + BASE_PATH);
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
uriMatcher.addURI(AUTHORITY, BASE_PATH, CAMPAIGNS);
uriMatcher.addURI(AUTHORITY, BASE_PATH + "/#", CAMPAIGNS_ID);
}
public static Uri uriForId(int id) {
return Uri.parse(BASE_URI.toString() + "/" + id);
}
private DBOpenHelper dbOpenHelper;
@Override
public boolean onCreate() {
dbOpenHelper = ((CommonsApplication)this.getContext().getApplicationContext()).getDbOpenHelper();
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables(Campaign.Table.TABLE_NAME);
int uriType = uriMatcher.match(uri);
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
Cursor cursor;
switch(uriType) {
case CAMPAIGNS:
cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);
break;
case CAMPAIGNS_ID:
cursor = queryBuilder.query(db,
Campaign.Table.ALL_FIELDS,
"_id = ?",
new String[] { uri.getLastPathSegment() },
null,
null,
sortOrder
);
break;
default:
throw new IllegalArgumentException("Unknown URI" + uri);
}
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues contentValues) {
int uriType = uriMatcher.match(uri);
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
long id = 0;
switch (uriType) {
case CAMPAIGNS:
sqlDB.beginTransaction();
// if the campaign already exists, rip it out and then re-insert
if(campaignExists(sqlDB, contentValues)) {
sqlDB.delete(
Campaign.Table.TABLE_NAME,
Campaign.Table.COLUMN_NAME + " = ?",
new String[]{contentValues.getAsString(Campaign.Table.COLUMN_NAME)}
);
}
id = sqlDB.insert(Campaign.Table.TABLE_NAME, null, contentValues);
sqlDB.endTransaction();
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return Uri.parse(BASE_URI + "/" + id);
}
@Override
public int delete(Uri uri, String s, String[] strings) {
int rows = 0;
int uriType = uriMatcher.match(uri);
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
switch(uriType) {
case CAMPAIGNS_ID:
rows = db.delete(Campaign.Table.TABLE_NAME,
"_id = ?",
new String[] { uri.getLastPathSegment() }
);
break;
default:
throw new IllegalArgumentException("Unknown URI" + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return rows;
}
private boolean campaignExists(SQLiteDatabase db, ContentValues campaign) {
Cursor cr = db.query(
Campaign.Table.TABLE_NAME,
new String[]{Campaign.Table.COLUMN_NAME},
Campaign.Table.COLUMN_NAME + " = ?",
new String[]{campaign.getAsString(Campaign.Table.COLUMN_NAME)},
"", "", ""
);
return cr != null && cr.getCount() != 0;
}
@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
Log.d("Commons", "Hello, bulk insert!");
int uriType = uriMatcher.match(uri);
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
sqlDB.beginTransaction();
switch (uriType) {
case CAMPAIGNS:
for(ContentValues value: values) {
Log.d("Commons", "Inserting! " + value.toString());
// if the campaign already exists, rip it out and then re-insert
if(campaignExists(sqlDB, value)) {
sqlDB.delete(
Campaign.Table.TABLE_NAME,
Campaign.Table.COLUMN_NAME + " = ?",
new String[]{value.getAsString(Campaign.Table.COLUMN_NAME)}
);
}
sqlDB.insert(Campaign.Table.TABLE_NAME, null, value);
}
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
sqlDB.setTransactionSuccessful();
sqlDB.endTransaction();
getContext().getContentResolver().notifyChange(uri, null);
return values.length;
}
@Override
public int update(Uri uri, ContentValues contentValues, String selection, String[] selectionArgs) {
/*
SQL Injection warnings: First, note that we're not exposing this to the outside world (exported="false")
Even then, we should make sure to sanitize all user input appropriately. Input that passes through ContentValues
should be fine. So only issues are those that pass in via concating.
In here, the only concat created argument is for id. It is cast to an int, and will error out otherwise.
*/
int uriType = uriMatcher.match(uri);
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
int rowsUpdated = 0;
switch (uriType) {
case CAMPAIGNS:
rowsUpdated = sqlDB.update(Campaign.Table.TABLE_NAME,
contentValues,
selection,
selectionArgs);
break;
case CAMPAIGNS_ID:
int id = Integer.valueOf(uri.getLastPathSegment());
if (TextUtils.isEmpty(selection)) {
rowsUpdated = sqlDB.update(Campaign.Table.TABLE_NAME,
contentValues,
Campaign.Table.COLUMN_ID + " = ?",
new String[] { String.valueOf(id) } );
} else {
throw new IllegalArgumentException("Parameter `selection` should be empty when updating an ID");
}
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri + " with type " + uriType);
}
getContext().getContentResolver().notifyChange(uri, null);
return rowsUpdated;
}
}

View file

@ -0,0 +1,47 @@
package org.wikimedia.commons.campaigns;
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 android.widget.TextView;
import com.actionbarsherlock.app.SherlockFragment;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.assist.SimpleImageLoadingListener;
import org.wikimedia.commons.CommonsApplication;
import org.wikimedia.commons.MediaWikiImageView;
import org.wikimedia.commons.R;
import org.wikimedia.commons.Utils;
import org.wikimedia.commons.campaigns.Campaign;
class CampaignsListAdapter extends CursorAdapter {
private DisplayImageOptions contributionDisplayOptions = Utils.getGenericDisplayOptions().build();;
private Activity activity;
public CampaignsListAdapter(Activity activity, Cursor c, int flags) {
super(activity, c, flags);
this.activity = activity;
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
View parent = activity.getLayoutInflater().inflate(android.R.layout.simple_list_item_1, viewGroup, false);
return parent;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
TextView campaignName = (TextView)view.findViewById(android.R.id.text1);
Campaign campaign = Campaign.fromCursor(cursor);
campaignName.setText(campaign.getTitle());
}
}

View file

@ -0,0 +1,95 @@
package org.wikimedia.commons.campaigns;
import android.accounts.Account;
import android.content.*;
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 org.wikimedia.commons.CommonsApplication;
import org.wikimedia.commons.Utils;
import org.wikimedia.commons.contributions.Contribution;
import org.wikimedia.commons.contributions.ContributionsContentProvider;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
public class CampaignsSyncAdapter extends AbstractThreadedSyncAdapter {
private static int COMMIT_THRESHOLD = 10;
public CampaignsSyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
}
private int getLimit() {
return 500; // FIXME: Parameterize!
}
@Override
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();
ApiResult result;
Boolean done = false;
String queryContinue = null;
while(!done) {
try {
MWApi.RequestBuilder builder = api.action("query")
.param("list", "allcampaigns")
// Disabled, since we want to modify local state if the campaign was disabled
// FIXME: To be more effecient, delete the disabled campaigns locally
//.param("ucenabledonly", "true")
.param("uclimit", getLimit());
if(!TextUtils.isEmpty(queryContinue)) {
builder.param("uccontinue", queryContinue);
}
result = builder.get();
} catch (IOException e) {
// 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());
return;
}
ArrayList<ApiResult> campaigns = result.getNodes("/api/query/allcampaigns/campaign");
Log.d("Commons", campaigns.size() + " results!");
ArrayList<ContentValues> campaignValues = new ArrayList<ContentValues>();
for(ApiResult campaignItem: campaigns) {
String name = campaignItem.getString("@name");
String body = campaignItem.getString(".");
Log.d("Commons", "Campaign body is " + body);
String trackingCat = campaignItem.getString("@trackingCategory");
Campaign campaign = Campaign.parse(name, body, trackingCat);
campaignValues.add(campaign.toContentValues());
if(campaignValues.size() % COMMIT_THRESHOLD == 0) {
try {
contentProviderClient.bulkInsert(CampaignsContentProvider.BASE_URI, campaignValues.toArray(new ContentValues[]{}));
} catch (RemoteException e) {
throw new RuntimeException(e);
}
campaignValues.clear();
}
}
if(campaignValues.size() != 0) {
try {
contentProviderClient.bulkInsert(CampaignsContentProvider.BASE_URI, campaignValues.toArray(new ContentValues[]{}));
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
queryContinue = result.getString("/api/query-continue/allcampaigns/@uccontinue");
if(TextUtils.isEmpty(queryContinue)) {
done = true;
}
}
}
}

View file

@ -0,0 +1,27 @@
package org.wikimedia.commons.campaigns;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import org.wikimedia.commons.contributions.ContributionsSyncAdapter;
public class CampaignsSyncService extends Service {
private static final Object sSyncAdapterLock = new Object();
private static CampaignsSyncAdapter sSyncAdapter = null;
@Override
public void onCreate() {
synchronized (sSyncAdapterLock) {
if (sSyncAdapter == null) {
sSyncAdapter = new CampaignsSyncAdapter(getApplicationContext(), true);
}
}
}
@Override
public IBinder onBind(Intent intent) {
return sSyncAdapter.getSyncAdapterBinder();
}
}

View file

@ -117,13 +117,17 @@ public class Contribution extends Media {
this.dateUploaded = date;
}
public String getTrackingTemplates() {
return "{{subst:unc}}"; // Remove when we have categorization
}
public String getPageContents() {
StringBuffer buffer = new StringBuffer();
SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd");
buffer
.append("== {{int:filedesc}} ==\n")
.append("{{Information\n")
.append("|description=").append(description).append("\n")
.append("|description=").append(getDescription()).append("\n")
.append("|source=").append("{{own}}\n")
.append("|author=[[User:").append(creator).append("|").append(creator).append("]]\n");
if(dateCreated != null) {
@ -133,9 +137,9 @@ public class Contribution extends Media {
buffer
.append("}}").append("\n")
.append("== {{int:license-header}} ==\n")
.append(Utils.licenseTemplateFor(license)).append("\n\n")
.append(Utils.licenseTemplateFor(getLicense())).append("\n\n")
.append("{{Uploaded from Mobile|platform=Android|version=").append(CommonsApplication.APPLICATION_VERSION).append("}}\n")
.append("{{subst:unc}}"); // Remove when we have categorization
.append(getTrackingTemplates());
return buffer.toString();
}

View file

@ -0,0 +1,96 @@
package org.wikimedia.commons.contributions;
import android.app.*;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import com.actionbarsherlock.app.SherlockFragment;
import org.wikimedia.commons.upload.ShareActivity;
import org.wikimedia.commons.upload.UploadService;
import java.io.File;
import java.io.IOException;
import java.util.Date;
public class ContributionController {
private SherlockFragment fragment;
private Activity activity;
private final static int SELECT_FROM_GALLERY = 1;
private final static int SELECT_FROM_CAMERA = 2;
public ContributionController(SherlockFragment fragment) {
this.fragment = fragment;
this.activity = fragment.getActivity();
}
// See http://stackoverflow.com/a/5054673/17865 for why this is done
private Uri lastGeneratedCaptureURI;
private Uri reGenerateImageCaptureURI() {
String storageState = Environment.getExternalStorageState();
if(storageState.equals(Environment.MEDIA_MOUNTED)) {
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Commons/images/" + new Date().getTime() + ".jpg";
File _photoFile = new File(path);
try {
if(_photoFile.exists() == false) {
_photoFile.getParentFile().mkdirs();
_photoFile.createNewFile();
}
} catch (IOException e) {
Log.e("Commons", "Could not create file: " + path, e);
}
return Uri.fromFile(_photoFile);
} else {
throw new RuntimeException("No external storage found!");
}
}
public void startCameraCapture() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
lastGeneratedCaptureURI = reGenerateImageCaptureURI();
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, lastGeneratedCaptureURI);
fragment.startActivityForResult(takePictureIntent, SELECT_FROM_CAMERA);
}
public void startGalleryPick() {
Intent pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT);
pickImageIntent.setType("image/*");
fragment.startActivityForResult(pickImageIntent, SELECT_FROM_GALLERY);
}
public void handleImagePicked(int requestCode, Intent data) {
Intent shareIntent = new Intent(activity, ShareActivity.class);
shareIntent.setAction(Intent.ACTION_SEND);
switch(requestCode) {
case SELECT_FROM_GALLERY:
shareIntent.setType(activity.getContentResolver().getType(data.getData()));
shareIntent.putExtra(Intent.EXTRA_STREAM, data.getData());
shareIntent.putExtra(UploadService.EXTRA_SOURCE, Contribution.SOURCE_GALLERY);
break;
case SELECT_FROM_CAMERA:
shareIntent.setType("image/jpeg"); //FIXME: Find out appropriate mime type
shareIntent.putExtra(Intent.EXTRA_STREAM, lastGeneratedCaptureURI);
shareIntent.putExtra(UploadService.EXTRA_SOURCE, Contribution.SOURCE_CAMERA);
break;
}
activity.startActivity(shareIntent);
}
public void saveState(Bundle outState) {
outState.putParcelable("lastGeneratedCaptureURI", lastGeneratedCaptureURI);
}
public void loadState(Bundle savedInstanceState) {
if(savedInstanceState != null) {
lastGeneratedCaptureURI = (Uri) savedInstanceState.getParcelable("lastGeneratedCaptureURI");
}
}
}

View file

@ -0,0 +1,26 @@
package org.wikimedia.commons.contributions;
import android.view.View;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import org.wikimedia.commons.MediaWikiImageView;
import org.wikimedia.commons.R;
class ContributionViewHolder {
final MediaWikiImageView imageView;
final TextView titleView;
final TextView stateView;
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);
stateView = (TextView)parent.findViewById(R.id.contributionState);
seqNumView = (TextView)parent.findViewById(R.id.contributionSequenceNumber);
progressView = (ProgressBar)parent.findViewById(R.id.contributionProgress);
}
}

View file

@ -8,6 +8,7 @@ import android.support.v4.content.Loader;
import android.content.*;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.widget.CursorAdapter;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
@ -16,12 +17,15 @@ import com.actionbarsherlock.view.MenuItem;
import org.wikimedia.commons.*;
import org.wikimedia.commons.auth.*;
import org.wikimedia.commons.campaigns.Campaign;
import org.wikimedia.commons.media.*;
import org.wikimedia.commons.upload.UploadService;
import java.util.ArrayList;
public class ContributionsActivity
extends AuthenticatedActivity
implements LoaderManager.LoaderCallbacks<Cursor>,
implements LoaderManager.LoaderCallbacks<Object>,
AdapterView.OnItemClickListener,
MediaDetailPagerFragment.MediaDetailProvider,
FragmentManager.OnBackStackChangedListener {
@ -31,6 +35,8 @@ public class ContributionsActivity
private ContributionsListFragment contributionsList;
private MediaDetailPagerFragment mediaDetails;
private Campaign campaign;
public ContributionsActivity() {
super(WikiAccountAuthenticator.COMMONS_ACCOUNT_TYPE);
}
@ -101,6 +107,10 @@ public class ContributionsActivity
setTitle(R.string.title_activity_contributions);
setContentView(R.layout.activity_contributions);
if(getIntent().hasExtra("campaign")) {
this.campaign = (Campaign) getIntent().getSerializableExtra("campaign");
}
contributionsList = (ContributionsListFragment)getSupportFragmentManager().findFragmentById(R.id.contributionsListFragment);
getSupportFragmentManager().addOnBackStackChangedListener(this);
@ -180,13 +190,7 @@ public class ContributionsActivity
public void onItemClick(AdapterView<?> adapterView, View view, int position, long item) {
Cursor cursor = (Cursor)adapterView.getItemAtPosition(position);
Contribution c = Contribution.fromCursor(cursor);
Log.d("Commons", "Clicking for " + c.toContentValues());
showDetail(position);
Log.d("Commons", "You clicked on:" + c.toContentValues().toString());
}
@Override
@ -194,31 +198,51 @@ public class ContributionsActivity
return super.onCreateOptionsMenu(menu);
}
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
public Loader onCreateLoader(int i, Bundle bundle) {
if(campaign == null) {
return new CursorLoader(this, ContributionsContentProvider.BASE_URI, Contribution.Table.ALL_FIELDS, CONTRIBUTION_SELECTION, null, CONTRIBUTION_SORT);
} else {
return new CategoryImagesLoader(this, campaign.getTrackingCategory());
}
}
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
allContributions = cursor;
contributionsList.setCursor(cursor);
public void onLoadFinished(Loader cursorLoader, Object result) {
if(campaign == null) {
Cursor cursor = (Cursor) result;
if(contributionsList.getAdapter() == null) {
contributionsList.setAdapter(new ContributionsListAdapter(this, cursor, 0));
} else {
((CursorAdapter)contributionsList.getAdapter()).swapCursor(cursor);
}
getSupportActionBar().setSubtitle(getResources().getQuantityString(R.plurals.contributions_subtitle, cursor.getCount(), cursor.getCount()));
} else {
contributionsList.setAdapter(new MediaListAdapter(this, (ArrayList<Media>) result));
}
}
public void onLoaderReset(Loader<Cursor> cursorLoader) {
contributionsList.setCursor(null);
public void onLoaderReset(Loader cursorLoader) {
if(campaign == null) {
((CursorAdapter) contributionsList.getAdapter()).swapCursor(null);
} else {
//((MediaListAdapter) contributionsList.getAdapter()).
// DO SOMETHING!
}
}
public Media getMediaAtPosition(int i) {
allContributions.moveToPosition(i);
return Contribution.fromCursor(allContributions);
if(campaign == null) {
return Contribution.fromCursor((Cursor) contributionsList.getAdapter().getItem(i));
} else {
return (Media) contributionsList.getAdapter().getItem(i);
}
}
public int getTotalMediaCount() {
if(allContributions == null) {
if(contributionsList.getAdapter() == null) {
return 0;
}
return allContributions.getCount();
return contributionsList.getAdapter().getCount();
}
public void notifyDatasetChanged() {

View file

@ -0,0 +1,107 @@
package org.wikimedia.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.actionbarsherlock.app.SherlockFragment;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.assist.SimpleImageLoadingListener;
import org.wikimedia.commons.CommonsApplication;
import org.wikimedia.commons.MediaWikiImageView;
import org.wikimedia.commons.R;
import org.wikimedia.commons.Utils;
class ContributionsListAdapter extends CursorAdapter {
private DisplayImageOptions contributionDisplayOptions = Utils.getGenericDisplayOptions().build();;
private Activity activity;
public ContributionsListAdapter(Activity activity, Cursor c, int flags) {
super(activity, c, flags);
this.activity = activity;
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
View parent = activity.getLayoutInflater().inflate(R.layout.layout_contribution, viewGroup, false);
parent.setTag(new ContributionViewHolder(parent));
return parent;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
final ContributionViewHolder views = (ContributionViewHolder)view.getTag();
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 = (MediaWikiImageView)views.imageView;
mwImageView.setMedia(contribution, ((CommonsApplication) activity.getApplicationContext()).getImageLoader());
// FIXME: For transparent images
} else {
com.nostra13.universalimageloader.core.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);
}
});
}
views.url = actualUrl;
}
BitmapDrawable actualImageDrawable = (BitmapDrawable)views.imageView.getDrawable();
if(actualImageDrawable != null && actualImageDrawable.getBitmap() != null && actualImageDrawable.getBitmap().hasAlpha()) {
views.imageView.setBackgroundResource(android.R.color.white);
} else {
views.imageView.setBackgroundDrawable(null);
}
views.titleView.setText(contribution.getDisplayTitle());
views.seqNumView.setText(String.valueOf(cursor.getPosition() + 1));
views.seqNumView.setVisibility(View.VISIBLE);
switch(contribution.getState()) {
case Contribution.STATE_COMPLETED:
views.stateView.setVisibility(View.GONE);
views.progressView.setVisibility(View.GONE);
views.stateView.setText("");
break;
case Contribution.STATE_QUEUED:
views.stateView.setVisibility(View.VISIBLE);
views.progressView.setVisibility(View.GONE);
views.stateView.setText(R.string.contribution_state_queued);
break;
case Contribution.STATE_IN_PROGRESS:
views.stateView.setVisibility(View.GONE);
views.progressView.setVisibility(View.VISIBLE);
long total = contribution.getDataLength();
long transferred = contribution.getTransferred();
if(transferred == 0 || transferred >= total) {
views.progressView.setIndeterminate(true);
} else {
views.progressView.setProgress((int)(((double)transferred / (double)total) * 100));
}
break;
case Contribution.STATE_FAILED:
views.stateView.setVisibility(View.VISIBLE);
views.stateView.setText(R.string.contribution_state_failed);
views.progressView.setVisibility(View.GONE);
break;
}
}
}

View file

@ -5,14 +5,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.v4.widget.CursorAdapter;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@ -23,226 +16,55 @@ import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import java.io.*;
import java.util.*;
import com.nostra13.universalimageloader.core.assist.SimpleImageLoadingListener;
import org.wikimedia.commons.*;
import org.wikimedia.commons.R;
import org.wikimedia.commons.upload.ShareActivity;
import org.wikimedia.commons.upload.UploadService;
public class ContributionsListFragment extends SherlockFragment {
private final static int SELECT_FROM_GALLERY = 1;
private final static int SELECT_FROM_CAMERA = 2;
private GridView contributionsList;
private TextView waitingMessage;
private TextView emptyMessage;
private ContributionsListAdapter contributionsAdapter;
private DisplayImageOptions contributionDisplayOptions;
private Cursor allContributions;
private static class ContributionViewHolder {
final ImageView imageView;
final TextView titleView;
final TextView stateView;
final TextView seqNumView;
final ProgressBar progressView;
String url;
ContributionViewHolder(View parent) {
imageView = (ImageView)parent.findViewById(R.id.contributionImage);
titleView = (TextView)parent.findViewById(R.id.contributionTitle);
stateView = (TextView)parent.findViewById(R.id.contributionState);
seqNumView = (TextView)parent.findViewById(R.id.contributionSequenceNumber);
progressView = (ProgressBar)parent.findViewById(R.id.contributionProgress);
}
}
private class ContributionsListAdapter extends CursorAdapter {
public ContributionsListAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
View parent = getActivity().getLayoutInflater().inflate(R.layout.layout_contribution, viewGroup, false);
parent.setTag(new ContributionViewHolder(parent));
return parent;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
// hack: hide the 'first sync' message once we've loaded a cell
clearSyncMessage();
final ContributionViewHolder views = (ContributionViewHolder)view.getTag();
Contribution contribution = Contribution.fromCursor(cursor);
String actualUrl = TextUtils.isEmpty(contribution.getImageUrl()) ? contribution.getLocalUri().toString() : contribution.getThumbnailUrl(320);
if(views.url == null || !views.url.equals(actualUrl)) {
if(actualUrl.startsWith("http")) {
MediaWikiImageView mwImageView = (MediaWikiImageView)views.imageView;
mwImageView.setMedia(contribution, ((CommonsApplication) getActivity().getApplicationContext()).getImageLoader());
// FIXME: For transparent images
} else {
com.nostra13.universalimageloader.core.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);
}
});
}
views.url = actualUrl;
}
BitmapDrawable actualImageDrawable = (BitmapDrawable)views.imageView.getDrawable();
if(actualImageDrawable != null && actualImageDrawable.getBitmap() != null && actualImageDrawable.getBitmap().hasAlpha()) {
views.imageView.setBackgroundResource(android.R.color.white);
} else {
views.imageView.setBackgroundDrawable(null);
}
views.titleView.setText(contribution.getDisplayTitle());
views.seqNumView.setText(String.valueOf(cursor.getPosition() + 1));
views.seqNumView.setVisibility(View.VISIBLE);
switch(contribution.getState()) {
case Contribution.STATE_COMPLETED:
views.stateView.setVisibility(View.GONE);
views.progressView.setVisibility(View.GONE);
views.stateView.setText("");
break;
case Contribution.STATE_QUEUED:
views.stateView.setVisibility(View.VISIBLE);
views.progressView.setVisibility(View.GONE);
views.stateView.setText(R.string.contribution_state_queued);
break;
case Contribution.STATE_IN_PROGRESS:
views.stateView.setVisibility(View.GONE);
views.progressView.setVisibility(View.VISIBLE);
long total = contribution.getDataLength();
long transferred = contribution.getTransferred();
if(transferred == 0 || transferred >= total) {
views.progressView.setIndeterminate(true);
} else {
views.progressView.setProgress((int)(((double)transferred / (double)total) * 100));
}
break;
case Contribution.STATE_FAILED:
views.stateView.setVisibility(View.VISIBLE);
views.stateView.setText(R.string.contribution_state_failed);
views.progressView.setVisibility(View.GONE);
break;
}
}
}
private ContributionController controller;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_contributions, container, false);
}
public void setCursor(Cursor cursor) {
if(allContributions == null) {
contributionsAdapter = new ContributionsListAdapter(this.getActivity(), cursor, 0);
contributionsList.setAdapter(contributionsAdapter);
public ListAdapter getAdapter() {
return contributionsList.getAdapter();
}
allContributions = cursor;
contributionsAdapter.swapCursor(cursor);
public void setAdapter(ListAdapter adapter) {
this.contributionsList.setAdapter(adapter);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
controller.saveState(outState);
outState.putInt("grid-position", contributionsList.getFirstVisiblePosition());
outState.putParcelable("lastGeneratedCaptureURI", lastGeneratedCaptureURI);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch(requestCode) {
case SELECT_FROM_GALLERY:
if(resultCode == Activity.RESULT_OK) {
Intent shareIntent = new Intent(getActivity(), ShareActivity.class);
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.setType(getActivity().getContentResolver().getType(data.getData()));
shareIntent.putExtra(Intent.EXTRA_STREAM, data.getData());
shareIntent.putExtra(UploadService.EXTRA_SOURCE, Contribution.SOURCE_GALLERY);
startActivity(shareIntent);
}
break;
case SELECT_FROM_CAMERA:
if(resultCode == Activity.RESULT_OK) {
Intent shareIntent = new Intent(getActivity(), ShareActivity.class);
shareIntent.setAction(Intent.ACTION_SEND);
Log.d("Commons", "Uri is " + lastGeneratedCaptureURI);
shareIntent.setType("image/jpeg"); //FIXME: Find out appropriate mime type
shareIntent.putExtra(Intent.EXTRA_STREAM, lastGeneratedCaptureURI);
shareIntent.putExtra(UploadService.EXTRA_SOURCE, Contribution.SOURCE_CAMERA);
startActivity(shareIntent);
}
break;
controller.handleImagePicked(requestCode, data);
}
}
// See http://stackoverflow.com/a/5054673/17865 for why this is done
private Uri lastGeneratedCaptureURI;
private void reGenerateImageCaptureURI() {
String storageState = Environment.getExternalStorageState();
if(storageState.equals(Environment.MEDIA_MOUNTED)) {
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Commons/images/" + new Date().getTime() + ".jpg";
File _photoFile = new File(path);
try {
if(_photoFile.exists() == false) {
_photoFile.getParentFile().mkdirs();
_photoFile.createNewFile();
}
} catch (IOException e) {
Log.e("Commons", "Could not create file: " + path, e);
}
lastGeneratedCaptureURI = Uri.fromFile(_photoFile);
} else {
throw new RuntimeException("No external storage found!");
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
case R.id.menu_from_gallery:
Intent pickImageIntent = new Intent(Intent.ACTION_GET_CONTENT);
pickImageIntent.setType("image/*");
startActivityForResult(pickImageIntent, SELECT_FROM_GALLERY);
controller.startGalleryPick();
return true;
case R.id.menu_from_camera:
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
reGenerateImageCaptureURI();
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, lastGeneratedCaptureURI);
startActivityForResult(takePictureIntent, SELECT_FROM_CAMERA);
controller.startCameraCapture();
return true;
case R.id.menu_settings:
Intent settingsIntent = new Intent(getActivity(), SettingsActivity.class);
@ -282,19 +104,25 @@ public class ContributionsListFragment extends SherlockFragment {
setHasOptionsMenu(true);
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
controller = new ContributionController(this);
controller.loadState(savedInstanceState);
contributionsList = (GridView)getView().findViewById(R.id.contributionsList);
waitingMessage = (TextView)getView().findViewById(R.id.waitingMessage);
emptyMessage = (TextView)getView().findViewById(R.id.waitingMessage);
contributionDisplayOptions = Utils.getGenericDisplayOptions().build();
contributionsList.setOnItemClickListener((AdapterView.OnItemClickListener)getActivity());
if(savedInstanceState != null) {
Log.d("Commons", "Scrolling to " + savedInstanceState.getInt("grid-position"));
lastGeneratedCaptureURI = (Uri) savedInstanceState.getParcelable("lastGeneratedCaptureURI");
contributionsList.setSelection(savedInstanceState.getInt("grid-position"));
}

View file

@ -0,0 +1,47 @@
package org.wikimedia.commons.contributions;
import android.app.Activity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import com.android.volley.toolbox.ImageLoader;
import org.wikimedia.commons.CommonsApplication;
import org.wikimedia.commons.Media;
import org.wikimedia.commons.R;
import java.util.ArrayList;
public class MediaListAdapter extends BaseAdapter {
private ArrayList<Media> mediaList;
private Activity activity;
public MediaListAdapter(Activity activity, ArrayList<Media> mediaList) {
this.mediaList = mediaList;
this.activity = activity;
}
public int getCount() {
return mediaList.size();
}
public Object getItem(int i) {
return mediaList.get(i);
}
public long getItemId(int i) {
return i;
}
public View getView(int i, View view, ViewGroup viewGroup) {
if(view == null) {
view = activity.getLayoutInflater().inflate(R.layout.layout_contribution, null, false);
view.setTag(new ContributionViewHolder(view));
}
Media m = (Media) getItem(i);
ContributionViewHolder holder = (ContributionViewHolder) view.getTag();
holder.imageView.setMedia(m, ((CommonsApplication)activity.getApplicationContext()).getImageLoader());
holder.titleView.setText(m.getDisplayTitle());
return view;
}
}

View file

@ -3,6 +3,7 @@ package org.wikimedia.commons.data;
import android.content.*;
import android.database.sqlite.*;
import org.wikimedia.commons.campaigns.Campaign;
import org.wikimedia.commons.category.Category;
import org.wikimedia.commons.contributions.*;
import org.wikimedia.commons.modifications.ModifierSequence;
@ -21,6 +22,7 @@ public class DBOpenHelper extends SQLiteOpenHelper{
Contribution.Table.onCreate(sqLiteDatabase);
ModifierSequence.Table.onCreate(sqLiteDatabase);
Category.Table.onCreate(sqLiteDatabase);
Campaign.Table.onCreate(sqLiteDatabase);
}
@Override
@ -28,5 +30,6 @@ public class DBOpenHelper extends SQLiteOpenHelper{
Contribution.Table.onUpdate(sqLiteDatabase, from, to);
ModifierSequence.Table.onUpdate(sqLiteDatabase, from, to);
Category.Table.onUpdate(sqLiteDatabase, from, to);
Campaign.Table.onUpdate(sqLiteDatabase, from, to);
}
}

View file

@ -0,0 +1,57 @@
package org.wikimedia.commons.media;
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
import android.util.Log;
import org.mediawiki.api.ApiResult;
import org.wikimedia.commons.CommonsApplication;
import org.wikimedia.commons.Media;
import org.wikimedia.commons.Utils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class CategoryImagesLoader extends AsyncTaskLoader<List<Media>>{
private final CommonsApplication app;
private final String category;
public CategoryImagesLoader(Context context, String category) {
super(context);
this.app = (CommonsApplication) context.getApplicationContext();
this.category = category;
}
@Override
protected void onStartLoading() {
super.onStartLoading();
super.forceLoad();
}
@Override
public List<Media> loadInBackground() {
ArrayList<Media> mediaList = new ArrayList<Media>();
ApiResult result;
try {
result = app.getApi().action("query")
.param("list", "categorymembers")
.param("cmtitle", "Category:" + category)
.param("cmprop", "title|timestamp")
.param("cmtype", "file")
.param("cmsort", "timestamp")
.param("cmdir", "descending")
.param("cmlimit", 50)
.get();
} catch (IOException e) {
throw new RuntimeException(e);
}
Log.d("Commons", Utils.getStringFromDOM(result.getDocument()));
List<ApiResult> members = result.getNodes("/api/query/categorymembers/cm");
for(ApiResult member : members) {
mediaList.add(new Media(member.getString("@title")));
}
return mediaList;
}
}

View file

@ -148,7 +148,7 @@ public class MediaDetailFragment extends SherlockFragment {
*/
String actualUrl = TextUtils.isEmpty(media.getImageUrl()) ? media.getLocalUri().toString() : media.getThumbnailUrl(640);
String actualUrl = (media.getLocalUri() != null && TextUtils.isEmpty(media.getLocalUri().toString())) ? media.getLocalUri().toString() : media.getThumbnailUrl(640);
if(actualUrl.startsWith("http")) {
ImageLoader loader = ((CommonsApplication)getActivity().getApplicationContext()).getImageLoader();
MediaWikiImageView mwImage = (MediaWikiImageView)image;

View file

@ -38,6 +38,7 @@ public class MultipleShareActivity
private MediaDetailPagerFragment mediaDetails;
private CategorizationFragment categorizationFragment;
private UploadController uploadController;
public MultipleShareActivity() {
super(WikiAccountAuthenticator.COMMONS_ACCOUNT_TYPE);
@ -67,8 +68,29 @@ public class MultipleShareActivity
}
public void OnMultipleUploadInitiated() {
StartMultipleUploadTask startUploads = new StartMultipleUploadTask();
Utils.executeAsyncTask(startUploads);
final ProgressDialog dialog = new ProgressDialog(MultipleShareActivity.this);
dialog.setIndeterminate(false);
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setMax(photosList.size());
dialog.setTitle(getResources().getQuantityString(R.plurals.starting_multiple_uploads, photosList.size(), photosList.size()));
dialog.show();
for(int i = 0; i < photosList.size(); i++) {
Contribution up = photosList.get(i);
final int uploadCount = i + 1; // Goddamn Java
uploadController.startUpload(up, new UploadController.ContributionUploadProgress() {
public void onUploadStarted(Contribution contribution) {
dialog.setProgress(uploadCount);
if(uploadCount == photosList.size()) {
dialog.dismiss();
Toast startingToast = Toast.makeText(getApplicationContext(), R.string.uploading_started, Toast.LENGTH_LONG);
startingToast.show();
}
}
});
}
uploadsList.setImageOnlyMode(true);
categorizationFragment = (CategorizationFragment) this.getSupportFragmentManager().findFragmentByTag("categorization");
@ -112,75 +134,6 @@ public class MultipleShareActivity
finish();
}
private class StartMultipleUploadTask extends AsyncTask<Void, Integer, Void> {
ProgressDialog dialog;
@Override
protected Void doInBackground(Void... voids) {
for(int i = 0; i < photosList.size(); i++) {
Contribution up = photosList.get(i);
String curMimetype = (String)up.getTag("mimeType");
if(curMimetype == null || TextUtils.isEmpty(curMimetype) || curMimetype.endsWith("*")) {
String mimeType = getContentResolver().getType(up.getLocalUri());
if(mimeType != null) {
up.setTag("mimeType", mimeType);
}
}
StartUploadTask startUploadTask = new StartUploadTask(MultipleShareActivity.this, uploadService, up);
try {
Utils.executeAsyncTask(startUploadTask);
startUploadTask.get();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
this.publishProgress(i);
}
return null;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
dialog = new ProgressDialog(MultipleShareActivity.this);
dialog.setIndeterminate(false);
dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
dialog.setMax(photosList.size());
dialog.setTitle(getResources().getQuantityString(R.plurals.starting_multiple_uploads, photosList.size(), photosList.size()));
dialog.show();
}
@Override
protected void onProgressUpdate(Integer... values) {
dialog.setProgress(values[0]);
}
@Override
protected void onPostExecute(Void aVoid) {
dialog.dismiss();
Toast startingToast = Toast.makeText(getApplicationContext(), R.string.uploading_started, Toast.LENGTH_LONG);
startingToast.show();
}
}
private UploadService uploadService;
private boolean isUploadServiceConnected;
private ServiceConnection uploadServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName componentName, IBinder binder) {
uploadService = (UploadService) ((HandlerService.HandlerServiceLocalBinder)binder).getService();
isUploadServiceConnected = true;
}
public void onServiceDisconnected(ComponentName componentName) {
// this should never happen
throw new RuntimeException("UploadService died but the rest of the process did not!");
}
};
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
@ -196,6 +149,7 @@ public class MultipleShareActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
uploadController = new UploadController(this);
setContentView(R.layout.activity_multiple_uploads);
app = (CommonsApplication)this.getApplicationContext();
@ -213,9 +167,7 @@ public class MultipleShareActivity
@Override
protected void onDestroy() {
super.onDestroy();
if(isUploadServiceConnected) {
unbindService(uploadServiceConnection);
}
uploadController.cleanup();
}
private void showDetail(int i) {
@ -267,11 +219,7 @@ public class MultipleShareActivity
.commit();
}
setTitle(getResources().getQuantityString(R.plurals.multiple_uploads_title, photosList.size(), photosList.size()));
Intent uploadServiceIntent = new Intent(getApplicationContext(), UploadService.class);
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
startService(uploadServiceIntent);
bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE);
uploadController.prepareService();
}
}

View file

@ -44,23 +44,17 @@ public class ShareActivity
private ImageView backgroundImageView;
private UploadService uploadService;
private boolean isUploadServiceConnected;
private ServiceConnection uploadServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName componentName, IBinder binder) {
uploadService = (UploadService) ((HandlerService.HandlerServiceLocalBinder)binder).getService();
isUploadServiceConnected = true;
}
public void onServiceDisconnected(ComponentName componentName) {
// this should never happen
throw new RuntimeException("UploadService died but the rest of the process did not!");
}
};
private UploadController uploadController;
public void uploadActionInitiated(String title, String description) {
StartUploadTask task = new SingleStartUploadTask(ShareActivity.this, uploadService, title, mediaUri, description, mimeType, source);
task.execute();
Toast startingToast = Toast.makeText(getApplicationContext(), R.string.uploading_started, Toast.LENGTH_LONG);
startingToast.show();
uploadController.startUpload(title, mediaUri, description, mimeType, source, new UploadController.ContributionUploadProgress() {
public void onUploadStarted(Contribution contribution) {
ShareActivity.this.contribution = contribution;
showPostUpload();
}
});
}
private void showPostUpload() {
@ -96,26 +90,6 @@ public class ShareActivity
finish();
}
private class SingleStartUploadTask extends StartUploadTask {
private SingleStartUploadTask(Activity context, UploadService uploadService, String rawTitle, Uri mediaUri, String description, String mimeType, String source) {
super(context, uploadService, rawTitle, mediaUri, description, mimeType, source);
}
@Override
protected void onPreExecute() {
Toast startingToast = Toast.makeText(getApplicationContext(), R.string.uploading_started, Toast.LENGTH_LONG);
startingToast.show();
}
@Override
protected void onPostExecute(Contribution contribution) {
super.onPostExecute(contribution);
ShareActivity.this.contribution = contribution;
showPostUpload();
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
@ -161,11 +135,7 @@ public class ShareActivity
.commit();
}
Intent uploadServiceIntent = new Intent(getApplicationContext(), UploadService.class);
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
startService(uploadServiceIntent);
bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE);
uploadController.prepareService();
}
@Override
@ -179,7 +149,7 @@ public class ShareActivity
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
uploadController = new UploadController(this);
setContentView(R.layout.activity_share);
app = (CommonsApplication)this.getApplicationContext();
@ -211,9 +181,7 @@ public class ShareActivity
@Override
protected void onDestroy() {
super.onDestroy();
if(isUploadServiceConnected) {
unbindService(uploadServiceConnection);
}
uploadController.cleanup();
}
@Override

View file

@ -1,115 +0,0 @@
package org.wikimedia.commons.upload;
import android.app.*;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.*;
import android.os.*;
import android.preference.PreferenceManager;
import android.provider.*;
import android.text.TextUtils;
import android.webkit.MimeTypeMap;
import java.io.*;
import java.util.*;
import org.wikimedia.commons.CommonsApplication;
import org.wikimedia.commons.Prefs;
import org.wikimedia.commons.Utils;
import org.wikimedia.commons.contributions.*;
public class StartUploadTask extends AsyncTask<Void, Void, Contribution> {
private Activity context;
private UploadService uploadService;
private Contribution contribution;
private CommonsApplication app;
public StartUploadTask(Activity context, UploadService uploadService, String rawTitle, Uri mediaUri, String description, String mimeType, String source) {
this.context = context;
this.uploadService = uploadService;
app = (CommonsApplication)context.getApplicationContext();
String title = rawTitle;
String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
// People are used to ".jpg" more than ".jpeg" which the system gives us.
if (extension != null && extension.toLowerCase().equals("jpeg")) {
extension = "jpg";
}
if(extension != null && !title.toLowerCase().endsWith(extension.toLowerCase())) {
title += "." + extension;
}
contribution = new Contribution(mediaUri, null, title, description, -1, null, null, app.getCurrentAccount().name, CommonsApplication.DEFAULT_EDIT_SUMMARY);
contribution.setTag("mimeType", mimeType);
contribution.setSource(source);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
String license = prefs.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA);
contribution.setLicense(license);
}
public StartUploadTask(Activity context, UploadService uploadService, Contribution contribution) {
this.context = context;
this.uploadService = uploadService;
this.contribution = contribution;
// Set things that have not been set!
if(contribution.getLicense() == null) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
String license = prefs.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA);
contribution.setLicense(license);
}
app = (CommonsApplication)context.getApplicationContext();
}
@Override
protected Contribution doInBackground(Void... voids) {
String title = contribution.getFilename();
long length;
try {
if(contribution.getDataLength() <= 0) {
length = context.getContentResolver().openAssetFileDescriptor(contribution.getLocalUri(), "r").getLength();
if(length == -1) {
// Let us find out the long way!
length = Utils.countBytes(context.getContentResolver().openInputStream(contribution.getLocalUri()));
}
contribution.setDataLength(length);
}
} catch(IOException e) {
throw new RuntimeException(e);
}
if(TextUtils.isEmpty(contribution.getCreator())) {
contribution.setCreator(app.getCurrentAccount().name);
}
if(contribution.getDescription() == null) {
contribution.setDescription("");
}
String mimeType = (String)contribution.getTag("mimeType");
if(mimeType.startsWith("image/") && contribution.getDateCreated() == null) {
Cursor cursor = context.getContentResolver().query(contribution.getLocalUri(),
new String[]{MediaStore.Images.ImageColumns.DATE_TAKEN}, null, null, null);
if(cursor != null && cursor.getCount() != 0) {
cursor.moveToFirst();
contribution.setDateCreated(new Date(cursor.getLong(0)));
} // FIXME: Alternate way of setting dateCreated if this data is not found
}
return contribution;
}
@Override
protected void onPostExecute(Contribution contribution) {
uploadService.queue(UploadService.ACTION_UPLOAD_FILE, contribution);
}
}

View file

@ -0,0 +1,161 @@
package org.wikimedia.commons.upload;
import android.app.Activity;
import android.content.*;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.webkit.MimeTypeMap;
import org.wikimedia.commons.CommonsApplication;
import org.wikimedia.commons.HandlerService;
import org.wikimedia.commons.Prefs;
import org.wikimedia.commons.Utils;
import org.wikimedia.commons.campaigns.Campaign;
import org.wikimedia.commons.campaigns.CampaignContribution;
import org.wikimedia.commons.contributions.Contribution;
import java.io.IOException;
import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
public class UploadController {
private UploadService uploadService;
private final Activity activity;
private Campaign campaign;
final CommonsApplication app;
public interface ContributionUploadProgress {
void onUploadStarted(Contribution contribution);
}
public UploadController(Activity activity) {
this.activity = activity;
app = (CommonsApplication)activity.getApplicationContext();
}
public UploadController(Activity activity, Campaign campaign) {
this(activity);
this.campaign = campaign;
}
private boolean isUploadServiceConnected;
private ServiceConnection uploadServiceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName componentName, IBinder binder) {
uploadService = (UploadService) ((HandlerService.HandlerServiceLocalBinder)binder).getService();
isUploadServiceConnected = true;
}
public void onServiceDisconnected(ComponentName componentName) {
// this should never happen
throw new RuntimeException("UploadService died but the rest of the process did not!");
}
};
public void prepareService() {
Intent uploadServiceIntent = new Intent(activity.getApplicationContext(), UploadService.class);
uploadServiceIntent.setAction(UploadService.ACTION_START_SERVICE);
activity.startService(uploadServiceIntent);
activity.bindService(uploadServiceIntent, uploadServiceConnection, Context.BIND_AUTO_CREATE);
}
public void cleanup() {
if(isUploadServiceConnected) {
activity.unbindService(uploadServiceConnection);
}
}
public void startUpload(String rawTitle, Uri mediaUri, String description, String mimeType, String source, ContributionUploadProgress onComplete) {
Contribution contribution;
String title = rawTitle;
String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
// People are used to ".jpg" more than ".jpeg" which the system gives us.
if (extension != null && extension.toLowerCase().equals("jpeg")) {
extension = "jpg";
}
if(extension != null && !title.toLowerCase().endsWith(extension.toLowerCase())) {
title += "." + extension;
}
if(campaign == null) {
contribution = new Contribution(mediaUri, null, title, description, -1, null, null, app.getCurrentAccount().name, CommonsApplication.DEFAULT_EDIT_SUMMARY);
} else {
contribution = new CampaignContribution(mediaUri, null, title, description, -1, null, null, app.getCurrentAccount().name, CommonsApplication.DEFAULT_EDIT_SUMMARY, campaign);
}
contribution.setTag("mimeType", mimeType);
contribution.setSource(source);
startUpload(contribution, onComplete);
}
public void startUpload(final Contribution contribution, final ContributionUploadProgress onComplete) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
if(TextUtils.isEmpty(contribution.getCreator())) {
contribution.setCreator(app.getCurrentAccount().name);
}
if(contribution.getDescription() == null) {
contribution.setDescription("");
}
String license = prefs.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA);
contribution.setLicense(license);
Utils.executeAsyncTask(new AsyncTask<Void, Void, Contribution>() {
// Fills up missing information about Contributions
// Only does things that involve some form of IO
// Runs in background thread
@Override
protected Contribution doInBackground(Void... voids /* stare into you */) {
long length;
try {
if(contribution.getDataLength() <= 0) {
length = activity.getContentResolver().openAssetFileDescriptor(contribution.getLocalUri(), "r").getLength();
if(length == -1) {
// Let us find out the long way!
length = Utils.countBytes(activity.getContentResolver().openInputStream(contribution.getLocalUri()));
}
contribution.setDataLength(length);
}
} catch(IOException e) {
throw new RuntimeException(e);
}
String mimeType = (String)contribution.getTag("mimeType");
if(mimeType == null || TextUtils.isEmpty(mimeType) || mimeType.endsWith("*")) {
mimeType = activity.getContentResolver().getType(contribution.getLocalUri());
if(mimeType != null) {
contribution.setTag("mimeType", mimeType);
}
}
if(mimeType.startsWith("image/") && contribution.getDateCreated() == null) {
Cursor cursor = activity.getContentResolver().query(contribution.getLocalUri(),
new String[]{MediaStore.Images.ImageColumns.DATE_TAKEN}, null, null, null);
if(cursor != null && cursor.getCount() != 0) {
cursor.moveToFirst();
contribution.setDateCreated(new Date(cursor.getLong(0)));
} // FIXME: Alternate way of setting dateCreated if this data is not found
}
return contribution;
}
@Override
protected void onPostExecute(Contribution contribution) {
super.onPostExecute(contribution);
uploadService.queue(UploadService.ACTION_UPLOAD_FILE, contribution);
onComplete.onUploadStarted(contribution);
}
});
}
}