From 0be6e87d5e06fcdc9a76cbc43c72e2a3e12fccf2 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 23 Jul 2013 16:16:42 +0530 Subject: [PATCH 01/14] Add Campaign & CampaignContribution classes Change-Id: Ib300e32b0d8146321b661ab5feb20ba9714bfd52 --- .../wikimedia/commons/campaigns/Campaign.java | 63 +++++++++++++++++++ .../campaigns/CampaignContribution.java | 40 ++++++++++++ .../commons/contributions/Contribution.java | 10 ++- 3 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 commons/src/main/java/org/wikimedia/commons/campaigns/Campaign.java create mode 100644 commons/src/main/java/org/wikimedia/commons/campaigns/CampaignContribution.java diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/Campaign.java b/commons/src/main/java/org/wikimedia/commons/campaigns/Campaign.java new file mode 100644 index 000000000..0eeb1dcef --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/Campaign.java @@ -0,0 +1,63 @@ +package org.wikimedia.commons.campaigns; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.ArrayList; + +public class Campaign { + private boolean enabled; + + private String autoAddWikitext; + private ArrayList autoAddCategories; + + private String name; + private String ownWorkLicenseDefault; + + private String defaultDescription; + + private JSONObject config; + + public boolean isEnabled() { + return enabled; + } + + public String getAutoAddWikitext() { + return autoAddWikitext; + } + + public ArrayList getAutoAddCategories() { + return autoAddCategories; + } + + public String getName() { + return name; + } + + public String getOwnWorkLicenseDefault() { + return ownWorkLicenseDefault; + } + + public String getDefaultDescription() { + return defaultDescription; + } + + public JSONObject getConfig() { + return config; + } + + public Campaign(String name, JSONObject config) { + this.config = config; + this.name = name; + if(config.has("autoAdd")) { + this.autoAddWikitext = config.optJSONObject("autoAdd").optString("wikitext", null); + if(config.optJSONObject("autoAdd").has("categories")) { + this.autoAddCategories = new ArrayList(); + JSONArray catsArray = config.optJSONObject("autoAdd").optJSONArray("categories"); + for(int i=0; i < catsArray.length(); i++) { + autoAddCategories.add(catsArray.optString(i)); + } + } + } + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignContribution.java b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignContribution.java new file mode 100644 index 000000000..1566c1430 --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignContribution.java @@ -0,0 +1,40 @@ +package org.wikimedia.commons.campaigns; + +import org.wikimedia.commons.contributions.Contribution; + +import java.util.ArrayList; + +public class CampaignContribution extends Contribution { + private Campaign campaign; + + private ArrayList fieldValues; + + 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"); + } + return buffer.toString(); + } + + @Override + public String getDescription() { + return super.getDescription(); + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/contributions/Contribution.java b/commons/src/main/java/org/wikimedia/commons/contributions/Contribution.java index f0f5a24bf..d482fea41 100644 --- a/commons/src/main/java/org/wikimedia/commons/contributions/Contribution.java +++ b/commons/src/main/java/org/wikimedia/commons/contributions/Contribution.java @@ -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("]]\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(); } From 27f4fe14818110a5035252385926f4da5af368d9 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Thu, 25 Jul 2013 19:49:41 +0530 Subject: [PATCH 02/14] Add Campaign Activity, Fragment and Fetcher Exposed as a separate launcher icon. Will add more cohesive UI in the future Change-Id: I9f15a9ac850d636d72b4892f2b5153f7e2b8ba28 --- commons/AndroidManifest.xml | 9 ++++ commons/res/layout/activity_campaigns.xml | 14 +++++ commons/res/layout/fragment_campaigns.xml | 18 +++++++ commons/res/layout/layout_campaign_item.xml | 12 +++++ .../commons/campaigns/CampaignActivity.java | 15 ++++++ .../campaigns/CampaignsListAdapter.java | 54 +++++++++++++++++++ .../campaigns/CampaignsListFragment.java | 39 ++++++++++++++ .../commons/campaigns/FetchCampaignsTask.java | 52 ++++++++++++++++++ 8 files changed, 213 insertions(+) create mode 100644 commons/res/layout/activity_campaigns.xml create mode 100644 commons/res/layout/fragment_campaigns.xml create mode 100644 commons/res/layout/layout_campaign_item.xml create mode 100644 commons/src/main/java/org/wikimedia/commons/campaigns/CampaignActivity.java create mode 100644 commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsListAdapter.java create mode 100644 commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsListFragment.java create mode 100644 commons/src/main/java/org/wikimedia/commons/campaigns/FetchCampaignsTask.java diff --git a/commons/AndroidManifest.xml b/commons/AndroidManifest.xml index a9b6f8d82..f3cb11fb4 100644 --- a/commons/AndroidManifest.xml +++ b/commons/AndroidManifest.xml @@ -82,6 +82,15 @@ android:label="@string/title_activity_settings" /> + + + + + + + + + + + + \ No newline at end of file diff --git a/commons/res/layout/fragment_campaigns.xml b/commons/res/layout/fragment_campaigns.xml new file mode 100644 index 000000000..3ff614f0e --- /dev/null +++ b/commons/res/layout/fragment_campaigns.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/commons/res/layout/layout_campaign_item.xml b/commons/res/layout/layout_campaign_item.xml new file mode 100644 index 000000000..79c0355dd --- /dev/null +++ b/commons/res/layout/layout_campaign_item.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignActivity.java b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignActivity.java new file mode 100644 index 000000000..d2ec06841 --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignActivity.java @@ -0,0 +1,15 @@ +package org.wikimedia.commons.campaigns; + +import android.app.Activity; +import android.os.Bundle; +import com.actionbarsherlock.app.SherlockFragmentActivity; +import org.wikimedia.commons.R; + +public class CampaignActivity extends SherlockFragmentActivity { + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_campaigns); + } + + +} \ No newline at end of file diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsListAdapter.java b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsListAdapter.java new file mode 100644 index 000000000..f1bc1fbeb --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsListAdapter.java @@ -0,0 +1,54 @@ +package org.wikimedia.commons.campaigns; + +import android.app.Activity; +import android.view.*; +import android.widget.*; +import org.wikimedia.commons.R; + +import java.util.ArrayList; + +public class CampaignsListAdapter extends BaseAdapter { + private ArrayList campaigns; + private Activity activity; + + public CampaignsListAdapter(Activity activity, ArrayList campaigns) { + this.campaigns = campaigns; + this.activity = activity; + } + + public ArrayList getCampaigns() { + return campaigns; + } + + public void setCampaigns(ArrayList campaigns) { + this.campaigns = campaigns; + } + + public int getCount() { + if(campaigns == null) { + return 0; + } + return campaigns.size(); + } + + public Object getItem(int i) { + return campaigns.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_campaign_item, null); + } + + TextView campaignName = (TextView)view.findViewById(R.id.campaignItemName); + + Campaign campaign = campaigns.get(i); + + campaignName.setText(campaign.getName()); + return view; + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsListFragment.java b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsListFragment.java new file mode 100644 index 000000000..51a2f4fa4 --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsListFragment.java @@ -0,0 +1,39 @@ +package org.wikimedia.commons.campaigns; + +import android.os.*; +import android.view.*; +import android.widget.*; +import com.actionbarsherlock.app.SherlockFragment; +import org.wikimedia.commons.*; + +import java.util.*; + +public class CampaignsListFragment extends SherlockFragment { + private ArrayList campaigns; + private CampaignsListAdapter listAdapter; + private GridView campaignsListView; + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_campaigns, container, false); + campaignsListView = (GridView)view.findViewById(R.id.campaignsList); + return view; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + listAdapter = new CampaignsListAdapter(getActivity(), campaigns); + campaignsListView.setAdapter(listAdapter); + Utils.executeAsyncTask(new FetchCampaignsTask(getActivity()){ + @Override + protected void onPostExecute(ArrayList campaigns) { + super.onPostExecute(campaigns); + CampaignsListFragment.this.campaigns = campaigns; + listAdapter.setCampaigns(campaigns); + listAdapter.notifyDataSetChanged(); + } + }); + } +} \ No newline at end of file diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/FetchCampaignsTask.java b/commons/src/main/java/org/wikimedia/commons/campaigns/FetchCampaignsTask.java new file mode 100644 index 000000000..8a822de42 --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/FetchCampaignsTask.java @@ -0,0 +1,52 @@ +package org.wikimedia.commons.campaigns; + +import android.content.Context; +import android.os.AsyncTask; +import android.util.Log; +import org.json.JSONException; +import org.json.JSONObject; +import org.mediawiki.api.ApiResult; +import org.mediawiki.api.MWApi; +import org.wikimedia.commons.CommonsApplication; +import org.wikimedia.commons.Utils; + +import java.io.IOException; +import java.util.ArrayList; + +public class FetchCampaignsTask extends AsyncTask>{ + + private Context context; + private MWApi api; + + public FetchCampaignsTask(Context context) { + this.context = context; + this.api = ((CommonsApplication)context.getApplicationContext()).getApi(); + } + + @Override + protected ArrayList doInBackground(Void... voids) { + ArrayList campaigns = new ArrayList(); + ApiResult result; + try { + result = api.action("query").param("prop", "revisions") + .param("rvprop", "content").param("generator", "allpages") + .param("gapnamespace", 460).param("gaplimit", 500).get(); //FIXME: Actually paginate! + } catch (IOException e) { + throw new RuntimeException(e); // FIXME: Do something else ;) + } + ArrayList pageNodes = result.getNodes("/api/query/pages/page"); + for(ApiResult pageNode : pageNodes) { + Log.d("Commons", Utils.getStringFromDOM(pageNode.getDocument())); + String configString = pageNode.getString("revisions/rev"); + String pageName = pageNode.getString("@title").split(":")[1]; + JSONObject config; + try { + config = new JSONObject(configString); + } catch (JSONException e) { + throw new RuntimeException(e); // NEVER HAPPENS! + } + campaigns.add(new Campaign(pageName, config)); + } + return campaigns; + } +} From 5f3132718edb8f38c86b35400cdda9ce5995e83e Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Thu, 25 Jul 2013 20:07:36 +0000 Subject: [PATCH 03/14] Split up contributions related code into separate classes A controller introduced to deal with common code for picking images from the gallery or camera. We should probably start using similar controller methods elsewhere. Splitting this particular one up because I'll be using it elsewhere GitHub: https://github.com/wikimedia/apps-android-commons/pull/39 Change-Id: Idc393414be921d9a0fd54fcb3e2dcd676d8cc08b --- .../contributions/ContributionController.java | 96 +++++++++ .../contributions/ContributionViewHolder.java | 25 +++ .../ContributionsListAdapter.java | 106 ++++++++++ .../ContributionsListFragment.java | 190 +----------------- 4 files changed, 237 insertions(+), 180 deletions(-) create mode 100644 commons/src/main/java/org/wikimedia/commons/contributions/ContributionController.java create mode 100644 commons/src/main/java/org/wikimedia/commons/contributions/ContributionViewHolder.java create mode 100644 commons/src/main/java/org/wikimedia/commons/contributions/ContributionsListAdapter.java diff --git a/commons/src/main/java/org/wikimedia/commons/contributions/ContributionController.java b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionController.java new file mode 100644 index 000000000..f6beac475 --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionController.java @@ -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"); + } + } + +} diff --git a/commons/src/main/java/org/wikimedia/commons/contributions/ContributionViewHolder.java b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionViewHolder.java new file mode 100644 index 000000000..bfa9bda3e --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionViewHolder.java @@ -0,0 +1,25 @@ +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.R; + +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); + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsListAdapter.java b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsListAdapter.java new file mode 100644 index 000000000..295f2566d --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsListAdapter.java @@ -0,0 +1,106 @@ +package org.wikimedia.commons.contributions; + +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 SherlockFragment fragment; + + public ContributionsListAdapter(SherlockFragment fragment, Cursor c, int flags) { + super(fragment.getActivity(), c, flags); + this.fragment = fragment; + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup viewGroup) { + View parent = fragment.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) { + 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) fragment.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; + } + + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsListFragment.java b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsListFragment.java index e8caa90b2..065f790d9 100644 --- a/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsListFragment.java +++ b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsListFragment.java @@ -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; @@ -25,135 +18,21 @@ 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) { @@ -162,7 +41,7 @@ public class ContributionsListFragment extends SherlockFragment { public void setCursor(Cursor cursor) { if(allContributions == null) { - contributionsAdapter = new ContributionsListAdapter(this.getActivity(), cursor, 0); + contributionsAdapter = new ContributionsListAdapter(this, cursor, 0); contributionsList.setAdapter(contributionsAdapter); } allContributions = cursor; @@ -172,77 +51,27 @@ public class ContributionsListFragment extends SherlockFragment { @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; + if(resultCode == Activity.RESULT_OK) { + 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); @@ -286,15 +115,16 @@ public class ContributionsListFragment extends SherlockFragment { 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")); } From 26b59085e5f4bec93503222dbe3716852f569785 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 30 Jul 2013 00:51:17 +0530 Subject: [PATCH 04/14] Update ActionBarSherlock to 4.4.0 Change-Id: I21ede463315ba66040252f776ab4e80caf09461d --- commons/pom.xml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/commons/pom.xml b/commons/pom.xml index 8db0b7fa6..3824a3e29 100644 --- a/commons/pom.xml +++ b/commons/pom.xml @@ -24,14 +24,8 @@ com.actionbarsherlock actionbarsherlock - 4.2.0 + 4.4.0 apklib - - - com.google.android - support-v4 - - android From e7a4672b82c02beb0d22a11eddaa6ab48fe6b8e1 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 30 Jul 2013 14:22:57 +0530 Subject: [PATCH 05/14] Add UploadController, move upload related functionality to it - Removes StartUploadActivity and children - All 'filling in' of missing contribution data is now done in one place - Campaigns are theoretically supported Change-Id: I18bac1b672e4a0f95cdcdc467dd137177feaf8e6 --- .../campaigns/CampaignContribution.java | 8 + .../commons/upload/MultipleShareActivity.java | 110 ++++-------- .../commons/upload/ShareActivity.java | 60 ++----- .../commons/upload/StartUploadTask.java | 115 ------------- .../commons/upload/UploadController.java | 162 ++++++++++++++++++ 5 files changed, 217 insertions(+), 238 deletions(-) delete mode 100644 commons/src/main/java/org/wikimedia/commons/upload/StartUploadTask.java create mode 100644 commons/src/main/java/org/wikimedia/commons/upload/UploadController.java diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignContribution.java b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignContribution.java index 1566c1430..111a2f910 100644 --- a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignContribution.java +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignContribution.java @@ -1,14 +1,22 @@ 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 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; } diff --git a/commons/src/main/java/org/wikimedia/commons/upload/MultipleShareActivity.java b/commons/src/main/java/org/wikimedia/commons/upload/MultipleShareActivity.java index 62b37482d..44e6a783a 100644 --- a/commons/src/main/java/org/wikimedia/commons/upload/MultipleShareActivity.java +++ b/commons/src/main/java/org/wikimedia/commons/upload/MultipleShareActivity.java @@ -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,33 @@ 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(); + } + } + + public boolean isJavaAPieceOfShit() { + return true; + } + }); + } + uploadsList.setImageOnlyMode(true); categorizationFragment = (CategorizationFragment) this.getSupportFragmentManager().findFragmentByTag("categorization"); @@ -112,75 +138,6 @@ public class MultipleShareActivity finish(); } - private class StartMultipleUploadTask extends AsyncTask { - - 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 +153,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 +171,7 @@ public class MultipleShareActivity @Override protected void onDestroy() { super.onDestroy(); - if(isUploadServiceConnected) { - unbindService(uploadServiceConnection); - } + uploadController.cleanup(); } private void showDetail(int i) { @@ -267,11 +223,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(); } } diff --git a/commons/src/main/java/org/wikimedia/commons/upload/ShareActivity.java b/commons/src/main/java/org/wikimedia/commons/upload/ShareActivity.java index 0fc955a4c..4010ad3cf 100644 --- a/commons/src/main/java/org/wikimedia/commons/upload/ShareActivity.java +++ b/commons/src/main/java/org/wikimedia/commons/upload/ShareActivity.java @@ -44,23 +44,21 @@ 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(); + } + + public boolean isJavaAPieceOfShit() { + return true; + } + }); } private void showPostUpload() { @@ -96,26 +94,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 +139,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 +153,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 +185,7 @@ public class ShareActivity @Override protected void onDestroy() { super.onDestroy(); - if(isUploadServiceConnected) { - unbindService(uploadServiceConnection); - } + uploadController.cleanup(); } @Override diff --git a/commons/src/main/java/org/wikimedia/commons/upload/StartUploadTask.java b/commons/src/main/java/org/wikimedia/commons/upload/StartUploadTask.java deleted file mode 100644 index 113eec975..000000000 --- a/commons/src/main/java/org/wikimedia/commons/upload/StartUploadTask.java +++ /dev/null @@ -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 { - - 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); - } -} \ No newline at end of file diff --git a/commons/src/main/java/org/wikimedia/commons/upload/UploadController.java b/commons/src/main/java/org/wikimedia/commons/upload/UploadController.java new file mode 100644 index 000000000..84f654951 --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/upload/UploadController.java @@ -0,0 +1,162 @@ +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; + final CommonsApplication app; + + public interface ContributionUploadProgress { + void onUploadStarted(Contribution contribution); + boolean isJavaAPieceOfShit(); + } + + public UploadController(Activity activity) { + this.activity = activity; + app = (CommonsApplication)activity.getApplicationContext(); + } + + 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); + } + } + + /* The JavaScript is Leaking!*/ + public void startUpload(String rawTitle, Uri mediaUri, String description, String mimeType, String source, ContributionUploadProgress onComplete) { + startUpload(rawTitle, mediaUri, description, mimeType, source, null, onComplete); + } + + public void startUpload(String rawTitle, Uri mediaUri, String description, String mimeType, String source, Campaign campaign, 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() { + + // 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); + assert onComplete.isJavaAPieceOfShit(); + onComplete.onUploadStarted(contribution); + } + }); + } + +} From a56fa072a101b3d11426c426b1721a61c43762e3 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Sun, 4 Aug 2013 18:03:07 +0530 Subject: [PATCH 06/14] Tone down the Java hate a little Change-Id: Ib1efbce3df6c136c423a566535c1b064a1c77b1b --- .../org/wikimedia/commons/upload/MultipleShareActivity.java | 4 ---- .../main/java/org/wikimedia/commons/upload/ShareActivity.java | 4 ---- .../java/org/wikimedia/commons/upload/UploadController.java | 2 -- 3 files changed, 10 deletions(-) diff --git a/commons/src/main/java/org/wikimedia/commons/upload/MultipleShareActivity.java b/commons/src/main/java/org/wikimedia/commons/upload/MultipleShareActivity.java index 44e6a783a..57bdab1c8 100644 --- a/commons/src/main/java/org/wikimedia/commons/upload/MultipleShareActivity.java +++ b/commons/src/main/java/org/wikimedia/commons/upload/MultipleShareActivity.java @@ -88,10 +88,6 @@ public class MultipleShareActivity startingToast.show(); } } - - public boolean isJavaAPieceOfShit() { - return true; - } }); } diff --git a/commons/src/main/java/org/wikimedia/commons/upload/ShareActivity.java b/commons/src/main/java/org/wikimedia/commons/upload/ShareActivity.java index 4010ad3cf..983018f20 100644 --- a/commons/src/main/java/org/wikimedia/commons/upload/ShareActivity.java +++ b/commons/src/main/java/org/wikimedia/commons/upload/ShareActivity.java @@ -54,10 +54,6 @@ public class ShareActivity ShareActivity.this.contribution = contribution; showPostUpload(); } - - public boolean isJavaAPieceOfShit() { - return true; - } }); } diff --git a/commons/src/main/java/org/wikimedia/commons/upload/UploadController.java b/commons/src/main/java/org/wikimedia/commons/upload/UploadController.java index 84f654951..00c9a41e5 100644 --- a/commons/src/main/java/org/wikimedia/commons/upload/UploadController.java +++ b/commons/src/main/java/org/wikimedia/commons/upload/UploadController.java @@ -31,7 +31,6 @@ public class UploadController { public interface ContributionUploadProgress { void onUploadStarted(Contribution contribution); - boolean isJavaAPieceOfShit(); } public UploadController(Activity activity) { @@ -153,7 +152,6 @@ public class UploadController { protected void onPostExecute(Contribution contribution) { super.onPostExecute(contribution); uploadService.queue(UploadService.ACTION_UPLOAD_FILE, contribution); - assert onComplete.isJavaAPieceOfShit(); onComplete.onUploadStarted(contribution); } }); From 2503f547315fe33a56973d3b8a94b0c9dff0d9eb Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Fri, 6 Sep 2013 05:54:13 +0530 Subject: [PATCH 07/14] Use a SyncProvider to sync all Campaigns The SyncService constantly deletes and re-creates the campaigns in the background to make sure they are up to date FIXME: Handle deleted or renamed campaigns Change-Id: I5d03995ada219481ea38887a8ea6d59fa11d2ac8 --- commons/AndroidManifest.xml | 18 ++ commons/res/layout/activity_campaigns.xml | 5 +- commons/res/values/strings.xml | 1 + commons/res/xml/campaigns_sync_adapter.xml | 9 + .../wikimedia/commons/campaigns/Campaign.java | 132 ++++++++++- .../commons/campaigns/CampaignActivity.java | 31 ++- .../campaigns/CampaignsContentProvider.java | 206 ++++++++++++++++++ .../campaigns/CampaignsListAdapter.java | 69 +++--- .../campaigns/CampaignsListFragment.java | 39 ---- .../campaigns/CampaignsSyncAdapter.java | 95 ++++++++ .../campaigns/CampaignsSyncService.java | 27 +++ .../commons/campaigns/FetchCampaignsTask.java | 52 ----- .../wikimedia/commons/data/DBOpenHelper.java | 3 + 13 files changed, 551 insertions(+), 136 deletions(-) create mode 100644 commons/res/xml/campaigns_sync_adapter.xml create mode 100644 commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsContentProvider.java delete mode 100644 commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsListFragment.java create mode 100644 commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsSyncAdapter.java create mode 100644 commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsSyncService.java delete mode 100644 commons/src/main/java/org/wikimedia/commons/campaigns/FetchCampaignsTask.java diff --git a/commons/AndroidManifest.xml b/commons/AndroidManifest.xml index f3cb11fb4..4e24f5d6a 100644 --- a/commons/AndroidManifest.xml +++ b/commons/AndroidManifest.xml @@ -105,6 +105,17 @@ android:name="android.accounts.AccountAuthenticator" android:resource="@xml/authenticator" /> + + + + + + @@ -136,6 +147,13 @@ android:authorities="org.wikimedia.commons.contributions.contentprovider" android:exported="false"> + + - diff --git a/commons/res/values/strings.xml b/commons/res/values/strings.xml index 80208b863..bac68d433 100644 --- a/commons/res/values/strings.xml +++ b/commons/res/values/strings.xml @@ -102,4 +102,5 @@ Avoid copyrighted materials you found from the Internet as well as images of posters, book covers, etc. You think you got it? Yes! + Campaigns diff --git a/commons/res/xml/campaigns_sync_adapter.xml b/commons/res/xml/campaigns_sync_adapter.xml new file mode 100644 index 000000000..778984c07 --- /dev/null +++ b/commons/res/xml/campaigns_sync_adapter.xml @@ -0,0 +1,9 @@ + + + diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/Campaign.java b/commons/src/main/java/org/wikimedia/commons/campaigns/Campaign.java index 0eeb1dcef..3eced64ef 100644 --- a/commons/src/main/java/org/wikimedia/commons/campaigns/Campaign.java +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/Campaign.java @@ -1,6 +1,10 @@ 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.util.ArrayList; @@ -17,16 +21,27 @@ public class Campaign { 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 getAutoAddCategories() { + if(!this.isParsed) { + this.parseConfig(); + } return autoAddCategories; } @@ -35,20 +50,32 @@ public class Campaign { } 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; } - public Campaign(String name, JSONObject config) { - this.config = config; - this.name = name; + 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")) { @@ -59,5 +86,104 @@ public class Campaign { } } } + 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_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; + } } } diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignActivity.java b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignActivity.java index d2ec06841..7b8489867 100644 --- a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignActivity.java +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignActivity.java @@ -1,15 +1,44 @@ package org.wikimedia.commons.campaigns; import android.app.Activity; +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.widget.ListView; import com.actionbarsherlock.app.SherlockFragmentActivity; import org.wikimedia.commons.R; -public class CampaignActivity extends SherlockFragmentActivity { +public class CampaignActivity + extends SherlockFragmentActivity + implements LoaderManager.LoaderCallbacks { + + private ListView campaignsListView; + private CampaignsListAdapter campaignsListAdapter; + private Cursor allCampaigns; + public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_campaigns); + campaignsListView = (ListView) findViewById(R.id.campaignsList); + getSupportLoaderManager().initLoader(0, null, this); } + public Loader onCreateLoader(int i, Bundle bundle) { + return new CursorLoader(this, CampaignsContentProvider.BASE_URI, Campaign.Table.ALL_FIELDS, "", null, ""); + } + public void onLoadFinished(Loader cursorLoader, Cursor cursor) { + if(campaignsListAdapter == null) { + campaignsListAdapter = new CampaignsListAdapter(this, cursor, 0); + campaignsListView.setAdapter(campaignsListAdapter); + } else { + campaignsListAdapter.swapCursor(cursor); + } + } + + public void onLoaderReset(Loader cursorLoader) { + campaignsListAdapter.swapCursor(null); + } } \ No newline at end of file diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsContentProvider.java b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsContentProvider.java new file mode 100644 index 000000000..80bd972bc --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsContentProvider.java @@ -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; + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsListAdapter.java b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsListAdapter.java index f1bc1fbeb..b43d38354 100644 --- a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsListAdapter.java +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsListAdapter.java @@ -1,54 +1,47 @@ package org.wikimedia.commons.campaigns; import android.app.Activity; -import android.view.*; -import android.widget.*; +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; -import java.util.ArrayList; +class CampaignsListAdapter extends CursorAdapter { -public class CampaignsListAdapter extends BaseAdapter { - private ArrayList campaigns; + private DisplayImageOptions contributionDisplayOptions = Utils.getGenericDisplayOptions().build();; private Activity activity; - public CampaignsListAdapter(Activity activity, ArrayList campaigns) { - this.campaigns = campaigns; + public CampaignsListAdapter(Activity activity, Cursor c, int flags) { + super(activity, c, flags); this.activity = activity; } - public ArrayList getCampaigns() { - return campaigns; + @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; } - public void setCampaigns(ArrayList campaigns) { - this.campaigns = campaigns; + @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()); } - public int getCount() { - if(campaigns == null) { - return 0; - } - return campaigns.size(); - } - - public Object getItem(int i) { - return campaigns.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_campaign_item, null); - } - - TextView campaignName = (TextView)view.findViewById(R.id.campaignItemName); - - Campaign campaign = campaigns.get(i); - - campaignName.setText(campaign.getName()); - return view; - } } diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsListFragment.java b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsListFragment.java deleted file mode 100644 index 51a2f4fa4..000000000 --- a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsListFragment.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.wikimedia.commons.campaigns; - -import android.os.*; -import android.view.*; -import android.widget.*; -import com.actionbarsherlock.app.SherlockFragment; -import org.wikimedia.commons.*; - -import java.util.*; - -public class CampaignsListFragment extends SherlockFragment { - private ArrayList campaigns; - private CampaignsListAdapter listAdapter; - private GridView campaignsListView; - - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.fragment_campaigns, container, false); - campaignsListView = (GridView)view.findViewById(R.id.campaignsList); - return view; - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - listAdapter = new CampaignsListAdapter(getActivity(), campaigns); - campaignsListView.setAdapter(listAdapter); - Utils.executeAsyncTask(new FetchCampaignsTask(getActivity()){ - @Override - protected void onPostExecute(ArrayList campaigns) { - super.onPostExecute(campaigns); - CampaignsListFragment.this.campaigns = campaigns; - listAdapter.setCampaigns(campaigns); - listAdapter.notifyDataSetChanged(); - } - }); - } -} \ No newline at end of file diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsSyncAdapter.java b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsSyncAdapter.java new file mode 100644 index 000000000..9365028dd --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsSyncAdapter.java @@ -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 campaigns = result.getNodes("/api/query/allcampaigns/campaign"); + Log.d("Commons", campaigns.size() + " results!"); + ArrayList campaignValues = new ArrayList(); + 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; + } + } + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsSyncService.java b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsSyncService.java new file mode 100644 index 000000000..d1975cccb --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignsSyncService.java @@ -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(); + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/FetchCampaignsTask.java b/commons/src/main/java/org/wikimedia/commons/campaigns/FetchCampaignsTask.java deleted file mode 100644 index 8a822de42..000000000 --- a/commons/src/main/java/org/wikimedia/commons/campaigns/FetchCampaignsTask.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.wikimedia.commons.campaigns; - -import android.content.Context; -import android.os.AsyncTask; -import android.util.Log; -import org.json.JSONException; -import org.json.JSONObject; -import org.mediawiki.api.ApiResult; -import org.mediawiki.api.MWApi; -import org.wikimedia.commons.CommonsApplication; -import org.wikimedia.commons.Utils; - -import java.io.IOException; -import java.util.ArrayList; - -public class FetchCampaignsTask extends AsyncTask>{ - - private Context context; - private MWApi api; - - public FetchCampaignsTask(Context context) { - this.context = context; - this.api = ((CommonsApplication)context.getApplicationContext()).getApi(); - } - - @Override - protected ArrayList doInBackground(Void... voids) { - ArrayList campaigns = new ArrayList(); - ApiResult result; - try { - result = api.action("query").param("prop", "revisions") - .param("rvprop", "content").param("generator", "allpages") - .param("gapnamespace", 460).param("gaplimit", 500).get(); //FIXME: Actually paginate! - } catch (IOException e) { - throw new RuntimeException(e); // FIXME: Do something else ;) - } - ArrayList pageNodes = result.getNodes("/api/query/pages/page"); - for(ApiResult pageNode : pageNodes) { - Log.d("Commons", Utils.getStringFromDOM(pageNode.getDocument())); - String configString = pageNode.getString("revisions/rev"); - String pageName = pageNode.getString("@title").split(":")[1]; - JSONObject config; - try { - config = new JSONObject(configString); - } catch (JSONException e) { - throw new RuntimeException(e); // NEVER HAPPENS! - } - campaigns.add(new Campaign(pageName, config)); - } - return campaigns; - } -} diff --git a/commons/src/main/java/org/wikimedia/commons/data/DBOpenHelper.java b/commons/src/main/java/org/wikimedia/commons/data/DBOpenHelper.java index af680650f..a8560a9f5 100644 --- a/commons/src/main/java/org/wikimedia/commons/data/DBOpenHelper.java +++ b/commons/src/main/java/org/wikimedia/commons/data/DBOpenHelper.java @@ -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); } } From 16913eed4e0eff5d4e0c1aad37da1fcd3d489d2b Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Thu, 19 Sep 2013 16:29:44 -0700 Subject: [PATCH 08/14] Generate remote image URL if it isn't set Since the imageURL can be generated if it isn't set, we can generate it once and then just return it. This is overriden when we explicitly set it if we get it back from the API Change-Id: I55939f2d9c0c2679847ec104f8029d9e533e202b --- commons/src/main/java/org/wikimedia/commons/Media.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/commons/src/main/java/org/wikimedia/commons/Media.java b/commons/src/main/java/org/wikimedia/commons/Media.java index d300f3088..ae3e5b024 100644 --- a/commons/src/main/java/org/wikimedia/commons/Media.java +++ b/commons/src/main/java/org/wikimedia/commons/Media.java @@ -56,6 +56,9 @@ public class Media implements Parcelable { } public String getImageUrl() { + if(imageUrl == null) { + imageUrl = Utils.makeThumbBaseUrl(this.getFilename()); + } return imageUrl; } @@ -100,7 +103,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() { From 119c973a06618d98bd727cf99529dfb9cb0b431c Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Thu, 19 Sep 2013 17:41:53 -0700 Subject: [PATCH 09/14] Use parens to clarify intent & functionality of a condition Precedence rules are for chumps. Change-Id: I5e46899b3d2e2af32b627e8cd61f085ad28ef2a2 --- .../src/main/java/org/wikimedia/commons/MediaWikiImageView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commons/src/main/java/org/wikimedia/commons/MediaWikiImageView.java b/commons/src/main/java/org/wikimedia/commons/MediaWikiImageView.java index 57bfd4399..1cdc61aac 100644 --- a/commons/src/main/java/org/wikimedia/commons/MediaWikiImageView.java +++ b/commons/src/main/java/org/wikimedia/commons/MediaWikiImageView.java @@ -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()); From 92e8d437a183fa8ac0c2ce616b3d853a4c8f6a3e Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Thu, 19 Sep 2013 18:01:01 -0700 Subject: [PATCH 10/14] Fix duplicated column definition This is why copy pasting code is bad, but I've no idea how exactly I can abstract out the ContentProvider related stuff Change-Id: Ib668ad8cd2302b832afc69b97951f3a6b1dcfe5b --- .../src/main/java/org/wikimedia/commons/campaigns/Campaign.java | 1 - 1 file changed, 1 deletion(-) diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/Campaign.java b/commons/src/main/java/org/wikimedia/commons/campaigns/Campaign.java index 3eced64ef..f7c24bcbe 100644 --- a/commons/src/main/java/org/wikimedia/commons/campaigns/Campaign.java +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/Campaign.java @@ -157,7 +157,6 @@ public class Campaign { COLUMN_ENABLED, COLUMN_TITLE, COLUMN_DESCRIPTION, - COLUMN_DESCRIPTION, COLUMN_TRACKING_CATEGORY, COLUMN_BODY }; From d7736f2048e1cac3e323f1c2d86fdcfe6c27a2a0 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Thu, 19 Sep 2013 18:04:05 -0700 Subject: [PATCH 11/14] Add tracking category when uploading with a Campaign Change-Id: I0790fc499c0b4960590f03222a1738cc72d7b173 --- .../org/wikimedia/commons/campaigns/CampaignContribution.java | 1 + 1 file changed, 1 insertion(+) diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignContribution.java b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignContribution.java index 111a2f910..a105c0e62 100644 --- a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignContribution.java +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignContribution.java @@ -38,6 +38,7 @@ public class CampaignContribution extends Contribution { } else { buffer.append("{{subst:unc}}\n"); } + buffer.append("[[Category:").append(campaign.getTrackingCategory()).append("\n"); return buffer.toString(); } From 2da5453ec13221c663f1898a55f65de6aa4d9d74 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Thu, 19 Sep 2013 18:18:41 -0700 Subject: [PATCH 12/14] Make the ImageView to be of the specific type Change-Id: I51ced26ea0b654457919d915e322d91eb211f781 --- .../commons/contributions/ContributionViewHolder.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/commons/src/main/java/org/wikimedia/commons/contributions/ContributionViewHolder.java b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionViewHolder.java index bfa9bda3e..3bf5adf4a 100644 --- a/commons/src/main/java/org/wikimedia/commons/contributions/ContributionViewHolder.java +++ b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionViewHolder.java @@ -4,10 +4,11 @@ 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 ImageView imageView; + final MediaWikiImageView imageView; final TextView titleView; final TextView stateView; final TextView seqNumView; @@ -16,7 +17,7 @@ class ContributionViewHolder { String url; ContributionViewHolder(View parent) { - imageView = (ImageView)parent.findViewById(R.id.contributionImage); + 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); From e0cae93c3cecebc09f9764536d6bae924ee0de0a Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Thu, 19 Sep 2013 18:19:17 -0700 Subject: [PATCH 13/14] Display contributions from other people to the campaign Attempts to be as minimally invasive as possible Change-Id: I1c9d7080d046199d5277385da625c180a8bacbfa --- .../java/org/wikimedia/commons/Media.java | 3 + .../java/org/wikimedia/commons/Utils.java | 1 + .../wikimedia/commons/campaigns/Campaign.java | 4 +- .../commons/campaigns/CampaignActivity.java | 14 ++++- .../contributions/ContributionsActivity.java | 62 +++++++++++++------ .../ContributionsListAdapter.java | 15 ++--- .../ContributionsListFragment.java | 24 ++++--- .../contributions/MediaListAdapter.java | 47 ++++++++++++++ .../commons/media/CategoryImagesLoader.java | 57 +++++++++++++++++ .../commons/media/MediaDetailFragment.java | 2 +- .../commons/upload/UploadController.java | 13 ++-- 11 files changed, 194 insertions(+), 48 deletions(-) create mode 100644 commons/src/main/java/org/wikimedia/commons/contributions/MediaListAdapter.java create mode 100644 commons/src/main/java/org/wikimedia/commons/media/CategoryImagesLoader.java diff --git a/commons/src/main/java/org/wikimedia/commons/Media.java b/commons/src/main/java/org/wikimedia/commons/Media.java index ae3e5b024..cacf1ff3e 100644 --- a/commons/src/main/java/org/wikimedia/commons/Media.java +++ b/commons/src/main/java/org/wikimedia/commons/Media.java @@ -144,6 +144,9 @@ public class Media implements Parcelable { protected String creator; + 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.localUri = localUri; diff --git a/commons/src/main/java/org/wikimedia/commons/Utils.java b/commons/src/main/java/org/wikimedia/commons/Utils.java index 0a9915a30..d8013fd0d 100644 --- a/commons/src/main/java/org/wikimedia/commons/Utils.java +++ b/commons/src/main/java/org/wikimedia/commons/Utils.java @@ -1,6 +1,7 @@ package org.wikimedia.commons; 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; diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/Campaign.java b/commons/src/main/java/org/wikimedia/commons/campaigns/Campaign.java index f7c24bcbe..61b796eab 100644 --- a/commons/src/main/java/org/wikimedia/commons/campaigns/Campaign.java +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/Campaign.java @@ -7,9 +7,11 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import java.io.Serializable; import java.util.ArrayList; -public class Campaign { +// FIXME: Implement Parcelable +public class Campaign implements Serializable { private boolean enabled; private String autoAddWikitext; diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignActivity.java b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignActivity.java index 7b8489867..2fafcb40e 100644 --- a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignActivity.java +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignActivity.java @@ -1,14 +1,18 @@ package org.wikimedia.commons.campaigns; import android.app.Activity; +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.R; +import org.wikimedia.commons.contributions.ContributionsActivity; public class CampaignActivity extends SherlockFragmentActivity @@ -16,12 +20,20 @@ public class CampaignActivity private ListView campaignsListView; private CampaignsListAdapter campaignsListAdapter; - private Cursor allCampaigns; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_campaigns); 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); } diff --git a/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsActivity.java b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsActivity.java index 0a9574960..a1951fa9c 100644 --- a/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsActivity.java +++ b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsActivity.java @@ -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, + implements LoaderManager.LoaderCallbacks, 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 onCreateLoader(int i, Bundle bundle) { - return new CursorLoader(this, ContributionsContentProvider.BASE_URI, Contribution.Table.ALL_FIELDS, CONTRIBUTION_SELECTION, null, CONTRIBUTION_SORT); + 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 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())); + getSupportActionBar().setSubtitle(getResources().getQuantityString(R.plurals.contributions_subtitle, cursor.getCount(), cursor.getCount())); + } else { + contributionsList.setAdapter(new MediaListAdapter(this, (ArrayList) result)); + } } - public void onLoaderReset(Loader 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() { diff --git a/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsListAdapter.java b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsListAdapter.java index 295f2566d..3ce0c4c03 100644 --- a/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsListAdapter.java +++ b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsListAdapter.java @@ -1,5 +1,6 @@ package org.wikimedia.commons.contributions; +import android.app.Activity; import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; @@ -19,16 +20,16 @@ import org.wikimedia.commons.Utils; class ContributionsListAdapter extends CursorAdapter { private DisplayImageOptions contributionDisplayOptions = Utils.getGenericDisplayOptions().build();; - private SherlockFragment fragment; + private Activity activity; - public ContributionsListAdapter(SherlockFragment fragment, Cursor c, int flags) { - super(fragment.getActivity(), c, flags); - this.fragment = fragment; + 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 = fragment.getActivity().getLayoutInflater().inflate(R.layout.layout_contribution, viewGroup, false); + View parent = activity.getLayoutInflater().inflate(R.layout.layout_contribution, viewGroup, false); parent.setTag(new ContributionViewHolder(parent)); return parent; } @@ -38,12 +39,12 @@ class ContributionsListAdapter extends CursorAdapter { final ContributionViewHolder views = (ContributionViewHolder)view.getTag(); Contribution contribution = Contribution.fromCursor(cursor); - String actualUrl = TextUtils.isEmpty(contribution.getImageUrl()) ? contribution.getLocalUri().toString() : contribution.getThumbnailUrl(320); + 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) fragment.getActivity().getApplicationContext()).getImageLoader()); + 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() { diff --git a/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsListFragment.java b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsListFragment.java index 065f790d9..0be5eaddb 100644 --- a/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsListFragment.java +++ b/commons/src/main/java/org/wikimedia/commons/contributions/ContributionsListFragment.java @@ -16,8 +16,6 @@ import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; -import com.nostra13.universalimageloader.core.DisplayImageOptions; - import org.wikimedia.commons.*; import org.wikimedia.commons.R; @@ -28,10 +26,6 @@ public class ContributionsListFragment extends SherlockFragment { private TextView waitingMessage; private TextView emptyMessage; - private ContributionsListAdapter contributionsAdapter; - - private Cursor allContributions; - private ContributionController controller; @Override @@ -39,13 +33,12 @@ public class ContributionsListFragment extends SherlockFragment { return inflater.inflate(R.layout.fragment_contributions, container, false); } - public void setCursor(Cursor cursor) { - if(allContributions == null) { - contributionsAdapter = new ContributionsListAdapter(this, cursor, 0); - contributionsList.setAdapter(contributionsAdapter); - } - allContributions = cursor; - contributionsAdapter.swapCursor(cursor); + public ListAdapter getAdapter() { + return contributionsList.getAdapter(); + } + + public void setAdapter(ListAdapter adapter) { + this.contributionsList.setAdapter(adapter); } @Override @@ -111,6 +104,11 @@ public class ContributionsListFragment extends SherlockFragment { setHasOptionsMenu(true); } + @Override + public void onDestroy() { + super.onDestroy(); + } + @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); diff --git a/commons/src/main/java/org/wikimedia/commons/contributions/MediaListAdapter.java b/commons/src/main/java/org/wikimedia/commons/contributions/MediaListAdapter.java new file mode 100644 index 000000000..33f846043 --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/contributions/MediaListAdapter.java @@ -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 mediaList; + private Activity activity; + + public MediaListAdapter(Activity activity, ArrayList 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; + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/media/CategoryImagesLoader.java b/commons/src/main/java/org/wikimedia/commons/media/CategoryImagesLoader.java new file mode 100644 index 000000000..02e517f35 --- /dev/null +++ b/commons/src/main/java/org/wikimedia/commons/media/CategoryImagesLoader.java @@ -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>{ + 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 loadInBackground() { + ArrayList mediaList = new ArrayList(); + 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 members = result.getNodes("/api/query/categorymembers/cm"); + for(ApiResult member : members) { + mediaList.add(new Media(member.getString("@title"))); + } + return mediaList; + } +} diff --git a/commons/src/main/java/org/wikimedia/commons/media/MediaDetailFragment.java b/commons/src/main/java/org/wikimedia/commons/media/MediaDetailFragment.java index 39343d825..d4971d4a8 100644 --- a/commons/src/main/java/org/wikimedia/commons/media/MediaDetailFragment.java +++ b/commons/src/main/java/org/wikimedia/commons/media/MediaDetailFragment.java @@ -80,7 +80,7 @@ public class MediaDetailFragment extends SherlockFragment { title.setBackgroundDrawable(null); } - 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; diff --git a/commons/src/main/java/org/wikimedia/commons/upload/UploadController.java b/commons/src/main/java/org/wikimedia/commons/upload/UploadController.java index 00c9a41e5..3a86db3fd 100644 --- a/commons/src/main/java/org/wikimedia/commons/upload/UploadController.java +++ b/commons/src/main/java/org/wikimedia/commons/upload/UploadController.java @@ -27,6 +27,7 @@ public class UploadController { private UploadService uploadService; private final Activity activity; + private Campaign campaign; final CommonsApplication app; public interface ContributionUploadProgress { @@ -35,7 +36,12 @@ public class UploadController { public UploadController(Activity activity) { this.activity = activity; - app = (CommonsApplication)activity.getApplicationContext(); + app = (CommonsApplication)activity.getApplicationContext(); + } + + public UploadController(Activity activity, Campaign campaign) { + this(activity); + this.campaign = campaign; } private boolean isUploadServiceConnected; @@ -64,12 +70,7 @@ public class UploadController { } } - /* The JavaScript is Leaking!*/ public void startUpload(String rawTitle, Uri mediaUri, String description, String mimeType, String source, ContributionUploadProgress onComplete) { - startUpload(rawTitle, mediaUri, description, mimeType, source, null, onComplete); - } - - public void startUpload(String rawTitle, Uri mediaUri, String description, String mimeType, String source, Campaign campaign, ContributionUploadProgress onComplete) { Contribution contribution; String title = rawTitle; From 4da4f7c5e949ae0bc9afcd9f7318a95e91f317bf Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Mon, 23 Sep 2013 16:52:49 +0530 Subject: [PATCH 14/14] Turn on Campaigns Sync by default Change-Id: Ief0f8a9dacf21468b396cc406a19d1dcc63fed1f --- .../main/java/org/wikimedia/commons/auth/LoginActivity.java | 2 ++ .../org/wikimedia/commons/campaigns/CampaignActivity.java | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/commons/src/main/java/org/wikimedia/commons/auth/LoginActivity.java b/commons/src/main/java/org/wikimedia/commons/auth/LoginActivity.java index d4730d7b0..573c0dfe9 100644 --- a/commons/src/main/java/org/wikimedia/commons/auth/LoginActivity.java +++ b/commons/src/main/java/org/wikimedia/commons/auth/LoginActivity.java @@ -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; diff --git a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignActivity.java b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignActivity.java index 2fafcb40e..cfcf5f413 100644 --- a/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignActivity.java +++ b/commons/src/main/java/org/wikimedia/commons/campaigns/CampaignActivity.java @@ -1,6 +1,7 @@ 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; @@ -11,6 +12,7 @@ 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; @@ -24,6 +26,8 @@ public class CampaignActivity 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() {