Use indeterminate progress indicator

Closes #99
pull/189/head
Alex Baker 11 years ago
parent dd772f717b
commit 16b149b76d

@ -6,16 +6,6 @@
package com.todoroo.astrid.sync;
public interface SyncResultCallback {
/**
* Increment max sync progress
*/
public void incrementMax(int incrementBy);
/**
* Increment current sync progress
*/
public void incrementProgress(int incrementBy);
/**
* Provider started sync
*/

@ -1,40 +0,0 @@
/**
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.sync;
/**
* Convenience class for implementing sync result callbacks--if we need a sync
* result callback that only implements a subset of the callback methods, it
* can extend this empty implementation
* @author Sam
*
*/
public abstract class SyncResultCallbackAdapter implements SyncResultCallback {
@Override
public void incrementMax(int incrementBy) {
// Empty implementation
}
@Override
public void incrementProgress(int incrementBy) {
// Empty implementation
}
@Override
public void started() {
// Empty implementation
}
@Override
public void finished() {
// Empty implementation
}
}

@ -20,6 +20,7 @@ import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;
@ -85,6 +86,7 @@ public class TaskListActivity extends AstridActivity implements OnPageChangeList
protected void onCreate(Bundle savedInstanceState) {
ThemeService.applyTheme(this);
super.onCreate(savedInstanceState);
supportRequestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
int contentView = getContentView();
setContentView(contentView);

@ -468,8 +468,6 @@ public class TaskListFragment extends InjectingListFragment implements OnSortSel
sortFlags = publicPrefs.getInt(SortHelper.PREF_SORT_FLAGS, 0);
sortSort = publicPrefs.getInt(SortHelper.PREF_SORT_SORT, 0);
sortFlags = SortHelper.setManualSort(sortFlags, isDraggable());
getView().findViewById(R.id.progressBar).setVisibility(View.GONE);
}
protected void setupQuickAddBar() {

@ -12,7 +12,7 @@ import android.os.IBinder;
import com.todoroo.andlib.service.ContextManager;
import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.gtasks.sync.GtasksSyncV2Provider;
import com.todoroo.astrid.sync.SyncResultCallbackAdapter;
import com.todoroo.astrid.sync.SyncResultCallback;
import com.todoroo.astrid.sync.SyncV2Provider;
import org.slf4j.Logger;
@ -58,7 +58,11 @@ public class GtasksBackgroundService extends InjectingService {
SyncV2Provider provider = gtasksSyncV2Provider;
if (provider.isActive()) {
provider.synchronizeActiveTasks(false, new SyncResultCallbackAdapter() {
provider.synchronizeActiveTasks(false, new SyncResultCallback() {
@Override
public void started() {
}
@Override
public void finished() {
gtasksPreferenceService.recordSuccessfulSync();

@ -22,7 +22,9 @@ import com.todoroo.astrid.dao.StoreObjectDao;
import com.todoroo.astrid.dao.TaskAttachmentDao;
import com.todoroo.astrid.data.StoreObject;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.helper.ProgressBarSyncResultCallback;
import org.tasks.sync.IndeterminateProgressBarSyncResultCallback;
import com.todoroo.astrid.service.MetadataService;
import com.todoroo.astrid.service.SyncV2Service;
import com.todoroo.astrid.service.TaskService;
@ -87,8 +89,7 @@ public class GtasksListFragment extends SubtasksListFragment {
private void refreshData(final boolean manual) {
((TextView)getView().findViewById(android.R.id.empty)).setText(R.string.DLG_loading);
syncService.synchronizeList(list, manual, new ProgressBarSyncResultCallback(getActivity(), this,
R.id.progressBar, new Runnable() {
syncService.synchronizeList(list, manual, new IndeterminateProgressBarSyncResultCallback(getActivity(), new Runnable() {
@Override
public void run() {
if (manual) {

@ -34,7 +34,6 @@ import com.todoroo.astrid.gtasks.api.GtasksApiUtilities;
import com.todoroo.astrid.gtasks.api.GtasksInvoker;
import com.todoroo.astrid.gtasks.auth.GtasksTokenValidator;
import com.todoroo.astrid.service.MetadataService;
import com.todoroo.astrid.service.SyncResultCallbackWrapper;
import com.todoroo.astrid.service.TaskService;
import com.todoroo.astrid.sync.SyncResultCallback;
import com.todoroo.astrid.sync.SyncV2Provider;
@ -110,15 +109,12 @@ public class GtasksSyncV2Provider extends SyncV2Provider {
final boolean isImport = false;
callback.started();
callback.incrementMax(20);
gtasksPreferenceService.recordSyncStart();
new Thread(new Runnable() {
@Override
public void run() {
callback.incrementProgress(1); // 5%
String authToken = getValidatedAuthToken();
final GtasksInvoker invoker = new GtasksInvoker(authToken);
try {
@ -135,28 +131,15 @@ public class GtasksSyncV2Provider extends SyncV2Provider {
return;
}
callback.incrementProgress(1); // 10%
final SyncResultCallbackWrapper.Partial callback0;
callback0 = new SyncResultCallbackWrapper.Partial(
callback, 2, 16
);
callback0.incrementMax(lists.length * 3);
final AtomicInteger finisher = new AtomicInteger(lists.length);
for (final StoreObject list : lists) {
new Thread(new Runnable() {
@Override
public void run() {
synchronizeListHelper(list, invoker, manual, handler, callback0, isImport);
callback.incrementProgress(3);
synchronizeListHelper(list, invoker, manual, handler, isImport);
if (finisher.decrementAndGet() == 0) {
callback0.incrementProgress(8);
SyncResultCallback callback1 = new SyncResultCallbackWrapper.Rescaled(
callback0, 1, callback0.getRemainderSize()
);
pushUpdated(invoker, callback1);
callback0.incrementProgress(8);
pushUpdated(invoker);
finishSync(callback);
}
}
@ -167,17 +150,16 @@ public class GtasksSyncV2Provider extends SyncV2Provider {
}).start();
}
private synchronized void pushUpdated(GtasksInvoker invoker, SyncResultCallback callback) {
private synchronized void pushUpdated(GtasksInvoker invoker) {
TodorooCursor<Task> queued = taskService.query(Query.select(Task.PROPERTIES).
join(Join.left(Metadata.TABLE, Criterion.and(MetadataCriteria.withKey(GtasksMetadata.METADATA_KEY), Task.ID.eq(Metadata.TASK)))).where(
Criterion.or(Task.MODIFICATION_DATE.gt(GtasksMetadata.LAST_SYNC),
Criterion.and(Task.USER_ID.neq(Task.USER_ID_SELF), GtasksMetadata.ID.isNotNull()), // XXX: Shouldn't this neq("")?
Metadata.KEY.isNull())));
pushTasks(queued, invoker, callback);
pushTasks(queued, invoker);
}
private synchronized void pushTasks(TodorooCursor<Task> queued, GtasksInvoker invoker, SyncResultCallback callback) {
callback.incrementMax(queued.getCount() * 10);
private synchronized void pushTasks(TodorooCursor<Task> queued, GtasksInvoker invoker) {
try {
Task task = new Task();
for (queued.moveToFirst(); !queued.isAfterLast(); queued.moveToNext()) {
@ -188,8 +170,6 @@ public class GtasksSyncV2Provider extends SyncV2Provider {
handler.handleException("gtasks-sync-io", e, e.getType()); //$NON-NLS-1$
} catch (IOException e) {
handler.handleException("gtasks-sync-io", e, e.toString()); //$NON-NLS-1$
} finally {
callback.incrementProgress(10);
}
}
} finally {
@ -211,21 +191,16 @@ public class GtasksSyncV2Provider extends SyncV2Provider {
final boolean isImport = false;
callback.started();
callback.incrementMax(8);
new Thread(new Runnable() {
@Override
public void run() {
callback.incrementProgress(4);
try {
String authToken = getValidatedAuthToken();
callback.incrementProgress(1);
gtasksSyncService.waitUntilEmpty();
callback.incrementProgress(1);
final GtasksInvoker service = new GtasksInvoker(authToken);
synchronizeListHelper(gtasksList, service, manual, null, callback, isImport);
synchronizeListHelper(gtasksList, service, manual, null, isImport);
} finally {
callback.incrementProgress(2);
callback.finished();
}
}
@ -247,7 +222,7 @@ public class GtasksSyncV2Provider extends SyncV2Provider {
private synchronized void synchronizeListHelper(StoreObject list, GtasksInvoker invoker,
boolean manual, SyncExceptionHandler errorHandler, SyncResultCallback callback, boolean isImport) {
boolean manual, SyncExceptionHandler errorHandler, boolean isImport) {
String listId = list.getValue(GtasksList.REMOTE_ID);
long lastSyncDate;
if (!manual && list.containsNonNullValue(GtasksList.LAST_SYNC)) {
@ -268,7 +243,7 @@ public class GtasksSyncV2Provider extends SyncV2Provider {
TodorooCursor<Task> qs = taskService.query(Query.select(Task.PROPERTIES).
join(Join.left(Metadata.TABLE, Criterion.and(MetadataCriteria.withKey(GtasksMetadata.METADATA_KEY), Task.ID.eq(Metadata.TASK)))).where(not_pushed_tasks)
);
pushTasks(qs, invoker, callback);
pushTasks(qs, invoker);
boolean includeDeletedAndHidden = lastSyncDate != 0;
try {
@ -276,7 +251,6 @@ public class GtasksSyncV2Provider extends SyncV2Provider {
includeDeletedAndHidden, lastSyncDate);
List<com.google.api.services.tasks.model.Task> tasks = taskList.getItems();
if (tasks != null) {
callback.incrementMax(tasks.size() * 10);
HashSet<Long> localIds = new HashSet<>(tasks.size());
for (com.google.api.services.tasks.model.Task t : tasks) {
GtasksTaskContainer container = parseRemoteTask(t, listId);
@ -289,7 +263,6 @@ public class GtasksSyncV2Provider extends SyncV2Provider {
DateUtilities.now() + 1000L);
write(container);
localIds.add(container.task.getId());
callback.incrementProgress(10);
}
list.setValue(GtasksList.LAST_SYNC, DateUtilities.now());
storeObjectDao.persist(list);

@ -1,168 +0,0 @@
/**
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.astrid.helper;
import android.app.Activity;
import android.support.v4.app.Fragment;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.widget.ProgressBar;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.astrid.sync.SyncResultCallbackAdapter;
import java.util.concurrent.atomic.AtomicInteger;
public class ProgressBarSyncResultCallback extends SyncResultCallbackAdapter {
private ProgressBar progressBar;
private final Activity activity;
private final Runnable onFinished;
private final AtomicInteger providers = new AtomicInteger(0);
public ProgressBarSyncResultCallback(Activity activity, Fragment fragment,
int progressBarId, Runnable onFinished) {
this.progressBar = (ProgressBar) fragment.getView().findViewById(progressBarId);
this.activity = activity;
this.onFinished = onFinished;
if(progressBar == null) {
progressBar = new ProgressBar(activity);
}
progressBar.setProgress(0);
progressBar.setMax(0);
}
@Override
public void finished() {
if(providers.decrementAndGet() == 0) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
progressBar.setMax(100);
progressBar.setProgress(100);
AlphaAnimation animation = new AlphaAnimation(1, 0);
animation.setFillAfter(true);
animation.setDuration(1000L);
progressBar.startAnimation(animation);
onFinished.run();
} catch (Exception e) {
// ignore, view could have been destroyed
}
}
});
new Thread() {
@Override
public void run() {
AndroidUtilities.sleepDeep(1000);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
progressBar.setVisibility(View.GONE);
} catch (Exception e) {
// ignore
}
}
});
}
}.start();
}
}
/**
* This method helps rescaling a progress bar to increase the number of data points that form
* the progress bar, but without reducing the % of progress already done.
*
* This is done by increasing the length of the progress bar and the amount of progress whilst
* keeping the remaining amount of progress left remains the same. This code makes the
* presumption that progress isn't reversed and only increases.
*
* @param p The current level of progress that has been made.
* @param m The current total of data points which makes up the progress bar (the maximum).
* @param x The number of data points to increase the progress bar by.
* @return An integer array of two values - the first is the new value for progress and the
* second is the new value for the maximum.
*/
public static int[] rescaleProgressValues(int p, int m, int x) {
/**
* If we have no progress yet, then a straight-forward increment of the max will suffice
* here (and is necessary, the below code will break otherwise).
*/
if (p == 0) {return new int[] {p, m + x};}
/**
* This is more of an error in caller code. We shouldn't be asked to rescale a progress
* bar that has currently reached 100%, but hasn't been declared as "finished". But in
* case this happens, and since we can't correctly rescale a progress bar that's at 100%,
* we just allow it to move back.
*/
if (p == m) {return new int[] {p, m + x};}
// This is what does the magic!
double m2d = ((double)(m*(m+x-p))) / (m-p);
int m2 = (int)Math.floor(m2d);
int p2 = m2 + p - m - x;
return new int[] {p2, m2};
}
private void rescaleProgressBarBy(int x) {
int[] new_values = rescaleProgressValues(progressBar.getProgress(), progressBar.getMax(), x);
if (new_values[1] != -1) {progressBar.setMax(new_values[1]);}
if (new_values[0] != -1) {progressBar.setProgress(new_values[0]);}
}
@Override
public void incrementMax(final int incrementBy) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
rescaleProgressBarBy(incrementBy);
} catch (Exception e) {
// ignore
}
}
});
}
@Override
public void incrementProgress(final int incrementBy) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
progressBar.incrementProgressBy(incrementBy);
} catch (Exception e) {
// ignore
}
}
});
}
@Override
public void started() {
if(providers.incrementAndGet() == 1) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
progressBar.setVisibility(View.VISIBLE);
AlphaAnimation animation = new AlphaAnimation(0, 1);
animation.setFillAfter(true);
animation.setDuration(1000L);
progressBar.startAnimation(animation);
}
});
}
}
}

@ -16,6 +16,7 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.widget.ArrayAdapter;
import com.todoroo.andlib.utility.AndroidUtilities;
@ -32,6 +33,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tasks.R;
import org.tasks.preferences.Preferences;
import org.tasks.sync.IndeterminateProgressBarSyncResultCallback;
import java.util.ArrayList;
import java.util.LinkedHashSet;
@ -67,13 +69,12 @@ public class SyncActionHelper {
// --- boilerplate
public SyncActionHelper(SyncV2Service syncService, final Activity activity, Preferences preferences, Fragment fragment) {
public SyncActionHelper(SyncV2Service syncService, final FragmentActivity activity, Preferences preferences, Fragment fragment) {
this.syncService = syncService;
this.activity = activity;
this.preferences = preferences;
this.fragment = fragment;
syncResultCallback = new ProgressBarSyncResultCallback(activity, fragment,
R.id.progressBar, new Runnable() {
syncResultCallback = new IndeterminateProgressBarSyncResultCallback(activity, new Runnable() {
@Override
public void run() {
activity.sendBroadcast(

@ -1,190 +0,0 @@
package com.todoroo.astrid.service;
import com.todoroo.andlib.service.ContextManager;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.helper.ProgressBarSyncResultCallback;
import com.todoroo.astrid.sync.SyncResultCallback;
import com.todoroo.astrid.widget.TasksWidget;
public class SyncResultCallbackWrapper implements SyncResultCallback {
private final SyncResultCallback wrapped;
public SyncResultCallbackWrapper(SyncResultCallback wrap) {
this.wrapped = wrap;
}
@Override
public void incrementMax(int incrementBy) {
wrapped.incrementMax(incrementBy);
}
@Override
public void incrementProgress(int incrementBy) {
wrapped.incrementProgress(incrementBy);
}
@Override
public void started() {
wrapped.started();
}
@Override
public void finished() {
wrapped.finished();
}
public static class WidgetUpdatingCallbackWrapper extends SyncResultCallbackWrapper {
public WidgetUpdatingCallbackWrapper(SyncResultCallback wrap) {
super(wrap);
}
@Override
public void started() {
super.started();
TasksWidget.suppressUpdateFlag = DateUtilities.now();
}
@Override
public void finished() {
super.finished();
TasksWidget.suppressUpdateFlag = 0L;
TasksWidget.updateWidgets(ContextManager.getContext());
}
}
/**
* This class can be used if you want to claim a relative proportion of the SyncResultCallback,
* but you may also want to increase the maximum size of data points as well. For example, there
* may be 10 data points, and you want to make use of half of them to indicate that once you
* are finished, you are half way through. But if you increase the maximum number of data
* points, the remainder that you're not interacting with will need to increase by the same
* proportion so that you still interact with half of the data points.
*
* Once you wrap this class around the original SyncResultCallback object and have finished
* using it, you then invoke the getRemainderSize method afterwards to see how many data points are
* left for you to use. You can then use the Rescaled wrapper to simplify caller logic afterwards.
*/
public static class Partial extends SyncResultCallbackWrapper {
protected final double factor;
protected int fragment_size;
protected int total_size;
/**
* Wraps a SyncResultCallback object.
*
* @param wrap Original object to wrap.
* @param fragment_size Number of data points that you want to interact with.
* @param total_size Total number of data points remaining and available in the original object.
*/
public Partial(SyncResultCallback wrap,
int fragment_size, int total_size) {
super(wrap);
this.factor = Math.ceil((double)total_size / fragment_size);
this.fragment_size = fragment_size;
this.total_size = total_size;
}
@Override
public void incrementMax(int incrementBy) {
this.fragment_size += incrementBy;
int new_total = (int)(this.factor * this.fragment_size);
super.incrementMax(new_total - total_size);
this.total_size = new_total;
}
public int getRemainderSize() {
return this.total_size - this.fragment_size;
}
}
/**
* Given a SyncResultCallback object with a certain number of data points, treat it as if it
* had a different number of data points, and allow the underlying wrapped object to receive a
* proportionally equivalent amount of data points.
*/
public static class Rescaled extends SyncResultCallbackWrapper {
protected int input_size;
protected int actual_size;
protected int input_count;
protected int actual_count;
public Rescaled(SyncResultCallback wrap,
int input_size, int actual_size) {
super(wrap);
if (input_size <= 0) throw new IllegalArgumentException("input_size is not +ve");
if (actual_size <= 0) throw new IllegalArgumentException("actual size is not +ve");
this.input_size = input_size;
this.actual_size = actual_size;
this.input_count = 0;
this.actual_count = 0;
// If we have more input data points than output, we'll increase the number of data
// points in the underlying one, because otherwise incremental updates fed into us
// might not show up in the underlying object.
if (actual_size < input_size) {
super.incrementMax(input_size - actual_size);
this.actual_size = input_size;
}
}
@Override
public void incrementProgress(int incrementBy) {
// Shortcut if we're no longer rescaling.
if (input_size == actual_size) {
super.incrementProgress(incrementBy);
return;
}
int new_input_count = input_count + incrementBy;
int new_actual_count = (new_input_count * actual_size) / input_size;
if (new_actual_count > actual_count) {
// This should always be the case, but we just sanity check it.
super.incrementProgress(new_actual_count - actual_count);
}
this.input_count = new_input_count;
this.actual_count = new_actual_count;
}
@Override
public void incrementMax(int incrementBy) {
// Shortcut if we're not longer rescaling.
if (input_size == actual_size) {
super.incrementMax(incrementBy);
return;
}
// If we increase the maximum used by the input, we want to rescale the values such
// that the progress (proportionally) remains the same - otherwise we'll appear to
// have made backward progress on the input side, which we can't indicate to the
// underlying object.
int[] rescaled_input = ProgressBarSyncResultCallback.rescaleProgressValues(
input_count, input_size, incrementBy
);
this.input_count = rescaled_input[0];
this.input_size = rescaled_input[1];
// No need to rescale internally if no data points exist.
if (input_count == 0) {return;}
// The point of this class is to increase the size of input so that it increases the
// amount of data points sent to the underlying object. Once the input size becomes the
// same as underlying size, we don't need to do anything other than send the same
// information to the underlying class.
//
// So:
// If input remains smaller than actual, we do nothing here.
// If input remains equal to actual, we just pass the same data along.
// If input goes from smaller to bigger, we need to catch up to make it equal.
if (input_count > actual_count) {
super.incrementMax(input_count - actual_count);
this.actual_count = input_count; // Switches off rescaling.
}
}
}
}

@ -6,7 +6,6 @@
package com.todoroo.astrid.service;
import com.todoroo.astrid.gtasks.sync.GtasksSyncV2Provider;
import com.todoroo.astrid.service.SyncResultCallbackWrapper.WidgetUpdatingCallbackWrapper;
import com.todoroo.astrid.sync.SyncResultCallback;
import com.todoroo.astrid.sync.SyncV2Provider;

@ -0,0 +1,28 @@
package com.todoroo.astrid.service;
import com.todoroo.andlib.service.ContextManager;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.sync.SyncResultCallback;
import com.todoroo.astrid.widget.TasksWidget;
public class WidgetUpdatingCallbackWrapper implements SyncResultCallback {
private SyncResultCallback wrap;
public WidgetUpdatingCallbackWrapper(SyncResultCallback wrap) {
this.wrap = wrap;
}
@Override
public void started() {
wrap.started();
TasksWidget.suppressUpdateFlag = DateUtilities.now();
}
@Override
public void finished() {
wrap.finished();
TasksWidget.suppressUpdateFlag = 0L;
TasksWidget.updateWidgets(ContextManager.getContext());
}
}

@ -0,0 +1,32 @@
package org.tasks.sync;
import android.support.v4.app.FragmentActivity;
import com.todoroo.astrid.sync.SyncResultCallback;
public class IndeterminateProgressBarSyncResultCallback implements SyncResultCallback{
private final FragmentActivity activity;
private Runnable onFinished;
public IndeterminateProgressBarSyncResultCallback(FragmentActivity activity, Runnable onFinished) {
this.activity = activity;
this.onFinished = onFinished;
}
@Override
public void finished() {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
activity.setProgressBarIndeterminateVisibility(false);
onFinished.run();
}
});
}
@Override
public void started() {
activity.setProgressBarIndeterminateVisibility(true);
}
}

@ -14,13 +14,6 @@
style="@style/Content"
android:orientation="vertical">
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="fill_parent"
android:layout_height="5dip"
style="@android:style/Widget.ProgressBar.Horizontal"
android:visibility="gone"/>
<!-- Body goes here -->
<!-- Footer -->

Loading…
Cancel
Save