Add scrollable widget

Closes #21
pull/46/merge
Alex Baker 11 years ago
parent a1c986d9e2
commit 2ad175c876

@ -79,6 +79,9 @@ public class DependencyInjectionService {
if(packageName.startsWith("org.weloveastrid")) {
return true;
}
if(packageName.startsWith("org.tasks")) {
return true;
}
return false;
}

@ -168,7 +168,13 @@
android:theme="@android:style/Theme" />
<!-- Activity that configures widget -->
<activity android:name="com.todoroo.astrid.widget.TasksWidget$ConfigActivity">
<activity android:name="com.todoroo.astrid.widget.WidgetConfigActivity">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
<activity android:name=".widget.ScrollableWidgetConfigActivity">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
@ -196,6 +202,7 @@
<!-- widgets -->
<receiver
android:name="com.todoroo.astrid.widget.TasksWidget"
android:enabled="@bool/is_pre_api_14"
android:label="@string/widget_mini">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
@ -205,6 +212,18 @@
android:resource="@xml/widget_provider_info" />
</receiver>
<receiver
android:name=".widget.ScrollableTasksWidget"
android:enabled="@bool/is_post_api_14"
android:label="@string/widget_mini">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/scrollable_widget_provider_info" />
</receiver>
<receiver android:name="com.todoroo.astrid.service.GlobalEventReceiver">
<intent-filter>
<action android:name="org.tasks.FLUSH_DETAILS" />
@ -225,7 +244,11 @@
<!-- ======================================================== Services = -->
<service android:name="com.todoroo.astrid.widget.TasksWidget$WidgetUpdateService" />
<service android:name="com.todoroo.astrid.widget.WidgetUpdateService" />
<service
android:name=".widget.ScrollableWidgetUpdateService"
android:permission="android.permission.BIND_REMOTEVIEWS"/>
<!-- ======================================================= Providers = -->

@ -44,9 +44,9 @@ import com.todoroo.astrid.utility.Flags;
import com.todoroo.astrid.voice.VoiceInputAssistant;
import com.todoroo.astrid.voice.VoiceOutputService;
import com.todoroo.astrid.voice.VoiceRecognizer;
import com.todoroo.astrid.widget.TasksWidget;
import org.tasks.R;
import org.tasks.widget.WidgetHelper;
import java.util.ArrayList;
import java.util.HashMap;
@ -406,7 +406,7 @@ public class EditPreferences extends TodorooPreferenceActivity {
findPreference(getString(R.string.p_use_dark_theme_widget)).setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
TasksWidget.updateWidgets(EditPreferences.this);
WidgetHelper.triggerUpdate(EditPreferences.this);
updatePreferences(preference, newValue);
return true;
}

@ -89,9 +89,9 @@ import com.todoroo.astrid.timers.TimerPlugin;
import com.todoroo.astrid.ui.QuickAddBar;
import com.todoroo.astrid.utility.AstridPreferences;
import com.todoroo.astrid.utility.Flags;
import com.todoroo.astrid.widget.TasksWidget;
import org.tasks.R;
import org.tasks.widget.WidgetHelper;
import java.util.List;
import java.util.Timer;
@ -719,7 +719,7 @@ public class TaskListFragment extends ListFragment implements OnSortSelectedList
if (resultCode == EditPreferences.RESULT_CODE_THEME_CHANGED || resultCode == EditPreferences.RESULT_CODE_PERFORMANCE_PREF_CHANGED) {
getActivity().finish();
getActivity().startActivity(getActivity().getIntent());
TasksWidget.updateWidgets(getActivity());
WidgetHelper.updateWidgets(getActivity());
return;
} else if (resultCode == SyncProviderPreferences.RESULT_CODE_SYNCHRONIZE) {
Preferences.setLong(SyncActionHelper.PREF_LAST_AUTO_SYNC, 0); // Forces autosync to occur after login
@ -1166,7 +1166,7 @@ public class TaskListFragment extends ListFragment implements OnSortSelectedList
editor.putInt(SortHelper.PREF_SORT_FLAGS, flags);
editor.putInt(SortHelper.PREF_SORT_SORT, sort);
editor.commit();
TasksWidget.updateWidgets(ContextManager.getContext());
WidgetHelper.updateWidgets(ContextManager.getContext());
}
}
}

