SuchABot has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/78360


Change subject: [WIP] Detail page pull-up panel for Android
......................................................................

[WIP] Detail page pull-up panel for Android

Yuvi, let me know if there's anything high-profile you want fixed before we 
merge this in. We can then think about syncing the look 'n' feel more between 
this and the iOS version, and add things like category and description editing 
in future...

Change-Id: I2efb3da61a586a2867a7c3cfc7a0ddea11c208dc
GitHub: https://github.com/wikimedia/apps-android-commons/pull/41
---
A commons/res/drawable/media_info_shadow.xml
A commons/res/layout/detail_category_item.xml
A commons/res/layout/detail_main_panel.xml
M commons/res/layout/fragment_media_detail.xml
M commons/src/main/java/org/wikimedia/commons/CommonsApplication.java
M commons/src/main/java/org/wikimedia/commons/Media.java
A commons/src/main/java/org/wikimedia/commons/MediaDataExtractor.java
M commons/src/main/java/org/wikimedia/commons/Utils.java
M commons/src/main/java/org/wikimedia/commons/contributions/Contribution.java
M commons/src/main/java/org/wikimedia/commons/media/MediaDetailFragment.java
A commons/src/main/java/org/wikimedia/commons/media/MediaDetailSpacer.java
11 files changed, 541 insertions(+), 30 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/apps/android/commons 
refs/changes/60/78360/1

diff --git a/commons/res/drawable/media_info_shadow.xml 
b/commons/res/drawable/media_info_shadow.xml
new file mode 100644
index 0000000..576f0d2
--- /dev/null
+++ b/commons/res/drawable/media_info_shadow.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android";>
+    <gradient
+            android:startColor="#00000000"
+            android:endColor="#ff000000"
+            android:angle="270"
+            >
+    </gradient>
+</shape>
\ 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 0000000..385d13d
--- /dev/null
+++ b/commons/res/layout/detail_category_item.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android";
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content"
+          android:minHeight="48dp"
+          android:padding="8dp"
+          android:gravity="center_vertical"
+          android:id="@+id/mediaDetailCategoryItemText"
+          android:textSize="18sp"
+          android:background="#AA000000"
+          />
diff --git a/commons/res/layout/detail_main_panel.xml 
b/commons/res/layout/detail_main_panel.xml
new file mode 100644
index 0000000..aa42ca5
--- /dev/null
+++ b/commons/res/layout/detail_main_panel.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android";
+              android:orientation="vertical"
+              android:layout_width="fill_parent"
+              android:layout_height="fill_parent">
+
+    <!-- Placeholder. Height gets set at runtime based on container size; the 
initial value is a hack to keep
+         the detail info offscreen until it's placed properly. May be a better 
way to do this. -->
+    <org.wikimedia.commons.media.MediaDetailSpacer
+            android:layout_width="fill_parent"
+            android:layout_height="1600dp"
+            android:id="@+id/mediaDetailSpacer"/>
+    <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:background="#AA000000"
+            android:padding="8dp">
+        <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Title of the media"
+                android:id="@+id/mediaDetailTitle"
+                android:layout_gravity="left|start"
+                android:textColor="@android:color/white"
+                android:textSize="18sp" /> <!-- 18sp == MediumText -->
+        <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Description of the media goes here. This can 
potentially be fairly long, and will need to wrap across multiple lines. We 
hope it looks nice though."
+                android:id="@+id/mediaDetailDesc"
+                android:layout_gravity="left|start"/>
+        <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Categories"
+                android:textSize="18sp"
+                android:layout_gravity="left|start"
+                android:paddingTop="24dp"/>
+        <LinearLayout
+                android:orientation="vertical"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:id="@+id/mediaDetailCategoryList"
+                android:layout_gravity="left|start"/>
+    </LinearLayout>
+</LinearLayout>
diff --git a/commons/res/layout/fragment_media_detail.xml 
b/commons/res/layout/fragment_media_detail.xml
index 6a4bdbd..b1d566a 100644
--- a/commons/res/layout/fragment_media_detail.xml
+++ b/commons/res/layout/fragment_media_detail.xml
@@ -28,31 +28,14 @@
                android:scaleType="fitCenter"
             />
 
-    <RelativeLayout
+    <ListView
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
-            android:layout_gravity="center|bottom"
-            android:background="#AA000000"
-            android:padding="8dp"
-            >
-
-        <EditText
-                android:id="@+id/mediaDetailTitle"
-                android:layout_width="fill_parent"
-                android:layout_height="wrap_content"
-                android:imeOptions="flagNoExtractUi"
-                android:inputType="textNoSuggestions"
-                android:singleLine="true"
-                android:textColor="#FFFFFF"/>
-       <!-- <TextView
-                android:id="@+id/mediaDetailDescription"
-                android:layout_width="fill_parent"
-                android:layout_height="wrap_content"
-                android:layout_below="@id/mediaDetailTitle"
-                android:layout_alignParentBottom="true"
-                style="?android:textAppearanceSmall"
-                android:textColor="#FFFFFFFF"
-                /> -->
-    </RelativeLayout>
+            android:id="@+id/mediaDetailListView"
+            android:divider="#00A0A0A0"
+            android:fillViewport="true"
+            android:background="@android:color/transparent"
+            android:cacheColorHint="@android:color/transparent"
+            />
 
 </FrameLayout>
