Refresh detailed change view on vertical swipe gesture

Change-Id: I1e4cd9464b7828197565a08e413b040f5dfcec1b
Signed-off-by: Edwin Kempin <ekempin@google.com>
diff --git a/app/src/main/java/com/google/reviewit/DetailedChangeFragment.java b/app/src/main/java/com/google/reviewit/DetailedChangeFragment.java
index f53b684..f8f427a 100644
--- a/app/src/main/java/com/google/reviewit/DetailedChangeFragment.java
+++ b/app/src/main/java/com/google/reviewit/DetailedChangeFragment.java
@@ -15,8 +15,10 @@
 package com.google.reviewit;
 
 import android.content.Intent;
+import android.os.AsyncTask;
 import android.os.Bundle;
 import android.support.annotation.LayoutRes;
+import android.support.v4.widget.SwipeRefreshLayout;
 import android.text.util.Linkify;
 import android.util.Log;
 import android.view.Menu;
@@ -27,6 +29,7 @@
 
 import com.google.gerrit.extensions.client.ChangeStatus;
 import com.google.reviewit.app.SortActionHandler;
+import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.reviewit.app.Change;
 import com.google.reviewit.util.ChangeUtil;
 import com.google.reviewit.widget.ChangeBox;
@@ -79,10 +82,30 @@
     Change change = getApp().getSortActionHandler().getCurrentChange();
     setTitle(getString(R.string.detailed_change_title, change.info._number));
     setHasOptionsMenu(true);
-    init();
+    init(change);
     zoomHandler = new ZoomHandler(v(R.id.scrollContent));
     TaskObserver.enableProgressBar(getWindow());
 
+    display(change);
+  }
+
+  private void init(final Change change) {
+    SwipeRefreshLayout swipeRefreshLayout =
+        (SwipeRefreshLayout) v(R.id.swipeRefreshLayout);
+    swipeRefreshLayout.setColorSchemeColors(R.color.progressBar);
+    swipeRefreshLayout.setRefreshing(true);
+    swipeRefreshLayout.setOnRefreshListener(
+        new SwipeRefreshLayout.OnRefreshListener() {
+          @Override
+          public void onRefresh() {
+            refresh(change);
+          }
+        });
+
+    tv(R.id.commitMessage).setLinksClickable(true);
+  }
+
+  private void display(Change change) {
     try {
       ChangeUtil.colorBackground(root, change);
       ((ChangeBox) v(R.id.changeBox)).display(getApp(), change);
@@ -93,21 +116,14 @@
       ((FileBox)v(R.id.fileBox)).display(this, change);
       // TODO show further change info, e.g. summary comments, hashtags,
       // related changes
+
+      ((SwipeRefreshLayout) v(R.id.swipeRefreshLayout)).setRefreshing(false);
     } catch (Throwable t) {
       Log.e(TAG, "Failed to display change", t);
       display(ErrorFragment.create(t));
     }
   }
 
-  @Override
-  public void dispatchTouchEvent(MotionEvent event) {
-    zoomHandler.dispatchTouchEvent(event);
-  }
-
-  private void init() {
-    tv(R.id.commitMessage).setLinksClickable(true);
-  }
-
   private void linkify() {
     TextView commitMsg = tv(R.id.commitMessage);
     Linkify.addLinks(commitMsg, PATTERN_CHANGE_ID, getServerUrl() + "#/q/");
@@ -115,6 +131,11 @@
     Linkify.addLinks(commitMsg, PATTERN_EMAIL, "");
   }
 
+  @Override
+  public void dispatchTouchEvent(MotionEvent event) {
+    zoomHandler.dispatchTouchEvent(event);
+  }
+
   private void displayChangeUrl(Change change) {
     WidgetUtil.setText(v(R.id.changeUrl), change.getUrl(getServerUrl()));
     setVisible(v(R.id.changeUrlBox));
@@ -125,6 +146,31 @@
     return FormatUtil.ensureSlash(serverUrl);
   }
 
+  private void refresh(final Change change) {
+    new AsyncTask<Void, Void, Change>() {
+      @Override
+      protected Change doInBackground(Void... v) {
+        try {
+          change.reload();
+          return change;
+        } catch (RestApiException e) {
+          // e.g. server not reachable
+          Log.e(TAG, "Reload failed", e);
+          return null;
+        }
+      }
+
+      @Override
+      protected void onPostExecute(Change change) {
+        if (change != null) {
+          ((ApprovalsView) v(R.id.approvals)).clear();
+          ((FileBox) v(R.id.fileBox)).clear();
+          display(change);
+        }
+      }
+    }.execute();
+  }
+
   @Override
   public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
     SortActionHandler actionHandler = getApp().getSortActionHandler();
