Skip to content

Commit fa28433

Browse files
author
Gabor Keszthelyi
committed
Show subtasks on details view. #442
1 parent 1b106c6 commit fa28433

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1684
-150
lines changed

.idea/dictionaries/dictionary.xml

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/inspectionProfiles/Project_Default.xml

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gradle.properties

+3-1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@ TARGET_SDK_VERSION=25
55
# dependency versions
66
SUPPORT_LIBRARY_VERSION=25.0.1
77
CONTENTPAL_VERSION=2d16bb5
8-
ROBOLECTRIC_VERSION=3.1.4
8+
ROBOLECTRIC_VERSION=3.1.4
9+
BOLTS_VERSION=a43822b
10+
ANDROID_TEST_RUNNER_VERSION=0.5

opentasks-provider/build.gradle

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ dependencies {
3535
androidTestCompile project(':opentaskspal')
3636
androidTestCompile 'com.github.dmfs.contentpal:contenttestpal:' + CONTENTPAL_VERSION
3737
androidTestCompile 'com.android.support:support-annotations:' + SUPPORT_LIBRARY_VERSION
38-
androidTestCompile 'com.android.support.test:runner:0.5'
39-
androidTestCompile 'com.android.support.test:rules:0.5'
38+
androidTestCompile 'com.android.support.test:runner:' + ANDROID_TEST_RUNNER_VERSION
39+
androidTestCompile 'com.android.support.test:rules:' + ANDROID_TEST_RUNNER_VERSION
4040
androidTestCompile 'org.hamcrest:hamcrest-library:1.3'
4141
}

opentasks/build.gradle