@ -25,7 +25,8 @@ import com.todoroo.astrid.data.Update;
import com.todoroo.astrid.data.UserActivity;
import com.todoroo.astrid.provider.Astrid2TaskProvider;
import com.todoroo.astrid.provider.Astrid3ContentProvider;
import com.todoroo.astrid.widget.TasksWidget;
import org.tasks.widget.WidgetHelper;
/**
* Database wrapper
@ -73,7 +74,7 @@ public class Database extends AbstractDatabase {
public void onDatabaseUpdated() {
Astrid2TaskProvider.notifyDatabaseModification();
Astrid3ContentProvider.notifyDatabaseModification();
TasksWidget.updateWidgets(ContextManager.getContext());
WidgetHelper.updateWidgets(ContextManager.getContext());
}
});
}

@ -7,14 +7,11 @@ package com.todoroo.astrid.service;
import android.Manifest;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.database.sqlite.SQLiteException;
@ -53,13 +50,14 @@ import com.todoroo.astrid.reminders.ReminderStartupReceiver;
import com.todoroo.astrid.tags.TaskToTagMetadata;
import com.todoroo.astrid.utility.AstridPreferences;
import com.todoroo.astrid.utility.Constants;
import com.todoroo.astrid.widget.TasksWidget.WidgetUpdateService;
import org.tasks.R;
import java.io.File;
import java.util.List;
import static org.tasks.widget.WidgetHelper.startWidgetService;
/**
* Service which handles jobs that need to be run when Astrid starts up.
*
@ -178,13 +176,7 @@ public class StartupService {
new Thread(new Runnable() {
@Override
public void run() {
// start widget updating alarm
AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, WidgetUpdateService.class);
PendingIntent pendingIntent = PendingIntent.getService(context,
0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
am.setInexactRepeating(AlarmManager.RTC, 0,
Constants.WIDGET_UPDATE_INTERVAL, pendingIntent);
startWidgetService(context);
ReengagementService.scheduleReengagementAlarm(context);
taskService.cleanup();

@ -3,7 +3,8 @@ 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;
import org.tasks.widget.WidgetHelper;
public class SyncResultCallbackWrapper implements SyncResultCallback {
private final SyncResultCallback wrapped;
@ -36,14 +37,14 @@ public class SyncResultCallbackWrapper implements SyncResultCallback {
@Override
public void started() {
super.started();
TasksWidget.suppressUpdateFlag = DateUtilities.now();
WidgetHelper.suppressUpdateFlag = DateUtilities.now();
}
@Override
public void finished() {
super.finished();
TasksWidget.suppressUpdateFlag = 0L;
TasksWidget.updateWidgets(ContextManager.getContext());
WidgetHelper.suppressUpdateFlag = 0L;
WidgetHelper.updateWidgets(ContextManager.getContext());
}
}

@ -7,7 +7,6 @@ package com.todoroo.astrid.service;
import android.app.Activity;
import android.graphics.PixelFormat;
import android.text.TextUtils;
import android.util.Log;
import android.view.WindowManager;
@ -43,13 +42,8 @@ public class ThemeService {
return getStyleForSetting(preference);
}
public static int getWidgetTheme() {
String preference = Preferences.getBoolean(R.string.p_use_dark_theme_widget, false) ? THEME_BLACK : THEME_WHITE;
if (TextUtils.isEmpty(preference)) {
return getTheme();
} else {
return getStyleForSetting(preference);
}
public static boolean isDarkWidgetTheme() {
return Preferences.getBoolean(R.string.p_use_dark_theme_widget, false);
}
private static int getStyleForSetting(String setting) {

@ -5,68 +5,18 @@
*/
package com.todoroo.astrid.widget;
import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.widget.RemoteViews;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.ContextManager;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.andlib.utility.Preferences;
import com.todoroo.astrid.actfm.TagViewFragment;
import com.todoroo.astrid.activity.TaskEditActivity;
import com.todoroo.astrid.activity.TaskEditFragment;
import com.todoroo.astrid.activity.TaskListActivity;
import com.todoroo.astrid.activity.TaskListFragment;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.FilterWithCustomIntent;
import com.todoroo.astrid.api.PermaSql;
import com.todoroo.astrid.core.CoreFilterExposer;
import com.todoroo.astrid.core.SortHelper;
import com.todoroo.astrid.dao.Database;
import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.service.AstridDependencyInjector;
import com.todoroo.astrid.service.TagDataService;
import com.todoroo.astrid.service.TaskService;
import com.todoroo.astrid.service.ThemeService;
import com.todoroo.astrid.subtasks.SubtasksHelper;
import com.todoroo.astrid.tags.TagFilterExposer;
import com.todoroo.astrid.utility.AstridPreferences;
import com.todoroo.astrid.utility.Constants;
import org.tasks.R;
public class TasksWidget extends AppWidgetProvider {
private static final int NUM_VISIBLE_TASKS = 25;
public static long suppressUpdateFlag = 0; // Timestamp--don't update widgets if this flag is non-zero and now() is within 5 minutes
private static final long SUPPRESS_TIME = DateUtilities.ONE_MINUTE * 5;
static {
AstridDependencyInjector.initialize();
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
try {
ContextManager.setContext(context);
super.onUpdate(context, appWidgetManager, appWidgetIds);
@ -82,327 +32,15 @@ public class TasksWidget extends AppWidgetProvider {
* Update all widgets
*/
public static void updateWidgets(Context context) {
if (suppressUpdateFlag > 0 && DateUtilities.now() - suppressUpdateFlag < SUPPRESS_TIME) {
return;
}
suppressUpdateFlag = 0;
context.startService(new Intent(context,
TasksWidget.WidgetUpdateService.class));
context.startService(new Intent(context, WidgetUpdateService.class));
}
/**
* Update widget with the given id
*/
public static void updateWidget(Context context, int id) {
Intent intent = new Intent(ContextManager.getContext(),
TasksWidget.WidgetUpdateService.class);
Intent intent = new Intent(ContextManager.getContext(), WidgetUpdateService.class);
intent.putExtra(WidgetUpdateService.EXTRA_WIDGET_ID, id);
context.startService(intent);
}
public static class ConfigActivity extends WidgetConfigActivity {
@Override
public void updateWidget() {
TasksWidget.updateWidget(this, mAppWidgetId);
}
}
public static class WidgetUpdateService extends Service {
public static final String EXTRA_WIDGET_ID = "widget_id"; //$NON-NLS-1$
@Autowired
Database database;
@Autowired
TaskService taskService;
@Autowired
TagDataService tagDataService;
@Override
public void onStart(final Intent intent, int startId) {
ContextManager.setContext(this);
new Thread(new Runnable() {
@Override
public void run() {
startServiceInBackgroundThread(intent);
}
}).start();
}
public void startServiceInBackgroundThread(Intent intent) {
ComponentName thisWidget = new ComponentName(this,
TasksWidget.class);
AppWidgetManager manager = AppWidgetManager.getInstance(this);
int extrasId = AppWidgetManager.INVALID_APPWIDGET_ID;
if(intent != null) {
extrasId = intent.getIntExtra(EXTRA_WIDGET_ID, extrasId);
}
if(extrasId == AppWidgetManager.INVALID_APPWIDGET_ID) {
int[] ids;
try {
ids = manager.getAppWidgetIds(thisWidget);
for(int id : ids) {
RemoteViews updateViews = buildUpdate(this, id);
manager.updateAppWidget(id, updateViews);
}
} catch (RuntimeException e) {
// "System server dead" was sometimes thrown here by the OS. Abort if that happens
}
} else {
RemoteViews updateViews = buildUpdate(this, extrasId);
manager.updateAppWidget(extrasId, updateViews);
}
stopSelf();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
public RemoteViews buildUpdate(Context context, int widgetId) {
DependencyInjectionService.getInstance().inject(this);
RemoteViews views = getThemedRemoteViews(context);
int numberOfTasks = NUM_VISIBLE_TASKS;
TodorooCursor<Task> cursor = null;
Filter filter = null;
try {
filter = getFilter(context, widgetId);
if (SubtasksHelper.isTagFilter(filter)) {
((FilterWithCustomIntent) filter).customTaskList = new ComponentName(context, TagViewFragment.class); // In case legacy widget was created with subtasks fragment
}
views.setTextViewText(R.id.widget_title, filter.title);
views.removeAllViews(R.id.taskbody);
SharedPreferences publicPrefs = AstridPreferences.getPublicPrefs(this);
int flags = publicPrefs.getInt(SortHelper.PREF_SORT_FLAGS, 0);
int sort = publicPrefs.getInt(SortHelper.PREF_SORT_SORT, 0);
String query = SortHelper.adjustQueryForFlagsAndSort(
filter.getSqlQuery(), flags, sort).replaceAll("LIMIT \\d+", "") + " LIMIT " + numberOfTasks;
String tagName = Preferences.getStringValue(WidgetConfigActivity.PREF_TITLE + widgetId);
query = SubtasksHelper.applySubtasksToWidgetFilter(filter, query, tagName, numberOfTasks);
database.openForReading();
cursor = taskService.fetchFiltered(query, null, Task.ID, Task.TITLE, Task.DUE_DATE, Task.COMPLETION_DATE);
Task task = new Task();
int i;
for (i = 0; i < cursor.getCount() && i < numberOfTasks; i++) {
cursor.moveToPosition(i);
task.readFromCursor(cursor);
String textContent;
Resources r = context.getResources();
int textColor = r
.getColor(isDarkTheme() ? R.color.widget_text_color_dark : R.color.widget_text_color_light);
textContent = task.getValue(Task.TITLE);
if(task.isCompleted()) {
textColor = r.getColor(R.color.task_list_done);
} else if(task.hasDueDate() && task.isOverdue()) {
textColor = r.getColor(R.color.task_list_overdue);
}
RemoteViews row = new RemoteViews(Constants.PACKAGE, R.layout.widget_row);
row.setTextViewText(R.id.text, textContent);
row.setTextColor(R.id.text, textColor);
views.addView(R.id.taskbody, row);
RemoteViews separator = new RemoteViews(Constants.PACKAGE, R.layout.widget_separator);
boolean isLastRow = (i == cursor.getCount() - 1) || (i == numberOfTasks - 1);
if (!isLastRow) {
views.addView(R.id.taskbody, separator);
}
}
for (; i < numberOfTasks; i++) {
RemoteViews row = new RemoteViews(Constants.PACKAGE, R.layout.widget_row);
row.setViewVisibility(R.id.text, View.INVISIBLE);
views.addView(R.id.taskbody, row);
}
} catch (Exception e) {
// can happen if database is not ready
Log.e("WIDGET-UPDATE", "Error updating widget", e);
} finally {
if(cursor != null) {
cursor.close();
}
}
Intent listIntent = new Intent(context, TaskListActivity.class);
String customIntent = Preferences.getStringValue(WidgetConfigActivity.PREF_CUSTOM_INTENT
+ widgetId);
if(customIntent != null) {
String serializedExtras = Preferences.getStringValue(WidgetConfigActivity.PREF_CUSTOM_EXTRAS
+ widgetId);
Bundle extras = AndroidUtilities.bundleFromSerializedString(serializedExtras);
listIntent.putExtras(extras);
}
listIntent.putExtra(TaskListActivity.TOKEN_SOURCE, Constants.SOURCE_WIDGET);
listIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
if(filter != null) {
listIntent.putExtra(TaskListFragment.TOKEN_FILTER, filter);
listIntent.setAction("L" + widgetId + filter.getSqlQuery());
} else {
listIntent.setAction("L" + widgetId);
}
if (filter instanceof FilterWithCustomIntent) {
listIntent.putExtras(((FilterWithCustomIntent) filter).customExtras);
}
PendingIntent pListIntent = PendingIntent.getActivity(context, widgetId,
listIntent, PendingIntent.FLAG_CANCEL_CURRENT);
if (pListIntent != null) {
views.setOnClickPendingIntent(R.id.taskbody, pListIntent);
}
Intent editIntent;
boolean tablet = AstridPreferences.useTabletLayout(context);
if (tablet) {
editIntent = new Intent(context, TaskListActivity.class);
editIntent.putExtra(TaskListActivity.OPEN_TASK, 0L);
}
else {
editIntent = new Intent(context, TaskEditActivity.class);
}
editIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
editIntent.putExtra(TaskEditFragment.OVERRIDE_FINISH_ANIM, false);
if(filter != null) {
editIntent.putExtra(TaskListFragment.TOKEN_FILTER, filter);
if (filter.valuesForNewTasks != null) {
String values = AndroidUtilities.contentValuesToSerializedString(filter.valuesForNewTasks);
values = PermaSql.replacePlaceholders(values);
editIntent.putExtra(TaskEditFragment.TOKEN_VALUES, values);
editIntent.setAction("E" + widgetId + values);
}
if (tablet) {
if (filter instanceof FilterWithCustomIntent) {
Bundle customExtras = ((FilterWithCustomIntent) filter).customExtras;
editIntent.putExtras(customExtras);
}
}
} else {
editIntent.setAction("E" + widgetId);
}
PendingIntent pEditIntent = PendingIntent.getActivity(context, -widgetId,
editIntent, 0);
if (pEditIntent != null) {
views.setOnClickPendingIntent(R.id.widget_button, pEditIntent);
views.setOnClickPendingIntent(R.id.widget_title, pEditIntent);
}
return views;
}
private boolean isDarkTheme() {
int theme = ThemeService.getWidgetTheme();
return theme == R.style.Tasks;
}
/**
* The reason we use a bunch of different but almost identical layouts is that there is a bug with
* Android 2.1 (level 7) that doesn't allow setting backgrounds on remote views. I know it's lame,
* but I didn't see a better solution. Alternatively, we could disallow theming widgets on
* Android 2.1.
*/
private RemoteViews getThemedRemoteViews(Context context) {
String packageName = context.getPackageName();
Resources r = context.getResources();
int layout;
RemoteViews views;
int titleColor;
int buttonDrawable;
if (isDarkTheme()) {
layout = R.layout.widget_initialized_dark;
titleColor = r.getColor(R.color.widget_text_color_dark);
buttonDrawable = R.drawable.ic_action_new_light;
} else {
layout = R.layout.widget_initialized;
titleColor = r.getColor(R.color.widget_text_color_light);
buttonDrawable = R.drawable.ic_action_new;
}
views = new RemoteViews(packageName, layout);
views.setTextColor(R.id.widget_title, titleColor);
views.setInt(R.id.widget_button, "setImageResource", buttonDrawable);
return views;
}
private Filter getFilter(Context context, int widgetId) {
// base our filter off the inbox filter, replace stuff if we have it
Filter filter = CoreFilterExposer.buildInboxFilter(getResources());
String sql = Preferences.getStringValue(WidgetConfigActivity.PREF_SQL + widgetId);
if(sql != null) {
filter.setSqlQuery(sql);
}
String title = Preferences.getStringValue(WidgetConfigActivity.PREF_TITLE + widgetId);
if(title != null) {
filter.title = title;
}
String contentValues = Preferences.getStringValue(WidgetConfigActivity.PREF_VALUES + widgetId);
if(contentValues != null) {
filter.valuesForNewTasks = AndroidUtilities.contentValuesFromSerializedString(contentValues);
}
String customComponent = Preferences.getStringValue(WidgetConfigActivity.PREF_CUSTOM_INTENT
+ widgetId);
if (customComponent != null) {
ComponentName component = ComponentName.unflattenFromString(customComponent);
filter = new FilterWithCustomIntent(filter.title, filter.title, filter.getSqlQuery(), filter.valuesForNewTasks);
((FilterWithCustomIntent) filter).customTaskList = component;
String serializedExtras = Preferences.getStringValue(WidgetConfigActivity.PREF_CUSTOM_EXTRAS
+ widgetId);
((FilterWithCustomIntent) filter).customExtras = AndroidUtilities.bundleFromSerializedString(serializedExtras);
}
// Validate tagData
long id = Preferences.getLong(WidgetConfigActivity.PREF_TAG_ID + widgetId, 0);
TagData tagData;
if (id > 0) {
tagData = tagDataService.fetchById(id, TagData.ID, TagData.NAME, TagData.TASK_COUNT, TagData.UUID, TagData.PICTURE, TagData.USER_ID, TagData.MEMBER_COUNT);
if (tagData != null && !tagData.getValue(TagData.NAME).equals(filter.title)) { // Tag has been renamed; rebuild filter
filter = TagFilterExposer.filterFromTagData(context, tagData);
Preferences.setString(WidgetConfigActivity.PREF_SQL + widgetId, filter.getSqlQuery());
Preferences.setString(WidgetConfigActivity.PREF_TITLE + widgetId, filter.title);
ContentValues newTaskValues = filter.valuesForNewTasks;
String contentValuesString = null;
if(newTaskValues != null) {
contentValuesString = AndroidUtilities.contentValuesToSerializedString(newTaskValues);
}
Preferences.setString(WidgetConfigActivity.PREF_VALUES + widgetId, contentValuesString);
if (filter instanceof FilterWithCustomIntent) {
String flattenedExtras = AndroidUtilities.bundleToSerializedString(((FilterWithCustomIntent) filter).customExtras);
if (flattenedExtras != null) {
Preferences.setString(WidgetConfigActivity.PREF_CUSTOM_EXTRAS + widgetId,
flattenedExtras);
}
}
}
} else {
tagData = tagDataService.getTagByName(filter.title, TagData.ID);
if (tagData != null) {
Preferences.setLong(WidgetConfigActivity.PREF_TAG_ID + widgetId, tagData.getId());
}
}
return filter;
}
}
}

@ -25,13 +25,13 @@ import com.todoroo.astrid.service.ThemeService;
import org.tasks.R;
abstract public class WidgetConfigActivity extends ListActivity {
public class WidgetConfigActivity extends ListActivity {
static final String PREF_TITLE = "widget-title-";
static final String PREF_SQL = "widget-sql-";
static final String PREF_VALUES = "widget-values-";
static final String PREF_CUSTOM_INTENT = "widget-intent-";
static final String PREF_CUSTOM_EXTRAS = "widget-extras-";
public static final String PREF_CUSTOM_INTENT = "widget-intent-";
public static final String PREF_CUSTOM_EXTRAS = "widget-extras-";
static final String PREF_TAG_ID = "widget-tag-id-";
int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
@ -42,7 +42,9 @@ abstract public class WidgetConfigActivity extends ListActivity {
super();
}
abstract public void updateWidget();
public void updateWidget() {
TasksWidget.updateWidget(this, mAppWidgetId);
}
@Override
public void onCreate(Bundle icicle) {

@ -0,0 +1,218 @@
package com.todoroo.astrid.widget;
import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.IBinder;
import android.util.Log;
import android.view.View;
import android.widget.RemoteViews;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.ContextManager;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.utility.Preferences;
import com.todoroo.astrid.actfm.TagViewFragment;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.FilterWithCustomIntent;
import com.todoroo.astrid.core.SortHelper;
import com.todoroo.astrid.dao.Database;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.service.AstridDependencyInjector;
import com.todoroo.astrid.service.TaskService;
import com.todoroo.astrid.service.ThemeService;
import com.todoroo.astrid.subtasks.SubtasksHelper;
import com.todoroo.astrid.utility.AstridPreferences;
import com.todoroo.astrid.utility.Constants;
import org.tasks.R;
import org.tasks.widget.WidgetHelper;
public class WidgetUpdateService extends Service {
static {
AstridDependencyInjector.initialize();
}
private static final int NUM_VISIBLE_TASKS = 25;
public static final String EXTRA_WIDGET_ID = "widget_id"; //$NON-NLS-1$
@Autowired
Database database;
@Autowired
TaskService taskService;
private final WidgetHelper widgetHelper = new WidgetHelper();
@Override
public void onStart(final Intent intent, int startId) {
ContextManager.setContext(this);
new Thread(new Runnable() {
@Override
public void run() {
startServiceInBackgroundThread(intent);
}
}).start();
}
public void startServiceInBackgroundThread(Intent intent) {
ComponentName thisWidget = new ComponentName(this,
TasksWidget.class);
AppWidgetManager manager = AppWidgetManager.getInstance(this);
int extrasId = AppWidgetManager.INVALID_APPWIDGET_ID;
if(intent != null) {
extrasId = intent.getIntExtra(EXTRA_WIDGET_ID, extrasId);
}
if(extrasId == AppWidgetManager.INVALID_APPWIDGET_ID) {
int[] ids;
try {
ids = manager.getAppWidgetIds(thisWidget);
for(int id : ids) {
RemoteViews updateViews = buildUpdate(this, id);
manager.updateAppWidget(id, updateViews);
}
} catch (RuntimeException e) {
// "System server dead" was sometimes thrown here by the OS. Abort if that happens
}
} else {
RemoteViews updateViews = buildUpdate(this, extrasId);
manager.updateAppWidget(extrasId, updateViews);
}
stopSelf();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
public RemoteViews buildUpdate(Context context, int widgetId) {
DependencyInjectionService.getInstance().inject(this);
RemoteViews views = getThemedRemoteViews(context);
int numberOfTasks = NUM_VISIBLE_TASKS;
TodorooCursor<Task> cursor = null;
Filter filter = null;
try {
filter = widgetHelper.getFilter(context, widgetId);
if (SubtasksHelper.isTagFilter(filter)) {
((FilterWithCustomIntent) filter).customTaskList = new ComponentName(context, TagViewFragment.class); // In case legacy widget was created with subtasks fragment
}
views.setTextViewText(R.id.widget_title, filter.title);
views.removeAllViews(R.id.taskbody);
SharedPreferences publicPrefs = AstridPreferences.getPublicPrefs(this);
int flags = publicPrefs.getInt(SortHelper.PREF_SORT_FLAGS, 0);
int sort = publicPrefs.getInt(SortHelper.PREF_SORT_SORT, 0);
String query = SortHelper.adjustQueryForFlagsAndSort(
filter.getSqlQuery(), flags, sort).replaceAll("LIMIT \\d+", "") + " LIMIT " + numberOfTasks;
String tagName = Preferences.getStringValue(WidgetConfigActivity.PREF_TITLE + widgetId);
query = SubtasksHelper.applySubtasksToWidgetFilter(filter, query, tagName, numberOfTasks);
database.openForReading();
cursor = taskService.fetchFiltered(query, null, Task.ID, Task.TITLE, Task.DUE_DATE, Task.COMPLETION_DATE);
Task task = new Task();
int i;
for (i = 0; i < cursor.getCount() && i < numberOfTasks; i++) {
cursor.moveToPosition(i);
task.readFromCursor(cursor);
String textContent;
Resources r = context.getResources();
int textColor = r
.getColor(ThemeService.isDarkWidgetTheme() ? R.color.widget_text_color_dark : R.color.widget_text_color_light);
textContent = task.getValue(Task.TITLE);
if(task.isCompleted()) {
textColor = r.getColor(R.color.task_list_done);
} else if(task.hasDueDate() && task.isOverdue()) {
textColor = r.getColor(R.color.task_list_overdue);
}
RemoteViews row = new RemoteViews(Constants.PACKAGE, R.layout.widget_row);
row.setTextViewText(R.id.text, textContent);
row.setTextColor(R.id.text, textColor);
views.addView(R.id.taskbody, row);
RemoteViews separator = new RemoteViews(Constants.PACKAGE, R.layout.widget_separator);
boolean isLastRow = (i == cursor.getCount() - 1) || (i == numberOfTasks - 1);
if (!isLastRow) {
views.addView(R.id.taskbody, separator);
}
}
for (; i < numberOfTasks; i++) {
RemoteViews row = new RemoteViews(Constants.PACKAGE, R.layout.widget_row);
row.setViewVisibility(R.id.text, View.INVISIBLE);
views.addView(R.id.taskbody, row);
}
} catch (Exception e) {
// can happen if database is not ready
Log.e("WIDGET-UPDATE", "Error updating widget", e);
} finally {
if(cursor != null) {
cursor.close();
}
}
PendingIntent pListIntent = widgetHelper.getListIntent(context, filter, widgetId);
if (pListIntent != null) {
views.setOnClickPendingIntent(R.id.taskbody, pListIntent);
}
PendingIntent pEditIntent = widgetHelper.getEditIntent(context, filter, widgetId);
if (pEditIntent != null) {
views.setOnClickPendingIntent(R.id.widget_button, pEditIntent);
views.setOnClickPendingIntent(R.id.widget_title, pEditIntent);
}
return views;
}
/**
* The reason we use a bunch of different but almost identical layouts is that there is a bug with
* Android 2.1 (level 7) that doesn't allow setting backgrounds on remote views. I know it's lame,
* but I didn't see a better solution. Alternatively, we could disallow theming widgets on
* Android 2.1.
*/
private RemoteViews getThemedRemoteViews(Context context) {
String packageName = context.getPackageName();
Resources r = context.getResources();
int layout;
RemoteViews views;
int titleColor;
int buttonDrawable;
if (ThemeService.isDarkWidgetTheme()) {
layout = R.layout.widget_initialized_dark;
titleColor = r.getColor(R.color.widget_text_color_dark);
buttonDrawable = R.drawable.ic_action_new_light;
} else {
layout = R.layout.widget_initialized;
titleColor = r.getColor(R.color.widget_text_color_light);
buttonDrawable = R.drawable.ic_action_new;
}
views = new RemoteViews(packageName, layout);
views.setTextColor(R.id.widget_title, titleColor);
views.setInt(R.id.widget_button, "setImageResource", buttonDrawable);
return views;
}
}

@ -0,0 +1,46 @@
package org.tasks.widget;
import android.annotation.TargetApi;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.os.Build;
import com.todoroo.andlib.service.ContextManager;
import org.tasks.R;
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public class ScrollableTasksWidget extends AppWidgetProvider {
private final WidgetHelper widgetHelper = new WidgetHelper();
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
ContextManager.setContext(context);
super.onUpdate(context, appWidgetManager, appWidgetIds);
ComponentName thisWidget = new ComponentName(context, ScrollableTasksWidget.class);
int[] ids = appWidgetManager.getAppWidgetIds(thisWidget);
for (int id : ids) {
appWidgetManager.updateAppWidget(id, widgetHelper.createScrollableWidget(context, id));
}
}
public static void updateWidgets(Context context) {
updateWidgets(context, null);
}
public static void updateWidget(Context context, int id) {
updateWidgets(context, new int[]{id});
}
private static void updateWidgets(Context context, int[] widgetIds) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
if (widgetIds == null) {
widgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, ScrollableTasksWidget.class));
}
appWidgetManager.notifyAppWidgetViewDataChanged(widgetIds, R.id.list_view);
}
}

@ -0,0 +1,131 @@
package org.tasks.widget;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Build;
import android.util.Log;
import android.widget.RemoteViews;
import android.widget.RemoteViewsService;
import com.todoroo.andlib.data.TodorooCursor;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.astrid.dao.Database;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.service.TaskService;
import com.todoroo.astrid.utility.Constants;
import org.tasks.R;
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public class ScrollableViewsFactory implements RemoteViewsService.RemoteViewsFactory {
@Autowired
Database database;
@Autowired
TaskService taskService;
private final Context context;
private String query;
private boolean dark;
private TodorooCursor<Task> cursor;
public ScrollableViewsFactory(Context context, String query, boolean dark) {
this.context = context;
this.query = query;
this.dark = dark;
}
@Override
public void onCreate() {
DependencyInjectionService.getInstance().inject(this);
database.openForReading();
cursor = getCursor();
}
@Override
public void onDataSetChanged() {
cursor = getCursor();
}
@Override
public void onDestroy() {
cursor.close();
}
@Override
public int getCount() {
return cursor.getCount();
}
@Override
public RemoteViews getViewAt(int position) {
return buildUpdate(position);
}
@Override
public RemoteViews getLoadingView() {
return null;
}
@Override
public int getViewTypeCount() {
return 1;
}
@Override
public long getItemId(int position) {
return getTask(position).getId();
}
@Override
public boolean hasStableIds() {
return true;
}
public RemoteViews buildUpdate(int position) {
try {
Task task = getTask(position);
String textContent;
Resources r = context.getResources();
int textColor = r.getColor(dark ? R.color.widget_text_color_dark : R.color.widget_text_color_light);
textContent = task.getValue(Task.TITLE);
if (task.isCompleted()) {
textColor = r.getColor(R.color.task_list_done);
} else if (task.hasDueDate() && task.isOverdue()) {
textColor = r.getColor(R.color.task_list_overdue);
}
RemoteViews row = new RemoteViews(Constants.PACKAGE, R.layout.widget_row);
row.setTextViewText(R.id.text, textContent);
row.setTextColor(R.id.text, textColor);
row.setOnClickFillInIntent(R.id.text, new Intent());
return row;
} catch (Exception e) {
// can happen if database is not ready
Log.e("WIDGET-UPDATE", "Error updating widget", e);
}
return null;
}
private TodorooCursor<Task> getCursor() {
return taskService.fetchFiltered(query, null, Task.ID, Task.TITLE, Task.DUE_DATE, Task.COMPLETION_DATE);
}
private Task getTask(int position) {
cursor.moveToPosition(position);
return new Task(cursor);
}
}

@ -0,0 +1,157 @@
package org.tasks.widget;
import android.app.ListActivity;
import android.appwidget.AppWidgetManager;
import android.content.ContentValues;
import android.content.Intent;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.View;
import android.widget.Button;
import android.widget.ListView;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.andlib.utility.Preferences;
import com.todoroo.astrid.adapter.FilterAdapter;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.FilterListItem;
import com.todoroo.astrid.api.FilterWithCustomIntent;
import com.todoroo.astrid.service.ThemeService;
import org.tasks.R;
public class ScrollableWidgetConfigActivity extends ListActivity {
static final String PREF_TITLE = "widget-title-";
static final String PREF_SQL = "widget-sql-";
static final String PREF_VALUES = "widget-values-";
static final String PREF_CUSTOM_INTENT = "widget-intent-";
static final String PREF_CUSTOM_EXTRAS = "widget-extras-";
static final String PREF_TAG_ID = "widget-tag-id-";
int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
FilterAdapter adapter = null;
private final WidgetHelper widgetHelper = new WidgetHelper();
public void updateWidget() {
// setup view for new widget
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(getApplicationContext());
appWidgetManager.updateAppWidget(mAppWidgetId, widgetHelper.createScrollableWidget(getApplicationContext(), mAppWidgetId));
ScrollableTasksWidget.updateWidget(this, mAppWidgetId);
}
@Override
public void onCreate(Bundle icicle) {
ThemeService.applyTheme(this);
ThemeService.setForceFilterInvert(true);
super.onCreate(icicle);
// Set the result to CANCELED. This will cause the widget host to cancel
// out of the widget placement if they press the back button.
setResult(RESULT_CANCELED);
// Set the view layout resource to use.
setContentView(R.layout.widget_config_activity);
setTitle(R.string.WCA_title);
// Find the widget id from the intent.
Intent intent = getIntent();
Bundle extras = intent.getExtras();
if (extras != null) {
mAppWidgetId = extras.getInt(
AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
}
// If they gave us an intent without the widget id, just bail.
if (mAppWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
finish();
}
// set up ui
adapter = new FilterAdapter(this, getListView(),
R.layout.filter_adapter_row, true, true);
adapter.filterStyle = R.style.TextAppearance_FLA_Filter_Widget;
setListAdapter(adapter);
Button button = (Button)findViewById(R.id.ok);
button.setOnClickListener(mOnClickListener);
}
View.OnClickListener mOnClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
// Save configuration options
saveConfiguration(adapter.getSelection());
updateWidget();
// Make sure we pass back the original appWidgetId
Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
setResult(RESULT_OK, resultValue);
finish();
}
};
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
Filter item = adapter.getItem(position);
adapter.setSelection(item);
}
@Override
protected void onResume() {
super.onResume();
adapter.registerRecevier();
}
@Override
protected void onPause() {
super.onPause();
adapter.unregisterRecevier();
}
@Override
protected void onStop() {
super.onStop();
ThemeService.setForceFilterInvert(false);
}
private void saveConfiguration(FilterListItem filterListItem){
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
String sql = null, contentValuesString = null, title = null;
if(filterListItem != null && filterListItem instanceof Filter) {
sql = ((Filter)filterListItem).getSqlQuery();
ContentValues values = ((Filter)filterListItem).valuesForNewTasks;
if(values != null) {
contentValuesString = AndroidUtilities.contentValuesToSerializedString(values);
}
title = ((Filter)filterListItem).title;
}
Preferences.setString(ScrollableWidgetConfigActivity.PREF_TITLE + mAppWidgetId, title);
Preferences.setString(ScrollableWidgetConfigActivity.PREF_SQL + mAppWidgetId, sql);
Preferences.setString(ScrollableWidgetConfigActivity.PREF_VALUES + mAppWidgetId, contentValuesString);
if(filterListItem instanceof FilterWithCustomIntent) {
String flattenedName = ((FilterWithCustomIntent)filterListItem).customTaskList.flattenToString();
Preferences.setString(ScrollableWidgetConfigActivity.PREF_CUSTOM_INTENT + mAppWidgetId,
flattenedName);
String flattenedExtras = AndroidUtilities.bundleToSerializedString(((FilterWithCustomIntent)filterListItem).customExtras);
if (flattenedExtras != null) {
Preferences.setString(ScrollableWidgetConfigActivity.PREF_CUSTOM_EXTRAS + mAppWidgetId,
flattenedExtras);
}
}
}
}

@ -0,0 +1,30 @@
package org.tasks.widget;
import android.annotation.TargetApi;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.widget.RemoteViewsService;
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public class ScrollableWidgetUpdateService extends RemoteViewsService {
public static final String QUERY_ID = "org.tasks.widget.query_id";
public static final String IS_DARK_THEME = "org.tasks.widget.is_dark_theme";
@Override
public RemoteViewsFactory onGetViewFactory(Intent intent) {
if (intent == null) {
return null;
}
Bundle extras = intent.getExtras();
if (extras == null) {
return null;
}
String queryId = extras.getString(QUERY_ID);
boolean isDarkTheme = extras.getBoolean(IS_DARK_THEME);
return new ScrollableViewsFactory(this, queryId, isDarkTheme);
}
}

@ -0,0 +1,259 @@
package org.tasks.widget;
import android.annotation.TargetApi;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.widget.RemoteViews;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.andlib.utility.Preferences;
import com.todoroo.astrid.actfm.TagViewFragment;
import com.todoroo.astrid.activity.TaskEditActivity;
import com.todoroo.astrid.activity.TaskEditFragment;
import com.todoroo.astrid.activity.TaskListActivity;
import com.todoroo.astrid.activity.TaskListFragment;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.api.FilterWithCustomIntent;
import com.todoroo.astrid.api.PermaSql;
import com.todoroo.astrid.core.CoreFilterExposer;
import com.todoroo.astrid.core.SortHelper;
import com.todoroo.astrid.data.TagData;
import com.todoroo.astrid.service.AstridDependencyInjector;
import com.todoroo.astrid.service.TagDataService;
import com.todoroo.astrid.service.ThemeService;
import com.todoroo.astrid.subtasks.SubtasksHelper;
import com.todoroo.astrid.tags.TagFilterExposer;
import com.todoroo.astrid.utility.AstridPreferences;
import com.todoroo.astrid.utility.Constants;
import com.todoroo.astrid.widget.TasksWidget;
import com.todoroo.astrid.widget.WidgetConfigActivity;
import com.todoroo.astrid.widget.WidgetUpdateService;
import org.tasks.R;
public class WidgetHelper {
static {
AstridDependencyInjector.initialize();
}
public static long suppressUpdateFlag = 0; // Timestamp--don't update widgets if this flag is non-zero and now() is within 5 minutes
private static final long SUPPRESS_TIME = DateUtilities.ONE_MINUTE * 5;
public static void updateWidgets(Context context) {
if (suppressUpdateFlag > 0 && DateUtilities.now() - suppressUpdateFlag < SUPPRESS_TIME) {
return;
}
suppressUpdateFlag = 0;
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
TasksWidget.updateWidgets(context);
} else {
ScrollableTasksWidget.updateWidgets(context);
}
}
public static void startWidgetService(Context context) {
Class widgetServiceClass = android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH
? WidgetUpdateService.class
: ScrollableWidgetUpdateService.class;
AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, widgetServiceClass);
PendingIntent pendingIntent = PendingIntent.getService(context,
0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
am.setInexactRepeating(AlarmManager.RTC, 0,
Constants.WIDGET_UPDATE_INTERVAL, pendingIntent);
}
public static void triggerUpdate(Context context) {
Class widgetClass = android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH
? TasksWidget.class
: ScrollableTasksWidget.class;
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
ComponentName thisWidget = new ComponentName(context, widgetClass);
Intent intent = new Intent(context, widgetClass);
intent.setAction("android.appwidget.action.APPWIDGET_UPDATE");
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetManager.getAppWidgetIds(thisWidget));
context.sendBroadcast(intent);
}
@Autowired
TagDataService tagDataService;
public WidgetHelper() {
DependencyInjectionService.getInstance().inject(this);
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public RemoteViews createScrollableWidget(Context context, int id) {
Filter filter = getFilter(context, id);
Intent rvIntent = new Intent(context, ScrollableWidgetUpdateService.class);
rvIntent.putExtra(ScrollableWidgetUpdateService.QUERY_ID, getQuery(context, filter, id));
rvIntent.putExtra(ScrollableWidgetUpdateService.IS_DARK_THEME, ThemeService.isDarkWidgetTheme());
rvIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id);
rvIntent.setData(Uri.parse(rvIntent.toUri(Intent.URI_INTENT_SCHEME)));
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), ThemeService.isDarkWidgetTheme() ? R.layout.scrollable_widget_dark : R.layout.scrollable_widget_light);
remoteViews.setTextViewText(R.id.widget_title, filter.title);
remoteViews.setRemoteAdapter(R.id.list_view, rvIntent);
remoteViews.setEmptyView(R.id.list_view, R.id.empty_view);
PendingIntent listIntent = getListIntent(context, filter, id);
if(listIntent != null) {
remoteViews.setOnClickPendingIntent(R.id.widget_title, listIntent);
remoteViews.setPendingIntentTemplate(R.id.list_view, listIntent);
}
PendingIntent editIntent = getEditIntent(context, filter, id);
if (editIntent != null) {
remoteViews.setOnClickPendingIntent(R.id.widget_button, editIntent);
}
return remoteViews;
}
public PendingIntent getListIntent(Context context, Filter filter, int widgetId) {
Intent listIntent = new Intent(context, TaskListActivity.class);
String customIntent = Preferences.getStringValue(WidgetConfigActivity.PREF_CUSTOM_INTENT
+ widgetId);
if(customIntent != null) {
String serializedExtras = Preferences.getStringValue(WidgetConfigActivity.PREF_CUSTOM_EXTRAS
+ widgetId);
Bundle extras = AndroidUtilities.bundleFromSerializedString(serializedExtras);
listIntent.putExtras(extras);
}
listIntent.putExtra(TaskListActivity.TOKEN_SOURCE, Constants.SOURCE_WIDGET);
listIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
if(filter != null) {
listIntent.putExtra(TaskListFragment.TOKEN_FILTER, filter);
listIntent.setAction("L" + widgetId + filter.getSqlQuery());
} else {
listIntent.setAction("L" + widgetId);
}
if (filter instanceof FilterWithCustomIntent) {
listIntent.putExtras(((FilterWithCustomIntent) filter).customExtras);
}
return PendingIntent.getActivity(context, widgetId,
listIntent, PendingIntent.FLAG_CANCEL_CURRENT);
}
public PendingIntent getEditIntent(Context context, Filter filter, int id) {
Intent editIntent;
boolean tablet = AstridPreferences.useTabletLayout(context);
if (tablet) {
editIntent = new Intent(context, TaskListActivity.class);
editIntent.putExtra(TaskListActivity.OPEN_TASK, 0L);
}
else {
editIntent = new Intent(context, TaskEditActivity.class);
}
editIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
editIntent.putExtra(TaskEditFragment.OVERRIDE_FINISH_ANIM, false);
if(filter != null) {
editIntent.putExtra(TaskListFragment.TOKEN_FILTER, filter);
if (filter.valuesForNewTasks != null) {
String values = AndroidUtilities.contentValuesToSerializedString(filter.valuesForNewTasks);
values = PermaSql.replacePlaceholders(values);
editIntent.putExtra(TaskEditFragment.TOKEN_VALUES, values);
editIntent.setAction("E" + id + values);
}
if (tablet) {
if (filter instanceof FilterWithCustomIntent) {
Bundle customExtras = ((FilterWithCustomIntent) filter).customExtras;
editIntent.putExtras(customExtras);
}
}
} else {
editIntent.setAction("E" + id);
}
return PendingIntent.getActivity(context, -id, editIntent, 0);
}
public Filter getFilter(Context context, int widgetId) {
// base our filter off the inbox filter, replace stuff if we have it
Filter filter = CoreFilterExposer.buildInboxFilter(context.getResources());
String sql = Preferences.getStringValue(ScrollableWidgetConfigActivity.PREF_SQL + widgetId);
if(sql != null) {
filter.setSqlQuery(sql);
}
String title = Preferences.getStringValue(ScrollableWidgetConfigActivity.PREF_TITLE + widgetId);
if(title != null) {
filter.title = title;
}
String contentValues = Preferences.getStringValue(ScrollableWidgetConfigActivity.PREF_VALUES + widgetId);
if(contentValues != null) {
filter.valuesForNewTasks = AndroidUtilities.contentValuesFromSerializedString(contentValues);
}
String customComponent = Preferences.getStringValue(ScrollableWidgetConfigActivity.PREF_CUSTOM_INTENT
+ widgetId);
if (customComponent != null) {
ComponentName component = ComponentName.unflattenFromString(customComponent);
filter = new FilterWithCustomIntent(filter.title, filter.title, filter.getSqlQuery(), filter.valuesForNewTasks);
((FilterWithCustomIntent) filter).customTaskList = component;
String serializedExtras = Preferences.getStringValue(ScrollableWidgetConfigActivity.PREF_CUSTOM_EXTRAS
+ widgetId);
((FilterWithCustomIntent) filter).customExtras = AndroidUtilities.bundleFromSerializedString(serializedExtras);
}
// Validate tagData
long id = Preferences.getLong(ScrollableWidgetConfigActivity.PREF_TAG_ID + widgetId, 0);
TagData tagData;
if (id > 0) {
tagData = tagDataService.fetchById(id, TagData.ID, TagData.NAME, TagData.TASK_COUNT, TagData.UUID, TagData.PICTURE, TagData.USER_ID, TagData.MEMBER_COUNT);
if (tagData != null && !tagData.getValue(TagData.NAME).equals(filter.title)) { // Tag has been renamed; rebuild filter
filter = TagFilterExposer.filterFromTagData(context, tagData);
Preferences.setString(ScrollableWidgetConfigActivity.PREF_SQL + widgetId, filter.getSqlQuery());
Preferences.setString(ScrollableWidgetConfigActivity.PREF_TITLE + widgetId, filter.title);
ContentValues newTaskValues = filter.valuesForNewTasks;
String contentValuesString = null;
if(newTaskValues != null) {
contentValuesString = AndroidUtilities.contentValuesToSerializedString(newTaskValues);
}
Preferences.setString(ScrollableWidgetConfigActivity.PREF_VALUES + widgetId, contentValuesString);
if (filter instanceof FilterWithCustomIntent) {
String flattenedExtras = AndroidUtilities.bundleToSerializedString(((FilterWithCustomIntent) filter).customExtras);
if (flattenedExtras != null) {
Preferences.setString(ScrollableWidgetConfigActivity.PREF_CUSTOM_EXTRAS + widgetId,
flattenedExtras);
}
}
}
} else {
tagData = tagDataService.getTagByName(filter.title, TagData.ID);
if (tagData != null) {
Preferences.setLong(ScrollableWidgetConfigActivity.PREF_TAG_ID + widgetId, tagData.getId());
}
}
return filter;
}
private String getQuery(Context context, Filter filter, int widgetId) {
if (SubtasksHelper.isTagFilter(filter)) {
((FilterWithCustomIntent) filter).customTaskList = new ComponentName(context, TagViewFragment.class); // In case legacy widget was created with subtasks fragment
}
SharedPreferences publicPrefs = AstridPreferences.getPublicPrefs(context);
int flags = publicPrefs.getInt(SortHelper.PREF_SORT_FLAGS, 0);
int sort = publicPrefs.getInt(SortHelper.PREF_SORT_SORT, 0);
String query = SortHelper.adjustQueryForFlagsAndSort(
filter.getSqlQuery(), flags, sort).replaceAll("LIMIT \\d+", "");
String tagName = Preferences.getStringValue(ScrollableWidgetConfigActivity.PREF_TITLE + widgetId);
return SubtasksHelper.applySubtasksToWidgetFilter(filter, query, tagName, 0);
}
}

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
** Copyright (c) 2012 Todoroo Inc
**
** See the file "LICENSE" for the full license governing this code.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/widget"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@android:color/transparent">
<LinearLayout
android:id="@+id/widget_header"
android:layout_width="match_parent"
android:layout_height="39dip"
android:layout_gravity="top"
android:gravity="top"
android:orientation="horizontal"
android:padding="3dp"
android:background="@color/widget_header_dark">
<TextView
android:id="@+id/widget_title"
android:layout_width="fill_parent"
android:layout_height="39dip"
android:layout_gravity="left|center_vertical"
android:paddingLeft="2dip"
android:textColor="@color/widget_text_color_dark"
android:textSize="16sp"
android:textStyle="bold"
android:maxLines="2"
android:layout_weight="1"
android:gravity="left|center_vertical"
android:ellipsize="end" />
<ImageButton
android:id="@+id/widget_button"
android:layout_width="33dip"
android:layout_height="39dip"
android:layout_gravity="right|center_vertical"
android:background="@android:color/transparent"
android:src="@drawable/ic_action_new_light"
android:scaleType="fitCenter"/>
</LinearLayout>
<ImageView
android:id="@+id/widget_header_separator"
android:layout_width="fill_parent"
android:layout_height="1px"
android:background="@color/dark_blue_theme_color"/>
<ListView
android:id="@+id/list_view"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:background="@color/widget_body_dark"/>
<TextView
android:id="@+id/empty_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:background="@color/widget_body_light"
android:layout_marginTop="37dip"
android:padding="10dip"
android:text="@string/TWi_loading"
style="@style/TextAppearance.Widget" />
</LinearLayout>

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
** Copyright (c) 2012 Todoroo Inc
**
** See the file "LICENSE" for the full license governing this code.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/widget"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@android:color/transparent">
<LinearLayout
android:id="@+id/widget_header"
android:layout_width="match_parent"
android:layout_height="39dip"
android:layout_gravity="top"
android:gravity="top"
android:orientation="horizontal"
android:padding="3dp"
android:background="@color/widget_header_light">
<TextView
android:id="@+id/widget_title"
android:layout_width="fill_parent"
android:layout_height="39dip"
android:layout_gravity="left|center_vertical"
android:paddingLeft="2dip"
android:textColor="@color/widget_text_color_light"
android:textSize="16sp"
android:textStyle="bold"
android:maxLines="2"
android:layout_weight="1"
android:gravity="left|center_vertical"
android:ellipsize="end" />
<ImageButton
android:id="@+id/widget_button"
android:layout_width="33dip"
android:layout_height="39dip"
android:layout_gravity="right|center_vertical"
android:background="@android:color/transparent"
android:src="@drawable/ic_action_new"
android:scaleType="fitCenter"/>
</LinearLayout>
<ImageView
android:id="@+id/widget_header_separator"
android:layout_width="fill_parent"
android:layout_height="1px"
android:background="@color/dark_blue_theme_color"/>
<ListView
android:id="@+id/list_view"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:background="@color/widget_body_light"/>
<TextView
android:id="@+id/empty_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:background="@color/widget_body_light"
android:layout_marginTop="37dip"
android:padding="10dip"
android:text="@string/TWi_loading"
style="@style/TextAppearance.Widget" />
</LinearLayout>

