Yuvipanda has uploaded a new change for review. https://gerrit.wikimedia.org/r/81139
Change subject: Media detail page redone as a "slide-up" panel. ...................................................................... Media detail page redone as a "slide-up" panel. Loads and displays default or English description, and categories. No caching of this info yet. Scrollable pane is a ListView, with the title/desc/category label in a 'header' view along with a spacer view. The height of the spacer is set dynamically to the height of the total fragment minus 48dp, giving room for an initially-visible title section and a little spillover so you can see it's scrollable. Clicking on a category in the cats list opens the category page in an external web browser. In the future this should open the category within the app, but we don't have a per-cat view yet. Description and category list are not yet editable. Change-Id: I46d0a77481dbe64a268a72f3efe49ae72168541f --- 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/res/values-qq/strings.xml M commons/res/values/strings.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 13 files changed, 586 insertions(+), 30 deletions(-) git pull ssh://gerrit.wikimedia.org:29418/apps/android/commons refs/changes/39/81139/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..dd6f9ae --- /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="@string/detail_panel_cats_label" + android:textSize="18sp" + android:layout_gravity="left|start" + android:paddingTop="24dp" android:textColor="@android:color/white"/> + <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/res/values-qq/strings.xml b/commons/res/values-qq/strings.xml index 956329b..e11b059 100644 --- a/commons/res/values-qq/strings.xml +++ b/commons/res/values-qq/strings.xml @@ -89,4 +89,7 @@ <string name="welcome_final_text">Message asking user if they understand what kinds of images to upload.</string> <string name="welcome_final_button_text">Button text for confirming the user understands what kinds of images to upload. {{Identical|Yes}}</string> + <string name="detail_panel_cats_label">Label for categories list in media detail panel</string> + <string name="detail_panel_cats_loading">Placeholder for categories list in media detail panel, while loading from network.</string> + <string name="detail_panel_cats_none">Placeholder for categories list in media detail panel, if no categories found.</string> </resources> diff --git a/commons/res/values/strings.xml b/commons/res/values/strings.xml index f3d4ac7..7bff4bb 100644 --- a/commons/res/values/strings.xml +++ b/commons/res/values/strings.xml @@ -102,4 +102,7 @@ <string name="welcome_copyright_subtext">Avoid copyrighted materials you found from the Internet as well as images of posters, book covers, etc.</string> <string name="welcome_final_text">You think you got it?</string> <string name="welcome_final_button_text">Yes!</string> + <string name="detail_panel_cats_label">Categories</string> + <string name="detail_panel_cats_loading">Loading...</string> + <string name="detail_panel_cats_none">None selected</string> </resources> diff --git a/commons/src/main/java/org/wikimedia/commons/CommonsApplication.java b/commons/src/main/java/org/wikimedia/commons/CommonsApplication.java index d41af87..ecf5097 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 @@ 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 b3623c1..f3bea5a 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..e06d441 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 { @@ -33,6 +41,8 @@ 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 @@ } 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<String> categoryNames; + private boolean categoriesLoaded = false; + private boolean categoriesPresent = false; + private ArrayAdapter categoryAdapter; + private ViewTreeObserver.OnGlobalLayoutListener observer; // for layout stuff, only used once! + private AsyncTask<Void,Void,Boolean> detailFetchTask; @Override @@ -50,6 +73,20 @@ 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 @@ 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<String>(); + categoryNames.add(getString(R.string.detail_panel_cats_loading)); - 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) { + 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 @@ 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 @@ 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()); + + 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 @@ } }); } - 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 @@ } }); + */ + + // 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 @@ 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/81139 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I46d0a77481dbe64a268a72f3efe49ae72168541f Gerrit-PatchSet: 1 Gerrit-Project: apps/android/commons Gerrit-Branch: master Gerrit-Owner: Yuvipanda <[email protected]> Gerrit-Reviewer: Brion VIBBER <[email protected]> _______________________________________________ MediaWiki-commits mailing list [email protected] https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits
