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 @@ + + + + + + + + + + + + + + + + + + + + + + +