diff --git a/app/src/main/java/com/google/reviewit/widget/ApprovalsView.java b/app/src/main/java/com/google/reviewit/widget/ApprovalsView.java
index 7b4d5ea..b97668b 100644
--- a/app/src/main/java/com/google/reviewit/widget/ApprovalsView.java
+++ b/app/src/main/java/com/google/reviewit/widget/ApprovalsView.java
@@ -138,6 +138,10 @@
     }
   }
 
+  public void clear() {
+    removeAllViews();
+  }
+
   private void addApprovalRow(
       ReviewItApp app, AccountInfo account, Map<String, LabelInfo> labels,
       TreeMap<String, Map<Integer, ApprovalInfo>> approvalsByLabel) {
diff --git a/app/src/main/java/com/google/reviewit/widget/InfoBox.java b/app/src/main/java/com/google/reviewit/widget/InfoBox.java
index 8d89387..fc18031 100644
--- a/app/src/main/java/com/google/reviewit/widget/InfoBox.java
+++ b/app/src/main/java/com/google/reviewit/widget/InfoBox.java
@@ -138,5 +138,14 @@
     }
   }
 
+  public void clear() {
+    TableLayout tl = (TableLayout) findViewById(R.id.info_table);
+    View child = tl.getChildAt(1);
+    while (child != null) {
+      tl.removeView(child);
+      child = tl.getChildAt(1);
+    }
+  }
+
   protected abstract void addRow(TableLayout tl, T entry);
 }
diff --git a/app/src/main/res/layout/content_detailed_change.xml b/app/src/main/res/layout/content_detailed_change.xml
index 2c11c03..72420b5 100644
--- a/app/src/main/res/layout/content_detailed_change.xml
+++ b/app/src/main/res/layout/content_detailed_change.xml
@@ -23,53 +23,59 @@
 
   <include layout="@layout/progress"/>
 
-  <ScrollView
+  <android.support.v4.widget.SwipeRefreshLayout
+    android:id="@+id/swipeRefreshLayout"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
-    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-                  xmlns:app="http://schemas.android.com/apk/res-auto"
-                  android:id="@+id/scrollContent"
-                  android:orientation="vertical"
-                  android:layout_width="wrap_content"
-                  android:layout_height="wrap_content"
-                  android:paddingBottom="@dimen/activity_vertical_margin"
-                  android:paddingLeft="@dimen/activity_horizontal_margin"
-                  android:paddingRight="@dimen/activity_horizontal_margin"
-                  android:paddingTop="@dimen/activity_vertical_margin">
+    <ScrollView
+      android:layout_width="match_parent"
+      android:layout_height="match_parent">
 
-      <com.google.reviewit.widget.ChangeBox
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:app="http://schemas.android.com/apk/res-auto"
-        android:id="@+id/changeBox"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"/>
+      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                    xmlns:app="http://schemas.android.com/apk/res-auto"
+                    android:id="@+id/scrollContent"
+                    android:orientation="vertical"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:paddingBottom="@dimen/activity_vertical_margin"
+                    android:paddingLeft="@dimen/activity_horizontal_margin"
+                    android:paddingRight="@dimen/activity_horizontal_margin"
+                    android:paddingTop="@dimen/activity_vertical_margin">
 
-      <View
-        android:layout_width="match_parent"
-        android:layout_height="10dp"/>
+        <com.google.reviewit.widget.ChangeBox
+          xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:app="http://schemas.android.com/apk/res-auto"
+          android:id="@+id/changeBox"
+          android:layout_width="match_parent"
+          android:layout_height="match_parent"/>
 
-      <com.google.reviewit.widget.ApprovalsView
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:app="http://schemas.android.com/apk/res-auto"
-        android:id="@+id/approvals"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content">
-      </com.google.reviewit.widget.ApprovalsView>
+        <View
+          android:layout_width="match_parent"
+          android:layout_height="10dp"/>
 
-      <View
-        android:layout_width="match_parent"
-        android:layout_height="10dp"/>
+        <com.google.reviewit.widget.ApprovalsView
+          xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:app="http://schemas.android.com/apk/res-auto"
+          android:id="@+id/approvals"
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content">
+        </com.google.reviewit.widget.ApprovalsView>
 
-      <com.google.reviewit.widget.FileBox
-        android:id="@+id/fileBox"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content">
-      </com.google.reviewit.widget.FileBox>
+        <View
+          android:layout_width="match_parent"
+          android:layout_height="10dp"/>
 
-      <View
-        android:layout_width="match_parent"
-        android:layout_height="10dp"/>
-    </LinearLayout>
-  </ScrollView>
+        <com.google.reviewit.widget.FileBox
+          android:id="@+id/fileBox"
+          android:layout_width="match_parent"
+          android:layout_height="wrap_content">
+        </com.google.reviewit.widget.FileBox>
+
+        <View
+          android:layout_width="match_parent"
+          android:layout_height="10dp"/>
+      </LinearLayout>
+    </ScrollView>
+  </android.support.v4.widget.SwipeRefreshLayout>
 </RelativeLayout>