getConstraints() {
121 | return allOf(isAssignableFrom(NavigationView.class),
122 | withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE),
123 | isDisplayingAtLeast(90)
124 | );
125 | }
126 | };
127 |
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/microsoft/sample/tasks/AppNavigationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample.tasks;
18 |
19 | import android.support.test.espresso.NoActivityResumedException;
20 | import android.support.test.filters.LargeTest;
21 | import android.support.test.rule.ActivityTestRule;
22 | import android.support.test.runner.AndroidJUnit4;
23 | import android.support.v4.widget.DrawerLayout;
24 | import android.view.Gravity;
25 |
26 | import com.microsoft.sample.R;
27 |
28 | import org.junit.Rule;
29 | import org.junit.Test;
30 | import org.junit.runner.RunWith;
31 |
32 | import static android.support.test.espresso.Espresso.onView;
33 | import static android.support.test.espresso.Espresso.pressBack;
34 | import static android.support.test.espresso.action.ViewActions.click;
35 | import static android.support.test.espresso.assertion.ViewAssertions.matches;
36 | import static android.support.test.espresso.contrib.DrawerActions.open;
37 | import static android.support.test.espresso.contrib.DrawerMatchers.isClosed;
38 | import static android.support.test.espresso.contrib.DrawerMatchers.isOpen;
39 | import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
40 | import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription;
41 | import static android.support.test.espresso.matcher.ViewMatchers.withId;
42 | import static com.microsoft.sample.TestUtils.getToolbarNavigationContentDescription;
43 | import static com.microsoft.sample.custom.action.NavigationViewActions.navigateTo;
44 | import static junit.framework.Assert.fail;
45 |
46 | /**
47 | * Tests for the {@link DrawerLayout} layout component in {@link TasksActivity} which manages
48 | * navigation within the app.
49 | */
50 | @RunWith(AndroidJUnit4.class)
51 | @LargeTest
52 | public class AppNavigationTest {
53 |
54 | /**
55 | * {@link ActivityTestRule} is a JUnit {@link Rule @Rule} to launch your activity under test.
56 | *
57 | *
58 | * Rules are interceptors which are executed for each test method and are important building
59 | * blocks of Junit tests.
60 | */
61 | @Rule
62 | public ActivityTestRule mActivityTestRule =
63 | new ActivityTestRule<>(TasksActivity.class);
64 |
65 | @Test
66 | public void clickOnStatisticsNavigationItem_ShowsStatisticsScreen() {
67 | openStatisticsScreen();
68 |
69 | // Check that statistics Activity was opened.
70 | onView(withId(R.id.statistics)).check(matches(isDisplayed()));
71 | }
72 |
73 | @Test
74 | public void clickOnListNavigationItem_ShowsListScreen() {
75 | openStatisticsScreen();
76 |
77 | openTasksScreen();
78 |
79 | // Check that Tasks Activity was opened.
80 | onView(withId(R.id.tasksContainer)).check(matches(isDisplayed()));
81 | }
82 |
83 | @Test
84 | public void clickOnAndroidHomeIcon_OpensNavigation() {
85 | // Check that left drawer is closed at startup
86 | onView(withId(R.id.drawer_layout))
87 | .check(matches(isClosed(Gravity.LEFT))); // Left Drawer should be closed.
88 |
89 | // Open Drawer
90 | onView(withContentDescription(getToolbarNavigationContentDescription(
91 | mActivityTestRule.getActivity(), R.id.toolbar))).perform(click());
92 |
93 | // Check if drawer is open
94 | onView(withId(R.id.drawer_layout))
95 | .check(matches(isOpen(Gravity.LEFT))); // Left drawer is open open.
96 | }
97 |
98 | @Test
99 | public void Statistics_backNavigatesToTasks() {
100 | openStatisticsScreen();
101 |
102 | // Press back to go back to the tasks list
103 | pressBack();
104 |
105 | // Check that Tasks Activity was restored.
106 | onView(withId(R.id.tasksContainer)).check(matches(isDisplayed()));
107 | }
108 |
109 | @Test
110 | public void backFromTasksScreen_ExitsApp() {
111 | // From the tasks screen, press back should exit the app.
112 | assertPressingBackExitsApp();
113 | }
114 |
115 | @Test
116 | public void backFromTasksScreenAfterStats_ExitsApp() {
117 | // This test checks that TasksActivity is a parent of StatisticsActivity
118 |
119 | // Open the stats screen
120 | openStatisticsScreen();
121 |
122 | // Open the tasks screen to restore the task
123 | openTasksScreen();
124 |
125 | // Pressing back should exit app
126 | assertPressingBackExitsApp();
127 | }
128 |
129 | private void assertPressingBackExitsApp() {
130 | try {
131 | pressBack();
132 | fail("Should kill the app and throw an exception");
133 | } catch (NoActivityResumedException e) {
134 | // Test OK
135 | }
136 | }
137 |
138 | private void openTasksScreen() {
139 | // Open Drawer to click on navigation item.
140 | onView(withId(R.id.drawer_layout))
141 | .check(matches(isClosed(Gravity.LEFT))) // Left Drawer should be closed.
142 | .perform(open()); // Open Drawer
143 |
144 | // Start tasks list screen.
145 | onView(withId(R.id.nav_view))
146 | .perform(navigateTo(R.id.list_navigation_menu_item));
147 | }
148 |
149 | private void openStatisticsScreen() {
150 | // Open Drawer to click on navigation item.
151 | onView(withId(R.id.drawer_layout))
152 | .check(matches(isClosed(Gravity.LEFT))) // Left Drawer should be closed.
153 | .perform(open()); // Open Drawer
154 |
155 | // Start statistics screen.
156 | onView(withId(R.id.nav_view))
157 | .perform(navigateTo(R.id.statistics_navigation_menu_item));
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/app/src/androidTestMock/java/com/microsoft/sample/addedittask/AddEditTaskScreenTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample.addedittask;
18 |
19 | import android.content.Intent;
20 | import android.content.res.Resources;
21 | import android.support.annotation.Nullable;
22 | import android.support.test.InstrumentationRegistry;
23 | import android.support.test.espresso.intent.rule.IntentsTestRule;
24 | import android.support.test.espresso.matcher.BoundedMatcher;
25 | import android.support.test.filters.LargeTest;
26 | import android.support.test.rule.ActivityTestRule;
27 | import android.support.test.runner.AndroidJUnit4;
28 | import android.support.v7.widget.Toolbar;
29 | import android.view.View;
30 |
31 | import com.microsoft.sample.R;
32 | import com.microsoft.sample.TestUtils;
33 | import com.microsoft.sample.data.FakeTasksRemoteDataSource;
34 | import com.microsoft.sample.data.Task;
35 | import com.microsoft.sample.data.source.TasksRepository;
36 |
37 | import org.hamcrest.Description;
38 | import org.hamcrest.Matcher;
39 | import org.junit.Rule;
40 | import org.junit.Test;
41 | import org.junit.runner.RunWith;
42 |
43 | import static android.support.test.espresso.Espresso.onView;
44 | import static android.support.test.espresso.action.ViewActions.clearText;
45 | import static android.support.test.espresso.action.ViewActions.click;
46 | import static android.support.test.espresso.assertion.ViewAssertions.matches;
47 | import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
48 | import static android.support.test.espresso.matcher.ViewMatchers.withId;
49 | import static com.microsoft.sample.R.id.toolbar;
50 |
51 | /**
52 | * Tests for the add task screen.
53 | */
54 | @RunWith(AndroidJUnit4.class)
55 | @LargeTest
56 | public class AddEditTaskScreenTest {
57 |
58 | private static final String TASK_ID = "1";
59 |
60 | /**
61 | * {@link IntentsTestRule} is an {@link ActivityTestRule} which inits and releases Espresso
62 | * Intents before and after each test run.
63 | *
64 | *
65 | * Rules are interceptors which are executed for each test method and are important building
66 | * blocks of Junit tests.
67 | */
68 | @Rule
69 | public ActivityTestRule mActivityTestRule =
70 | new ActivityTestRule<>(AddEditTaskActivity.class, false, false);
71 |
72 | @Test
73 | public void emptyTask_isNotSaved() {
74 | // Launch activity to add a new task
75 | launchNewTaskActivity(null);
76 |
77 | // Add invalid title and description combination
78 | onView(withId(R.id.add_task_title)).perform(clearText());
79 | onView(withId(R.id.add_task_description)).perform(clearText());
80 | // Try to save the task
81 | onView(withId(R.id.fab_edit_task_done)).perform(click());
82 |
83 | // Verify that the activity is still displayed (a correct task would close it).
84 | onView(withId(R.id.add_task_title)).check(matches(isDisplayed()));
85 | }
86 |
87 | @Test
88 | public void toolbarTitle_newTask_persistsRotation() {
89 | // Launch activity to add a new task
90 | launchNewTaskActivity(null);
91 |
92 | // Check that the toolbar shows the correct title
93 | onView(withId(toolbar)).check(matches(withToolbarTitle(R.string.add_task)));
94 |
95 | // Rotate activity
96 | TestUtils.rotateOrientation(mActivityTestRule.getActivity());
97 |
98 | // Check that the toolbar title is persisted
99 | onView(withId(toolbar)).check(matches(withToolbarTitle(R.string.add_task)));
100 | }
101 |
102 | @Test
103 | public void toolbarTitle_editTask_persistsRotation() {
104 | // Put a task in the repository and start the activity to edit it
105 | TasksRepository.destroyInstance();
106 | FakeTasksRemoteDataSource.getInstance().addTasks(new Task("Title1", "", TASK_ID, false));
107 | launchNewTaskActivity(TASK_ID);
108 |
109 | // Check that the toolbar shows the correct title
110 | onView(withId(toolbar)).check(matches(withToolbarTitle(R.string.edit_task)));
111 |
112 | // Rotate activity
113 | TestUtils.rotateOrientation(mActivityTestRule.getActivity());
114 |
115 | // check that the toolbar title is persisted
116 | onView(withId(toolbar)).check(matches(withToolbarTitle(R.string.edit_task)));
117 | }
118 |
119 | /**
120 | * @param taskId is null if used to add a new task, otherwise it edits the task.
121 | */
122 | private void launchNewTaskActivity(@Nullable String taskId) {
123 | Intent intent = new Intent(InstrumentationRegistry.getInstrumentation()
124 | .getTargetContext(), AddEditTaskActivity.class);
125 |
126 | intent.putExtra(AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID, taskId);
127 | mActivityTestRule.launchActivity(intent);
128 | }
129 |
130 | /**
131 | * Matches the toolbar title with a specific string resource.
132 | *
133 | * @param resourceId the ID of the string resource to match
134 | */
135 | public static Matcher withToolbarTitle(final int resourceId) {
136 | return new BoundedMatcher(Toolbar.class) {
137 |
138 | @Override
139 | public void describeTo(Description description) {
140 | description.appendText("with toolbar title from resource id: ");
141 | description.appendValue(resourceId);
142 | }
143 |
144 | @Override
145 | protected boolean matchesSafely(Toolbar toolbar) {
146 | CharSequence expectedText = "";
147 | try {
148 | expectedText = toolbar.getResources().getString(resourceId);
149 | } catch (Resources.NotFoundException ignored) {
150 | /* view could be from a context unaware of the resource id. */
151 | }
152 | CharSequence actualText = toolbar.getTitle();
153 | return expectedText.equals(actualText);
154 | }
155 | };
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/app/src/androidTestMock/java/com/microsoft/sample/statistics/StatisticsScreenTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample.statistics;
18 |
19 | import android.content.Intent;
20 | import android.support.test.InstrumentationRegistry;
21 | import android.support.test.rule.ActivityTestRule;
22 | import android.support.test.runner.AndroidJUnit4;
23 | import android.support.test.filters.LargeTest;
24 |
25 | import com.microsoft.sample.R;
26 | import com.microsoft.sample.data.FakeTasksRemoteDataSource;
27 | import com.microsoft.sample.data.Task;
28 | import com.microsoft.sample.data.source.TasksRepository;
29 | import com.microsoft.sample.taskdetail.TaskDetailActivity;
30 |
31 | import org.junit.Before;
32 | import org.junit.Rule;
33 | import org.junit.Test;
34 | import org.junit.runner.RunWith;
35 |
36 | import static android.support.test.espresso.Espresso.onView;
37 | import static android.support.test.espresso.assertion.ViewAssertions.matches;
38 | import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
39 | import static android.support.test.espresso.matcher.ViewMatchers.withText;
40 | import static org.hamcrest.Matchers.containsString;
41 |
42 | /**
43 | * Tests for the statistics screen.
44 | */
45 | @RunWith(AndroidJUnit4.class)
46 | @LargeTest
47 | public class StatisticsScreenTest {
48 |
49 | /**
50 | * {@link ActivityTestRule} is a JUnit {@link Rule @Rule} to launch your activity under test.
51 | *
52 | *
53 | * Rules are interceptors which are executed for each test method and are important building
54 | * blocks of Junit tests.
55 | */
56 | @Rule
57 | public ActivityTestRule mStatisticsActivityTestRule =
58 | new ActivityTestRule<>(StatisticsActivity.class, true, false);
59 |
60 | /**
61 | * Setup your test fixture with a fake task id. The {@link TaskDetailActivity} is started with
62 | * a particular task id, which is then loaded from the service API.
63 | *
64 | *
65 | * Note that this test runs hermetically and is fully isolated using a fake implementation of
66 | * the service API. This is a great way to make your tests more reliable and faster at the same
67 | * time, since they are isolated from any outside dependencies.
68 | */
69 | @Before
70 | public void intentWithStubbedTaskId() {
71 | // Given some tasks
72 | TasksRepository.destroyInstance();
73 | FakeTasksRemoteDataSource.getInstance().addTasks(new Task("Title1", "", false));
74 | FakeTasksRemoteDataSource.getInstance().addTasks(new Task("Title2", "", true));
75 |
76 | // Lazily start the Activity from the ActivityTestRule
77 | Intent startIntent = new Intent();
78 | mStatisticsActivityTestRule.launchActivity(startIntent);
79 | }
80 |
81 | @Test
82 | public void Tasks_ShowsNonEmptyMessage() throws Exception {
83 | // Check that the active and completed tasks text is displayed
84 | String expectedActiveTaskText = InstrumentationRegistry.getTargetContext()
85 | .getString(R.string.statistics_active_tasks);
86 | onView(withText(containsString(expectedActiveTaskText))).check(matches(isDisplayed()));
87 | String expectedCompletedTaskText = InstrumentationRegistry.getTargetContext()
88 | .getString(R.string.statistics_completed_tasks);
89 | onView(withText(containsString(expectedCompletedTaskText))).check(matches(isDisplayed()));
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/app/src/androidTestMock/java/com/microsoft/sample/taskdetail/TaskDetailScreenTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample.taskdetail;
18 |
19 | import android.app.Activity;
20 | import android.content.Intent;
21 | import android.support.test.rule.ActivityTestRule;
22 | import android.support.test.runner.AndroidJUnit4;
23 | import android.support.test.filters.LargeTest;
24 |
25 | import com.microsoft.sample.R;
26 | import com.microsoft.sample.TestUtils;
27 | import com.microsoft.sample.data.FakeTasksRemoteDataSource;
28 | import com.microsoft.sample.data.Task;
29 | import com.microsoft.sample.data.source.TasksRepository;
30 |
31 | import org.junit.Rule;
32 | import org.junit.Test;
33 | import org.junit.runner.RunWith;
34 |
35 | import static android.support.test.espresso.Espresso.onView;
36 | import static android.support.test.espresso.assertion.ViewAssertions.matches;
37 | import static android.support.test.espresso.matcher.ViewMatchers.isChecked;
38 | import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
39 | import static android.support.test.espresso.matcher.ViewMatchers.withId;
40 | import static android.support.test.espresso.matcher.ViewMatchers.withText;
41 | import static org.hamcrest.core.IsNot.not;
42 |
43 | /**
44 | * Tests for the tasks screen, the main screen which contains a list of all tasks.
45 | */
46 | @RunWith(AndroidJUnit4.class)
47 | @LargeTest
48 | public class TaskDetailScreenTest {
49 |
50 | private static String TASK_TITLE = "ATSL";
51 |
52 | private static String TASK_DESCRIPTION = "Rocks";
53 |
54 | /**
55 | * {@link Task} stub that is added to the fake service API layer.
56 | */
57 | private static Task ACTIVE_TASK = new Task(TASK_TITLE, TASK_DESCRIPTION, false);
58 |
59 | /**
60 | * {@link Task} stub that is added to the fake service API layer.
61 | */
62 | private static Task COMPLETED_TASK = new Task(TASK_TITLE, TASK_DESCRIPTION, true);
63 |
64 | /**
65 | * {@link ActivityTestRule} is a JUnit {@link Rule @Rule} to launch your activity under test.
66 | *
67 | *
68 | * Rules are interceptors which are executed for each test method and are important building
69 | * blocks of Junit tests.
70 | *
71 | *
72 | * Sometimes an {@link Activity} requires a custom start {@link Intent} to receive data
73 | * from the source Activity. ActivityTestRule has a feature which let's you lazily start the
74 | * Activity under test, so you can control the Intent that is used to start the target Activity.
75 | */
76 | @Rule
77 | public ActivityTestRule mTaskDetailActivityTestRule =
78 | new ActivityTestRule<>(TaskDetailActivity.class, true /* Initial touch mode */,
79 | false /* Lazily launch activity */);
80 |
81 | private void loadActiveTask() {
82 | startActivityWithWithStubbedTask(ACTIVE_TASK);
83 | }
84 |
85 | private void loadCompletedTask() {
86 | startActivityWithWithStubbedTask(COMPLETED_TASK);
87 | }
88 |
89 | /**
90 | * Setup your test fixture with a fake task id. The {@link TaskDetailActivity} is started with
91 | * a particular task id, which is then loaded from the service API.
92 | *
93 | *
94 | * Note that this test runs hermetically and is fully isolated using a fake implementation of
95 | * the service API. This is a great way to make your tests more reliable and faster at the same
96 | * time, since they are isolated from any outside dependencies.
97 | */
98 | private void startActivityWithWithStubbedTask(Task task) {
99 | // Add a task stub to the fake service api layer.
100 | TasksRepository.destroyInstance();
101 | FakeTasksRemoteDataSource.getInstance().addTasks(task);
102 |
103 | // Lazily start the Activity from the ActivityTestRule this time to inject the start Intent
104 | Intent startIntent = new Intent();
105 | startIntent.putExtra(TaskDetailActivity.EXTRA_TASK_ID, task.getId());
106 | mTaskDetailActivityTestRule.launchActivity(startIntent);
107 | }
108 |
109 | @Test
110 | public void activeTaskDetails_DisplayedInUi() throws Exception {
111 | loadActiveTask();
112 |
113 | // Check that the task title and description are displayed
114 | onView(withId(R.id.task_detail_title)).check(matches(withText(TASK_TITLE)));
115 | onView(withId(R.id.task_detail_description)).check(matches(withText(TASK_DESCRIPTION)));
116 | onView(withId(R.id.task_detail_complete)).check(matches(not(isChecked())));
117 | }
118 |
119 | @Test
120 | public void completedTaskDetails_DisplayedInUi() throws Exception {
121 | loadCompletedTask();
122 |
123 | // Check that the task title and description are displayed
124 | onView(withId(R.id.task_detail_title)).check(matches(withText(TASK_TITLE)));
125 | onView(withId(R.id.task_detail_description)).check(matches(withText(TASK_DESCRIPTION)));
126 | onView(withId(R.id.task_detail_complete)).check(matches(isChecked()));
127 | }
128 |
129 | @Test
130 | public void orientationChange_menuAndTaskPersist() {
131 | loadActiveTask();
132 |
133 | // Check delete menu item is displayed and is unique
134 | onView(withId(R.id.menu_delete)).check(matches(isDisplayed()));
135 |
136 | TestUtils.rotateOrientation(mTaskDetailActivityTestRule.getActivity());
137 |
138 | // Check that the task is shown
139 | onView(withId(R.id.task_detail_title)).check(matches(withText(TASK_TITLE)));
140 | onView(withId(R.id.task_detail_description)).check(matches(withText(TASK_DESCRIPTION)));
141 |
142 | // Check delete menu item is displayed and is unique
143 | onView(withId(R.id.menu_delete)).check(matches(isDisplayed()));
144 | }
145 |
146 | }
147 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
20 |
21 |
27 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
41 |
42 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/java/com/microsoft/sample/BasePresenter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample;
18 |
19 | public interface BasePresenter {
20 |
21 | void start();
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/microsoft/sample/BaseView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample;
18 |
19 | public interface BaseView {
20 |
21 | void setPresenter(T presenter);
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/microsoft/sample/addedittask/AddEditTaskActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample.addedittask;
18 |
19 | import android.os.Bundle;
20 | import android.support.annotation.Nullable;
21 | import android.support.annotation.VisibleForTesting;
22 | import android.support.test.espresso.IdlingResource;
23 | import android.support.v7.app.ActionBar;
24 | import android.support.v7.app.AppCompatActivity;
25 | import android.support.v7.widget.Toolbar;
26 |
27 | import com.microsoft.sample.Injection;
28 | import com.microsoft.sample.R;
29 | import com.microsoft.sample.util.ActivityUtils;
30 | import com.microsoft.sample.util.EspressoIdlingResource;
31 |
32 | /**
33 | * Displays an add or edit task screen.
34 | */
35 | public class AddEditTaskActivity extends AppCompatActivity {
36 |
37 | public static final int REQUEST_ADD_TASK = 1;
38 |
39 | public static final String SHOULD_LOAD_DATA_FROM_REPO_KEY = "SHOULD_LOAD_DATA_FROM_REPO_KEY";
40 |
41 | private AddEditTaskPresenter mAddEditTaskPresenter;
42 |
43 | private ActionBar mActionBar;
44 |
45 | @Override
46 | protected void onCreate(Bundle savedInstanceState) {
47 | super.onCreate(savedInstanceState);
48 | setContentView(R.layout.addtask_act);
49 |
50 | // Set up the toolbar.
51 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
52 | setSupportActionBar(toolbar);
53 | mActionBar = getSupportActionBar();
54 | mActionBar.setDisplayHomeAsUpEnabled(true);
55 | mActionBar.setDisplayShowHomeEnabled(true);
56 |
57 | AddEditTaskFragment addEditTaskFragment = (AddEditTaskFragment) getSupportFragmentManager()
58 | .findFragmentById(R.id.contentFrame);
59 |
60 | String taskId = getIntent().getStringExtra(AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID);
61 |
62 | setToolbarTitle(taskId);
63 |
64 | if (addEditTaskFragment == null) {
65 | addEditTaskFragment = AddEditTaskFragment.newInstance();
66 |
67 | if (getIntent().hasExtra(AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID)) {
68 | Bundle bundle = new Bundle();
69 | bundle.putString(AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID, taskId);
70 | addEditTaskFragment.setArguments(bundle);
71 | }
72 |
73 | ActivityUtils.addFragmentToActivity(getSupportFragmentManager(),
74 | addEditTaskFragment, R.id.contentFrame);
75 | }
76 |
77 | boolean shouldLoadDataFromRepo = true;
78 |
79 | // Prevent the presenter from loading data from the repository if this is a config change.
80 | if (savedInstanceState != null) {
81 | // Data might not have loaded when the config change happen, so we saved the state.
82 | shouldLoadDataFromRepo = savedInstanceState.getBoolean(SHOULD_LOAD_DATA_FROM_REPO_KEY);
83 | }
84 |
85 | // Create the presenter
86 | mAddEditTaskPresenter = new AddEditTaskPresenter(
87 | taskId,
88 | Injection.provideTasksRepository(getApplicationContext()),
89 | addEditTaskFragment,
90 | shouldLoadDataFromRepo);
91 | }
92 |
93 | private void setToolbarTitle(@Nullable String taskId) {
94 | if(taskId == null) {
95 | mActionBar.setTitle(R.string.add_task);
96 | } else {
97 | mActionBar.setTitle(R.string.edit_task);
98 | }
99 | }
100 |
101 | @Override
102 | protected void onSaveInstanceState(Bundle outState) {
103 | // Save the state so that next time we know if we need to refresh data.
104 | outState.putBoolean(SHOULD_LOAD_DATA_FROM_REPO_KEY, mAddEditTaskPresenter.isDataMissing());
105 | super.onSaveInstanceState(outState);
106 | }
107 |
108 | @Override
109 | public boolean onSupportNavigateUp() {
110 | onBackPressed();
111 | return true;
112 | }
113 |
114 | @VisibleForTesting
115 | public IdlingResource getCountingIdlingResource() {
116 | return EspressoIdlingResource.getIdlingResource();
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/app/src/main/java/com/microsoft/sample/addedittask/AddEditTaskContract.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample.addedittask;
18 |
19 | import com.microsoft.sample.BasePresenter;
20 | import com.microsoft.sample.BaseView;
21 |
22 | /**
23 | * This specifies the contract between the view and the presenter.
24 | */
25 | public interface AddEditTaskContract {
26 |
27 | interface View extends BaseView {
28 |
29 | void showEmptyTaskError();
30 |
31 | void showTasksList();
32 |
33 | void setTitle(String title);
34 |
35 | void setDescription(String description);
36 |
37 | boolean isActive();
38 | }
39 |
40 | interface Presenter extends BasePresenter {
41 |
42 | void saveTask(String title, String description);
43 |
44 | void populateTask();
45 |
46 | boolean isDataMissing();
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/java/com/microsoft/sample/addedittask/AddEditTaskFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample.addedittask;
18 |
19 | import android.app.Activity;
20 | import android.os.Bundle;
21 | import android.support.annotation.NonNull;
22 | import android.support.annotation.Nullable;
23 | import android.support.design.widget.FloatingActionButton;
24 | import android.support.design.widget.Snackbar;
25 | import android.support.v4.app.Fragment;
26 | import android.view.LayoutInflater;
27 | import android.view.View;
28 | import android.view.ViewGroup;
29 | import android.widget.TextView;
30 |
31 | import com.microsoft.sample.R;
32 |
33 | import static com.google.common.base.Preconditions.checkNotNull;
34 |
35 | /**
36 | * Main UI for the add task screen. Users can enter a task title and description.
37 | */
38 | public class AddEditTaskFragment extends Fragment implements AddEditTaskContract.View {
39 |
40 | public static final String ARGUMENT_EDIT_TASK_ID = "EDIT_TASK_ID";
41 |
42 | private AddEditTaskContract.Presenter mPresenter;
43 |
44 | private TextView mTitle;
45 |
46 | private TextView mDescription;
47 |
48 | public static AddEditTaskFragment newInstance() {
49 | return new AddEditTaskFragment();
50 | }
51 |
52 | public AddEditTaskFragment() {
53 | // Required empty public constructor
54 | }
55 |
56 | @Override
57 | public void onResume() {
58 | super.onResume();
59 | mPresenter.start();
60 | }
61 |
62 | @Override
63 | public void setPresenter(@NonNull AddEditTaskContract.Presenter presenter) {
64 | mPresenter = checkNotNull(presenter);
65 | }
66 |
67 | @Override
68 | public void onActivityCreated(Bundle savedInstanceState) {
69 | super.onActivityCreated(savedInstanceState);
70 |
71 | FloatingActionButton fab =
72 | (FloatingActionButton) getActivity().findViewById(R.id.fab_edit_task_done);
73 | fab.setImageResource(R.drawable.ic_done);
74 | fab.setOnClickListener(new View.OnClickListener() {
75 | @Override
76 | public void onClick(View v) {
77 | mPresenter.saveTask(mTitle.getText().toString(), mDescription.getText().toString());
78 | }
79 | });
80 | }
81 |
82 | @Nullable
83 | @Override
84 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
85 | Bundle savedInstanceState) {
86 | View root = inflater.inflate(R.layout.addtask_frag, container, false);
87 | mTitle = (TextView) root.findViewById(R.id.add_task_title);
88 | mDescription = (TextView) root.findViewById(R.id.add_task_description);
89 | setHasOptionsMenu(true);
90 | return root;
91 | }
92 |
93 | @Override
94 | public void showEmptyTaskError() {
95 | Snackbar.make(mTitle, getString(R.string.empty_task_message), Snackbar.LENGTH_LONG).show();
96 | }
97 |
98 | @Override
99 | public void showTasksList() {
100 | getActivity().setResult(Activity.RESULT_OK);
101 | getActivity().finish();
102 | }
103 |
104 | @Override
105 | public void setTitle(String title) {
106 | mTitle.setText(title);
107 | }
108 |
109 | @Override
110 | public void setDescription(String description) {
111 | mDescription.setText(description);
112 | }
113 |
114 | @Override
115 | public boolean isActive() {
116 | return isAdded();
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/app/src/main/java/com/microsoft/sample/addedittask/AddEditTaskPresenter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample.addedittask;
18 |
19 | import android.support.annotation.NonNull;
20 | import android.support.annotation.Nullable;
21 |
22 | import com.microsoft.sample.data.Task;
23 | import com.microsoft.sample.data.source.TasksDataSource;
24 |
25 | import static com.google.common.base.Preconditions.checkNotNull;
26 |
27 | /**
28 | * Listens to user actions from the UI ({@link AddEditTaskFragment}), retrieves the data and updates
29 | * the UI as required.
30 | */
31 | public class AddEditTaskPresenter implements AddEditTaskContract.Presenter,
32 | TasksDataSource.GetTaskCallback {
33 |
34 | @NonNull
35 | private final TasksDataSource mTasksRepository;
36 |
37 | @NonNull
38 | private final AddEditTaskContract.View mAddTaskView;
39 |
40 | @Nullable
41 | private String mTaskId;
42 |
43 | private boolean mIsDataMissing;
44 |
45 | /**
46 | * Creates a presenter for the add/edit view.
47 | *
48 | * @param taskId ID of the task to edit or null for a new task
49 | * @param tasksRepository a repository of data for tasks
50 | * @param addTaskView the add/edit view
51 | * @param shouldLoadDataFromRepo whether data needs to be loaded or not (for config changes)
52 | */
53 | public AddEditTaskPresenter(@Nullable String taskId, @NonNull TasksDataSource tasksRepository,
54 | @NonNull AddEditTaskContract.View addTaskView, boolean shouldLoadDataFromRepo) {
55 | mTaskId = taskId;
56 | mTasksRepository = checkNotNull(tasksRepository);
57 | mAddTaskView = checkNotNull(addTaskView);
58 | mIsDataMissing = shouldLoadDataFromRepo;
59 |
60 | mAddTaskView.setPresenter(this);
61 | }
62 |
63 | @Override
64 | public void start() {
65 | if (!isNewTask() && mIsDataMissing) {
66 | populateTask();
67 | }
68 | }
69 |
70 | @Override
71 | public void saveTask(String title, String description) {
72 | if (isNewTask()) {
73 | createTask(title, description);
74 | } else {
75 | updateTask(title, description);
76 | }
77 | }
78 |
79 | @Override
80 | public void populateTask() {
81 | if (isNewTask()) {
82 | throw new RuntimeException("populateTask() was called but task is new.");
83 | }
84 | mTasksRepository.getTask(mTaskId, this);
85 | }
86 |
87 | @Override
88 | public void onTaskLoaded(Task task) {
89 | // The view may not be able to handle UI updates anymore
90 | if (mAddTaskView.isActive()) {
91 | mAddTaskView.setTitle(task.getTitle());
92 | mAddTaskView.setDescription(task.getDescription());
93 | }
94 | mIsDataMissing = false;
95 | }
96 |
97 | @Override
98 | public void onDataNotAvailable() {
99 | // The view may not be able to handle UI updates anymore
100 | if (mAddTaskView.isActive()) {
101 | mAddTaskView.showEmptyTaskError();
102 | }
103 | }
104 |
105 | @Override
106 | public boolean isDataMissing() {
107 | return mIsDataMissing;
108 | }
109 |
110 | private boolean isNewTask() {
111 | return mTaskId == null;
112 | }
113 |
114 | private void createTask(String title, String description) {
115 | Task newTask = new Task(title, description);
116 | if (newTask.isEmpty()) {
117 | mAddTaskView.showEmptyTaskError();
118 | } else {
119 | mTasksRepository.saveTask(newTask);
120 | mAddTaskView.showTasksList();
121 | }
122 | }
123 |
124 | private void updateTask(String title, String description) {
125 | if (isNewTask()) {
126 | throw new RuntimeException("updateTask() was called but task is new.");
127 | }
128 | mTasksRepository.saveTask(new Task(title, description, mTaskId));
129 | mAddTaskView.showTasksList(); // After an edit, go back to the list.
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/app/src/main/java/com/microsoft/sample/data/Task.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample.data;
18 |
19 | import android.support.annotation.NonNull;
20 | import android.support.annotation.Nullable;
21 |
22 | import com.google.common.base.Objects;
23 | import com.google.common.base.Strings;
24 |
25 | import java.util.UUID;
26 |
27 | /**
28 | * Immutable model class for a Task.
29 | */
30 | public final class Task {
31 |
32 | @NonNull
33 | private final String mId;
34 |
35 | @Nullable
36 | private final String mTitle;
37 |
38 | @Nullable
39 | private final String mDescription;
40 |
41 | private final boolean mCompleted;
42 |
43 | /**
44 | * Use this constructor to create a new active Task.
45 | *
46 | * @param title title of the task
47 | * @param description description of the task
48 | */
49 | public Task(@Nullable String title, @Nullable String description) {
50 | this(title, description, UUID.randomUUID().toString(), false);
51 | }
52 |
53 | /**
54 | * Use this constructor to create an active Task if the Task already has an id (copy of another
55 | * Task).
56 | *
57 | * @param title title of the task
58 | * @param description description of the task
59 | * @param id id of the task
60 | */
61 | public Task(@Nullable String title, @Nullable String description, @NonNull String id) {
62 | this(title, description, id, false);
63 | }
64 |
65 | /**
66 | * Use this constructor to create a new completed Task.
67 | *
68 | * @param title title of the task
69 | * @param description description of the task
70 | * @param completed true if the task is completed, false if it's active
71 | */
72 | public Task(@Nullable String title, @Nullable String description, boolean completed) {
73 | this(title, description, UUID.randomUUID().toString(), completed);
74 | }
75 |
76 | /**
77 | * Use this constructor to specify a completed Task if the Task already has an id (copy of
78 | * another Task).
79 | *
80 | * @param title title of the task
81 | * @param description description of the task
82 | * @param id id of the task
83 | * @param completed true if the task is completed, false if it's active
84 | */
85 | public Task(@Nullable String title, @Nullable String description,
86 | @NonNull String id, boolean completed) {
87 | mId = id;
88 | mTitle = title;
89 | mDescription = description;
90 | mCompleted = completed;
91 | }
92 |
93 | @NonNull
94 | public String getId() {
95 | return mId;
96 | }
97 |
98 | @Nullable
99 | public String getTitle() {
100 | return mTitle;
101 | }
102 |
103 | @Nullable
104 | public String getTitleForList() {
105 | if (!Strings.isNullOrEmpty(mTitle)) {
106 | return mTitle;
107 | } else {
108 | return mDescription;
109 | }
110 | }
111 |
112 | @Nullable
113 | public String getDescription() {
114 | return mDescription;
115 | }
116 |
117 | public boolean isCompleted() {
118 | return mCompleted;
119 | }
120 |
121 | public boolean isActive() {
122 | return !mCompleted;
123 | }
124 |
125 | public boolean isEmpty() {
126 | return Strings.isNullOrEmpty(mTitle) &&
127 | Strings.isNullOrEmpty(mDescription);
128 | }
129 |
130 | @Override
131 | public boolean equals(Object o) {
132 | if (this == o) return true;
133 | if (o == null || getClass() != o.getClass()) return false;
134 | Task task = (Task) o;
135 | return Objects.equal(mId, task.mId) &&
136 | Objects.equal(mTitle, task.mTitle) &&
137 | Objects.equal(mDescription, task.mDescription);
138 | }
139 |
140 | @Override
141 | public int hashCode() {
142 | return Objects.hashCode(mId, mTitle, mDescription);
143 | }
144 |
145 | @Override
146 | public String toString() {
147 | return "Task with title " + mTitle;
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/app/src/main/java/com/microsoft/sample/data/source/TasksDataSource.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample.data.source;
18 |
19 | import android.support.annotation.NonNull;
20 |
21 | import com.microsoft.sample.data.Task;
22 |
23 | import java.util.List;
24 |
25 | /**
26 | * Main entry point for accessing tasks data.
27 | *
28 | * For simplicity, only getTasks() and getTask() have callbacks. Consider adding callbacks to other
29 | * methods to inform the user of network/database errors or successful operations.
30 | * For example, when a new task is created, it's synchronously stored in cache but usually every
31 | * operation on database or network should be executed in a different thread.
32 | */
33 | public interface TasksDataSource {
34 |
35 | interface LoadTasksCallback {
36 |
37 | void onTasksLoaded(List tasks);
38 |
39 | void onDataNotAvailable();
40 | }
41 |
42 | interface GetTaskCallback {
43 |
44 | void onTaskLoaded(Task task);
45 |
46 | void onDataNotAvailable();
47 | }
48 |
49 | void getTasks(@NonNull LoadTasksCallback callback);
50 |
51 | void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback);
52 |
53 | void saveTask(@NonNull Task task);
54 |
55 | void completeTask(@NonNull Task task);
56 |
57 | void completeTask(@NonNull String taskId);
58 |
59 | void activateTask(@NonNull Task task);
60 |
61 | void activateTask(@NonNull String taskId);
62 |
63 | void clearCompletedTasks();
64 |
65 | void refreshTasks();
66 |
67 | void deleteAllTasks();
68 |
69 | void deleteTask(@NonNull String taskId);
70 | }
71 |
--------------------------------------------------------------------------------
/app/src/main/java/com/microsoft/sample/data/source/local/TasksDbHelper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample.data.source.local;
18 |
19 | import android.content.Context;
20 | import android.database.sqlite.SQLiteDatabase;
21 | import android.database.sqlite.SQLiteOpenHelper;
22 |
23 | public class TasksDbHelper extends SQLiteOpenHelper {
24 | public static final int DATABASE_VERSION = 1;
25 |
26 | public static final String DATABASE_NAME = "Tasks.db";
27 |
28 | private static final String TEXT_TYPE = " TEXT";
29 |
30 | private static final String BOOLEAN_TYPE = " INTEGER";
31 |
32 | private static final String COMMA_SEP = ",";
33 |
34 | private static final String SQL_CREATE_ENTRIES =
35 | "CREATE TABLE " + TasksPersistenceContract.TaskEntry.TABLE_NAME + " (" +
36 | TasksPersistenceContract.TaskEntry.COLUMN_NAME_ENTRY_ID + TEXT_TYPE + " PRIMARY KEY," +
37 | TasksPersistenceContract.TaskEntry.COLUMN_NAME_TITLE + TEXT_TYPE + COMMA_SEP +
38 | TasksPersistenceContract.TaskEntry.COLUMN_NAME_DESCRIPTION + TEXT_TYPE + COMMA_SEP +
39 | TasksPersistenceContract.TaskEntry.COLUMN_NAME_COMPLETED + BOOLEAN_TYPE +
40 | " )";
41 |
42 | public TasksDbHelper(Context context) {
43 | super(context, DATABASE_NAME, null, DATABASE_VERSION);
44 | }
45 |
46 | public void onCreate(SQLiteDatabase db) {
47 | db.execSQL(SQL_CREATE_ENTRIES);
48 | }
49 |
50 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
51 | // Not required as at version 1
52 | }
53 |
54 | public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
55 | // Not required as at version 1
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/app/src/main/java/com/microsoft/sample/data/source/local/TasksPersistenceContract.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample.data.source.local;
18 |
19 | import android.provider.BaseColumns;
20 |
21 | /**
22 | * The contract used for the db to save the tasks locally.
23 | */
24 | public final class TasksPersistenceContract {
25 |
26 | // To prevent someone from accidentally instantiating the contract class,
27 | // give it an empty constructor.
28 | private TasksPersistenceContract() {}
29 |
30 | /* Inner class that defines the table contents */
31 | public static abstract class TaskEntry implements BaseColumns {
32 | public static final String TABLE_NAME = "tasks";
33 | public static final String COLUMN_NAME_ENTRY_ID = "entryid";
34 | public static final String COLUMN_NAME_TITLE = "title";
35 | public static final String COLUMN_NAME_DESCRIPTION = "description";
36 | public static final String COLUMN_NAME_COMPLETED = "completed";
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/java/com/microsoft/sample/data/source/remote/TasksRemoteDataSource.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample.data.source.remote;
18 |
19 | import android.os.Handler;
20 | import android.support.annotation.NonNull;
21 |
22 | import com.microsoft.sample.data.Task;
23 | import com.microsoft.sample.data.source.TasksDataSource;
24 | import com.google.common.collect.Lists;
25 |
26 | import java.util.Iterator;
27 | import java.util.LinkedHashMap;
28 | import java.util.Map;
29 |
30 | /**
31 | * Implementation of the data source that adds a latency simulating network.
32 | */
33 | public class TasksRemoteDataSource implements TasksDataSource {
34 |
35 | private static TasksRemoteDataSource INSTANCE;
36 |
37 | private static final int SERVICE_LATENCY_IN_MILLIS = 5000;
38 |
39 | private final static Map TASKS_SERVICE_DATA;
40 |
41 | static {
42 | TASKS_SERVICE_DATA = new LinkedHashMap<>(2);
43 | addTask("Build tower in Pisa", "Ground looks good, no foundation work required.");
44 | addTask("Finish bridge in Tacoma", "Found awesome girders at half the cost!");
45 | }
46 |
47 | public static TasksRemoteDataSource getInstance() {
48 | if (INSTANCE == null) {
49 | INSTANCE = new TasksRemoteDataSource();
50 | }
51 | return INSTANCE;
52 | }
53 |
54 | // Prevent direct instantiation.
55 | private TasksRemoteDataSource() {}
56 |
57 | private static void addTask(String title, String description) {
58 | Task newTask = new Task(title, description);
59 | TASKS_SERVICE_DATA.put(newTask.getId(), newTask);
60 | }
61 |
62 | /**
63 | * Note: {@link LoadTasksCallback#onDataNotAvailable()} is never fired. In a real remote data
64 | * source implementation, this would be fired if the server can't be contacted or the server
65 | * returns an error.
66 | */
67 | @Override
68 | public void getTasks(final @NonNull LoadTasksCallback callback) {
69 | // Simulate network by delaying the execution.
70 | Handler handler = new Handler();
71 | handler.postDelayed(new Runnable() {
72 | @Override
73 | public void run() {
74 | callback.onTasksLoaded(Lists.newArrayList(TASKS_SERVICE_DATA.values()));
75 | }
76 | }, SERVICE_LATENCY_IN_MILLIS);
77 | }
78 |
79 | /**
80 | * Note: {@link GetTaskCallback#onDataNotAvailable()} is never fired. In a real remote data
81 | * source implementation, this would be fired if the server can't be contacted or the server
82 | * returns an error.
83 | */
84 | @Override
85 | public void getTask(@NonNull String taskId, final @NonNull GetTaskCallback callback) {
86 | final Task task = TASKS_SERVICE_DATA.get(taskId);
87 |
88 | // Simulate network by delaying the execution.
89 | Handler handler = new Handler();
90 | handler.postDelayed(new Runnable() {
91 | @Override
92 | public void run() {
93 | callback.onTaskLoaded(task);
94 | }
95 | }, SERVICE_LATENCY_IN_MILLIS);
96 | }
97 |
98 | @Override
99 | public void saveTask(@NonNull Task task) {
100 | TASKS_SERVICE_DATA.put(task.getId(), task);
101 | }
102 |
103 | @Override
104 | public void completeTask(@NonNull Task task) {
105 | Task completedTask = new Task(task.getTitle(), task.getDescription(), task.getId(), true);
106 | TASKS_SERVICE_DATA.put(task.getId(), completedTask);
107 | }
108 |
109 | @Override
110 | public void completeTask(@NonNull String taskId) {
111 | // Not required for the remote data source because the {@link TasksRepository} handles
112 | // converting from a {@code taskId} to a {@link task} using its cached data.
113 | }
114 |
115 | @Override
116 | public void activateTask(@NonNull Task task) {
117 | Task activeTask = new Task(task.getTitle(), task.getDescription(), task.getId());
118 | TASKS_SERVICE_DATA.put(task.getId(), activeTask);
119 | }
120 |
121 | @Override
122 | public void activateTask(@NonNull String taskId) {
123 | // Not required for the remote data source because the {@link TasksRepository} handles
124 | // converting from a {@code taskId} to a {@link task} using its cached data.
125 | }
126 |
127 | @Override
128 | public void clearCompletedTasks() {
129 | Iterator> it = TASKS_SERVICE_DATA.entrySet().iterator();
130 | while (it.hasNext()) {
131 | Map.Entry entry = it.next();
132 | if (entry.getValue().isCompleted()) {
133 | it.remove();
134 | }
135 | }
136 | }
137 |
138 | @Override
139 | public void refreshTasks() {
140 | // Not required because the {@link TasksRepository} handles the logic of refreshing the
141 | // tasks from all the available data sources.
142 | }
143 |
144 | @Override
145 | public void deleteAllTasks() {
146 | TASKS_SERVICE_DATA.clear();
147 | }
148 |
149 | @Override
150 | public void deleteTask(@NonNull String taskId) {
151 | TASKS_SERVICE_DATA.remove(taskId);
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/app/src/main/java/com/microsoft/sample/statistics/StatisticsActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample.statistics;
18 |
19 | import android.os.Bundle;
20 | import android.support.design.widget.NavigationView;
21 | import android.support.v4.app.NavUtils;
22 | import android.support.v4.view.GravityCompat;
23 | import android.support.v4.widget.DrawerLayout;
24 | import android.support.v7.app.ActionBar;
25 | import android.support.v7.app.AppCompatActivity;
26 | import android.support.v7.widget.Toolbar;
27 | import android.view.MenuItem;
28 |
29 | import com.microsoft.sample.Injection;
30 | import com.microsoft.sample.R;
31 | import com.microsoft.sample.util.ActivityUtils;
32 |
33 | /**
34 | * Show statistics for tasks.
35 | */
36 | public class StatisticsActivity extends AppCompatActivity {
37 |
38 | private DrawerLayout mDrawerLayout;
39 |
40 | @Override
41 | protected void onCreate(Bundle savedInstanceState) {
42 | super.onCreate(savedInstanceState);
43 |
44 | setContentView(R.layout.statistics_act);
45 |
46 | // Set up the toolbar.
47 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
48 | setSupportActionBar(toolbar);
49 | ActionBar ab = getSupportActionBar();
50 | ab.setTitle(R.string.statistics_title);
51 | ab.setHomeAsUpIndicator(R.drawable.ic_menu);
52 | ab.setDisplayHomeAsUpEnabled(true);
53 |
54 | // Set up the navigation drawer.
55 | mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
56 | mDrawerLayout.setStatusBarBackground(R.color.colorPrimaryDark);
57 | NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
58 | if (navigationView != null) {
59 | setupDrawerContent(navigationView);
60 | }
61 |
62 | StatisticsFragment statisticsFragment = (StatisticsFragment) getSupportFragmentManager()
63 | .findFragmentById(R.id.contentFrame);
64 | if (statisticsFragment == null) {
65 | statisticsFragment = StatisticsFragment.newInstance();
66 | ActivityUtils.addFragmentToActivity(getSupportFragmentManager(),
67 | statisticsFragment, R.id.contentFrame);
68 | }
69 |
70 | new StatisticsPresenter(
71 | Injection.provideTasksRepository(getApplicationContext()), statisticsFragment);
72 | }
73 |
74 | @Override
75 | public boolean onOptionsItemSelected(MenuItem item) {
76 | switch (item.getItemId()) {
77 | case android.R.id.home:
78 | // Open the navigation drawer when the home icon is selected from the toolbar.
79 | mDrawerLayout.openDrawer(GravityCompat.START);
80 | return true;
81 | }
82 | return super.onOptionsItemSelected(item);
83 | }
84 |
85 | private void setupDrawerContent(NavigationView navigationView) {
86 | navigationView.setNavigationItemSelectedListener(
87 | new NavigationView.OnNavigationItemSelectedListener() {
88 | @Override
89 | public boolean onNavigationItemSelected(MenuItem menuItem) {
90 | switch (menuItem.getItemId()) {
91 | case R.id.list_navigation_menu_item:
92 | NavUtils.navigateUpFromSameTask(StatisticsActivity.this);
93 | break;
94 | case R.id.statistics_navigation_menu_item:
95 | // Do nothing, we're already on that screen
96 | break;
97 | default:
98 | break;
99 | }
100 | // Close the navigation drawer when an item is selected.
101 | menuItem.setChecked(true);
102 | mDrawerLayout.closeDrawers();
103 | return true;
104 | }
105 | });
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/app/src/main/java/com/microsoft/sample/statistics/StatisticsContract.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample.statistics;
18 |
19 | import com.microsoft.sample.BasePresenter;
20 | import com.microsoft.sample.BaseView;
21 |
22 | /**
23 | * This specifies the contract between the view and the presenter.
24 | */
25 | public interface StatisticsContract {
26 |
27 | interface View extends BaseView {
28 |
29 | void setProgressIndicator(boolean active);
30 |
31 | void showStatistics(int numberOfIncompleteTasks, int numberOfCompletedTasks);
32 |
33 | void showLoadingStatisticsError();
34 |
35 | boolean isActive();
36 | }
37 |
38 | interface Presenter extends BasePresenter {
39 |
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/com/microsoft/sample/statistics/StatisticsFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample.statistics;
18 |
19 | import android.os.Bundle;
20 | import android.support.annotation.NonNull;
21 | import android.support.annotation.Nullable;
22 | import android.support.v4.app.Fragment;
23 | import android.view.LayoutInflater;
24 | import android.view.View;
25 | import android.view.ViewGroup;
26 | import android.widget.TextView;
27 |
28 | import com.microsoft.sample.R;
29 |
30 | import static com.google.common.base.Preconditions.checkNotNull;
31 |
32 | /**
33 | * Main UI for the statistics screen.
34 | */
35 | public class StatisticsFragment extends Fragment implements StatisticsContract.View {
36 |
37 | private TextView mStatisticsTV;
38 |
39 | private StatisticsContract.Presenter mPresenter;
40 |
41 | public static StatisticsFragment newInstance() {
42 | return new StatisticsFragment();
43 | }
44 |
45 | @Override
46 | public void setPresenter(@NonNull StatisticsContract.Presenter presenter) {
47 | mPresenter = checkNotNull(presenter);
48 | }
49 |
50 | @Nullable
51 | @Override
52 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
53 | Bundle savedInstanceState) {
54 | View root = inflater.inflate(R.layout.statistics_frag, container, false);
55 | mStatisticsTV = (TextView) root.findViewById(R.id.statistics);
56 | return root;
57 | }
58 |
59 | @Override
60 | public void onResume() {
61 | super.onResume();
62 | mPresenter.start();
63 | }
64 |
65 | @Override
66 | public void setProgressIndicator(boolean active) {
67 | if (active) {
68 | mStatisticsTV.setText(getString(R.string.loading));
69 | } else {
70 | mStatisticsTV.setText("");
71 | }
72 | }
73 |
74 | @Override
75 | public void showStatistics(int numberOfIncompleteTasks, int numberOfCompletedTasks) {
76 | if (numberOfCompletedTasks == 0 && numberOfIncompleteTasks == 0) {
77 | mStatisticsTV.setText(getResources().getString(R.string.statistics_no_tasks));
78 | } else {
79 | String displayString = getResources().getString(R.string.statistics_active_tasks) + " "
80 | + numberOfIncompleteTasks + "\n" + getResources().getString(
81 | R.string.statistics_completed_tasks) + " " + numberOfCompletedTasks;
82 | mStatisticsTV.setText(displayString);
83 | }
84 | }
85 |
86 | @Override
87 | public void showLoadingStatisticsError() {
88 | mStatisticsTV.setText(getResources().getString(R.string.statistics_error));
89 | }
90 |
91 | @Override
92 | public boolean isActive() {
93 | return isAdded();
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/app/src/main/java/com/microsoft/sample/statistics/StatisticsPresenter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample.statistics;
18 |
19 | import android.support.annotation.NonNull;
20 |
21 | import com.microsoft.sample.data.Task;
22 | import com.microsoft.sample.data.source.TasksDataSource;
23 | import com.microsoft.sample.data.source.TasksRepository;
24 | import com.microsoft.sample.util.EspressoIdlingResource;
25 |
26 | import java.util.List;
27 |
28 | import static com.google.common.base.Preconditions.checkNotNull;
29 |
30 | /**
31 | * Listens to user actions from the UI ({@link StatisticsFragment}), retrieves the data and updates
32 | * the UI as required.
33 | */
34 | public class StatisticsPresenter implements StatisticsContract.Presenter {
35 |
36 | private final TasksRepository mTasksRepository;
37 |
38 | private final StatisticsContract.View mStatisticsView;
39 |
40 | public StatisticsPresenter(@NonNull TasksRepository tasksRepository,
41 | @NonNull StatisticsContract.View statisticsView) {
42 | mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null");
43 | mStatisticsView = checkNotNull(statisticsView, "StatisticsView cannot be null!");
44 |
45 | mStatisticsView.setPresenter(this);
46 | }
47 |
48 | @Override
49 | public void start() {
50 | loadStatistics();
51 | }
52 |
53 | private void loadStatistics() {
54 | mStatisticsView.setProgressIndicator(true);
55 |
56 | // The network request might be handled in a different thread so make sure Espresso knows
57 | // that the app is busy until the response is handled.
58 | EspressoIdlingResource.increment(); // App is busy until further notice
59 |
60 | mTasksRepository.getTasks(new TasksDataSource.LoadTasksCallback() {
61 | @Override
62 | public void onTasksLoaded(List tasks) {
63 | int activeTasks = 0;
64 | int completedTasks = 0;
65 |
66 | // This callback may be called twice, once for the cache and once for loading
67 | // the data from the server API, so we check before decrementing, otherwise
68 | // it throws "Counter has been corrupted!" exception.
69 | if (!EspressoIdlingResource.getIdlingResource().isIdleNow()) {
70 | EspressoIdlingResource.decrement(); // Set app as idle.
71 | }
72 |
73 | // We calculate number of active and completed tasks
74 | for (Task task : tasks) {
75 | if (task.isCompleted()) {
76 | completedTasks += 1;
77 | } else {
78 | activeTasks += 1;
79 | }
80 | }
81 | // The view may not be able to handle UI updates anymore
82 | if (!mStatisticsView.isActive()) {
83 | return;
84 | }
85 | mStatisticsView.setProgressIndicator(false);
86 |
87 | mStatisticsView.showStatistics(activeTasks, completedTasks);
88 | }
89 |
90 | @Override
91 | public void onDataNotAvailable() {
92 | // The view may not be able to handle UI updates anymore
93 | if (!mStatisticsView.isActive()) {
94 | return;
95 | }
96 | mStatisticsView.showLoadingStatisticsError();
97 | }
98 | });
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/app/src/main/java/com/microsoft/sample/taskdetail/TaskDetailActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 The Android Open Source Project
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 com.microsoft.sample.taskdetail;
18 |
19 | import android.os.Bundle;
20 | import android.support.annotation.VisibleForTesting;
21 | import android.support.test.espresso.IdlingResource;
22 | import android.support.v7.app.ActionBar;
23 | import android.support.v7.app.AppCompatActivity;
24 | import android.support.v7.widget.Toolbar;
25 |
26 | import com.microsoft.sample.Injection;
27 | import com.microsoft.sample.R;
28 | import com.microsoft.sample.util.ActivityUtils;
29 | import com.microsoft.sample.util.EspressoIdlingResource;
30 |
31 | /**
32 | * Displays task details screen.
33 | */
34 | public class TaskDetailActivity extends AppCompatActivity {
35 |
36 | public static final String EXTRA_TASK_ID = "TASK_ID";
37 |
38 | @Override
39 | protected void onCreate(Bundle savedInstanceState) {
40 | super.onCreate(savedInstanceState);
41 |
42 | setContentView(R.layout.taskdetail_act);
43 |
44 | // Set up the toolbar.
45 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
46 | setSupportActionBar(toolbar);
47 | ActionBar ab = getSupportActionBar();
48 | ab.setDisplayHomeAsUpEnabled(true);
49 | ab.setDisplayShowHomeEnabled(true);
50 |
51 | // Get the requested task id
52 | String taskId = getIntent().getStringExtra(EXTRA_TASK_ID);
53 |
54 | TaskDetailFragment taskDetailFragment = (TaskDetailFragment) getSupportFragmentManager()
55 | .findFragmentById(R.id.contentFrame);
56 |
57 | if (taskDetailFragment == null) {
58 | taskDetailFragment = TaskDetailFragment.newInstance(taskId);
59 |
60 | ActivityUtils.addFragmentToActivity(getSupportFragmentManager(),
61 | taskDetailFragment, R.id.contentFrame);
62 | }
63 |
64 | // Create the presenter
65 | new TaskDetailPresenter(
66 | taskId,
67 | Injection.provideTasksRepository(getApplicationContext()),
68 | taskDetailFragment);
69 | }
70 |
71 | @Override
72 | public boolean onSupportNavigateUp() {
73 | onBackPressed();
74 | return true;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/app/src/main/java/com/microsoft/sample/taskdetail/TaskDetailContract.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample.taskdetail;
18 |
19 | import com.microsoft.sample.BasePresenter;
20 | import com.microsoft.sample.BaseView;
21 |
22 | /**
23 | * This specifies the contract between the view and the presenter.
24 | */
25 | public interface TaskDetailContract {
26 |
27 | interface View extends BaseView {
28 |
29 | void setLoadingIndicator(boolean active);
30 |
31 | void showMissingTask();
32 |
33 | void hideTitle();
34 |
35 | void showTitle(String title);
36 |
37 | void hideDescription();
38 |
39 | void showDescription(String description);
40 |
41 | void showCompletionStatus(boolean complete);
42 |
43 | void showEditTask(String taskId);
44 |
45 | void showTaskDeleted();
46 |
47 | void showTaskMarkedComplete();
48 |
49 | void showTaskMarkedActive();
50 |
51 | boolean isActive();
52 | }
53 |
54 | interface Presenter extends BasePresenter {
55 |
56 | void editTask();
57 |
58 | void deleteTask();
59 |
60 | void completeTask();
61 |
62 | void activateTask();
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/app/src/main/java/com/microsoft/sample/taskdetail/TaskDetailFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 The Android Open Source Project
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 com.microsoft.sample.taskdetail;
18 |
19 | import android.app.Activity;
20 | import android.content.Intent;
21 | import android.os.Bundle;
22 | import android.support.annotation.NonNull;
23 | import android.support.annotation.Nullable;
24 | import android.support.design.widget.FloatingActionButton;
25 | import android.support.design.widget.Snackbar;
26 | import android.support.v4.app.Fragment;
27 | import android.view.LayoutInflater;
28 | import android.view.Menu;
29 | import android.view.MenuInflater;
30 | import android.view.MenuItem;
31 | import android.view.View;
32 | import android.view.ViewGroup;
33 | import android.widget.CheckBox;
34 | import android.widget.CompoundButton;
35 | import android.widget.TextView;
36 |
37 | import com.microsoft.sample.R;
38 | import com.microsoft.sample.addedittask.AddEditTaskActivity;
39 | import com.microsoft.sample.addedittask.AddEditTaskFragment;
40 | import com.google.common.base.Preconditions;
41 |
42 | import static com.google.common.base.Preconditions.checkNotNull;
43 |
44 | /**
45 | * Main UI for the task detail screen.
46 | */
47 | public class TaskDetailFragment extends Fragment implements TaskDetailContract.View {
48 |
49 | @NonNull
50 | private static final String ARGUMENT_TASK_ID = "TASK_ID";
51 |
52 | @NonNull
53 | private static final int REQUEST_EDIT_TASK = 1;
54 |
55 | private TaskDetailContract.Presenter mPresenter;
56 |
57 | private TextView mDetailTitle;
58 |
59 | private TextView mDetailDescription;
60 |
61 | private CheckBox mDetailCompleteStatus;
62 |
63 | public static TaskDetailFragment newInstance(@Nullable String taskId) {
64 | Bundle arguments = new Bundle();
65 | arguments.putString(ARGUMENT_TASK_ID, taskId);
66 | TaskDetailFragment fragment = new TaskDetailFragment();
67 | fragment.setArguments(arguments);
68 | return fragment;
69 | }
70 |
71 | @Override
72 | public void onResume() {
73 | super.onResume();
74 | mPresenter.start();
75 | }
76 |
77 | @Nullable
78 | @Override
79 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
80 | Bundle savedInstanceState) {
81 | View root = inflater.inflate(R.layout.taskdetail_frag, container, false);
82 | setHasOptionsMenu(true);
83 | mDetailTitle = (TextView) root.findViewById(R.id.task_detail_title);
84 | mDetailDescription = (TextView) root.findViewById(R.id.task_detail_description);
85 | mDetailCompleteStatus = (CheckBox) root.findViewById(R.id.task_detail_complete);
86 |
87 | // Set up floating action button
88 | FloatingActionButton fab =
89 | (FloatingActionButton) getActivity().findViewById(R.id.fab_edit_task);
90 |
91 | fab.setOnClickListener(new View.OnClickListener() {
92 | @Override
93 | public void onClick(View v) {
94 | mPresenter.editTask();
95 | }
96 | });
97 |
98 | return root;
99 | }
100 |
101 | @Override
102 | public void setPresenter(@NonNull TaskDetailContract.Presenter presenter) {
103 | mPresenter = checkNotNull(presenter);
104 | }
105 |
106 | @Override
107 | public boolean onOptionsItemSelected(MenuItem item) {
108 | switch (item.getItemId()) {
109 | case R.id.menu_delete:
110 | mPresenter.deleteTask();
111 | return true;
112 | }
113 | return false;
114 | }
115 |
116 | @Override
117 | public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
118 | inflater.inflate(R.menu.taskdetail_fragment_menu, menu);
119 | }
120 |
121 | @Override
122 | public void setLoadingIndicator(boolean active) {
123 | if (active) {
124 | mDetailTitle.setText("");
125 | mDetailDescription.setText(getString(R.string.loading));
126 | }
127 | }
128 |
129 | @Override
130 | public void hideDescription() {
131 | mDetailDescription.setVisibility(View.GONE);
132 | }
133 |
134 | @Override
135 | public void hideTitle() {
136 | mDetailTitle.setVisibility(View.GONE);
137 | }
138 |
139 | @Override
140 | public void showDescription(@NonNull String description) {
141 | mDetailDescription.setVisibility(View.VISIBLE);
142 | mDetailDescription.setText(description);
143 | }
144 |
145 | @Override
146 | public void showCompletionStatus(final boolean complete) {
147 | Preconditions.checkNotNull(mDetailCompleteStatus);
148 |
149 | mDetailCompleteStatus.setChecked(complete);
150 | mDetailCompleteStatus.setOnCheckedChangeListener(
151 | new CompoundButton.OnCheckedChangeListener() {
152 | @Override
153 | public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
154 | if (isChecked) {
155 | mPresenter.completeTask();
156 | } else {
157 | mPresenter.activateTask();
158 | }
159 | }
160 | });
161 | }
162 |
163 | @Override
164 | public void showEditTask(@NonNull String taskId) {
165 | Intent intent = new Intent(getContext(), AddEditTaskActivity.class);
166 | intent.putExtra(AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID, taskId);
167 | startActivityForResult(intent, REQUEST_EDIT_TASK);
168 | }
169 |
170 | @Override
171 | public void showTaskDeleted() {
172 | getActivity().finish();
173 | }
174 |
175 | public void showTaskMarkedComplete() {
176 | Snackbar.make(getView(), getString(R.string.task_marked_complete), Snackbar.LENGTH_LONG)
177 | .show();
178 | }
179 |
180 | @Override
181 | public void showTaskMarkedActive() {
182 | Snackbar.make(getView(), getString(R.string.task_marked_active), Snackbar.LENGTH_LONG)
183 | .show();
184 | }
185 |
186 | @Override
187 | public void onActivityResult(int requestCode, int resultCode, Intent data) {
188 | if (requestCode == REQUEST_EDIT_TASK) {
189 | // If the task was edited successfully, go back to the list.
190 | if (resultCode == Activity.RESULT_OK) {
191 | getActivity().finish();
192 | }
193 | }
194 | }
195 |
196 | @Override
197 | public void showTitle(@NonNull String title) {
198 | mDetailTitle.setVisibility(View.VISIBLE);
199 | mDetailTitle.setText(title);
200 | }
201 |
202 | @Override
203 | public void showMissingTask() {
204 | mDetailTitle.setText("");
205 | mDetailDescription.setText(getString(R.string.no_data));
206 | }
207 |
208 | @Override
209 | public boolean isActive() {
210 | return isAdded();
211 | }
212 |
213 | }
214 |
--------------------------------------------------------------------------------
/app/src/main/java/com/microsoft/sample/taskdetail/TaskDetailPresenter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample.taskdetail;
18 |
19 | import android.support.annotation.NonNull;
20 | import android.support.annotation.Nullable;
21 |
22 | import com.microsoft.sample.data.Task;
23 | import com.microsoft.sample.data.source.TasksDataSource;
24 | import com.microsoft.sample.data.source.TasksRepository;
25 | import com.google.common.base.Strings;
26 |
27 | import static com.google.common.base.Preconditions.checkNotNull;
28 |
29 | /**
30 | * Listens to user actions from the UI ({@link TaskDetailFragment}), retrieves the data and updates
31 | * the UI as required.
32 | */
33 | public class TaskDetailPresenter implements TaskDetailContract.Presenter {
34 |
35 | private final TasksRepository mTasksRepository;
36 |
37 | private final TaskDetailContract.View mTaskDetailView;
38 |
39 | @Nullable
40 | private String mTaskId;
41 |
42 | public TaskDetailPresenter(@Nullable String taskId,
43 | @NonNull TasksRepository tasksRepository,
44 | @NonNull TaskDetailContract.View taskDetailView) {
45 | mTaskId = taskId;
46 | mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null!");
47 | mTaskDetailView = checkNotNull(taskDetailView, "taskDetailView cannot be null!");
48 |
49 | mTaskDetailView.setPresenter(this);
50 | }
51 |
52 | @Override
53 | public void start() {
54 | openTask();
55 | }
56 |
57 | private void openTask() {
58 | if (Strings.isNullOrEmpty(mTaskId)) {
59 | mTaskDetailView.showMissingTask();
60 | return;
61 | }
62 |
63 | mTaskDetailView.setLoadingIndicator(true);
64 | mTasksRepository.getTask(mTaskId, new TasksDataSource.GetTaskCallback() {
65 | @Override
66 | public void onTaskLoaded(Task task) {
67 | // The view may not be able to handle UI updates anymore
68 | if (!mTaskDetailView.isActive()) {
69 | return;
70 | }
71 | mTaskDetailView.setLoadingIndicator(false);
72 | if (null == task) {
73 | mTaskDetailView.showMissingTask();
74 | } else {
75 | showTask(task);
76 | }
77 | }
78 |
79 | @Override
80 | public void onDataNotAvailable() {
81 | // The view may not be able to handle UI updates anymore
82 | if (!mTaskDetailView.isActive()) {
83 | return;
84 | }
85 | mTaskDetailView.showMissingTask();
86 | }
87 | });
88 | }
89 |
90 | @Override
91 | public void editTask() {
92 | if (Strings.isNullOrEmpty(mTaskId)) {
93 | mTaskDetailView.showMissingTask();
94 | return;
95 | }
96 | mTaskDetailView.showEditTask(mTaskId);
97 | }
98 |
99 | @Override
100 | public void deleteTask() {
101 | if (Strings.isNullOrEmpty(mTaskId)) {
102 | mTaskDetailView.showMissingTask();
103 | return;
104 | }
105 | mTasksRepository.deleteTask(mTaskId);
106 | mTaskDetailView.showTaskDeleted();
107 | }
108 |
109 | @Override
110 | public void completeTask() {
111 | if (Strings.isNullOrEmpty(mTaskId)) {
112 | mTaskDetailView.showMissingTask();
113 | return;
114 | }
115 | mTasksRepository.completeTask(mTaskId);
116 | mTaskDetailView.showTaskMarkedComplete();
117 | }
118 |
119 | @Override
120 | public void activateTask() {
121 | if (Strings.isNullOrEmpty(mTaskId)) {
122 | mTaskDetailView.showMissingTask();
123 | return;
124 | }
125 | mTasksRepository.activateTask(mTaskId);
126 | mTaskDetailView.showTaskMarkedActive();
127 | }
128 |
129 | private void showTask(@NonNull Task task) {
130 | String title = task.getTitle();
131 | String description = task.getDescription();
132 |
133 | if (Strings.isNullOrEmpty(title)) {
134 | mTaskDetailView.hideTitle();
135 | } else {
136 | mTaskDetailView.showTitle(title);
137 | }
138 |
139 | if (Strings.isNullOrEmpty(description)) {
140 | mTaskDetailView.hideDescription();
141 | } else {
142 | mTaskDetailView.showDescription(description);
143 | }
144 | mTaskDetailView.showCompletionStatus(task.isCompleted());
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/app/src/main/java/com/microsoft/sample/tasks/ScrollChildSwipeRefreshLayout.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample.tasks;
18 |
19 | import android.content.Context;
20 | import android.support.v4.view.ViewCompat;
21 | import android.support.v4.widget.SwipeRefreshLayout;
22 | import android.util.AttributeSet;
23 | import android.view.View;
24 |
25 | /**
26 | * Extends {@link SwipeRefreshLayout} to support non-direct descendant scrolling views.
27 | *
28 | * {@link SwipeRefreshLayout} works as expected when a scroll view is a direct child: it triggers
29 | * the refresh only when the view is on top. This class adds a way (@link #setScrollUpChild} to
30 | * define which view controls this behavior.
31 | */
32 | public class ScrollChildSwipeRefreshLayout extends SwipeRefreshLayout {
33 |
34 | private View mScrollUpChild;
35 |
36 | public ScrollChildSwipeRefreshLayout(Context context) {
37 | super(context);
38 | }
39 |
40 | public ScrollChildSwipeRefreshLayout(Context context, AttributeSet attrs) {
41 | super(context, attrs);
42 | }
43 |
44 | @Override
45 | public boolean canChildScrollUp() {
46 | if (mScrollUpChild != null) {
47 | return ViewCompat.canScrollVertically(mScrollUpChild, -1);
48 | }
49 | return super.canChildScrollUp();
50 | }
51 |
52 | public void setScrollUpChild(View view) {
53 | mScrollUpChild = view;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/com/microsoft/sample/tasks/TasksActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 The Android Open Source Project
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 com.microsoft.sample.tasks;
18 |
19 | import android.content.Intent;
20 | import android.os.Bundle;
21 | import android.support.annotation.VisibleForTesting;
22 | import android.support.design.widget.NavigationView;
23 | import android.support.test.espresso.IdlingResource;
24 | import android.support.v4.view.GravityCompat;
25 | import android.support.v4.widget.DrawerLayout;
26 | import android.support.v7.app.ActionBar;
27 | import android.support.v7.app.AppCompatActivity;
28 | import android.support.v7.widget.Toolbar;
29 | import android.view.MenuItem;
30 |
31 | import com.microsoft.sample.Injection;
32 | import com.microsoft.sample.R;
33 | import com.microsoft.sample.statistics.StatisticsActivity;
34 | import com.microsoft.sample.util.ActivityUtils;
35 | import com.microsoft.sample.util.EspressoIdlingResource;
36 |
37 | public class TasksActivity extends AppCompatActivity {
38 |
39 | private static final String CURRENT_FILTERING_KEY = "CURRENT_FILTERING_KEY";
40 |
41 | private DrawerLayout mDrawerLayout;
42 |
43 | private TasksPresenter mTasksPresenter;
44 |
45 | @Override
46 | protected void onCreate(Bundle savedInstanceState) {
47 | super.onCreate(savedInstanceState);
48 | setContentView(R.layout.tasks_act);
49 |
50 | // Set up the toolbar.
51 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
52 | setSupportActionBar(toolbar);
53 | ActionBar ab = getSupportActionBar();
54 | ab.setHomeAsUpIndicator(R.drawable.ic_menu);
55 | ab.setDisplayHomeAsUpEnabled(true);
56 |
57 | // Set up the navigation drawer.
58 | mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
59 | mDrawerLayout.setStatusBarBackground(R.color.colorPrimaryDark);
60 | NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
61 | if (navigationView != null) {
62 | setupDrawerContent(navigationView);
63 | }
64 |
65 | TasksFragment tasksFragment =
66 | (TasksFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
67 | if (tasksFragment == null) {
68 | // Create the fragment
69 | tasksFragment = TasksFragment.newInstance();
70 | ActivityUtils.addFragmentToActivity(
71 | getSupportFragmentManager(), tasksFragment, R.id.contentFrame);
72 | }
73 |
74 | // Create the presenter
75 | mTasksPresenter = new TasksPresenter(
76 | Injection.provideTasksRepository(getApplicationContext()), tasksFragment);
77 |
78 | // Load previously saved state, if available.
79 | if (savedInstanceState != null) {
80 | TasksFilterType currentFiltering =
81 | (TasksFilterType) savedInstanceState.getSerializable(CURRENT_FILTERING_KEY);
82 | mTasksPresenter.setFiltering(currentFiltering);
83 | }
84 | }
85 |
86 | @Override
87 | public void onSaveInstanceState(Bundle outState) {
88 | outState.putSerializable(CURRENT_FILTERING_KEY, mTasksPresenter.getFiltering());
89 |
90 | super.onSaveInstanceState(outState);
91 | }
92 |
93 | @Override
94 | public boolean onOptionsItemSelected(MenuItem item) {
95 | switch (item.getItemId()) {
96 | case android.R.id.home:
97 | // Open the navigation drawer when the home icon is selected from the toolbar.
98 | mDrawerLayout.openDrawer(GravityCompat.START);
99 | return true;
100 | }
101 | return super.onOptionsItemSelected(item);
102 | }
103 |
104 | private void setupDrawerContent(NavigationView navigationView) {
105 | navigationView.setNavigationItemSelectedListener(
106 | new NavigationView.OnNavigationItemSelectedListener() {
107 | @Override
108 | public boolean onNavigationItemSelected(MenuItem menuItem) {
109 | switch (menuItem.getItemId()) {
110 | case R.id.list_navigation_menu_item:
111 | // Do nothing, we're already on that screen
112 | break;
113 | case R.id.statistics_navigation_menu_item:
114 | Intent intent =
115 | new Intent(TasksActivity.this, StatisticsActivity.class);
116 | startActivity(intent);
117 | break;
118 | default:
119 | break;
120 | }
121 | // Close the navigation drawer when an item is selected.
122 | menuItem.setChecked(true);
123 | mDrawerLayout.closeDrawers();
124 | return true;
125 | }
126 | });
127 | }
128 |
129 | @VisibleForTesting
130 | public IdlingResource getCountingIdlingResource() {
131 | return EspressoIdlingResource.getIdlingResource();
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/app/src/main/java/com/microsoft/sample/tasks/TasksContract.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample.tasks;
18 |
19 | import android.support.annotation.NonNull;
20 |
21 | import com.microsoft.sample.BaseView;
22 | import com.microsoft.sample.data.Task;
23 | import com.microsoft.sample.BasePresenter;
24 |
25 | import java.util.List;
26 |
27 | /**
28 | * This specifies the contract between the view and the presenter.
29 | */
30 | public interface TasksContract {
31 |
32 | interface View extends BaseView {
33 |
34 | void setLoadingIndicator(boolean active);
35 |
36 | void showTasks(List tasks);
37 |
38 | void showAddTask();
39 |
40 | void showTaskDetailsUi(String taskId);
41 |
42 | void showTaskMarkedComplete();
43 |
44 | void showTaskMarkedActive();
45 |
46 | void showCompletedTasksCleared();
47 |
48 | void showLoadingTasksError();
49 |
50 | void showNoTasks();
51 |
52 | void showActiveFilterLabel();
53 |
54 | void showCompletedFilterLabel();
55 |
56 | void showAllFilterLabel();
57 |
58 | void showNoActiveTasks();
59 |
60 | void showNoCompletedTasks();
61 |
62 | void showSuccessfullySavedMessage();
63 |
64 | boolean isActive();
65 |
66 | void showFilteringPopUpMenu();
67 | }
68 |
69 | interface Presenter extends BasePresenter {
70 |
71 | void result(int requestCode, int resultCode);
72 |
73 | void loadTasks(boolean forceUpdate);
74 |
75 | void addNewTask();
76 |
77 | void openTaskDetails(@NonNull Task requestedTask);
78 |
79 | void completeTask(@NonNull Task completedTask);
80 |
81 | void activateTask(@NonNull Task activeTask);
82 |
83 | void clearCompletedTasks();
84 |
85 | void setFiltering(TasksFilterType requestType);
86 |
87 | TasksFilterType getFiltering();
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/app/src/main/java/com/microsoft/sample/tasks/TasksFilterType.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample.tasks;
18 |
19 | /**
20 | * Used with the filter spinner in the tasks list.
21 | */
22 | public enum TasksFilterType {
23 | /**
24 | * Do not filter tasks.
25 | */
26 | ALL_TASKS,
27 |
28 | /**
29 | * Filters only the active (not completed yet) tasks.
30 | */
31 | ACTIVE_TASKS,
32 |
33 | /**
34 | * Filters only the completed tasks.
35 | */
36 | COMPLETED_TASKS
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/java/com/microsoft/sample/util/ActivityUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample.util;
18 |
19 | import android.support.annotation.NonNull;
20 | import android.support.v4.app.Fragment;
21 | import android.support.v4.app.FragmentManager;
22 | import android.support.v4.app.FragmentTransaction;
23 |
24 | import static com.google.common.base.Preconditions.checkNotNull;
25 |
26 | /**
27 | * This provides methods to help Activities load their UI.
28 | */
29 | public class ActivityUtils {
30 |
31 | /**
32 | * The {@code fragment} is added to the container view with id {@code frameId}. The operation is
33 | * performed by the {@code fragmentManager}.
34 | *
35 | */
36 | public static void addFragmentToActivity (@NonNull FragmentManager fragmentManager,
37 | @NonNull Fragment fragment, int frameId) {
38 | checkNotNull(fragmentManager);
39 | checkNotNull(fragment);
40 | FragmentTransaction transaction = fragmentManager.beginTransaction();
41 | transaction.add(frameId, fragment);
42 | transaction.commit();
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/java/com/microsoft/sample/util/EspressoIdlingResource.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample.util;
18 |
19 | import android.support.test.espresso.IdlingResource;
20 |
21 | /**
22 | * Contains a static reference to {@link IdlingResource}, only available in the 'mock' build type.
23 | */
24 | public class EspressoIdlingResource {
25 |
26 | private static final String RESOURCE = "GLOBAL";
27 |
28 | private static SimpleCountingIdlingResource mCountingIdlingResource =
29 | new SimpleCountingIdlingResource(RESOURCE);
30 |
31 | public static void increment() {
32 | mCountingIdlingResource.increment();
33 | }
34 |
35 | public static void decrement() {
36 | mCountingIdlingResource.decrement();
37 | }
38 |
39 | public static IdlingResource getIdlingResource() {
40 | return mCountingIdlingResource;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/microsoft/sample/util/SimpleCountingIdlingResource.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 The Android Open Source Project
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 com.microsoft.sample.util;
18 |
19 | import android.support.test.espresso.IdlingResource;
20 |
21 | import java.util.concurrent.atomic.AtomicInteger;
22 |
23 | import static com.google.common.base.Preconditions.checkNotNull;
24 |
25 | /**
26 | * An simple counter implementation of {@link IdlingResource} that determines idleness by
27 | * maintaining an internal counter. When the counter is 0 - it is considered to be idle, when it is
28 | * non-zero it is not idle. This is very similar to the way a {@link java.util.concurrent.Semaphore}
29 | * behaves.
30 | *
31 | * This class can then be used to wrap up operations that while in progress should block tests from
32 | * accessing the UI.
33 | */
34 | public final class SimpleCountingIdlingResource implements IdlingResource {
35 |
36 | private final String mResourceName;
37 |
38 | private final AtomicInteger counter = new AtomicInteger(0);
39 |
40 | // written from main thread, read from any thread.
41 | private volatile ResourceCallback resourceCallback;
42 |
43 | /**
44 | * Creates a SimpleCountingIdlingResource
45 | *
46 | * @param resourceName the resource name this resource should report to Espresso.
47 | */
48 | public SimpleCountingIdlingResource(String resourceName) {
49 | mResourceName = checkNotNull(resourceName);
50 | }
51 |
52 | @Override
53 | public String getName() {
54 | return mResourceName;
55 | }
56 |
57 | @Override
58 | public boolean isIdleNow() {
59 | return counter.get() == 0;
60 | }
61 |
62 | @Override
63 | public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
64 | this.resourceCallback = resourceCallback;
65 | }
66 |
67 | /**
68 | * Increments the count of in-flight transactions to the resource being monitored.
69 | */
70 | public void increment() {
71 | counter.getAndIncrement();
72 | }
73 |
74 | /**
75 | * Decrements the count of in-flight transactions to the resource being monitored.
76 | *
77 | * If this operation results in the counter falling below 0 - an exception is raised.
78 | *
79 | * @throws IllegalStateException if the counter is below 0.
80 | */
81 | public void decrement() {
82 | int counterVal = counter.decrementAndGet();
83 | if (counterVal == 0) {
84 | // we've gone from non-zero to zero. That means we're idle now! Tell espresso.
85 | if (null != resourceCallback) {
86 | resourceCallback.onTransitionToIdle();
87 | }
88 | }
89 |
90 | if (counterVal < 0) {
91 | throw new IllegalArgumentException("Counter has been corrupted!");
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrosoftDocs/pipelines-android/bdbe042616ee133eca8c3a02a5378b66fabbf5fd/app/src/main/res/drawable-hdpi/logo.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrosoftDocs/pipelines-android/bdbe042616ee133eca8c3a02a5378b66fabbf5fd/app/src/main/res/drawable-mdpi/logo.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrosoftDocs/pipelines-android/bdbe042616ee133eca8c3a02a5378b66fabbf5fd/app/src/main/res/drawable-xhdpi/logo.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrosoftDocs/pipelines-android/bdbe042616ee133eca8c3a02a5378b66fabbf5fd/app/src/main/res/drawable-xxhdpi/logo.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrosoftDocs/pipelines-android/bdbe042616ee133eca8c3a02a5378b66fabbf5fd/app/src/main/res/drawable-xxxhdpi/logo.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_add.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_assignment_turned_in_24dp.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_check_circle_24dp.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_done.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_edit.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
22 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_filter_list.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_list.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_menu.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_statistics.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_statistics_100dp.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_statistics_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_verified_user_24dp.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/list_completed_touch_feedback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/touch_feedback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/addtask_act.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
24 |
25 |
29 |
30 |
33 |
34 |
42 |
43 |
44 |
48 |
49 |
53 |
54 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/addtask_frag.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
20 |
21 |
29 |
30 |
37 |
38 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/nav_header.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
25 |
26 |
31 |
32 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/statistics_act.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
25 |
26 |
30 |
31 |
34 |
35 |
43 |
44 |
45 |
49 |
50 |
51 |
52 |
53 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/statistics_frag.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
25 |
26 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/task_item.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
26 |
27 |
32 |
33 |
40 |
41 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/taskdetail_act.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
24 |
25 |
28 |
29 |
37 |
38 |
39 |
43 |
44 |
48 |
49 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/taskdetail_frag.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
25 |
26 |
32 |
33 |
39 |
40 |
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/tasks_act.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
25 |
26 |
30 |
31 |
34 |
35 |
43 |
44 |
45 |
49 |
50 |
54 |
55 |
64 |
65 |
66 |
67 |
68 |
69 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/tasks_frag.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
21 |
22 |
28 |
29 |
34 |
35 |
45 |
46 |
50 |
51 |
52 |
58 |
59 |
65 |
66 |
73 |
74 |
82 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/drawer_actions.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/filter_tasks.xml:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/taskdetail_fragment_menu.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/tasks_fragment_menu.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrosoftDocs/pipelines-android/bdbe042616ee133eca8c3a02a5378b66fabbf5fd/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrosoftDocs/pipelines-android/bdbe042616ee133eca8c3a02a5378b66fabbf5fd/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrosoftDocs/pipelines-android/bdbe042616ee133eca8c3a02a5378b66fabbf5fd/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrosoftDocs/pipelines-android/bdbe042616ee133eca8c3a02a5378b66fabbf5fd/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrosoftDocs/pipelines-android/bdbe042616ee133eca8c3a02a5378b66fabbf5fd/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
21 | 64dp
22 |
23 | 24dp
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 | #455A64
19 | #263238
20 | #D50000
21 |
22 | #CCCCCC
23 |
24 | #CFD8DC
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 | 16dp
20 | 16dp
21 |
22 | 8dp
23 |
24 | 16dp
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 | TO-DO-MVP
19 | New TO-DO
20 | Edit TO-DO
21 | Task marked complete
22 | Task marked active
23 | Error while loading tasks
24 | Completed tasks cleared
25 | Filter
26 | Clear completed
27 | Delete task
28 | TO-DOs
29 | Title
30 | Enter your TO-DO here.
31 | TO DOs cannot be empty
32 | TO-DO saved
33 | TO-DO List
34 | Statistics
35 | You have no tasks.
36 | Active tasks:
37 | Completed tasks:
38 | Error loading statistics.
39 | No data
40 | LOADING
41 |
42 |
43 | - @string/nav_all
44 | - @string/nav_active
45 | - @string/nav_completed
46 |
47 | All
48 | Active
49 | Completed
50 | All TO-DOs
51 | Active TO-DOs
52 | Completed TO-DOs
53 | You have no TO-DOs!
54 | You have no active TO-DOs!
55 | You have no completed TO-DOs!
56 | Add a TO-DO item +
57 | Refresh
58 |
59 |
60 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/mock/java/com/microsoft/sample/Injection.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 The Android Open Source Project
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 com.microsoft.sample;
18 |
19 | import android.content.Context;
20 | import android.support.annotation.NonNull;
21 |
22 | import com.microsoft.sample.data.FakeTasksRemoteDataSource;
23 | import com.microsoft.sample.data.source.TasksDataSource;
24 | import com.microsoft.sample.data.source.TasksRepository;
25 | import com.microsoft.sample.data.source.local.TasksLocalDataSource;
26 |
27 | import static com.google.common.base.Preconditions.checkNotNull;
28 |
29 | /**
30 | * Enables injection of mock implementations for
31 | * {@link TasksDataSource} at compile time. This is useful for testing, since it allows us to use
32 | * a fake instance of the class to isolate the dependencies and run a test hermetically.
33 | */
34 | public class Injection {
35 |
36 | public static TasksRepository provideTasksRepository(@NonNull Context context) {
37 | checkNotNull(context);
38 | return TasksRepository.getInstance(FakeTasksRemoteDataSource.getInstance(),
39 | TasksLocalDataSource.getInstance(context));
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/mock/java/com/microsoft/sample/data/FakeTasksRemoteDataSource.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample.data;
18 |
19 | import android.support.annotation.NonNull;
20 | import android.support.annotation.VisibleForTesting;
21 |
22 | import com.microsoft.sample.data.source.TasksDataSource;
23 | import com.google.common.collect.Lists;
24 |
25 | import java.util.Iterator;
26 | import java.util.LinkedHashMap;
27 | import java.util.Map;
28 |
29 | /**
30 | * Implementation of a remote data source with static access to the data for easy testing.
31 | */
32 | public class FakeTasksRemoteDataSource implements TasksDataSource {
33 |
34 | private static FakeTasksRemoteDataSource INSTANCE;
35 |
36 | private static final Map TASKS_SERVICE_DATA = new LinkedHashMap<>();
37 |
38 | // Prevent direct instantiation.
39 | private FakeTasksRemoteDataSource() {}
40 |
41 | public static FakeTasksRemoteDataSource getInstance() {
42 | if (INSTANCE == null) {
43 | INSTANCE = new FakeTasksRemoteDataSource();
44 | }
45 | return INSTANCE;
46 | }
47 |
48 | @Override
49 | public void getTasks(@NonNull LoadTasksCallback callback) {
50 | callback.onTasksLoaded(Lists.newArrayList(TASKS_SERVICE_DATA.values()));
51 | }
52 |
53 | @Override
54 | public void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback) {
55 | Task task = TASKS_SERVICE_DATA.get(taskId);
56 | callback.onTaskLoaded(task);
57 | }
58 |
59 | @Override
60 | public void saveTask(@NonNull Task task) {
61 | TASKS_SERVICE_DATA.put(task.getId(), task);
62 | }
63 |
64 | @Override
65 | public void completeTask(@NonNull Task task) {
66 | Task completedTask = new Task(task.getTitle(), task.getDescription(), task.getId(), true);
67 | TASKS_SERVICE_DATA.put(task.getId(), completedTask);
68 | }
69 |
70 | @Override
71 | public void completeTask(@NonNull String taskId) {
72 | // Not required for the remote data source.
73 | }
74 |
75 | @Override
76 | public void activateTask(@NonNull Task task) {
77 | Task activeTask = new Task(task.getTitle(), task.getDescription(), task.getId());
78 | TASKS_SERVICE_DATA.put(task.getId(), activeTask);
79 | }
80 |
81 | @Override
82 | public void activateTask(@NonNull String taskId) {
83 | // Not required for the remote data source.
84 | }
85 |
86 | @Override
87 | public void clearCompletedTasks() {
88 | Iterator> it = TASKS_SERVICE_DATA.entrySet().iterator();
89 | while (it.hasNext()) {
90 | Map.Entry entry = it.next();
91 | if (entry.getValue().isCompleted()) {
92 | it.remove();
93 | }
94 | }
95 | }
96 |
97 | public void refreshTasks() {
98 | // Not required because the {@link TasksRepository} handles the logic of refreshing the
99 | // tasks from all the available data sources.
100 | }
101 |
102 | @Override
103 | public void deleteTask(@NonNull String taskId) {
104 | TASKS_SERVICE_DATA.remove(taskId);
105 | }
106 |
107 | @Override
108 | public void deleteAllTasks() {
109 | TASKS_SERVICE_DATA.clear();
110 | }
111 |
112 | @VisibleForTesting
113 | public void addTasks(Task... tasks) {
114 | for (Task task : tasks) {
115 | TASKS_SERVICE_DATA.put(task.getId(), task);
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/app/src/prod/java/com/microsoft/sample/Injection.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 The Android Open Source Project
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 com.microsoft.sample;
18 |
19 | import android.content.Context;
20 | import android.support.annotation.NonNull;
21 |
22 | import com.microsoft.sample.data.source.local.TasksLocalDataSource;
23 | import com.microsoft.sample.data.source.TasksDataSource;
24 | import com.microsoft.sample.data.source.remote.TasksRemoteDataSource;
25 | import com.microsoft.sample.data.source.TasksRepository;
26 |
27 | import static com.google.common.base.Preconditions.checkNotNull;
28 |
29 | /**
30 | * Enables injection of production implementations for
31 | * {@link TasksDataSource} at compile time.
32 | */
33 | public class Injection {
34 |
35 | public static TasksRepository provideTasksRepository(@NonNull Context context) {
36 | checkNotNull(context);
37 | return TasksRepository.getInstance(TasksRemoteDataSource.getInstance(),
38 | TasksLocalDataSource.getInstance(context));
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/test/java/com/microsoft/sample/addedittask/AddEditTaskPresenterTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample.addedittask;
18 |
19 | import com.microsoft.sample.data.Task;
20 | import com.microsoft.sample.data.source.TasksDataSource;
21 | import com.microsoft.sample.data.source.TasksRepository;
22 |
23 | import org.junit.Before;
24 | import org.junit.Test;
25 | import org.mockito.ArgumentCaptor;
26 | import org.mockito.Captor;
27 | import org.mockito.Mock;
28 | import org.mockito.MockitoAnnotations;
29 |
30 | import static org.hamcrest.CoreMatchers.is;
31 | import static org.junit.Assert.assertThat;
32 | import static org.mockito.Matchers.any;
33 | import static org.mockito.Matchers.eq;
34 | import static org.mockito.Mockito.verify;
35 | import static org.mockito.Mockito.when;
36 |
37 | /**
38 | * Unit tests for the implementation of {@link AddEditTaskPresenter}.
39 | */
40 | public class AddEditTaskPresenterTest {
41 |
42 | @Mock
43 | private TasksRepository mTasksRepository;
44 |
45 | @Mock
46 | private AddEditTaskContract.View mAddEditTaskView;
47 |
48 | /**
49 | * {@link ArgumentCaptor} is a powerful Mockito API to capture argument values and use them to
50 | * perform further actions or assertions on them.
51 | */
52 | @Captor
53 | private ArgumentCaptor mGetTaskCallbackCaptor;
54 |
55 | private AddEditTaskPresenter mAddEditTaskPresenter;
56 |
57 | @Before
58 | public void setupMocksAndView() {
59 | // Mockito has a very convenient way to inject mocks by using the @Mock annotation. To
60 | // inject the mocks in the test the initMocks method needs to be called.
61 | MockitoAnnotations.initMocks(this);
62 |
63 | // The presenter wont't update the view unless it's active.
64 | when(mAddEditTaskView.isActive()).thenReturn(true);
65 | }
66 |
67 | @Test
68 | public void createPresenter_setsThePresenterToView(){
69 | // Get a reference to the class under test
70 | mAddEditTaskPresenter = new AddEditTaskPresenter(
71 | null, mTasksRepository, mAddEditTaskView, true);
72 |
73 | // Then the presenter is set to the view
74 | verify(mAddEditTaskView).setPresenter(mAddEditTaskPresenter);
75 | }
76 |
77 | @Test
78 | public void saveNewTaskToRepository_showsSuccessMessageUi() {
79 | // Get a reference to the class under test
80 | mAddEditTaskPresenter = new AddEditTaskPresenter(
81 | null, mTasksRepository, mAddEditTaskView, true);
82 |
83 | // When the presenter is asked to save a task
84 | mAddEditTaskPresenter.saveTask("New Task Title", "Some Task Description");
85 |
86 | // Then a task is saved in the repository and the view updated
87 | verify(mTasksRepository).saveTask(any(Task.class)); // saved to the model
88 | verify(mAddEditTaskView).showTasksList(); // shown in the UI
89 | }
90 |
91 | @Test
92 | public void saveTask_emptyTaskShowsErrorUi() {
93 | // Get a reference to the class under test
94 | mAddEditTaskPresenter = new AddEditTaskPresenter(
95 | null, mTasksRepository, mAddEditTaskView, true);
96 |
97 | // When the presenter is asked to save an empty task
98 | mAddEditTaskPresenter.saveTask("", "");
99 |
100 | // Then an empty not error is shown in the UI
101 | verify(mAddEditTaskView).showEmptyTaskError();
102 | }
103 |
104 | @Test
105 | public void saveExistingTaskToRepository_showsSuccessMessageUi() {
106 | // Get a reference to the class under test
107 | mAddEditTaskPresenter = new AddEditTaskPresenter(
108 | "1", mTasksRepository, mAddEditTaskView, true);
109 |
110 | // When the presenter is asked to save an existing task
111 | mAddEditTaskPresenter.saveTask("Existing Task Title", "Some Task Description");
112 |
113 | // Then a task is saved in the repository and the view updated
114 | verify(mTasksRepository).saveTask(any(Task.class)); // saved to the model
115 | verify(mAddEditTaskView).showTasksList(); // shown in the UI
116 | }
117 |
118 | @Test
119 | public void populateTask_callsRepoAndUpdatesView() {
120 | Task testTask = new Task("TITLE", "DESCRIPTION");
121 | // Get a reference to the class under test
122 | mAddEditTaskPresenter = new AddEditTaskPresenter(testTask.getId(),
123 | mTasksRepository, mAddEditTaskView, true);
124 |
125 | // When the presenter is asked to populate an existing task
126 | mAddEditTaskPresenter.populateTask();
127 |
128 | // Then the task repository is queried and the view updated
129 | verify(mTasksRepository).getTask(eq(testTask.getId()), mGetTaskCallbackCaptor.capture());
130 | assertThat(mAddEditTaskPresenter.isDataMissing(), is(true));
131 |
132 | // Simulate callback
133 | mGetTaskCallbackCaptor.getValue().onTaskLoaded(testTask);
134 |
135 | verify(mAddEditTaskView).setTitle(testTask.getTitle());
136 | verify(mAddEditTaskView).setDescription(testTask.getDescription());
137 | assertThat(mAddEditTaskPresenter.isDataMissing(), is(false));
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/app/src/test/java/com/microsoft/sample/statistics/StatisticsPresenterTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample.statistics;
18 |
19 | import com.microsoft.sample.data.Task;
20 | import com.microsoft.sample.data.source.TasksDataSource;
21 | import com.microsoft.sample.data.source.TasksRepository;
22 | import com.google.common.collect.Lists;
23 |
24 | import org.junit.Before;
25 | import org.junit.Test;
26 | import org.mockito.ArgumentCaptor;
27 | import org.mockito.Captor;
28 | import org.mockito.Mock;
29 | import org.mockito.MockitoAnnotations;
30 |
31 | import java.util.List;
32 |
33 | import static org.mockito.Mockito.verify;
34 | import static org.mockito.Mockito.when;
35 |
36 | /**
37 | * Unit tests for the implementation of {@link StatisticsPresenter}
38 | */
39 | public class StatisticsPresenterTest {
40 |
41 | private static List TASKS;
42 |
43 | @Mock
44 | private TasksRepository mTasksRepository;
45 |
46 | @Mock
47 | private StatisticsContract.View mStatisticsView;
48 |
49 | /**
50 | * {@link ArgumentCaptor} is a powerful Mockito API to capture argument values and use them to
51 | * perform further actions or assertions on them.
52 | */
53 | @Captor
54 | private ArgumentCaptor mLoadTasksCallbackCaptor;
55 |
56 |
57 | private StatisticsPresenter mStatisticsPresenter;
58 |
59 | @Before
60 | public void setupStatisticsPresenter() {
61 | // Mockito has a very convenient way to inject mocks by using the @Mock annotation. To
62 | // inject the mocks in the test the initMocks method needs to be called.
63 | MockitoAnnotations.initMocks(this);
64 |
65 | // Get a reference to the class under test
66 | mStatisticsPresenter = new StatisticsPresenter(mTasksRepository, mStatisticsView);
67 |
68 | // The presenter won't update the view unless it's active.
69 | when(mStatisticsView.isActive()).thenReturn(true);
70 |
71 | // We start the tasks to 3, with one active and two completed
72 | TASKS = Lists.newArrayList(new Task("Title1", "Description1"),
73 | new Task("Title2", "Description2", true), new Task("Title3", "Description3", true));
74 | }
75 |
76 | @Test
77 | public void createPresenter_setsThePresenterToView() {
78 | // Get a reference to the class under test
79 | mStatisticsPresenter = new StatisticsPresenter(mTasksRepository, mStatisticsView);
80 |
81 | // Then the presenter is set to the view
82 | verify(mStatisticsView).setPresenter(mStatisticsPresenter);
83 | }
84 |
85 | @Test
86 | public void loadEmptyTasksFromRepository_CallViewToDisplay() {
87 | // Given an initialized StatisticsPresenter with no tasks
88 | TASKS.clear();
89 |
90 | // When loading of Tasks is requested
91 | mStatisticsPresenter.start();
92 |
93 | //Then progress indicator is shown
94 | verify(mStatisticsView).setProgressIndicator(true);
95 |
96 | // Callback is captured and invoked with stubbed tasks
97 | verify(mTasksRepository).getTasks(mLoadTasksCallbackCaptor.capture());
98 | mLoadTasksCallbackCaptor.getValue().onTasksLoaded(TASKS);
99 |
100 | // Then progress indicator is hidden and correct data is passed on to the view
101 | verify(mStatisticsView).setProgressIndicator(false);
102 | verify(mStatisticsView).showStatistics(0, 0);
103 | }
104 |
105 | @Test
106 | public void loadNonEmptyTasksFromRepository_CallViewToDisplay() {
107 | // Given an initialized StatisticsPresenter with 1 active and 2 completed tasks
108 |
109 | // When loading of Tasks is requested
110 | mStatisticsPresenter.start();
111 |
112 | //Then progress indicator is shown
113 | verify(mStatisticsView).setProgressIndicator(true);
114 |
115 | // Callback is captured and invoked with stubbed tasks
116 | verify(mTasksRepository).getTasks(mLoadTasksCallbackCaptor.capture());
117 | mLoadTasksCallbackCaptor.getValue().onTasksLoaded(TASKS);
118 |
119 | // Then progress indicator is hidden and correct data is passed on to the view
120 | verify(mStatisticsView).setProgressIndicator(false);
121 | verify(mStatisticsView).showStatistics(1, 2);
122 | }
123 |
124 | @Test
125 | public void loadStatisticsWhenTasksAreUnavailable_CallErrorToDisplay() {
126 | // When statistics are loaded
127 | mStatisticsPresenter.start();
128 |
129 | // And tasks data isn't available
130 | verify(mTasksRepository).getTasks(mLoadTasksCallbackCaptor.capture());
131 | mLoadTasksCallbackCaptor.getValue().onDataNotAvailable();
132 |
133 | // Then an error message is shown
134 | verify(mStatisticsView).showLoadingStatisticsError();
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/app/src/test/java/com/microsoft/sample/tasks/TasksPresenterTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2016, The Android Open Source Project
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 com.microsoft.sample.tasks;
18 |
19 | import com.microsoft.sample.data.Task;
20 | import com.microsoft.sample.data.source.TasksDataSource.LoadTasksCallback;
21 | import com.microsoft.sample.data.source.TasksRepository;
22 | import com.google.common.collect.Lists;
23 |
24 | import org.junit.Before;
25 | import org.junit.Test;
26 | import org.mockito.ArgumentCaptor;
27 | import org.mockito.Captor;
28 | import org.mockito.InOrder;
29 | import org.mockito.Mock;
30 | import org.mockito.MockitoAnnotations;
31 |
32 | import java.util.List;
33 |
34 | import static org.junit.Assert.assertTrue;
35 | import static org.mockito.Matchers.any;
36 | import static org.mockito.Mockito.inOrder;
37 | import static org.mockito.Mockito.verify;
38 | import static org.mockito.Mockito.when;
39 |
40 | /**
41 | * Unit tests for the implementation of {@link TasksPresenter}
42 | */
43 | public class TasksPresenterTest {
44 |
45 | private static List TASKS;
46 |
47 | @Mock
48 | private TasksRepository mTasksRepository;
49 |
50 | @Mock
51 | private TasksContract.View mTasksView;
52 |
53 | /**
54 | * {@link ArgumentCaptor} is a powerful Mockito API to capture argument values and use them to
55 | * perform further actions or assertions on them.
56 | */
57 | @Captor
58 | private ArgumentCaptor mLoadTasksCallbackCaptor;
59 |
60 | private TasksPresenter mTasksPresenter;
61 |
62 | @Before
63 | public void setupTasksPresenter() {
64 | // Mockito has a very convenient way to inject mocks by using the @Mock annotation. To
65 | // inject the mocks in the test the initMocks method needs to be called.
66 | MockitoAnnotations.initMocks(this);
67 |
68 | // Get a reference to the class under test
69 | mTasksPresenter = new TasksPresenter(mTasksRepository, mTasksView);
70 |
71 | // The presenter won't update the view unless it's active.
72 | when(mTasksView.isActive()).thenReturn(true);
73 |
74 | // We start the tasks to 3, with one active and two completed
75 | TASKS = Lists.newArrayList(new Task("Title1", "Description1"),
76 | new Task("Title2", "Description2", true), new Task("Title3", "Description3", true));
77 | }
78 |
79 | @Test
80 | public void createPresenter_setsThePresenterToView() {
81 | // Get a reference to the class under test
82 | mTasksPresenter = new TasksPresenter(mTasksRepository, mTasksView);
83 |
84 | // Then the presenter is set to the view
85 | verify(mTasksView).setPresenter(mTasksPresenter);
86 | }
87 |
88 | @Test
89 | public void loadAllTasksFromRepositoryAndLoadIntoView() {
90 | // Given an initialized TasksPresenter with initialized tasks
91 | // When loading of Tasks is requested
92 | mTasksPresenter.setFiltering(TasksFilterType.ALL_TASKS);
93 | mTasksPresenter.loadTasks(true);
94 |
95 | // Callback is captured and invoked with stubbed tasks
96 | verify(mTasksRepository).getTasks(mLoadTasksCallbackCaptor.capture());
97 | mLoadTasksCallbackCaptor.getValue().onTasksLoaded(TASKS);
98 |
99 | // Then progress indicator is shown
100 | InOrder inOrder = inOrder(mTasksView);
101 | inOrder.verify(mTasksView).setLoadingIndicator(true);
102 | // Then progress indicator is hidden and all tasks are shown in UI
103 | inOrder.verify(mTasksView).setLoadingIndicator(false);
104 | ArgumentCaptor showTasksArgumentCaptor = ArgumentCaptor.forClass(List.class);
105 | verify(mTasksView).showTasks(showTasksArgumentCaptor.capture());
106 | assertTrue(showTasksArgumentCaptor.getValue().size() == 3);
107 | }
108 |
109 | @Test
110 | public void loadActiveTasksFromRepositoryAndLoadIntoView() {
111 | // Given an initialized TasksPresenter with initialized tasks
112 | // When loading of Tasks is requested
113 | mTasksPresenter.setFiltering(TasksFilterType.ACTIVE_TASKS);
114 | mTasksPresenter.loadTasks(true);
115 |
116 | // Callback is captured and invoked with stubbed tasks
117 | verify(mTasksRepository).getTasks(mLoadTasksCallbackCaptor.capture());
118 | mLoadTasksCallbackCaptor.getValue().onTasksLoaded(TASKS);
119 |
120 | // Then progress indicator is hidden and active tasks are shown in UI
121 | verify(mTasksView).setLoadingIndicator(false);
122 | ArgumentCaptor showTasksArgumentCaptor = ArgumentCaptor.forClass(List.class);
123 | verify(mTasksView).showTasks(showTasksArgumentCaptor.capture());
124 | assertTrue(showTasksArgumentCaptor.getValue().size() == 1);
125 | }
126 |
127 | @Test
128 | public void loadCompletedTasksFromRepositoryAndLoadIntoView() {
129 | // Given an initialized TasksPresenter with initialized tasks
130 | // When loading of Tasks is requested
131 | mTasksPresenter.setFiltering(TasksFilterType.COMPLETED_TASKS);
132 | mTasksPresenter.loadTasks(true);
133 |
134 | // Callback is captured and invoked with stubbed tasks
135 | verify(mTasksRepository).getTasks(mLoadTasksCallbackCaptor.capture());
136 | mLoadTasksCallbackCaptor.getValue().onTasksLoaded(TASKS);
137 |
138 | // Then progress indicator is hidden and completed tasks are shown in UI
139 | verify(mTasksView).setLoadingIndicator(false);
140 | ArgumentCaptor showTasksArgumentCaptor = ArgumentCaptor.forClass(List.class);
141 | verify(mTasksView).showTasks(showTasksArgumentCaptor.capture());
142 | assertTrue(showTasksArgumentCaptor.getValue().size() == 2);
143 | }
144 |
145 | @Test
146 | public void clickOnFab_ShowsAddTaskUi() {
147 | // When adding a new task
148 | mTasksPresenter.addNewTask();
149 |
150 | // Then add task UI is shown
151 | verify(mTasksView).showAddTask();
152 | }
153 |
154 | @Test
155 | public void clickOnTask_ShowsDetailUi() {
156 | // Given a stubbed active task
157 | Task requestedTask = new Task("Details Requested", "For this task");
158 |
159 | // When open task details is requested
160 | mTasksPresenter.openTaskDetails(requestedTask);
161 |
162 | // Then task detail UI is shown
163 | verify(mTasksView).showTaskDetailsUi(any(String.class));
164 | }
165 |
166 | @Test
167 | public void completeTask_ShowsTaskMarkedComplete() {
168 | // Given a stubbed task
169 | Task task = new Task("Details Requested", "For this task");
170 |
171 | // When task is marked as complete
172 | mTasksPresenter.completeTask(task);
173 |
174 | // Then repository is called and task marked complete UI is shown
175 | verify(mTasksRepository).completeTask(task);
176 | verify(mTasksView).showTaskMarkedComplete();
177 | }
178 |
179 | @Test
180 | public void activateTask_ShowsTaskMarkedActive() {
181 | // Given a stubbed completed task
182 | Task task = new Task("Details Requested", "For this task", true);
183 | mTasksPresenter.loadTasks(true);
184 |
185 | // When task is marked as activated
186 | mTasksPresenter.activateTask(task);
187 |
188 | // Then repository is called and task marked active UI is shown
189 | verify(mTasksRepository).activateTask(task);
190 | verify(mTasksView).showTaskMarkedActive();
191 | }
192 |
193 | @Test
194 | public void unavailableTasks_ShowsError() {
195 | // When tasks are loaded
196 | mTasksPresenter.setFiltering(TasksFilterType.ALL_TASKS);
197 | mTasksPresenter.loadTasks(true);
198 |
199 | // And the tasks aren't available in the repository
200 | verify(mTasksRepository).getTasks(mLoadTasksCallbackCaptor.capture());
201 | mLoadTasksCallbackCaptor.getValue().onDataNotAvailable();
202 |
203 | // Then an error message is shown
204 | verify(mTasksView).showLoadingTasksError();
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/azure-pipelines.yml:
--------------------------------------------------------------------------------
1 | # Android
2 | # Build your Android project with Gradle.
3 | # Add steps that test, sign, and distribute the APK, save build artifacts, and more:
4 | # https://docs.microsoft.com/azure/devops/pipelines/languages/android
5 |
6 | pool:
7 | vmImage: 'macOS-latest'
8 |
9 | steps:
10 | - task: Gradle@2
11 | inputs:
12 | workingDirectory: ''
13 | gradleWrapperFile: 'gradlew'
14 | gradleOptions: '-Xmx3072m'
15 | javaHomeOption: 'JDKVersion'
16 | jdkVersionOption: '1.8'
17 | jdkArchitectureOption: 'x64'
18 | publishJUnitResults: true
19 | testResultsFiles: '**/TEST-*.xml'
20 | tasks: 'assembleDebug test'
21 | displayName: gradlew assembleDebug test
22 |
23 | - task: CopyFiles@2
24 | inputs:
25 | contents: '**/*.apk'
26 | targetFolder: '$(build.artifactStagingDirectory)'
27 | displayName: Copy .apk files to artifact staging directory
28 |
29 | - task: PublishBuildArtifacts@1
30 | inputs:
31 | pathToPublish: '$(build.artifactStagingDirectory)'
32 | artifactName: 'drop'
33 | artifactType: 'container'
34 | displayName: Publish artifacts
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | jcenter()
4 | }
5 | dependencies {
6 | classpath 'com.android.tools.build:gradle:7.3.2'
7 |
8 | // NOTE: Do not place your application dependencies here; they belong
9 | // in the individual module build.gradle files
10 | }
11 | }
12 |
13 | allprojects {
14 | repositories {
15 | jcenter()
16 | }
17 | }
18 |
19 | task clean(type: Delete) {
20 | delete rootProject.buildDir
21 | }
22 |
23 | // Define versions in a single place
24 | ext {
25 | // Sdk and tools
26 | minSdkVersion = 10
27 | targetSdkVersion = 25
28 | compileSdkVersion = 25
29 | buildToolsVersion = '25.0.0'
30 |
31 | // App dependencies
32 | supportLibraryVersion = '25.3.1'
33 | guavaVersion = '18.0'
34 | junitVersion = '4.12'
35 | mockitoVersion = '1.10.19'
36 | powerMockito = '1.6.2'
37 | hamcrestVersion = '1.3'
38 | runnerVersion = '0.5'
39 | rulesVersion = '0.5'
40 | espressoVersion = '2.2.2'
41 | }
42 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MicrosoftDocs/pipelines-android/bdbe042616ee133eca8c3a02a5378b66fabbf5fd/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Mar 29 11:41:30 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------