+15-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ android {
1919
targetSdkVersion TARGET_SDK_VERSION.toInteger()
2020
versionCode gitCommitNo() * 10 // spread version code to allow inserting versions if necessary
2121
versionName version
22+
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
2223
}
2324
buildTypes {
2425
release {
@@ -41,6 +42,9 @@ android {
4142
sourceCompatibility JavaVersion.VERSION_1_7
4243
targetCompatibility JavaVersion.VERSION_1_7
4344
}
45+
dataBinding {
46+
enabled = true
47+
}
4448
}
4549

4650
dependencies {
@@ -51,14 +55,24 @@ dependencies {
5155
exclude group: 'xmlpull', module: 'xmlpull'
5256
}
5357
compile project(':opentasks-provider')
58+
compile project(':opentaskspal')
5459
compile 'com.google.android.apps.dashclock:dashclock-api:2.0.0'
5560
compile 'com.github.dmfs:color-picker:1.0'
5661
compile 'au.com.codeka:carrot:2.4.0'
5762
compile('com.github.dmfs.androidcarrot:androidcarrot:5a4baa0222') {
5863
exclude module: 'carrot'
5964
exclude group: 'com.android.support'
6065
}
61-
compile 'org.dmfs:essentials:1.9'
66+
compile 'org.dmfs:essentials:1.10'
67+
compile 'io.reactivex.rxjava2:rxjava:2.1.5'
68+
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
69+
compile 'com.github.dmfs.bolts:color-bolts:' + BOLTS_VERSION
6270

6371
testCompile 'junit:junit:4.12'
72+
androidTestCompile ('com.android.support.test:runner:' + ANDROID_TEST_RUNNER_VERSION) {
73+
exclude group: 'com.android.support', module: 'support-annotations'
74+
}
75+
androidTestCompile ('com.android.support.test:rules:' + ANDROID_TEST_RUNNER_VERSION) {
76+
exclude group: 'com.android.support', module: 'support-annotations'
77+
}
6478
}

opentasks/proguard.cfg

+4-1
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,7 @@
7777
java.lang.String TAG;
7878
@org.dmfs.android.retentionmagic.annotations.* <fields>;
7979
private long mId;
80-
}
80+
}
81+
82+
-dontwarn android.databinding.**
83+
-keep class android.databinding.** { *; }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Copyright 2017 dmfs GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.dmfs.tasks.utils;
18+
19+
import android.support.test.runner.AndroidJUnit4;
20+
import android.text.format.Time;
21+
22+
import org.dmfs.rfc5545.DateTime;
23+
import org.dmfs.rfc5545.Duration;
24+
import org.junit.Test;
25+
import org.junit.runner.RunWith;
26+
27+
import java.util.TimeZone;
28+
29+
30+
/**
31+
* Test for {@link DateFormatter#toTime(DateTime)} method.
32+
*
33+
* @author Gabor Keszthelyi
34+
*/
35+
@RunWith(AndroidJUnit4.class)
36+
public class DateTimeToTimeConversionTest
37+
{
38+
39+
@Test
40+
public void test_toTime_withVariousDateTimes()
41+
{
42+
assertCorrectlyConverted(DateTime.now());
43+
44+
assertCorrectlyConverted(DateTime.now(TimeZone.getTimeZone("UTC+04:00")));
45+
46+
assertCorrectlyConverted(DateTime.nowAndHere());
47+
48+
assertCorrectlyConverted(new DateTime(1509473781000L));
49+
50+
assertCorrectlyConverted(new DateTime(1509473781000L).addDuration(new Duration(1, 1, 0)));
51+
52+
assertCorrectlyConverted(DateTime.now(TimeZone.getTimeZone("UTC+04:00")).shiftTimeZone(TimeZone.getTimeZone("UTC+05:00")));
53+
54+
// Floating, all-day
55+
assertCorrectlyConverted(DateTime.now().toAllDay());
56+
57+
// Not DST (March 2017 in Hungary):
58+
assertCorrectlyConverted(new DateTime(TimeZone.getTimeZone("Europe/Budapest"), 2017, 2 - 1, 7, 15, 0, 0));
59+
assertCorrectlyConverted(new DateTime(2017, 2 - 1, 7, 15, 0, 0).shiftTimeZone(TimeZone.getTimeZone("Europe/Budapest")));
60+
assertCorrectlyConverted(new DateTime(2017, 2 - 1, 7, 15, 0, 0).swapTimeZone(TimeZone.getTimeZone("Europe/Budapest")));
61+
62+
// DST (July 2017 in Hungary):
63+
assertCorrectlyConverted(new DateTime(TimeZone.getTimeZone("Europe/Budapest"), 2017, 7 - 1, 7, 15, 0, 0));
64+
assertCorrectlyConverted(new DateTime(2017, 7 - 1, 7, 15, 0, 0).shiftTimeZone(TimeZone.getTimeZone("Europe/Budapest")));
65+
assertCorrectlyConverted(new DateTime(2017, 7 - 1, 7, 15, 0, 0).swapTimeZone(TimeZone.getTimeZone("Europe/Budapest")));
66+
}
67+
68+
69+
@Test(expected = IllegalArgumentException.class)
70+
public void test_toTime_forFloatingButNotAllDayDateTime_throwsSinceItIsNotSupported()
71+
{
72+
new DateFormatter(null).toTime(new DateTime(2017, 7 - 1, 7, 15, 0, 0));
73+
}
74+
75+
76+
private void assertCorrectlyConverted(DateTime dateTime)
77+
{
78+
Time time = new DateFormatter(null).toTime(dateTime);
79+
if (!isEquivalentDateTimeAndTime(dateTime, time))
80+
{
81+
throw new AssertionError(String.format("DateTime=%s and Time=%s are not equivalent", dateTime, time));
82+
}
83+
}
84+
85+
86+
/**
87+
* Contains the definition/requirement of when a {@link DateTime} and {@link Time} is considered equivalent in this project.
88+
*/
89+
private boolean isEquivalentDateTimeAndTime(DateTime dateTime, Time time)
90+
{
91+
// Time doesn't seem to store in millis precision, there is a 1000 multiplier internally when calculating millis,
92+
// so we can only compare to this precision:
93+
boolean millisMatch =
94+
dateTime.getTimestamp() / 1000
95+
==
96+
time.toMillis(false) / 1000;
97+
98+
boolean allDaysMatch = time.allDay == dateTime.isAllDay();
99+
100+
boolean timeZoneMatch =
101+
// If DateTime is floating, all-day then if the all-day flag is matched with Time (checked earlier)
102+
// then we consider the Time's timezone matching, we ignore that basically,
103+
// because Time always has a time zone, and there is no other way to represent all-day date-times with Time.
104+
(dateTime.isFloating() && dateTime.isAllDay())
105+
||
106+
// This is the regular case with non-floating DateTime
107+
(dateTime.getTimeZone() != null && time.timezone.equals(dateTime.getTimeZone().getID()));
108+
109+
return millisMatch && allDaysMatch && timeZoneMatch;
110+
}
111+
112+
}

opentasks/src/main/AndroidManifest.xml

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:tools="http://schemas.android.com/tools"
34
package="org.dmfs.tasks">
45

56
<uses-permission android:name="org.dmfs.permission.READ_TASKS"/>
@@ -11,12 +12,16 @@
1112
<uses-permission android:name="com.android.alarm.permission.SET_ALARM"/>
1213
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
1314

15+
<!--TODO Remove after https://github.com/dmfs/opentasks/issues/392-->
16+
<uses-sdk tools:overrideLibrary="org.dmfs.android.bolts"/>
17+
1418
<application
1519
android:icon="@drawable/ic_launcher"
1620
android:label="@string/app_name"
1721
android:name=".TasksApplication"
1822
android:taskAffinity="org.dmfs.tasks.TaskListActivity"
19-
android:theme="@style/OpenTasksAppTheme">
23+
android:theme="@style/OpenTasksAppTheme"
24+
android:supportsRtl="false">
2025

2126
<!-- TaskListActivity listens for MAIN intents -->
2227
<activity

opentasks/src/main/java/org/dmfs/tasks/EditTaskFragment.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
import org.dmfs.tasks.model.OnContentChangeListener;
6161
import org.dmfs.tasks.model.Sources;
6262
import org.dmfs.tasks.model.TaskFieldAdapters;
63+
import org.dmfs.tasks.utils.BasicTaskDetailsUi;
6364
import org.dmfs.tasks.utils.ContentValueMapper;
6465
import org.dmfs.tasks.utils.OnModelLoadedListener;
6566
import org.dmfs.tasks.utils.RecentlyUsedLists;
@@ -809,7 +810,7 @@ public void saveAndExit()
809810
activity.finish();
810811
if (isNewTask)
811812
{
812-
activity.startActivity(new Intent("android.intent.action.VIEW", mTaskUri));
813+
new BasicTaskDetailsUi(mTaskUri).show(activity);
813814
}
814815
}
815816
else

opentasks/src/main/java/org/dmfs/tasks/ViewTaskActivity.java

+2-12
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import android.annotation.SuppressLint;
2020
import android.content.Intent;
21-
import android.graphics.Color;
2221
import android.net.Uri;
2322
import android.os.Build.VERSION;
2423
import android.os.Bundle;
@@ -30,6 +29,7 @@
3029

3130
import org.dmfs.tasks.model.ContentSet;
3231
import org.dmfs.tasks.utils.BaseActivity;
32+
import org.dmfs.tasks.utils.Darkened;
3333

3434

