diff --git a/commons/res/drawable/media_info_shadow.xml b/commons/res/drawable/media_info_shadow.xml
new file mode 100644
index 000000000..576f0d24e
--- /dev/null
+++ b/commons/res/drawable/media_info_shadow.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/commons/res/layout/detail_category_item.xml b/commons/res/layout/detail_category_item.xml
new file mode 100644
index 000000000..385d13ddb
--- /dev/null
+++ b/commons/res/layout/detail_category_item.xml
@@ -0,0 +1,12 @@
+
+
+
diff --git a/commons/res/layout/detail_main_panel.xml b/commons/res/layout/detail_main_panel.xml
new file mode 100644
index 000000000..dd6f9ae15
--- /dev/null
+++ b/commons/res/layout/detail_main_panel.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/commons/res/layout/fragment_media_detail.xml b/commons/res/layout/fragment_media_detail.xml
index 6a4bdbd6c..b1d566acd 100644
--- a/commons/res/layout/fragment_media_detail.xml
+++ b/commons/res/layout/fragment_media_detail.xml
@@ -28,31 +28,14 @@
android:scaleType="fitCenter"
/>
-
-
-
-
-
+ android:id="@+id/mediaDetailListView"
+ android:divider="#00A0A0A0"
+ android:fillViewport="true"
+ android:background="@android:color/transparent"
+ android:cacheColorHint="@android:color/transparent"
+ />
\ No newline at end of file
diff --git a/commons/res/values-qq/strings.xml b/commons/res/values-qq/strings.xml
index 956329ba4..e11b05916 100644
--- a/commons/res/values-qq/strings.xml
+++ b/commons/res/values-qq/strings.xml
@@ -89,4 +89,7 @@
Message asking user if they understand what kinds of images to upload.
Button text for confirming the user understands what kinds of images to upload.
{{Identical|Yes}}
+ Label for categories list in media detail panel
+ Placeholder for categories list in media detail panel, while loading from network.
+ Placeholder for categories list in media detail panel, if no categories found.
diff --git a/commons/res/values/strings.xml b/commons/res/values/strings.xml
index f3d4ac748..7bff4bb3a 100644
--- a/commons/res/values/strings.xml
+++ b/commons/res/values/strings.xml
@@ -102,4 +102,7 @@
Avoid copyrighted materials you found from the Internet as well as images of posters, book covers, etc.
You think you got it?
Yes!
+ Categories
+ Loading...
+ None selected
diff --git a/commons/src/main/java/org/wikimedia/commons/CommonsApplication.java b/commons/src/main/java/org/wikimedia/commons/CommonsApplication.java
index d41af879b..ecf5097bf 100644
--- a/commons/src/main/java/org/wikimedia/commons/CommonsApplication.java
+++ b/commons/src/main/java/org/wikimedia/commons/CommonsApplication.java
@@ -210,5 +210,4 @@ public class CommonsApplication extends Application {
return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA) ||
pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT);
}
-
}
diff --git a/commons/src/main/java/org/wikimedia/commons/Media.java b/commons/src/main/java/org/wikimedia/commons/Media.java
index d300f3088..2d33c76ff 100644
--- a/commons/src/main/java/org/wikimedia/commons/Media.java
+++ b/commons/src/main/java/org/wikimedia/commons/Media.java
@@ -2,6 +2,7 @@ package org.wikimedia.commons;
import android.net.Uri;
import android.os.*;
+import android.util.Log;
import java.util.*;
import java.util.regex.*;
@@ -19,6 +20,8 @@ public class Media implements Parcelable {
};
protected Media() {
+ this.categories = new ArrayList();
+ this.descriptions = new HashMap();
}
private HashMap tags = new HashMap();
@@ -127,10 +130,11 @@ public class Media implements Parcelable {
this.license = license;
}
+ // Primary metadata fields
protected Uri localUri;
protected String imageUrl;
protected String filename;
- protected String description;
+ protected String description; // monolingual description on input...
protected long dataLength;
protected Date dateCreated;
protected Date dateUploaded;
@@ -141,8 +145,45 @@ public class Media implements Parcelable {
protected String creator;
+ protected ArrayList categories; // as loaded at runtime?
+ protected Map descriptions; // multilingual descriptions as loaded
+
+ public ArrayList getCategories() {
+ return (ArrayList)categories.clone(); // feels dirty
+ }
+
+ public void setCategories(List categories) {
+ this.categories.removeAll(this.categories);
+ this.categories.addAll(categories);
+ }
+
+ public void setDescriptions(Map descriptions) {
+ for (String key : this.descriptions.keySet()) {
+ this.descriptions.remove(key);
+ }
+ for (String key : descriptions.keySet()) {
+ this.descriptions.put(key, descriptions.get(key));
+ }
+ }
+
+ public String getDescription(String preferredLanguage) {
+ if (descriptions.containsKey(preferredLanguage)) {
+ // See if the requested language is there.
+ return descriptions.get(preferredLanguage);
+ } else if (descriptions.containsKey("en")) {
+ // Ah, English. Language of the world, until the Chinese crush us.
+ return descriptions.get("en");
+ } else if (descriptions.containsKey("default")) {
+ // No languages marked...
+ return descriptions.get("default");
+ } else {
+ // FIXME: return the first available non-English description?
+ return "";
+ }
+ }
public Media(Uri localUri, String imageUrl, String filename, String description, long dataLength, Date dateCreated, Date dateUploaded, String creator) {
+ this();
this.localUri = localUri;
this.imageUrl = imageUrl;
this.filename = filename;
@@ -170,6 +211,8 @@ public class Media implements Parcelable {
parcel.writeInt(width);
parcel.writeInt(height);
parcel.writeString(license);
+ parcel.writeStringList(categories);
+ parcel.writeMap(descriptions);
}
public Media(Parcel in) {
@@ -185,6 +228,8 @@ public class Media implements Parcelable {
width = in.readInt();
height = in.readInt();
license = in.readString();
+ in.readStringList(categories);
+ descriptions = in.readHashMap(ClassLoader.getSystemClassLoader());
}
public void setDescription(String description) {
diff --git a/commons/src/main/java/org/wikimedia/commons/MediaDataExtractor.java b/commons/src/main/java/org/wikimedia/commons/MediaDataExtractor.java
new file mode 100644
index 000000000..04957b522
--- /dev/null
+++ b/commons/src/main/java/org/wikimedia/commons/MediaDataExtractor.java
@@ -0,0 +1,252 @@
+package org.wikimedia.commons;
+
+import android.util.Log;
+import org.mediawiki.api.ApiResult;
+import org.mediawiki.api.MWApi;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Fetch additional media data from the network that we don't store locally.
+ *
+ * This includes things like category lists and multilingual descriptions,
+ * which are not intrinsic to the media and may change due to editing.
+ */
+public class MediaDataExtractor {
+ private boolean fetched;
+ private boolean processed;
+
+ private String filename;
+ private ArrayList categories;
+ private Map descriptions;
+ private String author;
+ private Date date;
+
+ /**
+ * @param filename of the target media object, should include 'File:' prefix
+ */
+ public MediaDataExtractor(String filename) {
+ this.filename = filename;
+ categories = new ArrayList();
+ descriptions = new HashMap();
+ fetched = false;
+ processed = false;
+ }
+
+ /**
+ * Actually fetch the data over the network.
+ * todo: use local caching?
+ *
+ * Warning: synchronous i/o, call on a background thread
+ */
+ public void fetch() throws IOException {
+ if (fetched) {
+ throw new IllegalStateException("Tried to call MediaDataExtractor.fetch() again.");
+ }
+
+ MWApi api = CommonsApplication.createMWApi();
+ ApiResult result = api.action("query")
+ .param("prop", "revisions")
+ .param("titles", filename)
+ .param("rvprop", "content")
+ .param("rvlimit", 1)
+ .param("rvgeneratexml", 1)
+ .get();
+
+ processResult(result);
+ fetched = true;
+ }
+
+ private void processResult(ApiResult result) throws IOException {
+
+ String wikiSource = result.getString("/api/query/pages/page/revisions/rev");
+ String parseTreeXmlSource = result.getString("/api/query/pages/page/revisions/rev/@parsetree");
+
+ // In-page category links are extracted from source, as XML doesn't cover [[links]]
+ extractCategories(wikiSource);
+
+ // Description template info is extracted from preprocessor XML
+ processWikiParseTree(parseTreeXmlSource);
+ }
+
+ /**
+ * We could fetch all category links from API, but we actually only want the ones
+ * directly in the page source so they're editable. In the future this may change.
+ *
+ * @param source wikitext source code
+ */
+ private void extractCategories(String source) {
+ Pattern regex = Pattern.compile("\\[\\[\\s*Category\\s*:([^]]*)\\s*\\]\\]", Pattern.CASE_INSENSITIVE);
+ Matcher matcher = regex.matcher(source);
+ while (matcher.find()) {
+ String cat = matcher.group(1).trim();
+ categories.add(cat);
+ }
+ }
+
+ private void processWikiParseTree(String source) throws IOException {
+ Document doc;
+ try {
+ DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+ doc = docBuilder.parse(new ByteArrayInputStream(source.getBytes("UTF-8")));
+ } catch (ParserConfigurationException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalStateException e) {
+ throw new IOException(e);
+ } catch (SAXException e) {
+ throw new IOException(e);
+ }
+ Node templateNode = findTemplate(doc.getDocumentElement(), "information");
+ if (templateNode != null) {
+ Node descriptionNode = findTemplateParameter(templateNode, "description");
+ descriptions = getMultilingualText(descriptionNode);
+
+ Node authorNode = findTemplateParameter(templateNode, "author");
+ author = Utils.getStringFromDOM(authorNode);
+ }
+ }
+
+ private Node findTemplate(Element parentNode, String title) throws IOException {
+ String ucTitle= Utils.capitalize(title);
+ NodeList nodes = parentNode.getChildNodes();
+ for (int i = 0; i < nodes.getLength(); i++) {
+ Node node = nodes.item(i);
+ if (node.getNodeName().equals("template")) {
+ String foundTitle = getTemplateTitle(node);
+ if (Utils.capitalize(foundTitle).equals(ucTitle)) {
+ return node;
+ }
+ }
+ }
+ return null;
+ }
+
+ private String getTemplateTitle(Node templateNode) throws IOException {
+ NodeList nodes = templateNode.getChildNodes();
+ for (int i = 0; i < nodes.getLength(); i++) {
+ Node node = nodes.item(i);
+ if (node.getNodeName().equals("title")) {
+ return node.getTextContent().trim();
+ }
+ }
+ throw new IOException("Template has no title element.");
+ }
+
+ private static abstract class TemplateChildNodeComparator {
+ abstract public boolean match(Node node);
+ }
+
+ private Node findTemplateParameter(Node templateNode, String name) throws IOException {
+ final String theName = name;
+ return findTemplateParameter(templateNode, new TemplateChildNodeComparator() {
+ @Override
+ public boolean match(Node node) {
+ return (Utils.capitalize(node.getTextContent().trim()).equals(Utils.capitalize(theName)));
+ }
+ });
+ }
+
+ private Node findTemplateParameter(Node templateNode, int index) throws IOException {
+ final String theIndex = "" + index;
+ return findTemplateParameter(templateNode, new TemplateChildNodeComparator() {
+ @Override
+ public boolean match(Node node) {
+ Element el = (Element)node;
+ if (el.getTextContent().trim().equals(theIndex)) {
+ return true;
+ } else if (el.getAttribute("index") != null && el.getAttribute("index").trim().equals(theIndex)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ });
+ }
+
+ private Node findTemplateParameter(Node templateNode, TemplateChildNodeComparator comparator) throws IOException {
+ NodeList nodes = templateNode.getChildNodes();
+ for (int i = 0; i < nodes.getLength(); i++) {
+ Node node = nodes.item(i);
+ if (node.getNodeName().equals("part")) {
+ NodeList childNodes = node.getChildNodes();
+ for (int j = 0; j < childNodes.getLength(); j++) {
+ Node childNode = childNodes.item(j);
+ if (childNode.getNodeName().equals("name")) {
+ if (comparator.match(childNode)) {
+ // yay! Now fetch the value node.
+ for (int k = j + 1; k < childNodes.getLength(); k++) {
+ Node siblingNode = childNodes.item(k);
+ if (siblingNode.getNodeName().equals("value")) {
+ return siblingNode;
+ }
+ }
+ throw new IOException("No value node found for matched template parameter.");
+ }
+ }
+ }
+ }
+ }
+ throw new IOException("No matching template parameter node found.");
+ }
+
+ // Extract a dictionary of multilingual texts from a subset of the parse tree.
+ // Texts are wrapped in things like {{en|foo} or {{en|1=foo bar}}.
+ // Text outside those wrappers is stuffed into a 'default' faux language key if present.
+ private Map getMultilingualText(Node parentNode) throws IOException {
+ Map texts = new HashMap();
+ StringBuilder localText = new StringBuilder();
+
+ NodeList nodes = parentNode.getChildNodes();
+ for (int i = 0; i < nodes.getLength(); i++) {
+ Node node = nodes.item(i);
+ if (node.getNodeName().equals("template")) {
+ // process a template node
+ String title = getTemplateTitle(node);
+ if (title.length() < 3) {
+ // Hopefully a language code. Nasty hack!
+ String lang = title;
+ Node valueNode = findTemplateParameter(node, 1);
+ String value = Utils.getStringFromDOM(valueNode); // hope there's no subtemplates or formatting for now
+ texts.put(lang, value);
+ }
+ } else if (node.getNodeType() == Node.TEXT_NODE) {
+ localText.append(node.getTextContent());
+ }
+ }
+
+ // Some descriptions don't list multilingual variants
+ String defaultText = localText.toString().trim();
+ if (defaultText.length() > 0) {
+ texts.put("default", localText.toString());
+ }
+ return texts;
+ }
+
+ /**
+ * Take our metadata and inject it into a live Media object.
+ * Media object might contain stale or cached data, or emptiness.
+ * @param media
+ */
+ public void fill(Media media) {
+ if (!fetched) {
+ throw new IllegalStateException("Tried to call MediaDataExtractor.fill() before fetch().");
+ }
+
+ media.setCategories(categories);
+ media.setDescriptions(descriptions);
+
+ // add author, date, etc fields
+ }
+}
diff --git a/commons/src/main/java/org/wikimedia/commons/Utils.java b/commons/src/main/java/org/wikimedia/commons/Utils.java
index b3623c10b..f3bea5a54 100644
--- a/commons/src/main/java/org/wikimedia/commons/Utils.java
+++ b/commons/src/main/java/org/wikimedia/commons/Utils.java
@@ -1,5 +1,6 @@
package org.wikimedia.commons;
+import android.net.Uri;
import android.os.*;
import com.nostra13.universalimageloader.core.*;
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
@@ -174,4 +175,23 @@ public class Utils {
throw new RuntimeException("Unrecognized license value");
}
+ public static String implode(String glue, Iterable pieces) {
+ StringBuffer buffer = new StringBuffer();
+ boolean first = true;
+ for (String piece : pieces) {
+ if (first) {
+ first = false;
+ } else {
+ buffer.append(glue);
+ }
+ buffer.append(pieces);
+ }
+ return buffer.toString();
+ }
+
+ public static Uri uriForWikiPage(String name) {
+ String underscored = name.trim().replace(" ", "_");
+ String uriStr = CommonsApplication.HOME_URL + urlEncode(underscored);
+ return Uri.parse(uriStr);
+ }
}
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..e57ee7b24 100644
--- a/commons/src/main/java/org/wikimedia/commons/contributions/Contribution.java
+++ b/commons/src/main/java/org/wikimedia/commons/contributions/Contribution.java
@@ -204,6 +204,7 @@ public class Contribution extends Media {
}
public Contribution() {
+ super();
timestamp = new Date(System.currentTimeMillis());
}
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..e06d441b8 100644
--- a/commons/src/main/java/org/wikimedia/commons/media/MediaDetailFragment.java
+++ b/commons/src/main/java/org/wikimedia/commons/media/MediaDetailFragment.java
@@ -1,9 +1,12 @@
package org.wikimedia.commons.media;
+import android.content.Intent;
import android.graphics.*;
+import android.net.Uri;
import android.os.*;
import android.text.*;
import android.util.Log;
+import android.util.TypedValue;
import android.view.*;
import android.widget.*;
import com.actionbarsherlock.app.SherlockFragment;
@@ -14,8 +17,13 @@ import com.nostra13.universalimageloader.core.assist.ImageLoadingListener;
import com.android.volley.toolbox.*;
+import org.mediawiki.api.ApiResult;
+import org.mediawiki.api.MWApi;
import org.wikimedia.commons.*;
+import java.io.IOException;
+import java.util.ArrayList;
+
public class MediaDetailFragment extends SherlockFragment {
private boolean editable;
@@ -33,6 +41,8 @@ public class MediaDetailFragment extends SherlockFragment {
Bundle state = new Bundle();
state.putBoolean("editable", editable);
state.putInt("index", index);
+ state.putInt("listIndex", 0);
+ state.putInt("listTop", 0);
mf.setArguments(state);
@@ -40,9 +50,22 @@ public class MediaDetailFragment extends SherlockFragment {
}
private ImageView image;
- private EditText title;
+ //private EditText title;
private ProgressBar loadingProgress;
private ImageView loadingFailed;
+ private MediaDetailSpacer spacer;
+ private int initialListIndex = 0;
+ private int initialListTop = 0;
+
+ private TextView title;
+ private TextView desc;
+ private ListView listView;
+ private ArrayList categoryNames;
+ private boolean categoriesLoaded = false;
+ private boolean categoriesPresent = false;
+ private ArrayAdapter categoryAdapter;
+ private ViewTreeObserver.OnGlobalLayoutListener observer; // for layout stuff, only used once!
+ private AsyncTask detailFetchTask;
@Override
@@ -50,6 +73,20 @@ public class MediaDetailFragment extends SherlockFragment {
super.onSaveInstanceState(outState);
outState.putInt("index", index);
outState.putBoolean("editable", editable);
+
+ getScrollPosition();
+ outState.putInt("listIndex", initialListIndex);
+ outState.putInt("listTop", initialListTop);
+ }
+
+ private void getScrollPosition() {
+ int initialListIndex = listView.getFirstVisiblePosition();
+ View firstVisibleItem = listView.getChildAt(initialListIndex);
+ if (firstVisibleItem == null) {
+ initialListTop = 0;
+ } else {
+ initialListTop = firstVisibleItem.getTop();
+ }
}
@Override
@@ -59,19 +96,46 @@ public class MediaDetailFragment extends SherlockFragment {
if(savedInstanceState != null) {
editable = savedInstanceState.getBoolean("editable");
index = savedInstanceState.getInt("index");
+ initialListIndex = savedInstanceState.getInt("listIndex");
+ initialListTop = savedInstanceState.getInt("listTop");
} else {
editable = getArguments().getBoolean("editable");
index = getArguments().getInt("index");
}
final Media media = detailProvider.getMediaAtPosition(index);
+ categoryNames = new ArrayList();
+ categoryNames.add(getString(R.string.detail_panel_cats_loading));
+
+ final View view = inflater.inflate(R.layout.fragment_media_detail, container, false);
- View view = inflater.inflate(R.layout.fragment_media_detail, container, false);
image = (ImageView) view.findViewById(R.id.mediaDetailImage);
- title = (EditText) view.findViewById(R.id.mediaDetailTitle);
loadingProgress = (ProgressBar) view.findViewById(R.id.mediaDetailImageLoading);
loadingFailed = (ImageView) view.findViewById(R.id.mediaDetailImageFailed);
+ listView = (ListView) view.findViewById(R.id.mediaDetailListView);
+
+ // Detail consists of a list view with main pane in header view, plus category list.
+ View detailView = getActivity().getLayoutInflater().inflate(R.layout.detail_main_panel, null, false);
+ listView.addHeaderView(detailView, null, false);
+ categoryAdapter = new ArrayAdapter(getActivity(), R.layout.detail_category_item, categoryNames);
+ listView.setAdapter(categoryAdapter);
+ listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ public void onItemClick(AdapterView> adapterView, View view, int position, long id) {
+ if (categoriesLoaded && categoriesPresent) {
+ String selectedCategoryTitle = "Category:" + categoryNames.get(position - 1);
+ Intent viewIntent = new Intent();
+ viewIntent.setAction(Intent.ACTION_VIEW);
+ viewIntent.setData(Utils.uriForWikiPage(selectedCategoryTitle));
+ startActivity(viewIntent);
+ }
+ }
+ });
+
+ spacer = (MediaDetailSpacer) detailView.findViewById(R.id.mediaDetailSpacer);
+ title = (TextView) detailView.findViewById(R.id.mediaDetailTitle);
+ desc = (TextView) detailView.findViewById(R.id.mediaDetailDesc);
// Enable or disable editing on the title
+ /*
title.setClickable(editable);
title.setFocusable(editable);
title.setCursorVisible(editable);
@@ -79,6 +143,8 @@ public class MediaDetailFragment extends SherlockFragment {
if(!editable) {
title.setBackgroundDrawable(null);
}
+ */
+
String actualUrl = TextUtils.isEmpty(media.getImageUrl()) ? media.getLocalUri().toString() : media.getThumbnailUrl(640);
if(actualUrl.startsWith("http")) {
@@ -88,6 +154,56 @@ public class MediaDetailFragment extends SherlockFragment {
mwImage.setMedia(media, loader);
Log.d("Volley", actualUrl);
// FIXME: For transparent images
+
+ // Load image metadata: desc, license, categories
+ // FIXME: keep the spinner going while we load data
+ // FIXME: cache this data
+ detailFetchTask = new AsyncTask() {
+ private MediaDataExtractor extractor;
+
+ @Override
+ protected void onPreExecute() {
+ extractor = new MediaDataExtractor(media.getFilename());
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... voids) {
+ try {
+ extractor.fetch();
+ return Boolean.TRUE;
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return Boolean.FALSE;
+ }
+
+ @Override
+ protected void onPostExecute(Boolean success) {
+ detailFetchTask = null;
+
+ if (success.booleanValue()) {
+ extractor.fill(media);
+
+ // Fill some fields
+ desc.setText(media.getDescription("en"));
+
+ categoryNames.removeAll(categoryNames);
+ categoryNames.addAll(media.getCategories());
+
+ categoriesLoaded = true;
+ categoriesPresent = (categoryNames.size() > 0);
+ if (!categoriesPresent) {
+ // Stick in a filler element.
+ categoryNames.add(getString(R.string.detail_panel_cats_none));
+ }
+
+ categoryAdapter.notifyDataSetChanged();
+ } else {
+ Log.d("Commons", "Failed to load photo details.");
+ }
+ }
+ };
+ Utils.executeAsyncTask(detailFetchTask);
} else {
com.nostra13.universalimageloader.core.ImageLoader.getInstance().displayImage(actualUrl, image, displayOptions, new ImageLoadingListener() {
public void onLoadingStarted(String s, View view) {
@@ -113,8 +229,11 @@ public class MediaDetailFragment extends SherlockFragment {
}
});
}
- title.setText(media.getDisplayTitle());
+ title.setText(media.getDisplayTitle());
+ desc.setText("");
+
+ /*
title.addTextChangedListener(new TextWatcher() {
public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
@@ -130,6 +249,35 @@ public class MediaDetailFragment extends SherlockFragment {
}
});
+ */
+
+ // Layout observer to size the spacer item relative to the available space.
+ // There may be a .... better way to do this.
+ observer = new ViewTreeObserver.OnGlobalLayoutListener() {
+ private int currentHeight = -1;
+
+ public void onGlobalLayout() {
+ int viewHeight = view.getHeight();
+ //int textHeight = title.getLineHeight();
+ int paddingDp = 48;
+ float paddingPx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, paddingDp, getResources().getDisplayMetrics());
+ int newHeight = viewHeight - Math.round(paddingPx);
+
+ if (newHeight != currentHeight) {
+ currentHeight = newHeight;
+ ViewGroup.LayoutParams params = spacer.getLayoutParams();
+ params.height = newHeight;
+ spacer.setLayoutParams(params);
+
+ // hack hack to trigger relayout
+ categoryAdapter.notifyDataSetChanged();
+
+ listView.setSelectionFromTop(initialListIndex, initialListTop);
+ }
+
+ }
+ };
+ view.getViewTreeObserver().addOnGlobalLayoutListener(observer);
return view;
}
@@ -139,4 +287,17 @@ public class MediaDetailFragment extends SherlockFragment {
displayOptions = Utils.getGenericDisplayOptions().build();
}
+
+ @Override
+ public void onDestroyView() {
+ if (detailFetchTask != null) {
+ detailFetchTask.cancel(true);
+ detailFetchTask = null;
+ }
+ if (observer != null) {
+ getView().getViewTreeObserver().removeGlobalOnLayoutListener(observer); // old Android was on crack. CRACK IS WHACK
+ observer = null;
+ }
+ super.onDestroyView();
+ }
}
diff --git a/commons/src/main/java/org/wikimedia/commons/media/MediaDetailSpacer.java b/commons/src/main/java/org/wikimedia/commons/media/MediaDetailSpacer.java
new file mode 100644
index 000000000..49b048535
--- /dev/null
+++ b/commons/src/main/java/org/wikimedia/commons/media/MediaDetailSpacer.java
@@ -0,0 +1,19 @@
+package org.wikimedia.commons.media;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+
+public class MediaDetailSpacer extends View {
+ public MediaDetailSpacer(Context context) {
+ super(context);
+ }
+
+ public MediaDetailSpacer(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MediaDetailSpacer(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+}