UI matching in detail view to iOS version

* ListView -> LinearLayout for categories
* Add labels for fields
* Cool transparent rectangles!
* Padding adjustments...
* Darken image when scrolling detail panel up
* Placeholders for empty desc, license fields

Change-Id: I0e4c4348e741af3560d455ee4b793c2743626fbf
This commit is contained in:
Brion Vibber 2013-10-11 12:58:53 -07:00
parent 5e1fa56f36
commit e46a1fac88
5 changed files with 251 additions and 134 deletions

View file

@ -1,12 +1,22 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minHeight="48dp" android:minHeight="48dp"
android:padding="8dp" android:padding="12dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:id="@+id/mediaDetailCategoryItemText" android:id="@+id/mediaDetailCategoryItemText"
android:textSize="18sp" android:textSize="14sp"
android:background="#AA000000" android:textColor="@android:color/white"
android:background="#20ffffff"
/> />
<org.wikimedia.commons.media.MediaDetailSpacer
android:layout_width="fill_parent"
android:layout_height="8dp"/>
</LinearLayout>

View file

@ -1,56 +0,0 @@
<?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="License link"
android:id="@+id/mediaDetailLicense"
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="@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>

View file

@ -28,14 +28,137 @@
android:scaleType="fitCenter" android:scaleType="fitCenter"
/> />
<ListView <ScrollView
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/mediaDetailListView" android:id="@+id/mediaDetailScrollView"
android:divider="#00A0A0A0"
android:fillViewport="true" android:fillViewport="true"
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:cacheColorHint="@android:color/transparent" android:cacheColorHint="@android:color/transparent">
/>
<LinearLayout 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="16dp"
android:id="@+id/mediaDetailSpacer"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#AA000000"
android:padding="16dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#20ffffff"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:text="Title"
android:textSize="16sp"
android:textStyle="bold"
android:paddingBottom="6dp"/>
<TextView
android:layout_width="match_parent"
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:background="#20ffffff"
android:textSize="14sp"
android:padding="12dp"/>
</LinearLayout>
<org.wikimedia.commons.media.MediaDetailSpacer
android:layout_width="fill_parent"
android:layout_height="8dp"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#20ffffff"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Description"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold"
android:paddingBottom="6dp"/>
<TextView
android:layout_width="match_parent"
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:background="#20ffffff"
android:id="@+id/mediaDetailDesc"
android:textColor="@android:color/white"
android:layout_gravity="left|start"
android:textSize="14sp"
android:padding="12dp"/>
</LinearLayout>
<org.wikimedia.commons.media.MediaDetailSpacer
android:layout_width="fill_parent"
android:layout_height="8dp"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#20ffffff"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:text="License"
android:textSize="16sp"
android:textStyle="bold"
android:paddingBottom="6dp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="License link"
android:id="@+id/mediaDetailLicense"
android:layout_gravity="left|start"
android:background="#20ffffff"
android:textColor="@android:color/white"
android:textSize="14sp"
android:padding="12dp"/>
</LinearLayout>
<org.wikimedia.commons.media.MediaDetailSpacer
android:layout_width="fill_parent"
android:layout_height="8dp"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#20ffffff"
android:padding="16dp"
android:textStyle="bold">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/detail_panel_cats_label"
android:textSize="16sp"
android:layout_gravity="left|start"
android:textColor="@android:color/white"
android:paddingBottom="6dp"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/mediaDetailCategoryContainer"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>
</FrameLayout> </FrameLayout>

View file

@ -138,5 +138,7 @@
<string name="detail_panel_cats_label">Categories</string> <string name="detail_panel_cats_label">Categories</string>
<string name="detail_panel_cats_loading">Loading...</string> <string name="detail_panel_cats_loading">Loading...</string>
<string name="detail_panel_cats_none">None selected</string> <string name="detail_panel_cats_none">None selected</string>
<string name="detail_description_empty">No description</string>
<string name="detail_license_empty">Unknown license</string>
<string name="provider_campaigns">Campaigns</string> <string name="provider_campaigns">Campaigns</string>
</resources> </resources>

View file

@ -2,7 +2,6 @@ package org.wikimedia.commons.media;
import android.content.Intent; import android.content.Intent;
import android.graphics.*; import android.graphics.*;
import android.net.Uri;
import android.os.*; import android.os.*;
import android.text.*; import android.text.*;
import android.util.Log; import android.util.Log;
@ -10,15 +9,12 @@ import android.util.TypedValue;
import android.view.*; import android.view.*;
import android.widget.*; import android.widget.*;
import com.actionbarsherlock.app.SherlockFragment; import com.actionbarsherlock.app.SherlockFragment;
import com.android.volley.toolbox.NetworkImageView;
import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.assist.FailReason; import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.assist.ImageLoadingListener; import com.nostra13.universalimageloader.core.assist.ImageLoadingListener;
import com.android.volley.toolbox.*; import com.android.volley.toolbox.*;
import org.mediawiki.api.ApiResult;
import org.mediawiki.api.MWApi;
import org.wikimedia.commons.*; import org.wikimedia.commons.*;
import java.io.IOException; import java.io.IOException;
@ -54,19 +50,20 @@ public class MediaDetailFragment extends SherlockFragment {
private ProgressBar loadingProgress; private ProgressBar loadingProgress;
private ImageView loadingFailed; private ImageView loadingFailed;
private MediaDetailSpacer spacer; private MediaDetailSpacer spacer;
private int initialListIndex = 0;
private int initialListTop = 0; private int initialListTop = 0;
private TextView title; private TextView title;
private TextView desc; private TextView desc;
private TextView license; private TextView license;
private ListView listView; private LinearLayout categoryContainer;
private ScrollView scrollView;
private ArrayList<String> categoryNames; private ArrayList<String> categoryNames;
private boolean categoriesLoaded = false; private boolean categoriesLoaded = false;
private boolean categoriesPresent = false; private boolean categoriesPresent = false;
private ArrayAdapter categoryAdapter; private ViewTreeObserver.OnGlobalLayoutListener layoutListener; // for layout stuff, only used once!
private ViewTreeObserver.OnGlobalLayoutListener observer; // for layout stuff, only used once! private ViewTreeObserver.OnScrollChangedListener scrollListener;
private AsyncTask<Void,Void,Boolean> detailFetchTask; private AsyncTask<Void,Void,Boolean> detailFetchTask;
private LicenseList licenseList;
@Override @Override
@ -76,18 +73,11 @@ public class MediaDetailFragment extends SherlockFragment {
outState.putBoolean("editable", editable); outState.putBoolean("editable", editable);
getScrollPosition(); getScrollPosition();
outState.putInt("listIndex", initialListIndex);
outState.putInt("listTop", initialListTop); outState.putInt("listTop", initialListTop);
} }
private void getScrollPosition() { private void getScrollPosition() {
int initialListIndex = listView.getFirstVisiblePosition(); initialListTop = scrollView.getScrollY();
View firstVisibleItem = listView.getChildAt(initialListIndex);
if (firstVisibleItem == null) {
initialListTop = 0;
} else {
initialListTop = firstVisibleItem.getTop();
}
} }
@Override @Override
@ -97,11 +87,11 @@ public class MediaDetailFragment extends SherlockFragment {
if(savedInstanceState != null) { if(savedInstanceState != null) {
editable = savedInstanceState.getBoolean("editable"); editable = savedInstanceState.getBoolean("editable");
index = savedInstanceState.getInt("index"); index = savedInstanceState.getInt("index");
initialListIndex = savedInstanceState.getInt("listIndex");
initialListTop = savedInstanceState.getInt("listTop"); initialListTop = savedInstanceState.getInt("listTop");
} else { } else {
editable = getArguments().getBoolean("editable"); editable = getArguments().getBoolean("editable");
index = getArguments().getInt("index"); index = getArguments().getInt("index");
initialListTop = 0;
} }
final Media media = detailProvider.getMediaAtPosition(index); final Media media = detailProvider.getMediaAtPosition(index);
categoryNames = new ArrayList<String>(); categoryNames = new ArrayList<String>();
@ -112,29 +102,16 @@ public class MediaDetailFragment extends SherlockFragment {
image = (ImageView) view.findViewById(R.id.mediaDetailImage); image = (ImageView) view.findViewById(R.id.mediaDetailImage);
loadingProgress = (ProgressBar) view.findViewById(R.id.mediaDetailImageLoading); loadingProgress = (ProgressBar) view.findViewById(R.id.mediaDetailImageLoading);
loadingFailed = (ImageView) view.findViewById(R.id.mediaDetailImageFailed); loadingFailed = (ImageView) view.findViewById(R.id.mediaDetailImageFailed);
listView = (ListView) view.findViewById(R.id.mediaDetailListView); scrollView = (ScrollView) view.findViewById(R.id.mediaDetailScrollView);
// Detail consists of a list view with main pane in header view, plus category list. // 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); spacer = (MediaDetailSpacer) view.findViewById(R.id.mediaDetailSpacer);
listView.addHeaderView(detailView, null, false); title = (TextView) view.findViewById(R.id.mediaDetailTitle);
categoryAdapter = new ArrayAdapter(getActivity(), R.layout.detail_category_item, categoryNames); desc = (TextView) view.findViewById(R.id.mediaDetailDesc);
listView.setAdapter(categoryAdapter); license = (TextView) view.findViewById(R.id.mediaDetailLicense);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { categoryContainer = (LinearLayout) view.findViewById(R.id.mediaDetailCategoryContainer);
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); licenseList = new LicenseList(getActivity());
title = (TextView) detailView.findViewById(R.id.mediaDetailTitle);
desc = (TextView) detailView.findViewById(R.id.mediaDetailDesc);
license = (TextView) detailView.findViewById(R.id.mediaDetailLicense);
// Enable or disable editing on the title // Enable or disable editing on the title
/* /*
@ -162,11 +139,9 @@ public class MediaDetailFragment extends SherlockFragment {
// FIXME: cache this data // FIXME: cache this data
detailFetchTask = new AsyncTask<Void, Void, Boolean>() { detailFetchTask = new AsyncTask<Void, Void, Boolean>() {
private MediaDataExtractor extractor; private MediaDataExtractor extractor;
private LicenseList licenseList;
@Override @Override
protected void onPreExecute() { protected void onPreExecute() {
licenseList = new LicenseList(getActivity());
extractor = new MediaDataExtractor(media.getFilename(), licenseList); extractor = new MediaDataExtractor(media.getFilename(), licenseList);
} }
@ -189,17 +164,8 @@ public class MediaDetailFragment extends SherlockFragment {
extractor.fill(media); extractor.fill(media);
// Fill some fields // Fill some fields
desc.setText(media.getDescription("en")); desc.setText(prettyDescription(media));
license.setText(prettyLicense(media));
String licenseKey = media.getLicense();
License licenseObj = licenseList.get(licenseKey);
if (licenseObj == null) {
license.setText(licenseKey);
} else {
license.setText(licenseObj.getName());
}
Log.d("Commons", "Media license is: " + media.getLicense());
categoryNames.removeAll(categoryNames); categoryNames.removeAll(categoryNames);
categoryNames.addAll(media.getCategories()); categoryNames.addAll(media.getCategories());
@ -210,8 +176,7 @@ public class MediaDetailFragment extends SherlockFragment {
// Stick in a filler element. // Stick in a filler element.
categoryNames.add(getString(R.string.detail_panel_cats_none)); categoryNames.add(getString(R.string.detail_panel_cats_none));
} }
rebuildCatList();
categoryAdapter.notifyDataSetChanged();
} else { } else {
Log.d("Commons", "Failed to load photo details."); Log.d("Commons", "Failed to load photo details.");
} }
@ -266,15 +231,23 @@ public class MediaDetailFragment extends SherlockFragment {
}); });
*/ */
// Layout observer to size the spacer item relative to the available space. // Progressively darken the image in the background when we scroll detail pane up
scrollListener = new ViewTreeObserver.OnScrollChangedListener() {
public void onScrollChanged() {
updateTheDarkness();
}
};
view.getViewTreeObserver().addOnScrollChangedListener(scrollListener);
// Layout layoutListener to size the spacer item relative to the available space.
// There may be a .... better way to do this. // There may be a .... better way to do this.
observer = new ViewTreeObserver.OnGlobalLayoutListener() { layoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
private int currentHeight = -1; private int currentHeight = -1;
public void onGlobalLayout() { public void onGlobalLayout() {
int viewHeight = view.getHeight(); int viewHeight = view.getHeight();
//int textHeight = title.getLineHeight(); //int textHeight = title.getLineHeight();
int paddingDp = 48; int paddingDp = 112;
float paddingPx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, paddingDp, getResources().getDisplayMetrics()); float paddingPx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, paddingDp, getResources().getDisplayMetrics());
int newHeight = viewHeight - Math.round(paddingPx); int newHeight = viewHeight - Math.round(paddingPx);
@ -284,15 +257,12 @@ public class MediaDetailFragment extends SherlockFragment {
params.height = newHeight; params.height = newHeight;
spacer.setLayoutParams(params); spacer.setLayoutParams(params);
// hack hack to trigger relayout scrollView.scrollTo(0, initialListTop);
categoryAdapter.notifyDataSetChanged();
listView.setSelectionFromTop(initialListIndex, initialListTop);
} }
} }
}; };
view.getViewTreeObserver().addOnGlobalLayoutListener(observer); view.getViewTreeObserver().addOnGlobalLayoutListener(layoutListener);
return view; return view;
} }
@ -309,10 +279,78 @@ public class MediaDetailFragment extends SherlockFragment {
detailFetchTask.cancel(true); detailFetchTask.cancel(true);
detailFetchTask = null; detailFetchTask = null;
} }
if (observer != null) { if (layoutListener != null) {
getView().getViewTreeObserver().removeGlobalOnLayoutListener(observer); // old Android was on crack. CRACK IS WHACK getView().getViewTreeObserver().removeGlobalOnLayoutListener(layoutListener); // old Android was on crack. CRACK IS WHACK
observer = null; layoutListener = null;
}
if (scrollListener != null) {
getView().getViewTreeObserver().removeOnScrollChangedListener(scrollListener);
scrollListener = null;
} }
super.onDestroyView(); super.onDestroyView();
} }
private void rebuildCatList() {
// @fixme add the category items
for (String cat : categoryNames) {
View catLabel = buildCatLabel(cat);
categoryContainer.addView(catLabel);
}
}
private View buildCatLabel(String cat) {
final String catName = cat;
final View item = getLayoutInflater(null).inflate(R.layout.detail_category_item, null, false);
final TextView textView = (TextView)item.findViewById(R.id.mediaDetailCategoryItemText);
textView.setText(cat);
if (categoriesLoaded && categoriesPresent) {
textView.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
String selectedCategoryTitle = "Category:" + catName;
Intent viewIntent = new Intent();
viewIntent.setAction(Intent.ACTION_VIEW);
viewIntent.setData(Utils.uriForWikiPage(selectedCategoryTitle));
startActivity(viewIntent);
}
});
}
return item;
}
private void updateTheDarkness() {
// You must face the darkness alone
int scrollY = scrollView.getScrollY();
int scrollMax = getView().getHeight();
float scrollPercentage = (float)scrollY / (float)scrollMax;
final float transparencyMax = 0.75f;
if (scrollPercentage > transparencyMax) {
scrollPercentage = transparencyMax;
}
image.setAlpha(1.0f - scrollPercentage);
}
private String prettyDescription(Media media) {
// @todo use UI language when multilingual descs are available
String desc = media.getDescription("en").trim();
if (desc.equals("")) {
return getString(R.string.detail_description_empty);
} else {
return desc;
}
}
private String prettyLicense(Media media) {
String licenseKey = media.getLicense();
Log.d("Commons", "Media license is: " + licenseKey);
if (licenseKey.equals("")) {
return getString(R.string.detail_license_empty);
}
License licenseObj = licenseList.get(licenseKey);
if (licenseObj == null) {
return licenseKey;
} else {
return licenseObj.getName();
}
}
} }