Skip to content

Commit

Permalink
Update notifications, implements #400 (#809)
Browse files Browse the repository at this point in the history
When a task is modified we compare the current notification state to the new one and update the notification if necessary.

In certain cases we remove the notification now:

* the task was unpinned (by the sync adapter)
* the start of a task has been postponed
* the due date of a task has been postponed
* a task has been completed
  • Loading branch information
dmfs authored Jun 12, 2019
1 parent eee9b4c commit a097413
Show file tree
Hide file tree
Showing 14 changed files with 254 additions and 97 deletions.
2 changes: 1 addition & 1 deletion dependencies.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
def jems_version = '1.22'
def jems_version = '1.23'
def contentpal_version = '0.5'
def androidx_test_runner_version = '1.1.1'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,7 @@ public static Uri getContentUri(String authority)
*
* @author Marten Gajda <marten@dmfs.org>
*/
public interface TaskColumns
public interface TaskColumns extends BaseColumns
{

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
import org.dmfs.tasks.utils.BaseActivity;
import org.dmfs.tasks.utils.ExpandableGroupDescriptor;
import org.dmfs.tasks.utils.SearchHistoryHelper;
import org.dmfs.tasks.utils.Unchecked;
import org.dmfs.jems.single.adapters.Unchecked;
import org.dmfs.tasks.utils.colors.DarkenedForStatusBar;


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@
import android.os.RemoteException;

import org.dmfs.android.contentpal.RowDataSnapshot;
import org.dmfs.opentaskspal.readdata.TaskPin;
import org.dmfs.opentaskspal.readdata.TaskVersion;
import org.dmfs.tasks.actions.utils.NotificationPrefs;
import org.dmfs.tasks.contract.TaskContract;
import org.dmfs.tasks.notification.state.RowStateInfo;
import org.json.JSONException;
import org.json.JSONObject;

Expand All @@ -43,13 +43,17 @@ public void execute(Context context, ContentProviderClient contentProviderClient
{
try
{
RowStateInfo rsi = new RowStateInfo(data);
new NotificationPrefs(context).next()
.edit()
.putString(
taskUri.toString(),
new JSONObject()
.put("version", new TaskVersion(data).value())
.put("ongoing", new TaskPin(data).value()).toString())
.put("started", rsi.started())
.put("due", rsi.due())
.put("done", rsi.done())
.put("ongoing", rsi.pinned()).toString())
.apply();
}
catch (JSONException e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ protected void onHandleWork(@NonNull Intent intent)
for (RowSnapshot<TaskContract.Tasks> snapshot : new QueryRowSet<>(
new TasksView(taskUri.getAuthority(), contentProviderClient),
new org.dmfs.android.contentpal.projections.Composite<>(
(Projection<TaskContract.Tasks>) Id.PROJECTION,
Id.PROJECTION,
EffectiveDueDate.PROJECTION,
TaskStart.PROJECTION,
TaskPin.PROJECTION,
Expand Down Expand Up @@ -148,8 +148,7 @@ private TaskAction resolveAction(String action)
// create undo notification
new PostUndoAction(),
// create delayed action to finish the completion
new DelayedAction(ACTION_FINISH_COMPLETE, UNDO_TIMEOUT_MILLIS)
);
new DelayedAction(ACTION_FINISH_COMPLETE, UNDO_TIMEOUT_MILLIS));

case ACTION_PIN_TASK:
// just pin, let TaskNotificationService do the rest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import android.content.SharedPreferences;
import android.net.Uri;

import org.dmfs.android.contentpal.Projection;
import org.dmfs.android.contentpal.predicates.AnyOf;
import org.dmfs.android.contentpal.predicates.EqArg;
import org.dmfs.android.contentpal.predicates.In;
Expand All @@ -35,8 +34,11 @@
import org.dmfs.jems.iterable.decorators.Mapped;
import org.dmfs.jems.optional.Optional;
import org.dmfs.jems.pair.Pair;
import org.dmfs.opentaskspal.readdata.EffectiveDueDate;
import org.dmfs.opentaskspal.readdata.Id;
import org.dmfs.opentaskspal.readdata.TaskIsClosed;
import org.dmfs.opentaskspal.readdata.TaskPin;
import org.dmfs.opentaskspal.readdata.TaskStart;
import org.dmfs.opentaskspal.readdata.TaskVersion;
import org.dmfs.opentaskspal.views.TasksView;
import org.dmfs.tasks.JobIds;
Expand All @@ -45,6 +47,7 @@
import org.dmfs.tasks.contract.TaskContract.Tasks;
import org.dmfs.tasks.notification.state.PrefState;
import org.dmfs.tasks.notification.state.RowState;
import org.dmfs.tasks.notification.state.StateInfo;
import org.dmfs.tasks.notification.state.TaskNotificationState;

import androidx.annotation.NonNull;
Expand Down Expand Up @@ -99,7 +102,7 @@ protected void onHandleWork(@NonNull Intent intent)
* Notifications of tasks which no longer exist are removed.
* Notifications of tasks which have been pinned are added.
* Notifications of tasks which have been unpinned are removed.
* Notifications of tasks which have changed otherwise ae updated.
* Notifications of tasks which have changed otherwise are updated.
*/
String authority = getString(R.string.opentasks_authority);

Expand All @@ -114,11 +117,12 @@ protected void onHandleWork(@NonNull Intent intent)
new Mapped<>(snapShot -> new RowState(authority, snapShot.values()),
new QueryRowSet<>(
new Sorted<>(Tasks._ID, new TasksView(authority, getContentResolver().acquireContentProviderClient(authority))),
new Composite<>((Projection<Tasks>) Id.PROJECTION, TaskVersion.PROJECTION, TaskPin.PROJECTION),
new Composite<>(Id.PROJECTION, TaskVersion.PROJECTION, TaskPin.PROJECTION, TaskIsClosed.PROJECTION,
EffectiveDueDate.PROJECTION, TaskStart.PROJECTION),
new AnyOf(
// task is either pinned or has a notification
new EqArg(Tasks.PINNED, 1),
new In(Tasks._ID, new Mapped<>(p -> ContentUris.parseId(p.task()), currentNotifications))))),
// NOTE due to a bug in diff, the logic is currently reversed
(o, o2) -> (int) (ContentUris.parseId(o.task()) - ContentUris.parseId(o2.task()))))
{
if (!diff.left().isPresent())
Expand All @@ -135,17 +139,22 @@ else if (!diff.right().isPresent())
{
if (diff.left().value().taskVersion() != diff.right().value().taskVersion())
{
if (diff.left().value().ongoing() && !diff.right().value().ongoing())
// the task has been updated -> update the notification if necessary
StateInfo before = diff.left().value().info();
StateInfo now = diff.right().value().info();
if (!now.pinned() && // don't remove pinned notifications
(before.pinned() // pin was removed
|| before.started() && !now.started() // start was deferred or removed
|| !now.started() && before.due() && !now.due() // due was deferred or removed
|| !before.done() && now.done() // task was closed
))
{
// task has been unpinned, remove the notification
// notification is obsolete
removeTaskNotification(diff.left().value().task());
}
else
{
// task was updated, also update the notification
// TODO: if the original reason for the notification is no longer true, remove the notification.
// example: the due date of a task with a due notification is moved to the future
// see https://github.com/dmfs/opentasks/issues/400
ActionService.startAction(this, ActionService.ACTION_RENOTIFY, diff.left().value().task());
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2019 dmfs GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.dmfs.tasks.notification.state;

import org.json.JSONObject;

import androidx.annotation.NonNull;


/**
* The {@link StateInfo} stored in a {@link JSONObject}.
*
* @author Marten Gajda
*/
final class JsonStateInfo implements StateInfo
{

private final JSONObject mObject;


JsonStateInfo(@NonNull JSONObject object)
{
mObject = object;
}


@Override
public boolean pinned()
{
return mObject.optBoolean("ongoing", false);
}


@Override
public boolean due()
{
return mObject.optBoolean("due", false);
}


@Override
public boolean started()
{
return mObject.optBoolean("started", false);
}


@Override
public boolean done()
{
return mObject.optBoolean("done", false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,15 @@

import android.net.Uri;

import org.json.JSONException;
import org.dmfs.jems.single.Single;
import org.dmfs.jems.single.adapters.Unchecked;
import org.dmfs.jems.single.elementary.Frozen;
import org.json.JSONObject;

import java.util.Map;

import androidx.annotation.NonNull;


/**
* The {@link TaskNotificationState} or a task stored in the shared preferences (represented by a {@link Map.Entry}).
Expand All @@ -32,14 +36,17 @@
public final class PrefState implements TaskNotificationState
{
private final Map.Entry<String, ?> mEntry;
private final Single<JSONObject> mStateObject;


public PrefState(Map.Entry<String, ?> entry)
public PrefState(@NonNull Map.Entry<String, ?> entry)
{
mEntry = entry;
mStateObject = new Frozen<>(new Unchecked<>("Could not parse notification JSON object", () -> new JSONObject(entry.getValue().toString())));
}


@NonNull
@Override
public Uri task()
{
Expand All @@ -50,26 +57,14 @@ public Uri task()
@Override
public int taskVersion()
{
return jsonObject().optInt("version", 0);
return mStateObject.value().optInt("version", 0);
}


@NonNull
@Override
public boolean ongoing()
{
return jsonObject().optBoolean("ongoing", false);
}


private JSONObject jsonObject()
public StateInfo info()
{
try
{
return new JSONObject(mEntry.getValue().toString());
}
catch (JSONException e)
{
throw new RuntimeException(String.format("Can't parse JSONObject %s", mEntry.getValue()), e);
}
return new JsonStateInfo(mStateObject.value());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@

import org.dmfs.android.contentpal.RowDataSnapshot;
import org.dmfs.opentaskspal.readdata.Id;
import org.dmfs.opentaskspal.readdata.TaskPin;
import org.dmfs.opentaskspal.readdata.TaskVersion;
import org.dmfs.tasks.contract.TaskContract;

import androidx.annotation.NonNull;


/**
* The {@link TaskNotificationState} of a {@link RowDataSnapshot} of a task.
Expand All @@ -37,13 +38,14 @@ public final class RowState implements TaskNotificationState
private final RowDataSnapshot<TaskContract.Tasks> mRow;


public RowState(String authority, RowDataSnapshot<TaskContract.Tasks> row)
public RowState(@NonNull String authority, @NonNull RowDataSnapshot<TaskContract.Tasks> row)
{
mAuthority = authority;
mRow = row;
}


@NonNull
@Override
public Uri task()
{
Expand All @@ -58,9 +60,10 @@ public int taskVersion()
}


@NonNull
@Override
public boolean ongoing()
public StateInfo info()
{
return new TaskPin(mRow).value();
return new RowStateInfo(mRow);
}
}
Loading

0 comments on commit a097413

Please sign in to comment.