Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show subtasks on details view. #442 #469

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

lemonboston
Copy link
Contributor

This is far from finalized but I submit it now to get the discussion going.

Questions, notes:

Will using Tasks.PARENT_ID be okay for the subtask query?

UI: How should the subtask list look like? Same view repeated, with only the first having the icon, but same spaces between them as with other task details? Or should subtasks be listed within a 'frame' with maybe less vertical gap between them? Use some actual frame lines?
(Current state is just a quick initial setup, not intended to stay like this)

About the loading using RxJava2, I guess there can be many questions there, please check the code and let me know.

I haven't given much thought about packaging the new classes yet, so that's left as well.

Important thing about the loading: currently it is started in onLoadFinished() because when it was in onCreateView(), the view items didn't show up after loading the screen the second time. Not sure what that was, maybe it's only because SubtaskView currently adds the items to a LinearLayout and calls requestLayout() after it, all within a ScrollView, so maybe if that happens before the original content is loaded, then it doesn't work for some reason.

Please check the loading multiple times, it varies greatly for me, sometimes immediate, sometimes delayed compared to the original content.
So unfortunately this multiple loading does add some uncertainty here. As I mentioned before, I think one approach could be to do one loading, so zip/merge the 2 flows together and deliver the results once. That requires to 'Rxify' those loading, if we used Rx for the zip.

A little bit related that compared to current state where loading is handled in the fragment, I think it could actually be nicer to have a custom view for the subtasks (/properties) part that handles loading on its own, when let's say update(mTaskUri) is callled on it. But this would mean separate loading.

Please feel free to also comment on specifics already even though it is in initial state.

@lemonboston lemonboston self-assigned this Oct 26, 2017
@lemonboston lemonboston requested a review from dmfs October 26, 2017 18:38
@lemonboston
Copy link
Contributor Author

Shall we create subtasks master branch? To integrate the stories for this epic, like sorting out the relations as well, etc.

@dmfs
Copy link
Owner

dmfs commented Oct 26, 2017

How about using the list view row layout for the subtasks on the details view? Or something similar?
That would probably mean to omit the icon and replace it with a label "Subtasks".

@dmfs
Copy link
Owner

dmfs commented Oct 27, 2017

I created two mock-ups of this view (for some reason the fonts of the subtasks are not correct)

screenshot-2017-10-27 ot - proto io player 2
screenshot-2017-10-27 ot - proto io player 3

@dmfs
Copy link
Owner

dmfs commented Oct 30, 2017

Are you aware that the due date on the sub tasks is not correct? It shows Jan 1st 1970.
Also, please re-add the progress indication (the gradient). I know that my mocks don't have any but that's because I didn't find a way to do that in proto.io

/**
* @author Gabor Keszthelyi
*/
public final class DueTime implements Single<Optional<DateTime>>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does it have to be a Single? Just make it an Optional.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, that was totally unnecessary. Changed it.

/**
* @author Gabor Keszthelyi
*/
// TODO Is it optional?
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, every task belongs to a list which has a color.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I've updated the class to use Bolts Color and removed the TODO questions.

*
* @author Gabor Keszthelyi
*/
// TODO Use Color type from Bolts library when available
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just import color-bolts via jitpack.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, done, this is using Bolts Color now.

