diff --git a/app/src/androidTest/java/com/github/mobile/tests/issue/IssueFilterTest.java b/app/src/androidTest/java/com/github/mobile/tests/issue/IssueFilterTest.java
index f131a79b..bd3767ba 100644
--- a/app/src/androidTest/java/com/github/mobile/tests/issue/IssueFilterTest.java
+++ b/app/src/androidTest/java/com/github/mobile/tests/issue/IssueFilterTest.java
@@ -17,9 +17,9 @@
import android.test.AndroidTestCase;
+import com.github.mobile.api.model.Milestone;
import com.github.mobile.core.issue.IssueFilter;
-import org.eclipse.egit.github.core.Milestone;
import org.eclipse.egit.github.core.Repository;
import org.eclipse.egit.github.core.User;
@@ -56,7 +56,8 @@ public void testEqualFilter() {
assertEquals(filter1, filter2);
assertEquals(filter1.hashCode(), filter2.hashCode());
- Milestone milestone = new Milestone().setNumber(3);
+ Milestone milestone = new Milestone();
+ milestone.number = 3;
filter1.setMilestone(milestone);
assertFalse(filter1.equals(filter2));
filter2.setMilestone(milestone);
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 03cbbae9..adc41791 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -95,6 +95,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
accountProvider) {
.addInterceptor(new RequestConfiguration(accountProvider))
.build();
+ JsonAdapter adapter =
+ new Moshi.Builder().add(new DateAdapter()).build().adapter(Milestone.class).serializeNulls();
Moshi converter = new Moshi.Builder()
.add(new DateAdapter())
+ .add(Milestone.class, adapter)
.build();
return new Retrofit.Builder()
diff --git a/app/src/main/java/com/github/mobile/Intents.java b/app/src/main/java/com/github/mobile/Intents.java
index d571dd3d..c199c12f 100644
--- a/app/src/main/java/com/github/mobile/Intents.java
+++ b/app/src/main/java/com/github/mobile/Intents.java
@@ -18,6 +18,7 @@
import static org.eclipse.egit.github.core.RepositoryId.createFromUrl;
import android.content.Intent;
+import com.github.mobile.api.model.Milestone;
import com.github.mobile.api.model.Project;
import com.github.mobile.api.model.Team;
@@ -81,6 +82,11 @@ public class Intents {
*/
public static final String EXTRA_ISSUE = INTENT_EXTRA_PREFIX + "ISSUE";
+ /**
+ * Milestone handle
+ */
+ public static final String EXTRA_MILESTONE = INTENT_EXTRA_PREFIX + "MILESTONE";
+
/**
* Issue number collection handle
*/
@@ -250,6 +256,16 @@ public Builder issue(Issue issue) {
issue).add(EXTRA_ISSUE_NUMBER, issue.getNumber());
}
+ /**
+ * Add milestone to intent being built up
+ *
+ * @param milestone
+ * @return this builder
+ */
+ public Builder milestone(Milestone milestone) {
+ return add(EXTRA_MILESTONE, milestone);
+ }
+
/**
* Add project to intent being built up
*
diff --git a/app/src/main/java/com/github/mobile/RequestCodes.java b/app/src/main/java/com/github/mobile/RequestCodes.java
index fbeb0715..049232ae 100644
--- a/app/src/main/java/com/github/mobile/RequestCodes.java
+++ b/app/src/main/java/com/github/mobile/RequestCodes.java
@@ -109,4 +109,14 @@ public interface RequestCodes {
* Request to delete a comment
*/
int COMMENT_DELETE = 15;
+
+ /**
+ * Request to view milestone
+ */
+ int MILESTONE_VIEW = 16;
+
+ /**
+ * Request to edit milestone
+ */
+ int MILESTONE_EDIT = 17;
}
diff --git a/app/src/main/java/com/github/mobile/ServicesModule.java b/app/src/main/java/com/github/mobile/ServicesModule.java
index 6724a0f9..08372cef 100644
--- a/app/src/main/java/com/github/mobile/ServicesModule.java
+++ b/app/src/main/java/com/github/mobile/ServicesModule.java
@@ -134,6 +134,11 @@ MilestoneService milestoneService(GitHubClient client) {
return new MilestoneService(client);
}
+ @Provides
+ com.github.mobile.api.service.MilestoneService milestoneService(Retrofit retrofit) {
+ return retrofit.create(com.github.mobile.api.service.MilestoneService.class);
+ }
+
@Provides
LabelService labelService(GitHubClient client) {
return new LabelService(client);
diff --git a/app/src/main/java/com/github/mobile/api/model/Issue.java b/app/src/main/java/com/github/mobile/api/model/Issue.java
index 69d02aa5..50337028 100644
--- a/app/src/main/java/com/github/mobile/api/model/Issue.java
+++ b/app/src/main/java/com/github/mobile/api/model/Issue.java
@@ -15,10 +15,11 @@
*/
package com.github.mobile.api.model;
+import java.io.Serializable;
import java.util.Date;
import java.util.List;
-public class Issue {
+public class Issue implements Serializable {
public long id;
public Repository repository;
@@ -76,6 +77,11 @@ public org.eclipse.egit.github.core.Issue getOldModel() {
issue.setCreatedAt(created_at);
issue.setClosedAt(closed_at);
issue.setUpdatedAt(updated_at);
+
+ if (milestone != null){
+ issue.setMilestone(milestone.getOldModel());
+ }
+
if (user != null) {
issue.setUser(user.getOldModel());
}
@@ -87,4 +93,4 @@ public org.eclipse.egit.github.core.Issue getOldModel() {
}
return issue;
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/github/mobile/api/model/Milestone.java b/app/src/main/java/com/github/mobile/api/model/Milestone.java
index 9c3b099c..dcc6231a 100644
--- a/app/src/main/java/com/github/mobile/api/model/Milestone.java
+++ b/app/src/main/java/com/github/mobile/api/model/Milestone.java
@@ -15,9 +15,11 @@
*/
package com.github.mobile.api.model;
+import java.io.Serializable;
import java.util.Date;
-public class Milestone {
+public class Milestone implements Serializable {
+ public static final String MS_STATE_OPEN = "open";
public long id;
public int number;
@@ -41,4 +43,40 @@ public class Milestone {
public Date closed_at;
public Date due_on;
+
+ private String url;
+
+ public Milestone() {
+ state = MS_STATE_OPEN;
+ }
+
+ public Milestone(org.eclipse.egit.github.core.Milestone milestone) {
+ this.number = milestone.getNumber();
+ this.state = milestone.getState();
+ this.title = milestone.getTitle();
+ this.description = milestone.getDescription();
+ org.eclipse.egit.github.core.User creator = milestone.getCreator();
+ this.creator = creator == null ? null : new User(creator);
+ this.open_issues = milestone.getOpenIssues();
+ this.closed_issues = milestone.getClosedIssues();
+ this.created_at = milestone.getCreatedAt();
+ this.url = milestone.getUrl();
+ this.due_on = milestone.getDueOn();
+ }
+
+ public org.eclipse.egit.github.core.Milestone getOldModel() {
+ org.eclipse.egit.github.core.Milestone milestone = new org.eclipse.egit.github.core.Milestone();
+ milestone.setCreatedAt(created_at);
+ milestone.setDueOn(due_on);
+ milestone.setClosedIssues(closed_issues);
+ milestone.setNumber(number);
+ milestone.setOpenIssues(open_issues);
+ milestone.setDescription(description);
+ milestone.setState(state);
+ milestone.setTitle(title);
+ milestone.setUrl(url);
+ milestone.setCreator(creator.getOldModel());
+
+ return milestone;
+ }
}
diff --git a/app/src/main/java/com/github/mobile/api/service/IssueService.java b/app/src/main/java/com/github/mobile/api/service/IssueService.java
index bd45f7f8..e7881f23 100644
--- a/app/src/main/java/com/github/mobile/api/service/IssueService.java
+++ b/app/src/main/java/com/github/mobile/api/service/IssueService.java
@@ -35,6 +35,13 @@ Call getIssue(
@Path("repo") String repo,
@Path("number") long number);
+ @Headers("Accept: application/vnd.github.squirrel-girl-preview")
+ @GET("repos/{owner}/{repo}/issues")
+ Call> getIssues(
+ @Path("owner") String owner,
+ @Path("repo") String repo,
+ @Query("milestone") String milestone);
+
@Headers({"Accept: application/vnd.github.v3.full+json",
"Accept: application/vnd.github.mockingbird-preview",
"Accept: application/vnd.github.squirrel-girl-preview"})
diff --git a/app/src/main/java/com/github/mobile/api/service/MilestoneService.java b/app/src/main/java/com/github/mobile/api/service/MilestoneService.java
new file mode 100644
index 00000000..dbff0cae
--- /dev/null
+++ b/app/src/main/java/com/github/mobile/api/service/MilestoneService.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2016 Jon Ander Peñalba
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.github.mobile.api.service;
+
+import com.github.mobile.api.model.Issue;
+import com.github.mobile.api.model.Milestone;
+import java.util.List;
+
+import retrofit2.Call;
+import retrofit2.http.Body;
+import retrofit2.http.DELETE;
+import retrofit2.http.GET;
+import retrofit2.http.Headers;
+import retrofit2.http.PATCH;
+import retrofit2.http.POST;
+import retrofit2.http.Path;
+import retrofit2.http.Query;
+
+public interface MilestoneService {
+ @GET("repos/{owner}/{repo}/milestones/{number}")
+ Call getMilestone(
+ @Path("owner") String owner,
+ @Path("repo") String repo,
+ @Path("number") long number);
+
+ @GET("repos/{owner}/{repo}/milestones")
+ Call> getMilestones(
+ @Path("owner") String owner,
+ @Path("repo") String repo);
+
+ @Headers("Accept: application/vnd.github.squirrel-girl-preview")
+ @GET("repos/{owner}/{repo}/issues")
+ Call> getIssues(
+ @Path("owner") String owner,
+ @Path("repo") String repo,
+ @Query("milestone") long milestone);
+
+ @POST("repos/{owner}/{repo}/milestones")
+ Call createMilestone (
+ @Path("owner") String owner,
+ @Path("repo") String repo,
+ @Body Milestone milestone);
+
+ @PATCH("repos/{owner}/{repo}/milestones/{number}")
+ Call editMilestone (
+ @Path("owner") String owner,
+ @Path("repo") String repo,
+ @Path("number") long number,
+ @Body Milestone milestone);
+
+ @DELETE("repos/{owner}/{repo}/milestones/{number}")
+ Call deleteMilestone(
+ @Path("owner") String owner,
+ @Path("repo") String repo,
+ @Path("number") long number);
+}
diff --git a/app/src/main/java/com/github/mobile/core/issue/IssueFilter.java b/app/src/main/java/com/github/mobile/core/issue/IssueFilter.java
index ce6f0136..dbf38d90 100644
--- a/app/src/main/java/com/github/mobile/core/issue/IssueFilter.java
+++ b/app/src/main/java/com/github/mobile/core/issue/IssueFilter.java
@@ -15,6 +15,8 @@
*/
package com.github.mobile.core.issue;
+import com.github.mobile.api.model.Milestone;
+
import static java.lang.String.CASE_INSENSITIVE_ORDER;
import static org.eclipse.egit.github.core.service.IssueService.DIRECTION_DESCENDING;
import static org.eclipse.egit.github.core.service.IssueService.FIELD_DIRECTION;
@@ -39,7 +41,6 @@
import java.util.TreeSet;
import org.eclipse.egit.github.core.Label;
-import org.eclipse.egit.github.core.Milestone;
import org.eclipse.egit.github.core.Repository;
import org.eclipse.egit.github.core.User;
@@ -185,7 +186,7 @@ public Map toFilterMap() {
if (milestone != null)
filter.put(FILTER_MILESTONE,
- Integer.toString(milestone.getNumber()));
+ Integer.toString(milestone.number));
if (labels != null && !labels.isEmpty()) {
StringBuilder labelsQuery = new StringBuilder();
@@ -217,7 +218,7 @@ public CharSequence toDisplay() {
segments.add("Assignee: " + assignee.getLogin());
if (milestone != null)
- segments.add("Milestone: " + milestone.getTitle());
+ segments.add("Milestone: " + milestone.title);
if (labels != null && !labels.isEmpty()) {
StringBuilder builder = new StringBuilder("Labels: ");
@@ -243,7 +244,7 @@ public CharSequence toDisplay() {
public int hashCode() {
return Arrays.hashCode(new Object[] { open,
assignee != null ? assignee.getId() : null,
- milestone != null ? milestone.getNumber() : null,
+ milestone != null ? milestone.number : null,
assignee != null ? assignee.getId() : null,
repository != null ? repository.getId() : null, labels });
}
@@ -257,7 +258,7 @@ private boolean isEqual(Object a, Object b) {
private boolean isEqual(Milestone a, Milestone b) {
if (a == null && b == null)
return true;
- return a != null && b != null && a.getNumber() == b.getNumber();
+ return a != null && b != null && a.number == b.number;
}
private boolean isEqual(User a, User b) {
diff --git a/app/src/main/java/com/github/mobile/core/milestone/AddIssueTask.java b/app/src/main/java/com/github/mobile/core/milestone/AddIssueTask.java
new file mode 100644
index 00000000..b08bad33
--- /dev/null
+++ b/app/src/main/java/com/github/mobile/core/milestone/AddIssueTask.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2012 GitHub Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.github.mobile.core.milestone;
+
+import android.accounts.Account;
+
+import com.github.mobile.R;
+import com.github.mobile.api.model.Issue;
+import com.github.mobile.api.service.IssueService;
+import com.github.mobile.api.service.MilestoneService;
+import com.github.mobile.core.issue.IssueStore;
+import com.github.mobile.ui.DialogFragmentActivity;
+import com.github.mobile.ui.ProgressDialogTask;
+import com.github.mobile.ui.milestone.IssueDialog;
+import com.google.inject.Inject;
+
+import org.eclipse.egit.github.core.IRepositoryIdProvider;
+import org.eclipse.egit.github.core.Milestone;
+
+import static com.github.mobile.RequestCodes.ISSUE_MILESTONE_UPDATE;
+
+/**
+ * Task to add an issue to a milestone
+ */
+public class AddIssueTask extends ProgressDialogTask {
+
+ @Inject
+ private IssueService service;
+
+ @Inject
+ private MilestoneService milestoneService;
+
+ @Inject
+ private IssueStore store;
+
+ private final IssueDialog issueDialog;
+
+ private final IRepositoryIdProvider repositoryId;
+
+ private int issueNumber;
+
+ private final int milestoneNumber;
+
+ /**
+ * Create task to add an issue to a milestone
+ *
+ * @param activity
+ * @param repositoryId
+ * @param milestoneNumber
+ */
+ public AddIssueTask(final DialogFragmentActivity activity,
+ final IRepositoryIdProvider repositoryId, final int milestoneNumber) {
+ super(activity);
+
+ this.repositoryId = repositoryId;
+ this.milestoneNumber = milestoneNumber;
+ issueDialog = new IssueDialog(activity, ISSUE_MILESTONE_UPDATE,
+ repositoryId, service);
+ }
+
+ @Override
+ protected com.github.mobile.api.model.Milestone run(Account account) throws Exception {
+ org.eclipse.egit.github.core.Issue editedIssue = new org.eclipse.egit.github.core.Issue();
+ editedIssue.setNumber(issueNumber);
+ editedIssue.setMilestone(new Milestone().setNumber(milestoneNumber));
+ store.editIssue(repositoryId, editedIssue);
+ String[] rep = repositoryId.generateId().split("/");
+ return milestoneService.getMilestone(rep[0], rep[1], milestoneNumber).execute().body();
+ }
+
+ /**
+ * Prompt for issue selection
+ *
+ * @return this task
+ */
+ public AddIssueTask prompt() {
+ issueDialog.show();
+ return this;
+ }
+
+ /**
+ * Add issue to the milestone
+ *
+ * @param issue
+ * @return this task
+ */
+ public AddIssueTask edit(Issue issue) {
+ if (issue != null)
+ issueNumber = issue.number;
+ else
+ issueNumber = -1;
+
+ showIndeterminate(R.string.updating_milestone);
+
+ super.execute();
+
+ return this;
+ }
+}
diff --git a/app/src/main/java/com/github/mobile/core/milestone/CreateMilestoneTask.java b/app/src/main/java/com/github/mobile/core/milestone/CreateMilestoneTask.java
new file mode 100644
index 00000000..642e151f
--- /dev/null
+++ b/app/src/main/java/com/github/mobile/core/milestone/CreateMilestoneTask.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2012 GitHub Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.github.mobile.core.milestone;
+
+import android.accounts.Account;
+import android.app.Activity;
+import android.util.Log;
+
+import com.github.mobile.R;
+import com.github.mobile.api.model.Milestone;
+import com.github.mobile.api.service.MilestoneService;
+import com.github.mobile.ui.ProgressDialogTask;
+import com.github.mobile.util.ToastUtils;
+import com.google.inject.Inject;
+
+public class CreateMilestoneTask extends ProgressDialogTask {
+ private static final String TAG = "CreateMilestoneTask";
+
+ @Inject
+ private MilestoneService service;
+
+ private final String owner;
+ private final String repo;
+ private final Milestone milestone;
+
+ /**
+ * Create task to create an {@link Milestone}
+ *
+ * @param activity
+ * @param owner
+ * @param repo
+ * @param milestone
+ */
+ public CreateMilestoneTask(final Activity activity,
+ final String owner,
+ final String repo,
+ final Milestone milestone) {
+ super(activity);
+ this.owner = owner;
+ this.repo = repo;
+ this.milestone = milestone;
+ }
+
+ /**
+ * Create milestone
+ *
+ * @return this task
+ */
+ public CreateMilestoneTask create() {
+ showIndeterminate(R.string.creating_milestone);
+
+ execute();
+ return this;
+ }
+
+ @Override
+ public Milestone run(Account account) throws Exception {
+ return service.createMilestone(owner, repo, milestone).execute().body();
+ }
+
+ @Override
+ protected void onException(Exception e) throws RuntimeException {
+ super.onException(e);
+
+ Log.e(TAG, "Exception creating milestone", e);
+ ToastUtils.show((Activity) getContext(), e.getMessage());
+ }
+}
diff --git a/app/src/main/java/com/github/mobile/core/milestone/DeleteMilestoneTask.java b/app/src/main/java/com/github/mobile/core/milestone/DeleteMilestoneTask.java
new file mode 100644
index 00000000..b2cbc642
--- /dev/null
+++ b/app/src/main/java/com/github/mobile/core/milestone/DeleteMilestoneTask.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2012 GitHub Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.github.mobile.core.milestone;
+
+import android.accounts.Account;
+import android.app.Activity;
+import android.util.Log;
+
+import com.github.mobile.R;
+import com.github.mobile.api.model.Milestone;
+import com.github.mobile.api.service.MilestoneService;
+import com.github.mobile.ui.ProgressDialogTask;
+import com.github.mobile.util.ToastUtils;
+import com.google.inject.Inject;
+
+public class DeleteMilestoneTask extends ProgressDialogTask {
+ private static final String TAG = "DeleteMilestoneTask";
+
+ @Inject
+ private MilestoneService service;
+
+ private final String owner;
+ private final String repo;
+ private final Milestone milestone;
+
+ /**
+ * Create task to delete an {@link Milestone}
+ *
+ * @param activity
+ * @param owner
+ * @param repo
+ * @param milestone
+ */
+ public DeleteMilestoneTask(final Activity activity,
+ final String owner,
+ final String repo,
+ final Milestone milestone) {
+ super(activity);
+ this.owner = owner;
+ this.repo = repo;
+ this.milestone = milestone;
+ }
+
+ /**
+ * Delete milestone
+ *
+ * @return this task
+ */
+ public DeleteMilestoneTask create() {
+ showIndeterminate(R.string.deleting_milestone);
+
+ execute();
+ return this;
+ }
+
+ @Override
+ public Milestone run(Account account) throws Exception {
+ service.deleteMilestone(owner, repo, milestone.number).execute();
+ return milestone;
+ }
+
+ @Override
+ protected void onException(Exception e) throws RuntimeException {
+ super.onException(e);
+
+ Log.e(TAG, "Exception deleting milestone", e);
+ ToastUtils.show((Activity) getContext(), e.getMessage());
+ }
+}
diff --git a/app/src/main/java/com/github/mobile/core/milestone/EditMilestoneTask.java b/app/src/main/java/com/github/mobile/core/milestone/EditMilestoneTask.java
new file mode 100644
index 00000000..33036bb5
--- /dev/null
+++ b/app/src/main/java/com/github/mobile/core/milestone/EditMilestoneTask.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2012 GitHub Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.github.mobile.core.milestone;
+
+import android.accounts.Account;
+import android.app.Activity;
+import android.util.Log;
+
+import com.github.mobile.R;
+import com.github.mobile.api.model.Milestone;
+import com.github.mobile.api.service.MilestoneService;
+import com.github.mobile.ui.ProgressDialogTask;
+import com.github.mobile.util.ToastUtils;
+import com.google.inject.Inject;
+
+public class EditMilestoneTask extends ProgressDialogTask {
+ private static final String TAG = "EditMilestoneTask";
+
+ @Inject
+ private MilestoneService service;
+
+ private final String owner;
+ private final String repo;
+ private final Milestone milestone;
+
+ /**
+ * Create task to edit an {@link Milestone}
+ *
+ * @param activity
+ * @param owner
+ * @param repo
+ * @param milestone
+ */
+ public EditMilestoneTask(final Activity activity,
+ final String owner,
+ final String repo,
+ final Milestone milestone) {
+ super(activity);
+ this.owner = owner;
+ this.repo = repo;
+ this.milestone = milestone;
+ }
+
+ /**
+ * Edit milestone
+ *
+ * @return this task
+ */
+ public EditMilestoneTask edit() {
+ showIndeterminate(R.string.updating_milestone);
+
+ execute();
+ return this;
+ }
+
+ @Override
+ public Milestone run(Account account) throws Exception {
+ return service.editMilestone(owner, repo, milestone.number, milestone).execute().body();
+ }
+
+ @Override
+ protected void onException(Exception e) throws RuntimeException {
+ super.onException(e);
+
+ Log.e(TAG, "Exception editing milestone", e);
+ ToastUtils.show((Activity) getContext(), e.getMessage());
+ }
+}
diff --git a/app/src/main/java/com/github/mobile/ui/issue/EditIssuesFilterActivity.java b/app/src/main/java/com/github/mobile/ui/issue/EditIssuesFilterActivity.java
index 37e78fb6..c2e60b2b 100644
--- a/app/src/main/java/com/github/mobile/ui/issue/EditIssuesFilterActivity.java
+++ b/app/src/main/java/com/github/mobile/ui/issue/EditIssuesFilterActivity.java
@@ -145,7 +145,12 @@ public void onClick(View v) {
milestoneDialog = new MilestoneDialog(
EditIssuesFilterActivity.this, REQUEST_MILESTONE,
repository, milestones);
- milestoneDialog.show(filter.getMilestone());
+ com.github.mobile.api.model.Milestone milestone = filter.getMilestone();
+ if (milestone == null) {
+ milestoneDialog.show(null);
+ } else {
+ milestoneDialog.show(milestone.getOldModel());
+ }
}
};
@@ -236,9 +241,9 @@ private void updateLabels() {
}
private void updateMilestone() {
- Milestone selected = filter.getMilestone();
+ com.github.mobile.api.model.Milestone selected = filter.getMilestone();
if (selected != null)
- milestoneText.setText(selected.getTitle());
+ milestoneText.setText(selected.getOldModel().getTitle());
else
milestoneText.setText(R.string.none);
}
@@ -265,7 +270,7 @@ public void onDialogResult(int requestCode, int resultCode, Bundle arguments) {
updateLabels();
break;
case REQUEST_MILESTONE:
- filter.setMilestone(MilestoneDialogFragment.getSelected(arguments));
+ filter.setMilestone(new com.github.mobile.api.model.Milestone(MilestoneDialogFragment.getSelected(arguments)));
updateMilestone();
break;
case REQUEST_ASSIGNEE:
diff --git a/app/src/main/java/com/github/mobile/ui/issue/FilterListAdapter.java b/app/src/main/java/com/github/mobile/ui/issue/FilterListAdapter.java
index ba471a59..0e0517f8 100644
--- a/app/src/main/java/com/github/mobile/ui/issue/FilterListAdapter.java
+++ b/app/src/main/java/com/github/mobile/ui/issue/FilterListAdapter.java
@@ -76,7 +76,7 @@ protected void update(int position, IssueFilter filter) {
} else
setGone(3, true);
- Milestone milestone = filter.getMilestone();
+ Milestone milestone = filter.getMilestone().getOldModel();
if (milestone != null)
ViewUtils.setGone(setText(4, milestone.getTitle()), false);
else
diff --git a/app/src/main/java/com/github/mobile/ui/issue/IssuesFragment.java b/app/src/main/java/com/github/mobile/ui/issue/IssuesFragment.java
index a8982d22..1f535114 100644
--- a/app/src/main/java/com/github/mobile/ui/issue/IssuesFragment.java
+++ b/app/src/main/java/com/github/mobile/ui/issue/IssuesFragment.java
@@ -151,7 +151,8 @@ private void updateFilterSummary() {
} else
labels.setVisibility(GONE);
- Milestone filterMilestone = filter.getMilestone();
+ com.github.mobile.api.model.Milestone newFilterMs = filter.getMilestone();
+ Milestone filterMilestone = newFilterMs == null ? null : newFilterMs.getOldModel();
if (filterMilestone != null) {
milestone.setText(filterMilestone.getTitle());
milestone.setVisibility(VISIBLE);
diff --git a/app/src/main/java/com/github/mobile/ui/milestone/EditMilestoneActivity.java b/app/src/main/java/com/github/mobile/ui/milestone/EditMilestoneActivity.java
new file mode 100644
index 00000000..e78529d5
--- /dev/null
+++ b/app/src/main/java/com/github/mobile/ui/milestone/EditMilestoneActivity.java
@@ -0,0 +1,340 @@
+package com.github.mobile.ui.milestone;
+
+
+import android.accounts.Account;
+import android.app.DatePickerDialog;
+import android.content.Intent;
+import android.support.v7.app.ActionBar;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+import android.widget.DatePicker;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import com.github.mobile.Intents;
+import com.github.mobile.R;
+
+import com.github.mobile.accounts.AccountUtils;
+import com.github.mobile.accounts.AuthenticatedUserTask;
+import com.github.mobile.api.model.Milestone;
+import com.github.mobile.core.milestone.CreateMilestoneTask;
+import com.github.mobile.core.milestone.EditMilestoneTask;
+import com.github.mobile.ui.DialogFragmentActivity;
+import com.github.mobile.ui.issue.MilestoneDialog;
+import com.github.mobile.ui.repo.RepositoryMilestonesActivity;
+import com.github.mobile.util.ToastUtils;
+import com.google.inject.Inject;
+
+import org.eclipse.egit.github.core.Repository;
+import org.eclipse.egit.github.core.RepositoryId;
+import org.eclipse.egit.github.core.service.CollaboratorService;
+import org.eclipse.egit.github.core.service.MilestoneService;
+
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+import static com.github.mobile.Intents.EXTRA_MILESTONE;
+import static com.github.mobile.Intents.EXTRA_REPOSITORY;
+import static com.github.mobile.Intents.EXTRA_REPOSITORY_NAME;
+import static com.github.mobile.Intents.EXTRA_REPOSITORY_OWNER;
+
+/*
+ * Activity to edit or create a milestone
+ */
+public class EditMilestoneActivity extends DialogFragmentActivity {
+
+
+ /**
+ * Create intent to create milestone
+ *
+ * @param repository
+ * @return intent
+ */
+ public static Intent createIntent(Repository repository) {
+ return createIntent(null, repository, repository.getOwner().getLogin(),
+ repository.getName());
+ }
+
+ /**
+ * Create intent to edit milestone
+ *
+ * @param milestone
+ * @param repositoryOwner
+ * @param repositoryName
+ * @return intent
+ */
+ public static Intent createIntent(final Milestone milestone,
+ final Repository repository,
+ final String repositoryOwner, final String repositoryName) {
+ Intents.Builder builder = new Intents.Builder("repo.milestones.edit.VIEW");
+ builder.add(EXTRA_REPOSITORY_NAME, repositoryName);
+ builder.add(EXTRA_REPOSITORY_OWNER, repositoryOwner);
+ builder.add(EXTRA_REPOSITORY, repository);
+ if (milestone != null)
+ builder.milestone(milestone);
+ return builder.toIntent();
+ }
+
+
+ // Param views
+ private EditText etTitle;
+ private EditText etDescription;
+ private TextView etDate;
+
+ // Param
+ private Date mDate;
+
+ private MilestoneDialog milestoneDialog;
+
+ @Inject
+ private MilestoneService milestoneService;
+
+ @Inject
+ private CollaboratorService collaboratorService;
+
+ private Milestone milestone;
+
+ private RepositoryId repositoryId;
+ private Repository repository;
+
+ private MenuItem saveItem;
+
+ private SimpleDateFormat sd;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.milestone_edit);
+
+ sd = new SimpleDateFormat(getApplicationContext().getString(R.string.ms_date_format));
+ sd.setTimeZone(TimeZone.getTimeZone("Zulu"));
+
+ etTitle = finder.find(R.id.et_milestone_title);
+ etDescription = finder.find(R.id.et_milestone_description);
+ etDate = finder.find(R.id.tv_milestone_date);
+ Button twoWeeksButton = finder.find(R.id.b_two_weeks);
+ Button monthButton = finder.find(R.id.b_month);
+ Button chooseDateButton = finder.find(R.id.b_choose_date);
+ Button clear = finder.find(R.id.b_clear);
+
+ chooseDateButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final Calendar dateAndTime = Calendar.getInstance();
+
+ new DatePickerDialog(EditMilestoneActivity.this, R.style.Theme_AppCompat_DayNight_Dialog, new DatePickerDialog.OnDateSetListener() {
+ @Override
+ public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
+ dateAndTime.set(Calendar.YEAR, year);
+ dateAndTime.set(Calendar.MONTH, monthOfYear);
+ dateAndTime.set(Calendar.DAY_OF_MONTH, dayOfMonth);
+ final Date startDate = dateAndTime.getTime();
+ updateDate(startDate);
+ }
+ }, dateAndTime.get(Calendar.YEAR), dateAndTime.get(Calendar.MONTH), dateAndTime.get(Calendar.DAY_OF_MONTH)).show();
+
+ }
+ });
+
+ twoWeeksButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final Calendar dateAndTime = Calendar.getInstance();
+ int noOfDays = 14; //two weeks
+ Date dateOfOrder = new Date();
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(dateOfOrder);
+ dateAndTime.add(Calendar.DAY_OF_YEAR, noOfDays);
+ updateDate(dateAndTime.getTime());
+ }
+ });
+
+ monthButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final Calendar dateAndTime = Calendar.getInstance();
+ dateAndTime.add(Calendar.MONTH, 1);
+ updateDate(dateAndTime.getTime());
+ }
+ });
+
+ clear.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ updateDate(null);
+ }
+ });
+
+ checkCollaboratorStatus();
+
+ Intent intent = getIntent();
+
+ if (savedInstanceState != null)
+ milestone = (Milestone) savedInstanceState.getSerializable(EXTRA_MILESTONE);
+ if (milestone == null)
+ milestone = (Milestone) intent.getSerializableExtra(EXTRA_MILESTONE);
+ if (milestone == null)
+ milestone = new Milestone();
+
+ repository = (Repository) intent.getSerializableExtra(EXTRA_REPOSITORY);
+ repositoryId = RepositoryId.create(
+ intent.getStringExtra(EXTRA_REPOSITORY_OWNER),
+ intent.getStringExtra(EXTRA_REPOSITORY_NAME));
+
+ repository = getSerializableExtra(EXTRA_REPOSITORY);
+
+ ActionBar actionBar = getSupportActionBar();
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ if (milestone.number > 0)
+ actionBar.setTitle(milestone.title);
+ else
+ actionBar.setTitle(R.string.ms_new_milestone);
+ actionBar.setSubtitle(repositoryId.generateId());
+
+ etTitle.setText(milestone.title);
+ etDescription.setText(milestone.description);
+ }
+
+ @Override
+ public void onDialogResult(int requestCode, int resultCode, Bundle arguments) {
+ if (RESULT_OK != resultCode)
+ return;
+ switch (requestCode) {
+ //todo think about cases
+ }
+ }
+
+ private void showMainContent() {
+ finder.find(R.id.sv_milestone_content).setVisibility(View.VISIBLE);
+ finder.find(R.id.pb_loading).setVisibility(View.GONE);
+ }
+
+ private void showCollaboratorOptions() {
+ updateMilestone();
+ }
+
+ private void updateMilestone() {
+ if (milestone != null) {
+ etTitle.setText(milestone.title);
+ etDescription.setText(milestone.description);
+ Date dueOn = milestone.due_on;
+ updateDate(dueOn);
+ } else {
+ etTitle.setText(R.string.none);
+ etDescription.setText(R.string.none);
+ etDate.setText("");
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ outState.putSerializable(EXTRA_MILESTONE, milestone);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu options) {
+ getMenuInflater().inflate(R.menu.milestone_edit, options);
+ saveItem = options.findItem(R.id.m_apply);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ finish();
+ return true;
+ case R.id.m_apply:
+ if (etTitle.getText().toString().isEmpty()){
+ ToastUtils.show(this, R.string.ms_empty_title_error);
+ return false;
+ }
+ ActionBar actionBar = getSupportActionBar();
+ actionBar.setTitle(milestone.title);
+ milestone.title = etTitle.getText().toString();
+ milestone.description = etDescription.getText().toString();
+ milestone.due_on = mDate;
+ if (milestone.created_at == null) {
+ new CreateMilestoneTask(this, repositoryId.getOwner(), repositoryId.getName(), milestone) {
+
+ @Override
+ protected void onSuccess(Milestone created) throws Exception {
+ super.onSuccess(created);
+
+ Intent intent = RepositoryMilestonesActivity.createIntent(repository);
+ startActivity(intent);
+ }
+
+ }.create();
+ } else {
+ new EditMilestoneTask(this, repositoryId.getOwner(), repositoryId.getName(), milestone) {
+
+ @Override
+ protected void onSuccess(Milestone editedMilestone)
+ throws Exception {
+ super.onSuccess(editedMilestone);
+
+ Intent intent = new Intent();
+ intent.putExtra(EXTRA_MILESTONE, editedMilestone);
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+ }.edit();
+ }
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ private void checkCollaboratorStatus() {
+ new AuthenticatedUserTask(this) {
+
+ @Override
+ public Boolean run(Account account) throws Exception {
+ return collaboratorService.isCollaborator(
+ repositoryId, AccountUtils.getLogin(EditMilestoneActivity.this));
+ }
+
+ @Override
+ protected void onSuccess(Boolean isCollaborator) throws Exception {
+ super.onSuccess(isCollaborator);
+
+ showMainContent();
+ if (isCollaborator)
+ showCollaboratorOptions();
+ }
+
+ @Override
+ protected void onException(Exception e) throws RuntimeException {
+ super.onException(e);
+
+ showMainContent();
+ }
+ }.execute();
+ }
+
+ private void updateDate(Date date) {
+ if (date == null) {
+ etDate.setVisibility(View.GONE);
+ mDate = null;
+ milestone.due_on = null;
+ } else {
+ etDate.setVisibility(View.VISIBLE);
+ Calendar c = Calendar.getInstance();
+ c.setTime(date);
+ c.add(Calendar.HOUR, 8);
+ mDate = c.getTime();
+ SimpleDateFormat sd = new SimpleDateFormat(getApplicationContext().getString(R.string.ms_date_format));
+ String fdate = sd.format(date);
+ etDate.setText(fdate);
+ }
+ }
+}
diff --git a/app/src/main/java/com/github/mobile/ui/milestone/IssueDialog.java b/app/src/main/java/com/github/mobile/ui/milestone/IssueDialog.java
new file mode 100644
index 00000000..29cd4f89
--- /dev/null
+++ b/app/src/main/java/com/github/mobile/ui/milestone/IssueDialog.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2012 GitHub Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.github.mobile.ui.milestone;
+
+import android.accounts.Account;
+import android.util.Log;
+
+import com.github.mobile.R;
+import com.github.mobile.api.model.Issue;
+import com.github.mobile.api.service.IssueService;
+import com.github.mobile.ui.DialogFragmentActivity;
+import com.github.mobile.ui.ProgressDialogTask;
+import com.github.mobile.util.ToastUtils;
+
+import org.eclipse.egit.github.core.IRepositoryIdProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Dialog helper to display a list of issues to select one from
+ */
+public class IssueDialog {
+
+ private static final String TAG = "IssueDialog";
+
+ private IssueService service;
+
+ private ArrayList repositoryIssues;
+
+ private final int requestCode;
+
+ private final DialogFragmentActivity activity;
+
+ private final IRepositoryIdProvider repository;
+
+ /**
+ * Create dialog helper to display issue
+ *
+ * @param activity
+ * @param requestCode
+ * @param repository
+ * @param service
+ */
+ public IssueDialog(final DialogFragmentActivity activity,
+ final int requestCode, final IRepositoryIdProvider repository,
+ final IssueService service) {
+ this.activity = activity;
+ this.requestCode = requestCode;
+ this.repository = repository;
+ this.service = service;
+ }
+
+ /**
+ * Get issues
+ *
+ * @return list of issues
+ */
+ public List getIssues() {
+ return repositoryIssues;
+ }
+
+ public void show() {
+ new ProgressDialogTask>(activity) {
+
+ @Override
+ public ArrayList run(Account account) throws Exception {
+ ArrayList issues = new ArrayList();
+ String[] repid = repository.generateId().split("/");
+ issues.addAll(service.getIssues(repid[0], repid[1], "none").execute().body());
+ return issues;
+ }
+
+ @Override
+ protected void onSuccess(ArrayList all) throws Exception {
+ super.onSuccess(all);
+
+ repositoryIssues = all;
+ IssueDialogFragment.show(activity, requestCode,
+ activity.getString(R.string.ms_select_issue), null,
+ repositoryIssues);
+ }
+
+ @Override
+ protected void onException(Exception e) throws RuntimeException {
+ super.onException(e);
+
+ Log.d(TAG, "Exception loading issues", e);
+ ToastUtils.show(activity, e, R.string.error_issues_load);
+ }
+
+ @Override
+ public void execute() {
+ showIndeterminate(R.string.loading_issues);
+
+ super.execute();
+ }
+ }.execute();
+ }
+}
diff --git a/app/src/main/java/com/github/mobile/ui/milestone/IssueDialogFragment.java b/app/src/main/java/com/github/mobile/ui/milestone/IssueDialogFragment.java
new file mode 100644
index 00000000..39ebd6b0
--- /dev/null
+++ b/app/src/main/java/com/github/mobile/ui/milestone/IssueDialogFragment.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2012 GitHub Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.github.mobile.ui.milestone;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ListView;
+
+import com.github.kevinsawicki.wishlist.SingleTypeAdapter;
+import com.github.mobile.R;
+import com.github.mobile.api.model.Issue;
+import com.github.mobile.ui.DialogFragmentActivity;
+import com.github.mobile.ui.SingleChoiceDialogFragment;
+
+import java.util.ArrayList;
+
+import static android.app.Activity.RESULT_OK;
+import static android.content.DialogInterface.BUTTON_NEGATIVE;
+
+/**
+ * Dialog fragment to add an issue to milestone
+ */
+public class IssueDialogFragment extends SingleChoiceDialogFragment {
+
+ private static class IssueListAdapter extends
+ SingleTypeAdapter {
+
+
+ public IssueListAdapter(LayoutInflater inflater,
+ Issue[] issues) {
+ super(inflater, R.layout.milestone_item);
+
+ setItems(issues);
+ }
+
+ @Override
+ protected int[] getChildViewIds() {
+ return new int[] { R.id.rb_selected, R.id.tv_milestone_title,
+ R.id.tv_milestone_description };
+ }
+
+ @Override
+ protected void update(int position, Issue item) {
+ setText(1, item.title);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return getItem(position).number;
+ }
+ }
+
+ /**
+ * Get selected milestone from results bundle
+ *
+ * @param arguments
+ * @return milestone
+ */
+ public static Issue getSelected(Bundle arguments) {
+ return (Issue) arguments.getSerializable(ARG_SELECTED);
+ }
+
+ /**
+ * Confirm message and deliver callback to given activity
+ *
+ * @param activity
+ * @param requestCode
+ * @param title
+ * @param message
+ * @param choices
+ */
+ public static void show(final DialogFragmentActivity activity,
+ final int requestCode, final String title, final String message,
+ ArrayList choices) {
+ show(activity, requestCode, title, message, choices, -1,
+ new IssueDialogFragment());
+ }
+
+ @Override
+ public Dialog onCreateDialog(final Bundle savedInstanceState) {
+ Activity activity = getActivity();
+
+ final AlertDialog dialog = createDialog();
+ dialog.setButton(BUTTON_NEGATIVE, activity.getString(R.string.cancel),
+ this);
+
+ LayoutInflater inflater = activity.getLayoutInflater();
+
+ ListView view = (ListView) inflater.inflate(R.layout.dialog_list_view,
+ null);
+ view.setOnItemClickListener(new OnItemClickListener() {
+
+ @Override
+ public void onItemClick(AdapterView> parent, View view,
+ int position, long id) {
+ onClick(dialog, position);
+ }
+ });
+
+ ArrayList choices = getChoices();
+ IssueListAdapter adapter = new IssueListAdapter(inflater,
+ choices.toArray(new Issue[choices.size()]));
+ view.setAdapter(adapter);
+ dialog.setView(view);
+
+ return dialog;
+ }
+
+ @SuppressWarnings("unchecked")
+ private ArrayList getChoices() {
+ return (ArrayList) getArguments().getSerializable(
+ ARG_CHOICES);
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ super.onClick(dialog, which);
+
+ switch (which) {
+ case BUTTON_NEGATIVE:
+ break;
+ default:
+ getArguments().putSerializable(ARG_SELECTED,
+ getChoices().get(which));
+ onResult(RESULT_OK);
+ }
+ }
+}
diff --git a/app/src/main/java/com/github/mobile/ui/milestone/MilestoneFragment.java b/app/src/main/java/com/github/mobile/ui/milestone/MilestoneFragment.java
new file mode 100644
index 00000000..a16f74a5
--- /dev/null
+++ b/app/src/main/java/com/github/mobile/ui/milestone/MilestoneFragment.java
@@ -0,0 +1,170 @@
+/*
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.github.mobile.ui.milestone;
+
+import android.content.Context;
+import android.graphics.drawable.GradientDrawable;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import com.github.mobile.R;
+import com.github.mobile.ui.DialogFragment;
+
+import org.eclipse.egit.github.core.Milestone;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+
+import static com.github.mobile.Intents.EXTRA_MILESTONE;
+
+/**
+ * Fragment to display a milestone.
+ */
+public class MilestoneFragment extends DialogFragment {
+ private Milestone milestone;
+
+ private TextView milestoneTitle;
+ private TextView milestoneDueTo;
+ private TextView milestoneDescription;
+ private ProgressBar milestoneProgress;
+ private TextView milestoneProgressPercentage;
+ private TextView milestoneTime;
+
+ private final static int MS_TIME_PAST_DAYS = -100;
+ private final static int MS_TIME_OK_DAYS = 100;
+
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+
+ milestone = ((com.github.mobile.api.model.Milestone) getSerializableExtra(EXTRA_MILESTONE)).getOldModel();
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.repo_milestone_item, null);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Bundle bundle = getArguments();
+ if (bundle != null && bundle.getSerializable(EXTRA_MILESTONE) != null)
+ milestone = ((com.github.mobile.api.model.Milestone) bundle.getSerializable(EXTRA_MILESTONE)).getOldModel();
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ milestoneTitle = (TextView) finder.find(R.id.tv_milestone_name);
+ milestoneDueTo = (TextView) finder.find(R.id.tv_milestone_due_to);
+ milestoneDescription = (TextView) finder.find(R.id.tv_milestone_description);
+ milestoneProgress = (ProgressBar) finder.find(R.id.pB_milestone_completion_progress);
+ milestoneProgressPercentage = (TextView) finder.find(R.id.tv_milestone_progress_percentage);
+ milestoneTime = (TextView) finder.find(R.id.tv_milestone_time);
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ if (milestone != null) {
+ updateMilestone(milestone);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu optionsMenu, MenuInflater inflater) {
+ inflater.inflate(R.menu.milestone_view, optionsMenu);
+ }
+
+ private void updateMilestone(final Milestone milestone) {
+ if (!isUsable()) {
+ return;
+ }
+
+ milestoneTitle.setText(milestone.getTitle());
+ DateFormat sdf = SimpleDateFormat.getDateInstance();
+
+ if (milestone.getDueOn() != null) {
+ milestoneDueTo.setVisibility(View.VISIBLE);
+ milestoneDueTo.setText(sdf.format(milestone.getDueOn()));
+ } else {
+ milestoneDueTo.setVisibility(View.GONE);
+ }
+
+ if (milestone.getDescription() != null) {
+ milestoneDescription.setVisibility(View.VISIBLE);
+ milestoneDescription.setText(milestone.getDescription());
+ } else {
+ milestoneDescription.setVisibility(View.GONE);
+ }
+
+ int totalIssues = milestone.getClosedIssues() + milestone.getOpenIssues();
+ int progress = totalIssues == 0 ? 0 : milestone.getClosedIssues() * 100 / totalIssues;
+ milestoneProgress.setProgress(progress);
+ milestoneProgressPercentage.setText(String.valueOf(progress));
+
+ Date dueOn = milestone.getDueOn();
+ if (dueOn != null) {
+ Date current = Calendar.getInstance().getTime();
+ String state = milestone.getState();
+ boolean open = state.equals("open");
+ long days = MS_TIME_OK_DAYS;
+ long diff = dueOn.getTime() - current.getTime();
+ days = TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS);
+ GradientDrawable back = (GradientDrawable) milestoneTime.getBackground();
+ if (!open) {
+ milestoneTime.setVisibility(View.VISIBLE);
+ milestoneTime.setText(R.string.status_closed);
+ back.setColor(getResources().getColor(R.color.milestone_badge_default));
+ } else if (MS_TIME_PAST_DAYS <= days && days < 0 && open) {
+ milestoneTime.setVisibility(View.VISIBLE);
+ milestoneTime.setText(getString(R.string.ms_time_past) + " " + (-days) + " " + getString(R.string.ms_days));
+ back.setColor(getResources().getColor(R.color.milestone_badge_red));
+ } else if (0 <= days && days < MS_TIME_OK_DAYS && open) {
+ milestoneTime.setVisibility(View.VISIBLE);
+ milestoneTime.setText(days + " " + getString(R.string.ms_days));
+ back.setColor(getResources().getColor(R.color.milestone_badge_default));
+ } else {
+ milestoneTime.setText("");
+ back.setColor(getResources().getColor(R.color.background));
+ }
+ } else {
+ milestoneTime.setVisibility(View.GONE);
+ }
+ }
+}
diff --git a/app/src/main/java/com/github/mobile/ui/milestone/MilestoneViewActivity.java b/app/src/main/java/com/github/mobile/ui/milestone/MilestoneViewActivity.java
new file mode 100644
index 00000000..73ea33d4
--- /dev/null
+++ b/app/src/main/java/com/github/mobile/ui/milestone/MilestoneViewActivity.java
@@ -0,0 +1,196 @@
+package com.github.mobile.ui.milestone;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.FragmentTransaction;
+import android.support.v7.app.ActionBar;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import com.github.mobile.Intents;
+import com.github.mobile.R;
+import com.github.mobile.api.model.Issue;
+import com.github.mobile.api.model.Milestone;
+import com.github.mobile.api.service.IssueService;
+import com.github.mobile.core.issue.IssueFilter;
+import com.github.mobile.core.milestone.AddIssueTask;
+import com.github.mobile.ui.DialogFragmentActivity;
+import com.github.mobile.ui.issue.IssuesFragment;
+import com.google.inject.Inject;
+
+import org.eclipse.egit.github.core.Repository;
+import org.eclipse.egit.github.core.User;
+
+import static com.github.mobile.Intents.EXTRA_ISSUE_FILTER;
+import static com.github.mobile.Intents.EXTRA_MILESTONE;
+import static com.github.mobile.Intents.EXTRA_REPOSITORY;
+import static com.github.mobile.Intents.EXTRA_REPOSITORY_NAME;
+import static com.github.mobile.Intents.EXTRA_REPOSITORY_OWNER;
+import static com.github.mobile.Intents.EXTRA_USER;
+import static com.github.mobile.RequestCodes.ISSUE_MILESTONE_UPDATE;
+import static com.github.mobile.RequestCodes.MILESTONE_EDIT;
+
+/**
+ * Activity to display milestone detailed view
+ */
+public class MilestoneViewActivity extends DialogFragmentActivity {
+ /**
+ * Create intent for this activity
+ *
+ * @param repository
+ * @return intent
+ */
+ public static Intent createIntent(Repository repository, Milestone milestone, int position) {
+ return new Intents.Builder("repo.milestone.VIEW")
+ .repo(repository)
+ .milestone(milestone).toIntent();
+ }
+
+ private Repository repository;
+ private Milestone milestone;
+ private IssueDialog issueDialog;
+
+ private AddIssueTask issueTask;
+
+ @Inject
+ private IssueService issueService;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.milestone_view);
+
+ repository = getSerializableExtra(EXTRA_REPOSITORY);
+ milestone = getSerializableExtra(EXTRA_MILESTONE);
+
+ ActionBar actionBar = getSupportActionBar();
+ actionBar.setTitle(milestone.title);
+ actionBar.setSubtitle(R.string.milestone);
+ actionBar.setDisplayHomeAsUpEnabled(true);
+
+ DialogFragmentActivity dialogActivity = (DialogFragmentActivity) this;
+
+ MilestoneFragment milestoneFragment = new MilestoneFragment();
+
+ Bundle args = new Bundle();
+ if (repository != null) {
+ args.putString(EXTRA_REPOSITORY_NAME, repository.getName());
+ User owner = repository.getOwner();
+ args.putString(EXTRA_REPOSITORY_OWNER, owner.getLogin());
+ args.putSerializable(EXTRA_USER, owner);
+ }
+ milestoneFragment.setArguments(args);
+
+ IssuesFragment issuesFragment = new IssuesFragment();
+
+ IssueFilter filter = new IssueFilter(repository);
+ filter.setMilestone(milestone);
+ filter.setOpen(true);
+ getIntent().putExtra(EXTRA_ISSUE_FILTER, filter);
+
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+
+ transaction.add(R.id.ms_description, milestoneFragment);
+ transaction.add(R.id.ms_issues, issuesFragment);
+
+ transaction.commit();
+
+ issueTask = new AddIssueTask(dialogActivity, repository,
+ milestone.number) {
+
+ @Override
+ protected void onSuccess(Milestone editedMilestone) throws Exception {
+ super.onSuccess(editedMilestone);
+ IssuesFragment issuesFragment = new IssuesFragment();
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ transaction.replace(R.id.ms_issues, issuesFragment);
+ transaction.commit();
+ milestone = editedMilestone;
+ updateMilestone();
+ }
+ };
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ finish();
+ return true;
+ case R.id.add_ms_menu_item:
+ issueTask.prompt();
+ return true;
+ case R.id.m_edit: {
+ Intent intent = EditMilestoneActivity.createIntent(milestone, repository,
+ repository.getOwner().getLogin(), repository.getName());
+ startActivityForResult(intent, MILESTONE_EDIT);
+ return true;
+ }
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.milestone_view, menu);
+ return true;
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if(MILESTONE_EDIT == requestCode) {
+ if(data != null) { // user has changed something
+ milestone = (Milestone) data.getSerializableExtra(EXTRA_MILESTONE);
+ updateMilestone();
+ }
+ }
+ }
+
+ @Override
+ public void onDialogResult(int requestCode, int resultCode, Bundle arguments) {
+ if (RESULT_OK != resultCode)
+ return;
+
+ switch (requestCode) {
+ case ISSUE_MILESTONE_UPDATE:
+ Issue issue = IssueDialogFragment.getSelected(arguments);
+ issueTask.edit(issue);
+ break;
+ }
+ }
+
+ private void updateMilestone() {
+ MilestoneFragment milestoneFragment = new MilestoneFragment();
+ IssuesFragment issuesFragment = new IssuesFragment();
+
+ if(milestone != null) {
+ ActionBar actionBar = getSupportActionBar();
+ actionBar.setTitle(milestone.title);
+ actionBar.setSubtitle(R.string.milestone);
+ actionBar.setDisplayHomeAsUpEnabled(true);
+
+ Bundle args = new Bundle();
+ if (repository != null) {
+ args.putString(EXTRA_REPOSITORY_NAME, repository.getName());
+ User owner = repository.getOwner();
+ args.putString(EXTRA_REPOSITORY_OWNER, owner.getLogin());
+ args.putSerializable(EXTRA_USER, owner);
+ args.putSerializable(EXTRA_MILESTONE, milestone);
+ }
+ milestoneFragment.setArguments(args);
+
+ IssueFilter filter = new IssueFilter(repository);
+ filter.setMilestone(milestone);
+ filter.setOpen(true);
+ getIntent().putExtra(EXTRA_ISSUE_FILTER, filter);
+ }
+
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ transaction.replace(R.id.ms_description, milestoneFragment);
+ transaction.replace(R.id.ms_issues, issuesFragment);
+ transaction.commit();
+ }
+}
diff --git a/app/src/main/java/com/github/mobile/ui/repo/MilestoneListAdapter.java b/app/src/main/java/com/github/mobile/ui/repo/MilestoneListAdapter.java
new file mode 100644
index 00000000..7ab8b479
--- /dev/null
+++ b/app/src/main/java/com/github/mobile/ui/repo/MilestoneListAdapter.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2013 GitHub Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.github.mobile.ui.repo;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+
+import com.github.kevinsawicki.wishlist.SingleTypeAdapter;
+import com.github.mobile.R;
+import com.github.mobile.api.model.Milestone;
+
+
+import java.text.SimpleDateFormat;
+/**
+ * List adapter for a list of milestones
+ */
+public class MilestoneListAdapter extends SingleTypeAdapter {
+
+ private final Context context;
+ private static final String EMPTY_STR = "";
+ /**
+ * Create milestone list adapter
+ *
+ * @param context
+ * @param elements
+ */
+ public MilestoneListAdapter(final Context context,
+ final Milestone[] elements) {
+ super(LayoutInflater.from(context), R.layout.milestone_list_item);
+ this.context = context.getApplicationContext();
+ setItems(elements);
+ }
+
+ @Override
+ protected int[] getChildViewIds() {
+ return new int[]{R.id.tv_milestone_title,
+ R.id.tv_milestone_due_to,
+ R.id.tv_milestone_opened_iss_number,
+ R.id.tv_milestone_closed_iss_number};
+ }
+
+ @Override
+ protected void update(int position, Milestone milestone) {
+ SimpleDateFormat sdf = new SimpleDateFormat(context.getString(R.string.ms_date_format));
+
+ setText(0, milestone.title);
+ setText(1,
+ (milestone.due_on != null) ?
+ context.getString(R.string.ms_due_by) + sdf.format(milestone.due_on)
+ : EMPTY_STR);
+ setText(2, String.valueOf(milestone.open_issues));
+ setText(3, String.valueOf(milestone.closed_issues));
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/github/mobile/ui/repo/RepositoryMilestonesActivity.java b/app/src/main/java/com/github/mobile/ui/repo/RepositoryMilestonesActivity.java
new file mode 100644
index 00000000..8a48e252
--- /dev/null
+++ b/app/src/main/java/com/github/mobile/ui/repo/RepositoryMilestonesActivity.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2013 GitHub Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.github.mobile.ui.repo;
+
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
+import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
+import static com.github.mobile.Intents.EXTRA_REPOSITORY;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v7.app.ActionBar;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import com.github.mobile.Intents;
+import com.github.mobile.R;
+import com.github.mobile.ui.DialogFragmentActivity;
+import com.github.mobile.ui.milestone.EditMilestoneActivity;
+
+import org.eclipse.egit.github.core.Repository;
+
+/**
+ * Activity to view repository milestones
+ */
+public class RepositoryMilestonesActivity extends DialogFragmentActivity {
+
+ /**
+ * Create intent for this activity
+ *
+ * @param repository
+ * @return intent
+ */
+ public static Intent createIntent(Repository repository) {
+ return new Intents.Builder("repo.milestones.VIEW").repo(repository).toIntent();
+ }
+
+ private Repository repository;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.repo_milestones);
+
+ repository = getSerializableExtra(EXTRA_REPOSITORY);
+
+ ActionBar actionBar = getSupportActionBar();
+ actionBar.setTitle(repository.getName());
+ actionBar.setSubtitle(R.string.milestone);
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ navigateToRepository();
+ return true;
+ case R.id.add_ms_menu_item:
+ //creating new milestone
+ Intent i = EditMilestoneActivity.createIntent(repository);
+ i.addFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
+ startActivity(i);
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.milestone, menu);
+ return true;
+ }
+
+ @Override
+ public void onBackPressed() {
+ navigateToRepository();
+ super.onBackPressed();
+ }
+
+ private void navigateToRepository() {
+ Intent intent = RepositoryViewActivity.createIntent(repository);
+ intent.addFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
+ startActivity(intent);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/github/mobile/ui/repo/RepositoryMilestonesFragment.java b/app/src/main/java/com/github/mobile/ui/repo/RepositoryMilestonesFragment.java
new file mode 100644
index 00000000..7f90d2ee
--- /dev/null
+++ b/app/src/main/java/com/github/mobile/ui/repo/RepositoryMilestonesFragment.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2013 GitHub Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.github.mobile.ui.repo;
+
+import static com.github.mobile.Intents.EXTRA_REPOSITORY;
+import static com.github.mobile.RequestCodes.MILESTONE_VIEW;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.content.Loader;
+import android.view.View;
+import android.widget.ListView;
+
+import com.github.kevinsawicki.wishlist.SingleTypeAdapter;
+import com.github.mobile.R;
+import com.github.mobile.ThrowableLoader;
+import com.github.mobile.api.model.Milestone;
+import com.github.mobile.ui.ItemListFragment;
+import com.github.mobile.ui.milestone.MilestoneViewActivity;
+import com.google.inject.Inject;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.egit.github.core.Repository;
+import org.eclipse.egit.github.core.service.MilestoneService;
+
+/**
+ * Fragment to display a list of milestones for a specific repository
+ */
+public class RepositoryMilestonesFragment extends ItemListFragment {
+ public static final String MILESTONES_STATE_ALL = "all";
+
+ /**
+ * Milestone service
+ */
+ @Inject
+ protected MilestoneService service;
+
+ private Repository repo;
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+
+ repo = getSerializableExtra(EXTRA_REPOSITORY);
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ setEmptyText(R.string.no_milestone);
+ }
+
+ @Override
+ public Loader> onCreateLoader(int id, Bundle args) {
+ return new ThrowableLoader>(getActivity(), items) {
+
+ @Override
+ public List loadData() throws Exception {
+ return convertMilestonesList(service.getMilestones(repo, MILESTONES_STATE_ALL));
+ }
+ };
+ }
+
+ private List convertMilestonesList(List milestoneList) {
+ List newList = new ArrayList();
+ for (org.eclipse.egit.github.core.Milestone m : milestoneList) {
+ newList.add(new Milestone(m));
+ }
+ return newList;
+
+ }
+
+ @Override
+ protected SingleTypeAdapter createAdapter(List items) {
+ return new MilestoneListAdapter(getActivity(),
+ items.toArray(new Milestone[items.size()]));
+ }
+
+ @Override
+ public void onListItemClick(ListView l, View v, int position, long id) {
+ startActivityForResult(MilestoneViewActivity.createIntent(repo, (Milestone) l.getItemAtPosition(position), position), MILESTONE_VIEW);
+ }
+
+ @Override
+ protected int getErrorMessage(Exception exception) {
+ return R.string.error_milestones_load;
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if(MILESTONE_VIEW == requestCode)
+ {
+ refreshWithProgress();
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/github/mobile/ui/repo/RepositoryViewActivity.java b/app/src/main/java/com/github/mobile/ui/repo/RepositoryViewActivity.java
index bd61df70..8cc8a41c 100644
--- a/app/src/main/java/com/github/mobile/ui/repo/RepositoryViewActivity.java
+++ b/app/src/main/java/com/github/mobile/ui/repo/RepositoryViewActivity.java
@@ -198,6 +198,9 @@ public boolean onOptionsItemSelected(MenuItem item) {
case R.id.m_contributors:
startActivity(RepositoryContributorsActivity.createIntent(repository));
return true;
+ case R.id.m_milestone:
+ startActivity(RepositoryMilestonesActivity.createIntent(repository));
+ return true;
case R.id.m_parent_repo:
if (repository.getParent() == null) {
// TODO: save parent in OrganizationRepositories so we don't need to do this
diff --git a/app/src/main/res/drawable/rounded_corners.xml b/app/src/main/res/drawable/rounded_corners.xml
new file mode 100644
index 00000000..681bf50c
--- /dev/null
+++ b/app/src/main/res/drawable/rounded_corners.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/milestone_edit.xml b/app/src/main/res/layout/milestone_edit.xml
new file mode 100644
index 00000000..a7da3c81
--- /dev/null
+++ b/app/src/main/res/layout/milestone_edit.xml
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/milestone_list_item.xml b/app/src/main/res/layout/milestone_list_item.xml
new file mode 100644
index 00000000..334bca71
--- /dev/null
+++ b/app/src/main/res/layout/milestone_list_item.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/milestone_view.xml b/app/src/main/res/layout/milestone_view.xml
new file mode 100644
index 00000000..f6184573
--- /dev/null
+++ b/app/src/main/res/layout/milestone_view.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/repo_milestone_item.xml b/app/src/main/res/layout/repo_milestone_item.xml
new file mode 100644
index 00000000..f56f07e6
--- /dev/null
+++ b/app/src/main/res/layout/repo_milestone_item.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/repo_milestones.xml b/app/src/main/res/layout/repo_milestones.xml
new file mode 100644
index 00000000..967ce78d
--- /dev/null
+++ b/app/src/main/res/layout/repo_milestones.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/milestone.xml b/app/src/main/res/menu/milestone.xml
new file mode 100644
index 00000000..1fabbfea
--- /dev/null
+++ b/app/src/main/res/menu/milestone.xml
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/milestone_edit.xml b/app/src/main/res/menu/milestone_edit.xml
new file mode 100644
index 00000000..d6cf3858
--- /dev/null
+++ b/app/src/main/res/menu/milestone_edit.xml
@@ -0,0 +1,27 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/milestone_view.xml b/app/src/main/res/menu/milestone_view.xml
new file mode 100644
index 00000000..91487fa6
--- /dev/null
+++ b/app/src/main/res/menu/milestone_view.xml
@@ -0,0 +1,31 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/repository.xml b/app/src/main/res/menu/repository.xml
index dd3b7eb6..a8103379 100644
--- a/app/src/main/res/menu/repository.xml
+++ b/app/src/main/res/menu/repository.xml
@@ -29,6 +29,12 @@
android:id="@+id/m_contributors"
app:showAsAction="never"
android:title="@string/contributors"/>
+
+
+
- Закрыть задачу
Переоткрыть задачу
Закрыта
- Объеденино
+ Объединено
Описание отсутствует.
Закрыть
Переоткрыть
@@ -344,5 +344,27 @@
Отобразить Markdown
Копировать хэш
Скопировано в буфер обмена
+
+ Показать закрытые
+ Показать открытые
+
+ "Закрытые: "
+ "До "
+ "Открытые: "
+ Выбрать дату
+ 2 недели
+ Месяц
+ % завершено
+ Просрочен
+ дней
+ Выбрать задачу
+ Новая цель
+ Ошибка при сохранении с пустым заголовком
+ Цели
+
+ Создаем цель…
+ Удаляем цель…
+ начата проверка
+ Родительский репозиторий
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 2c5183d6..d6aeb2d9 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -83,6 +83,8 @@
#BD2C00
#6CC644
#4078C0
+ #616161
+ #bf360c
#3F51B5
#1A237E
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index be7ef48e..b5bc2ebc 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -256,6 +256,7 @@
Reopening Issue…
Avatar
Creating Issue…
+ Creating Milestone…
created\u0020
updated\u0020
opened\u0020
@@ -311,6 +312,7 @@
Navigate to…
Navigate to %s
%d commits
+ Deleting milestone…
repositories
@@ -346,5 +348,24 @@
Copy hash
Copied to clipboard
Parent repository
+
+ Display closed
+ Display opened
+
+ "Due date "
+ "Opened: "
+ "Closed: "
+ New milestone
+ 2 weeks
+ month
+ Choose date
+ % completed
+ past due by
+ days
+ dd MMM yyyy
+ Select Issue
+ Saving with empty title failed
+ Milestones
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 76265341..af8ec38a 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -172,6 +172,27 @@
- wrap_content
+
+
+
+
+
+