3535
/**
@@ -132,16 +132,6 @@ public void onDelete(Uri taskUri)
132132
}
133133

134134

135-
private int darkenColor(int color)
136-
{
137-
float[] hsv = new float[3];
138-
Color.colorToHSV(color, hsv);
139-
hsv[2] = hsv[2] * 0.75f;
140-
color = Color.HSVToColor(hsv);
141-
return color;
142-
}
143-
144-
145135
@SuppressLint("NewApi")
146136
@Override
147137
public void updateColor(int color)
@@ -151,7 +141,7 @@ public void updateColor(int color)
151141
{
152142
Window window = getWindow();
153143
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
154-
window.setStatusBarColor(darkenColor(color));
144+
window.setStatusBarColor(new Darkened(color).argb());
155145
}
156146
}
157147

opentasks/src/main/java/org/dmfs/tasks/ViewTaskFragment.java

+31-12
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,13 @@
5252
import android.view.animation.AlphaAnimation;
5353
import android.widget.TextView;
5454

55+
import org.dmfs.android.contentpal.RowDataSnapshot;
5556
import org.dmfs.android.retentionmagic.SupportFragment;
5657
import org.dmfs.android.retentionmagic.annotations.Parameter;
5758
import org.dmfs.android.retentionmagic.annotations.Retain;
59+
import org.dmfs.tasks.contract.TaskContract;
5860
import org.dmfs.tasks.contract.TaskContract.Tasks;
61+
import org.dmfs.tasks.data.SubtasksSource;
5962
import org.dmfs.tasks.model.ContentSet;
6063
import org.dmfs.tasks.model.Model;
6164
import org.dmfs.tasks.model.OnContentChangeListener;
@@ -64,13 +67,18 @@
6467
import org.dmfs.tasks.notification.TaskNotificationHandler;
6568
import org.dmfs.tasks.share.ShareIntentFactory;
6669
import org.dmfs.tasks.utils.ContentValueMapper;
70+
import org.dmfs.tasks.utils.Darkened;
6771
import org.dmfs.tasks.utils.OnModelLoadedListener;
72+
import org.dmfs.tasks.widget.SubtasksView;
6873
import org.dmfs.tasks.widget.TaskView;
6974

7075
import java.util.Arrays;
7176
import java.util.HashSet;
7277
import java.util.Set;
7378

79+
import io.reactivex.disposables.Disposable;
80+
import io.reactivex.functions.Consumer;
81+
7482

7583
/**
7684
* A fragment representing a single Task detail screen. This fragment is either contained in a {@link TaskListActivity} in two-pane mode (on tablets) or in a
@@ -131,6 +139,8 @@ public class ViewTaskFragment extends SupportFragment
131139
*/
132140
private TaskView mDetailView;
133141

142+
private Disposable mDisposable;
143+
134144
private int mListColor;
135145
private int mOldStatus = -1;
136146
private boolean mPinned = false;
@@ -208,14 +218,6 @@ public static ViewTaskFragment newInstance(Uri uri)
208218
}
209219

210220

211-
/**
212-
* Mandatory empty constructor for the fragment manager to instantiate the fragment (e.g. upon screen orientation changes).
213-
*/
214-
public ViewTaskFragment()
215-
{
216-
}
217-
218-
219221
@Override
220222
public void onCreate(Bundle savedInstanceState)
221223
{
@@ -266,6 +268,7 @@ public void onDestroyView()
266268
mDetailView.setValues(null);
267269
}
268270

271+
mDisposable.dispose();
269272
}
270273

271274

@@ -400,8 +403,8 @@ public void loadUri(Uri uri)
400403

401404
if ((oldUri == null) != (uri == null))
402405
{
403-
/*
404-
* getActivity().invalidateOptionsMenu() doesn't work in Android 2.x so use the compat lib
406+
/*
407+
* getActivity().invalidateOptionsMenu() doesn't work in Android 2.x so use the compat lib
405408
*/
406409
ActivityCompat.invalidateOptionsMenu(getActivity());
407410
}
@@ -491,8 +494,8 @@ public void onModelLoaded(Model model)
491494
@Override
492495
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)
493496
{
494-
/*
495-
* Don't show any options if we don't have a task to show.
497+
/*
498+
* Don't show any options if we don't have a task to show.
496499
*/
497500
if (mTaskUri != null)
498501
{
@@ -717,6 +720,22 @@ public void onContentLoaded(ContentSet contentSet)
717720
postUpdateView();
718721
}
719722
}
723+
724+
mDisposable = new SubtasksSource(mAppContext, mTaskUri)
725+
.subscribe(new Consumer<Iterable<RowDataSnapshot<TaskContract.Tasks>>>()
726+
{
727+
@Override
728+
public void accept(Iterable<RowDataSnapshot<TaskContract.Tasks>> subTasks)
729+
{
730+
if (subTasks.iterator().hasNext())
731+
{
732+
new SubtasksView(mContent).update(subTasks);
733+
((TextView) mContent.findViewById(R.id.opentasks_view_item_task_details_subtitles_section_header))
734+
.setTextColor(new Darkened(mListColor).argb());
735+
mContent.requestLayout();
736+
}
737+
}
738+
});
720739
}
721740

722741

0 commit comments

Comments
 (0)