{
super(new QueryRowSet<>(new TasksView(authority, client), new ReferringTo<>(TaskContract.Tasks.PARENT_ID, parentTask)));
super(new QueryRowSet<>(
new TasksView(authority, client, projection),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This TaskView should actually be passed to a ctor. There could be a secondary ctor which creates a default TaskView though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

super(new QueryRowSet<>(new TasksView(authority, client), new ReferringTo<>(TaskContract.Tasks.PARENT_ID, parentTask)));
super(new QueryRowSet<>(
new TasksView(authority, client, projection),
new EqArg(TaskContract.Tasks.PARENT_ID, ContentUris.parseId(parentTask))));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's wrong with the ReferringTo version? I'd definitely prefer it over the EqArg solution. To create a RowReference from a Uri use RowUriReference.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I just didn't find that RowUriReference.. Updated now to use ReferringTo.

*
* @author Gabor Keszthelyi
*/
public final class Populateable implements SmartView<Iterable<ListItem>>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name of this class is definitely wrong. It's not a decorator so it must contain a noun.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've changed the way this population is implemented (removed ListItems).

if (due.isPresent())
{
String formattedDue = new DateFormatter(context).format(
new AndroidTime(due.value()).value(),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the wrong idea. Please don't create new classes which work with Time. Instead add a format method to DateFormatter which converts from DateTime to Time internally and delegates to the original method (eventually we'll move the functionality over to the new method). This will help us to get rid of Time more quickly instead of spreading new traces of it all over our code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, it's better to keep those at a single place currently. I've changed this.

I plan to add tests to the conversion method. But since you know the topic much better, could you please help me by telling whether this looks good to you?
(I am not sure of what test cases are needed, so that will need review as well)

private Time toTime(DateTime dateTime)
{
	Time time = new Time();
	time.set(dateTime.getTimestamp());
	time.timezone = dateTime.getTimeZone().getID();
	time.allDay = dateTime.isAllDay();
	return time;
}

*
* @author Gabor Keszthelyi
*/
public final class DarkenedColor implements Color
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is primarily supposed to be used as a decorator. Consider calling it just Darkened.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, it is a decorator, I've changed the name.
Also imported latest color-bolts with ValueColor and using that in the ctor now.

{
Time time = new Time();
time.set(dateTime.getTimestamp());
time.timezone = dateTime.getTimeZone().getID();
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will crash if the dateTime has no time zone (I didn't have Optional back then) like it's the case for all-day tasks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I spent more time now with this conversion, updated the conversion code and the test as well (which now passes). I assumed that we don't support non-all-day floating DateTime because we can't represent that with Time. So the conversion throws now in that case.

It is also not clear what is best way to set the alll-day flag on Time, directly or with a set(year, month, day) method, I left a TODO to check back on that.

Let me know if this DateFormatter update should be submitted with a separate ticket and pull request.

Tasks.TASK_COLOR,
Tasks.LIST_COLOR,
Tasks.DUE
};
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is why the progress bar doesn't appear in the details view, you just don't load it (so it's always returned as null). You also have to make sure to load TIMEZONE and IS_ALLDAY in order to get DUE right. That might explain the "Jan 1st 1970" issue, not sure.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I've added them.

What do you think about instead of passing RowDataSnapshots directly to UI, using specific data/model types that the actual UI needs?
In general this may require creating many of these special, 'UI-local' data types but it decouples the UI from the internals of the data layer representations.

Something like this:

interface SubtasksUiData extends Iterable<SubtaskUiData>
{
      Color parentTaskColor();
}

interface SubtaskUiData
{
       CharSequence title();
       Optional<DateTime> due();
       Optional<Integer> percentComplete();
}

So SubtatskView could be SmartView<SubtasksUiData>,
SubtaskUiData would of course adapt RowDataSnapshot using the smaller adapters. There could be less potential for this kind of problem of missing columns from the projection.
(Maybe the small adapters could also expose the required projection in a constant? Not very elegant.. not sure what's the best way to handle that clearly.)


public Subtasks(String authority, ContentProviderClient client, @NonNull RowSnapshot<TaskContract.Tasks> parentTask)
public Subtasks(View<TaskContract.Tasks> view, @NonNull Uri parentTask)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ctor should take a RowReference, everything else (Uri, RowSnapshot ) is subject to secondary ctors.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But we need authority and client and projection as well, right? So:

public Subtasks(@NonNull RowReference<Tasks> parentTask, @NonNull String authority, @NonNull ContentProviderClient client, @NonNull String... projection)

(I will probably create separate ticket for these taskspal related changes as well, later.)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the primary ctor should only take a View<Tasks> and the RowReference<Tasks>.
Everything else (including creation of a View<Tasks> from authority, client and projection) is merely for convenience and should be done by secondary ctors.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I've update the constructor.

public Integer apply(CharSequence charSequence)
{
// TODO Extract these kind of functions?
return Integer.valueOf(charSequence.toString());
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is another question: does it make sense to add another method to RowSnapshotData called numberData(…) returning an Optional<Number>? This could make it easier in many cases.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see now that Number has these intValue(), longValue(), etc, methods that we could use in the client code, but how could you implement returning those Number values in RowDataSnapshot correctly with general code? Not knowing which Number subtype to use.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my idea was to extend RowSnapshotData with a method like this

@NonNull
Optional<Number> numberData(@NonNull String key);

A number numberData could then return an Optional like so

public Optional<Number> numberData(@NonNull String key)
{
   return new Mapped<CharSequence, Number>(chars -> new StringNumber(chars.toString()), charData(key));
}

…

public final static class StringNumber extends Number
{
   // for some weird reason Integer.parseInt(…) et al. expect a String when they would work just as well with a CharSequence :-|

    private final String mData;
    public StringNumber(String data) {mData = data;}

    public int intValue()
    {
       return Integer.parseInt(mData);
    }

   public float floatValue()
   {
      return Float.parseFloat(mData);
   }


   //etc …
}

So this could almost count as a convenience method but I think it can really improve our code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds nice to me. The type may need to be T extends Number I think, to be able to get an Optional<Integer> for example and use the the default value method like .value(3).

Create a ticket in ContentPal?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, that Optional<Integer> wouldn't work of course, since it's actually an Optional<StringNumber>... so I am not sure how to use a default value easily with Optional`

@dmfs
Copy link
Owner

dmfs commented Nov 9, 2017

How much efforts would it take to split this into multiple PRs? For instance, the progressbar smartview would make sense on its own. Same goes for the OpenTasksPal changes.

@lemonboston
Copy link
Contributor Author

I agree that it would be better as multiple PRs. I will split it and create tickets for them.
The DateTime support on the formatter with the corresponding DateTime to Time conversion will also be one.

@lemonboston lemonboston force-pushed the stories/442-show-subtasks branch 2 times, most recently from 25a8ac6 to fa28433 Compare November 13, 2017 19:16
@dmfs
Copy link
Owner

dmfs commented Nov 20, 2017

@lemonboston please rebase.

@lemonboston lemonboston force-pushed the stories/442-show-subtasks branch 2 times, most recently from dfb9bb1 to 379c06a Compare November 20, 2017 14:22
@lemonboston
Copy link
Contributor Author

I've rebased this one, but please note that there is #484 for the OpenTasksPal parts of this changeset and other parts may be submitted separately as well, so it is not worth reviewing this branch further at the moment.

@lemonboston lemonboston force-pushed the stories/442-show-subtasks branch 3 times, most recently from e9f8cc6 to f653ee7 Compare December 20, 2017 21:22
@lemonboston lemonboston force-pushed the stories/442-show-subtasks branch 3 times, most recently from 0da7e7d to a171f54 Compare December 22, 2017 14:10
@lemonboston
Copy link
Contributor Author

lemonboston commented Dec 22, 2017

I've updated this pull request with changes from master and also changed several other things.

Most notably, the two SmartViews (SubtasksView and SubtaskView) don't receive RowDataSnapshot any more but dedicatated Params objects, so that the UI is decoupled from the details of the data access layer this way. These Views no longer need to be concerned about projections for example.

The rx.Single that loads the required data - SubtasksViewParamsSource - now produces those ready made parameter objects. It is a special class for this purpose but uses re-usable components inside. It adapts the RowDataSnapshot to those Params before delivering them, so all those details, including the query as well, are hidden in this class.
I like this decoupling of data access layer representation details from the presentation layer.

Another important change I had to make was to move the loading from the ViewTaskFragment's onCreateView() to the updateView() method. This was because on rotation, the updateView() method is called with a delayed post() and it clears the whole content view, so the subtasks part was not shown. Not sure if there is a better option currently where to put it without starting to change those tangled code. I think the way forward could be to refactor the whole fragment, use rx for the loadings of ContentSet and Model so that everything is delivered together. I think any loading could be wrapped in RxJava sources without problems, the part that I haven't checked so far is the automatic updates. But I think that could be included, too. The Observable would just deliver the ContentSet-Model-SubtaskData the same way for initial loading and for updates as well. Most of those states and null checks involved could be removed.
So I think even without getting rid of ContentSet first, we could reduce complexities around loading and state with putting an Rx layer on top, sort of between the data-access and the UI.
(I'd volunteer for such a task. Maybe just a PoC first to see how it would work.)

One more note regarding the RxJava usage: From the four main source types of RxJava2 I used the SingleSource here, since we just have one result to emit. I started using that interface first for declarative classes, but to be able to use any operator on it, like map(), it has to 'wrapped' with the rx.Single class, calling Single.wrap(singleSource).map(..). So I ended up just using Single instead to avoid those boilerplates.
(Side note that in Kotlin I think this problem could be solved with extension functions on SingleSource.)

Back to the loading of the UI, the subtasks section now sometimes appears later, sometimes it is loaded quickly so that it appears to be appearing together with the rest. Because it varies I am not sure if we should try to add some animation, or how to smoothen it.
(Especially if we consider switching to unified rx loading, then it wouldn't be needed at that point.)

The branch is ready for review. I am looking forward to hear what you think.

@lemonboston
Copy link
Contributor Author

Regarding the packaging of the new classes, I created a detailsscreen package for classes that are completely special to the details screen. ViewTaskFragment could come here as well if you see it fit, I just didn't want to make unnecessary changes here.

Classes in the new readdata package of the app are related to loading data from the provider using RxJava.

I was also able to remove TaskUri class, there was no need for it in that form.

@lemonboston
Copy link
Contributor Author

I've rebased this and added commit which changes the subtask loading RxJava source class that it is no longer responsible to know about the views. The RowDataSnapshot -> View.Params adapter classes are now top level ones.

@dmfs dmfs force-pushed the stories/442-show-subtasks branch 4 times, most recently from 15250db to c09f128 Compare September 26, 2018 13:22
@mkampl
Copy link

mkampl commented Dec 5, 2018

Hi there, this feature would be handy for me and for an organization I am currently trying to manage the tasks via nextcloud. What is the status of merging this subtask feature?

@dmfs
Copy link
Owner

dmfs commented Dec 7, 2018

This PR needs to wait for recurrence support which has currently highest priority (after fixing #728). There may be a few required changes after recurrence support is done. I'l get to that whenever my schedule allows me to.

@dmfs dmfs force-pushed the stories/442-show-subtasks branch 3 times, most recently from d2d9519 to 5f12777 Compare April 11, 2020 21:47
@dmfs dmfs force-pushed the stories/442-show-subtasks branch from 5f12777 to 4ec5152 Compare February 7, 2021 19:53
@dmfs dmfs force-pushed the stories/442-show-subtasks branch from 4ec5152 to bd36385 Compare February 7, 2021 19:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants