Fix Memory Leak in Order Stats Card
I merged PR #321 and shorty after I remembered that non-static anonymous class instantiations will keep a reference to their hosting view. 
<img src="https://user-images.githubusercontent.com/5810477/44614866-d9097500-a7f2-11e8-9f95-a6568c4d9590.gif" width="45"/> 
With Kotin, the anonymous class instance will be declared static, but a bunch of accessors are added if the code accesses anything in the hosting class. This code snippet:

```
fun initView(...) {
	/* .... */
	
	lastUpdatedHandler = Handler()
	lastUpdatedRunnable = Runnable {
	    updateRecencyMessage()
	    lastUpdatedHandler.postDelayed(lastUpdatedRunnable, UPDATE_DELAY_TIME_MS)
	}
}
```

Currently this is not too big of a deal because the top-level fragments are never destroyed, just hidden. If this code was in one of the child fragment views, like `OrderDetailOrderStatusView`, then the hosting fragment and all of it's views would not be garbage collected, and everytime a new order detail was opened, a new instance would be created, and so on. The objects would continue to live in memory until the delayed runnable ran to completion. Since this runnable resubmits itself, all instances would live in memory until the process is destroyed. 

Also, this loop continues to run even when the dashboard is no longer the active view. 

Both issues could be fixed by telling the view it should cancel this handler callback, or `DashboardFragment` could host the handler and runnable and tell the custom view when to update it's "last updated" field and then flush when it's hidden. 

cc: @nbradbury 


diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardStatsView.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardStatsView.kt
index 0e199886e7..fa0f16d1ac 100644
--- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardStatsView.kt
+++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardStatsView.kt
@@ -1,9 +1,11 @@
 package com.woocommerce.android.ui.dashboard
 
 import android.content.Context
+import android.os.Handler
 import android.support.annotation.StringRes
 import android.support.design.widget.TabLayout
 import android.support.v4.content.ContextCompat
+import android.text.format.DateFormat
 import android.util.AttributeSet
 import android.view.View
 import android.widget.LinearLayout
@@ -21,6 +23,8 @@ import com.woocommerce.android.util.DateUtils
 import kotlinx.android.synthetic.main.dashboard_stats.view.*
 import org.wordpress.android.fluxc.store.WCStatsStore.StatsGranularity
 import org.wordpress.android.fluxc.utils.SiteUtils
+import org.wordpress.android.util.DateTimeUtils
+import java.util.Date
 import java.util.Timer
 import java.util.TimerTask
 
@@ -32,6 +36,7 @@ class DashboardStatsView @JvmOverloads constructor(ctx: Context, attrs: Attribut
 
     companion object {
         private const val PROGRESS_DELAY_TIME_MS = 200L
+        private const val UPDATE_DELAY_TIME_MS = 60 * 1000L
     }
 
     var activeGranularity: StatsGranularity = DEFAULT_STATS_GRANULARITY
@@ -49,6 +54,11 @@ class DashboardStatsView @JvmOverloads constructor(ctx: Context, attrs: Attribut
     private var progressDelayTimer: Timer? = null
     private var progressDelayTimerTask: TimerTask? = null
 
+    private lateinit var lastUpdatedRunnable: Runnable
+    private lateinit var lastUpdatedHandler: Handler
+
+    private var lastUpdated: Date? = null
+
     fun initView(
         period: StatsGranularity = DEFAULT_STATS_GRANULARITY,
         listener: DashboardStatsListener,
@@ -88,6 +98,12 @@ class DashboardStatsView @JvmOverloads constructor(ctx: Context, attrs: Attribut
         })
 
         initChart()
+
+        lastUpdatedHandler = Handler()
+        lastUpdatedRunnable = Runnable {
+            updateRecencyMessage()
+            lastUpdatedHandler.postDelayed(lastUpdatedRunnable, UPDATE_DELAY_TIME_MS)
+        }
     }
 
     /**
@@ -149,6 +165,7 @@ class DashboardStatsView @JvmOverloads constructor(ctx: Context, attrs: Attribut
             chart.setNoDataTextColor(ContextCompat.getColor(context, R.color.graph_no_data_text_color))
             chart.setNoDataText(context.getString(R.string.dashboard_state_no_data))
             chart.clear()
+            clearLastUpdated()
             return
         }
 
@@ -163,6 +180,8 @@ class DashboardStatsView @JvmOverloads constructor(ctx: Context, attrs: Attribut
 
             invalidate() // Draw/redraw the graph
         }
+
+        resetLastUpdated()
     }
 
     fun showErrorView(show: Boolean) {
@@ -203,6 +222,7 @@ class DashboardStatsView @JvmOverloads constructor(ctx: Context, attrs: Attribut
             override fun run() {
                 this@DashboardStatsView.post {
                     barchart_progress.visibility = View.VISIBLE
+                    clearLastUpdated()
                 }
             }
         }
@@ -222,6 +242,68 @@ class DashboardStatsView @JvmOverloads constructor(ctx: Context, attrs: Attribut
         }
     }
 
+    private fun clearLastUpdated() {
+        lastUpdated = null
+        updateRecencyMessage()
+    }
+
+    private fun resetLastUpdated() {
+        lastUpdated = Date()
+        updateRecencyMessage()
+    }
+
+    private fun updateRecencyMessage() {
+        dashboard_recency_text.text = getRecencyMessage()
+        lastUpdatedHandler.removeCallbacks(lastUpdatedRunnable)
+
+        if (lastUpdated != null) {
+            lastUpdatedHandler.postDelayed(lastUpdatedRunnable, UPDATE_DELAY_TIME_MS)
+        }
+    }
+
+    /**
+     * Returns the text to use for the "recency message" which tells the user when stats were last updated
+     */
+    private fun getRecencyMessage(): String? {
+        if (lastUpdated == null) {
+            return null
+        }
+
+        val now = Date()
+
+        // up to 2 minutes -> "Updated moments ago"
+        val minutes = DateTimeUtils.minutesBetween(now, lastUpdated)
+        if (minutes <= 2) {
+            return context.getString(R.string.dashboard_stats_updated_now)
+        }
+
+        // up to 59 minutes -> "Updated 5 minutes ago"
+        if (minutes <= 59) {
+            return String.format(context.getString(R.string.dashboard_stats_updated_minutes), minutes)
+        }
+
+        // 1 hour -> "Updated 1 hour ago"
+        val hours = DateTimeUtils.hoursBetween(now, lastUpdated)
+        if (hours == 1) {
+            return context.getString(R.string.dashboard_stats_updated_one_hour)
+        }
+
+        // up to 23 hours -> "Updated 5 hours ago"
+        if (hours <= 23) {
+            return String.format(context.getString(R.string.dashboard_stats_updated_hours), hours)
+        }
+
+        // up to 47 hours -> "Updated 1 day ago"
+        if (hours <= 47) {
+            return context.getString(R.string.dashboard_stats_updated_one_day)
+        }
+
+        // otherwise date & time
+        val dateStr = DateFormat.getDateFormat(context).format(lastUpdated)
+        val timeStr = DateFormat.getTimeFormat(context).format(lastUpdated)
+        return String.format(context.getString(R.string.dashboard_stats_updated_date_time), "$dateStr $timeStr")
+    }
+
     private inner class StartEndDateAxisFormatter : IAxisValueFormatter {
         override fun getFormattedValue(value: Float, axis: AxisBase): String {
             return when (value) {
diff --git a/WooCommerce/src/main/res/layout/dashboard_stats.xml b/WooCommerce/src/main/res/layout/dashboard_stats.xml
index ae31b25a8d..f7baf77ec5 100644
--- a/WooCommerce/src/main/res/layout/dashboard_stats.xml
+++ b/WooCommerce/src/main/res/layout/dashboard_stats.xml
@@ -140,4 +140,11 @@
             tools:visibility="gone"/>
     </FrameLayout>
 
+    <TextView
+        android:id="@+id/dashboard_recency_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        tools:text="dashboard_recency_text"/>
+
 </merge>
diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml
index 23baecbd0f..21920afbb6 100644
--- a/WooCommerce/src/main/res/values/strings.xml
+++ b/WooCommerce/src/main/res/values/strings.xml
@@ -93,6 +93,13 @@
     <string name="dashboard_stats_error">Error fetching data</string>
     <string name="dashboard_stats_error_content_description">Error image</string>
 
+    <string name="dashboard_stats_updated_now">Updated moments ago</string>
+    <string name="dashboard_stats_updated_minutes">Updated %d minutes ago</string>
+    <string name="dashboard_stats_updated_one_hour">Updated 1 hour ago</string>
+    <string name="dashboard_stats_updated_hours">Updated %d hours ago</string>
+    <string name="dashboard_stats_updated_one_day">Updated 1 day ago</string>
+    <string name="dashboard_stats_updated_date_time">Updated on %s</string>
+
     <string name="dashboard_top_earners_title">Top performers</string>
     <string name="dashboard_top_earners_description">Gain insights into how products are performing on your store</string>
     <string name="dashboard_top_earners_total_spend">Total spend</string>
['WooCommerce/src/main/res/layout/dashboard_stats.xml', 'WooCommerce/src/main/res/values/strings.xml', 'WooCommerce/src/main/kotlin/com/woocommerce/android/ui/dashboard/DashboardStatsView.kt']