\ No newline at end of file
diff --git 
a/commons/src/main/java/org/wikimedia/commons/CommonsApplication.java 
b/commons/src/main/java/org/wikimedia/commons/CommonsApplication.java
index 0ab42fb..e39da06 100644
--- a/commons/src/main/java/org/wikimedia/commons/CommonsApplication.java
+++ b/commons/src/main/java/org/wikimedia/commons/CommonsApplication.java
@@ -197,5 +197,4 @@
         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 d300f30..2d33c76 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 @@
 
 import android.net.Uri;
 import android.os.*;
+import android.util.Log;
 
 import java.util.*;
 import java.util.regex.*;
@@ -19,6 +20,8 @@
     };
 
     protected Media() {
+        this.categories = new ArrayList<String>();
+        this.descriptions = new HashMap<String, String>();
     }
 
     private HashMap<String, Object> tags = new HashMap<String, Object>();
@@ -127,10 +130,11 @@
         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 @@
 
     protected String creator;
 
+    protected ArrayList<String> categories; // as loaded at runtime?
+    protected Map<String, String> descriptions; // multilingual descriptions 
as loaded
+
+    public ArrayList<String> getCategories() {
+        return (ArrayList<String>)categories.clone(); // feels dirty
+    }
+
+    public void setCategories(List<String> categories) {
+        this.categories.removeAll(this.categories);
+        this.categories.addAll(categories);
+    }
+
+    public void setDescriptions(Map<String,String> 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 @@
         parcel.writeInt(width);
         parcel.writeInt(height);
         parcel.writeString(license);
+        parcel.writeStringList(categories);
+        parcel.writeMap(descriptions);
     }
 
     public Media(Parcel in) {
@@ -185,6 +228,8 @@
         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 0000000..04957b5
--- /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<String> categories;
+    private Map<String, String> 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<String>();
+        descriptions = new HashMap<String, String>();
+        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<String, String> getMultilingualText(Node parentNode) throws 
IOException {
+        Map<String, String> texts = new HashMap<String, String>();
+        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 0a9915a..6bb0a9e 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 @@
         throw new RuntimeException("Unrecognized license value");
     }
 
+    public static String implode(String glue, Iterable<String> 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 f0f5a24..e57ee7b 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 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 39343d8..8b20078 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,7 +17,12 @@
 
 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 {
 
@@ -40,9 +48,17 @@
     }
 
     private ImageView image;
-    private EditText title;
+    //private EditText title;
     private ProgressBar loadingProgress;
     private ImageView loadingFailed;
+    private MediaDetailSpacer spacer;
+    private TextView title;
+    private TextView desc;
+    private ListView listView;
+    private ArrayList<String> categoryNames;
+    private ArrayAdapter categoryAdapter;
+    private ViewTreeObserver.OnGlobalLayoutListener observer; // for layout 
stuff, only used once!
+    private AsyncTask<Void,Void,Boolean> detailFetchTask;
 
 
     @Override
@@ -64,14 +80,36 @@
             index = getArguments().getInt("index");
         }
         final Media media = detailProvider.getMediaAtPosition(index);
+        categoryNames = new ArrayList<String>();
 
-        View view = inflater.inflate(R.layout.fragment_media_detail, 
container, false);
+        final View view = inflater.inflate(R.layout.fragment_media_detail, 
container, false);
+
         image = (ImageView) view.findViewById(R.id.mediaDetailImage);
-        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) {
+                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 +117,8 @@
         if(!editable) {
             title.setBackgroundDrawable(null);
         }
+        */
+
 
         String actualUrl = TextUtils.isEmpty(media.getImageUrl()) ? 
media.getLocalUri().toString() : media.getThumbnailUrl(640);
         if(actualUrl.startsWith("http")) {
@@ -88,6 +128,46 @@
             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<Void, Void, Boolean>() {
+                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());
+                        categoryAdapter.notifyDataSetChanged();
+                    }
+                }
+            };
+            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 +193,11 @@
                 }
             });
         }
-        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 +213,32 @@
 
             }
         });
+        */
+
+        // 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();
+                }
+            }
+        };
+        view.getViewTreeObserver().addOnGlobalLayoutListener(observer);
         return view;
     }
 
@@ -139,4 +248,17 @@
 
         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 0000000..49b0485
--- /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);
+    }
+}

-- 
To view, visit https://gerrit.wikimedia.org/r/78360
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I2efb3da61a586a2867a7c3cfc7a0ddea11c208dc
Gerrit-PatchSet: 1
Gerrit-Project: apps/android/commons
Gerrit-Branch: master
Gerrit-Owner: SuchABot <yuvipanda+sucha...@gmail.com>
Gerrit-Reviewer: Brion VIBBER <br...@wikimedia.org>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to