@ -19,21 +19,21 @@
android:orientation="horizontal"
android:padding="3dp"
android:background="@color/widget_header_light">
<TextView
android:id="@+id/widget_title"
android:layout_width="fill_parent"
android:layout_width="fill_parent"
android:layout_height="39dip"
android:layout_gravity="left|center_vertical"
android:layout_gravity="left|center_vertical"
android:paddingLeft="2dip"
android:textColor="@color/widget_text_color_light"
android:textSize="16sp"
android:textStyle="bold"
android:maxLines="2"
android:layout_weight="1"
android:gravity="left|center_vertical"
android:gravity="left|center_vertical"
android:ellipsize="end" />
<ImageButton
android:id="@+id/widget_button"
android:layout_width="33dip"
@ -42,7 +42,7 @@
android:background="@android:color/transparent"
android:src="@drawable/ic_action_new"
android:scaleType="fitCenter"/>
</LinearLayout>
<ImageView

@ -19,21 +19,21 @@
android:orientation="horizontal"
android:padding="3dp"
android:background="@color/widget_header_dark">
<TextView
android:id="@+id/widget_title"
android:layout_width="fill_parent"
android:layout_width="fill_parent"
android:layout_height="39dip"
android:layout_gravity="left|center_vertical"
android:layout_gravity="left|center_vertical"
android:paddingLeft="2dip"
android:textColor="@color/widget_text_color_light"
android:textSize="16sp"
android:textStyle="bold"
android:maxLines="2"
android:layout_weight="1"
android:gravity="left|center_vertical"
android:gravity="left|center_vertical"
android:ellipsize="end" />
<ImageButton
android:id="@+id/widget_button"
android:layout_width="33dip"
@ -42,9 +42,9 @@
android:background="@android:color/transparent"
android:src="@drawable/ic_action_new"
android:scaleType="fitCenter"/>
</LinearLayout>
<ImageView
android:id="@+id/widget_header_separator"
android:layout_width="fill_parent"

@ -18,21 +18,21 @@
android:orientation="horizontal"
android:padding="3dp"
android:background="@color/widget_header_light">
<TextView
android:id="@+id/widget_title"
android:layout_width="fill_parent"
android:layout_width="fill_parent"
android:layout_height="39dip"
android:layout_gravity="left|center_vertical"
android:layout_gravity="left|center_vertical"
android:paddingLeft="2dip"
android:textColor="@color/widget_text_color_light"
android:textSize="16sp"
android:textStyle="bold"
android:maxLines="2"
android:layout_weight="1"
android:gravity="left|center_vertical"
android:gravity="left|center_vertical"
android:ellipsize="end" />
<ImageButton
android:id="@+id/widget_button"
android:layout_width="33dip"
@ -41,9 +41,9 @@
android:background="@android:color/transparent"
android:src="@drawable/ic_action_new"
android:scaleType="fitCenter"/>
</LinearLayout>
<ImageView
android:id="@+id/widget_header_separator"
android:layout_width="fill_parent"

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="is_pre_api_14">false</bool>
<bool name="is_post_api_14">true</bool>
</resources>

@ -42,5 +42,8 @@
<declare-styleable name="DateAndTimePicker">
<attr name="shortcutLabels" format="reference"/>
</declare-styleable>
<bool name="is_pre_api_14">true</bool>
<bool name="is_post_api_14">false</bool>
</resources>

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
** Copyright (c) 2012 Todoroo Inc
**
** See the file "LICENSE" for the full license governing this code.
-->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:configure="org.tasks.widget.ScrollableWidgetConfigActivity"
android:minWidth="142dip"
android:minHeight="142dip"
android:minResizeWidth="142dip"
android:minResizeHeight="142dip"
android:updatePeriodMillis="86400000"
android:initialLayout="@layout/scrollable_widget_light"
android:resizeMode="horizontal|vertical"/>

@ -6,7 +6,7 @@
-->
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:configure="com.todoroo.astrid.widget.TasksWidget$ConfigActivity"
android:configure="com.todoroo.astrid.widget.WidgetConfigActivity"
android:minWidth="142dip"
android:minHeight="142dip"
android:minResizeWidth="142dip"

Loading…
Cancel
Save